packages feed

hlibsass 0.1.6.1 → 0.1.7.0

raw patch · 105 files changed

+11652/−9580 lines, 105 filessetup-changedPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

- Bindings.Libsass.Base: sass_resolve_file :: CString -> Ptr CString -> IO CString
- Bindings.Libsass.Context: sass_option_get_plugin_path :: Ptr SassOptions -> IO CString
+ Bindings.Libsass.Context: sass_compiler_find_file :: CString -> Ptr SassCompiler -> IO CString
+ Bindings.Libsass.Context: sass_compiler_find_include :: CString -> Ptr SassCompiler -> IO CString
+ Bindings.Libsass.Context: sass_compiler_get_callee_entry :: Ptr SassCompiler -> CSize -> IO SassCalleeEntry
+ Bindings.Libsass.Context: sass_compiler_get_callee_stack_size :: Ptr SassCompiler -> IO CSize
+ Bindings.Libsass.Context: sass_compiler_get_last_callee :: Ptr SassCompiler -> IO SassCalleeEntry
+ Bindings.Libsass.Context: sass_find_file :: CString -> Ptr SassOptions -> IO CString
+ Bindings.Libsass.Context: sass_find_include :: CString -> Ptr SassOptions -> IO CString
+ Bindings.Libsass.Context: sass_option_get_include_path_size :: Ptr SassOptions -> IO CSize
+ Bindings.Libsass.Functions: sass_callee_get_column :: SassCalleeEntry -> IO CSize
+ Bindings.Libsass.Functions: sass_callee_get_env :: SassCalleeEntry -> IO SassEnvFrame
+ Bindings.Libsass.Functions: sass_callee_get_line :: SassCalleeEntry -> IO CSize
+ Bindings.Libsass.Functions: sass_callee_get_name :: SassCalleeEntry -> IO CString
+ Bindings.Libsass.Functions: sass_callee_get_path :: SassCalleeEntry -> IO CString
+ Bindings.Libsass.Functions: sass_callee_get_type :: SassCalleeEntry -> IO CInt
+ Bindings.Libsass.Functions: sass_delete_function :: SassFunctionEntry -> IO ()
+ Bindings.Libsass.Functions: sass_delete_function_list :: SassFunctionList -> IO ()
+ Bindings.Libsass.Functions: sass_delete_importer_list :: SassImporterList -> IO ()
+ Bindings.Libsass.Functions: sass_env_get_global :: SassEnvFrame -> CString -> IO (Ptr SassValue)
+ Bindings.Libsass.Functions: sass_env_get_lexical :: SassEnvFrame -> CString -> IO (Ptr SassValue)
+ Bindings.Libsass.Functions: sass_env_get_local :: SassEnvFrame -> CString -> IO (Ptr SassValue)
+ Bindings.Libsass.Functions: sass_env_set_global :: SassEnvFrame -> CString -> Ptr SassValue -> IO ()
+ Bindings.Libsass.Functions: sass_env_set_lexical :: SassEnvFrame -> CString -> Ptr SassValue -> IO ()
+ Bindings.Libsass.Functions: sass_env_set_local :: SassEnvFrame -> CString -> Ptr SassValue -> IO ()
+ Bindings.Libsass.Types: SassCalleeCFunction :: SassCalleeType
+ Bindings.Libsass.Types: SassCalleeFunction :: SassCalleeType
+ Bindings.Libsass.Types: SassCalleeMixin :: SassCalleeType
+ Bindings.Libsass.Types: data SassCallee
+ Bindings.Libsass.Types: data SassCalleeType
+ Bindings.Libsass.Types: data SassEnv
+ Bindings.Libsass.Types: instance GHC.Classes.Eq Bindings.Libsass.Types.SassCalleeType
+ Bindings.Libsass.Types: instance GHC.Enum.Enum Bindings.Libsass.Types.SassCalleeType
+ Bindings.Libsass.Types: instance GHC.Show.Show Bindings.Libsass.Types.SassCalleeType
+ Bindings.Libsass.Types: type SassCalleeEntry = Ptr SassCallee
+ Bindings.Libsass.Types: type SassEnvFrame = Ptr SassEnv
+ Bindings.Libsass.Values: sass_list_get_is_bracketed :: Ptr SassValue -> IO Bool
+ Bindings.Libsass.Values: sass_list_set_is_bracketed :: Ptr SassValue -> Bool -> IO ()
- Bindings.Libsass.Context: sass_option_get_include_path :: Ptr SassOptions -> IO CString
+ Bindings.Libsass.Context: sass_option_get_include_path :: Ptr SassOptions -> CSize -> IO CString
- Bindings.Libsass.Values: sass_make_list :: CSize -> CInt -> IO (Ptr SassValue)
+ Bindings.Libsass.Values: sass_make_list :: CSize -> CInt -> Bool -> IO (Ptr SassValue)

Files

Bindings/Libsass/Base.hs view
@@ -25,11 +25,6 @@     :: CString     -> IO CString -foreign import ccall unsafe "sass/base.h" sass_resolve_file-    :: CString-    -> Ptr CString-    -> IO CString- foreign import ccall unsafe "sass/base.h" libsass_version     :: CString 
Bindings/Libsass/Context.hs view
@@ -139,14 +139,6 @@     :: Ptr SassOptions     -> IO CString -foreign import ccall unsafe "sass/context.h" sass_option_get_plugin_path-    :: Ptr SassOptions-    -> IO CString--foreign import ccall unsafe "sass/context.h" sass_option_get_include_path-    :: Ptr SassOptions-    -> IO CString- foreign import ccall unsafe "sass/context.h" sass_option_get_source_map_file     :: Ptr SassOptions     -> IO CString@@ -306,6 +298,15 @@     :: Ptr SassContext     -> IO (Ptr CString) +foreign import ccall unsafe "sass/context.h" sass_option_get_include_path_size+    :: Ptr SassOptions+    -> IO CSize++foreign import ccall unsafe "sass/context.h" sass_option_get_include_path+    :: Ptr SassOptions+    -> CSize+    -> IO CString+ foreign import ccall unsafe "sass/context.h" sass_context_get_included_files_size     :: Ptr SassContext     -> IO CSize@@ -363,6 +364,19 @@     -> CSize     -> IO SassImportEntry +foreign import ccall unsafe "sass/context.h" sass_compiler_get_callee_stack_size+    :: Ptr SassCompiler+    -> IO CSize++foreign import ccall unsafe "sass/context.h" sass_compiler_get_last_callee+    :: Ptr SassCompiler+    -> IO SassCalleeEntry++foreign import ccall unsafe "sass/context.h" sass_compiler_get_callee_entry+    :: Ptr SassCompiler+    -> CSize+    -> IO SassCalleeEntry+ foreign import ccall unsafe "sass/context.h" sass_option_push_plugin_path     :: Ptr SassOptions     -> CString@@ -372,3 +386,24 @@     :: Ptr SassOptions     -> CString     -> IO ()++foreign import ccall unsafe "sass/context.h" sass_find_file+    :: CString+    -> Ptr SassOptions+    -> IO CString++foreign import ccall unsafe "sass/context.h" sass_find_include+    :: CString+    -> Ptr SassOptions+    -> IO CString++foreign import ccall unsafe "sass/context.h" sass_compiler_find_file+    :: CString+    -> Ptr SassCompiler+    -> IO CString++foreign import ccall unsafe "sass/context.h" sass_compiler_find_include+    :: CString+    -> Ptr SassCompiler+    -> IO CString+    
Bindings/Libsass/Functions.hs view
@@ -20,6 +20,10 @@     -> SassImporterEntry     -> IO () +foreign import ccall unsafe "sass/functions.h" sass_delete_importer_list+    :: SassImporterList+    -> IO ()+ foreign import ccall unsafe "sass/functions.h" sass_make_importer     :: SassImporterFn     -> CDouble@@ -76,6 +80,63 @@     -> CSize     -> IO SassImportEntry +foreign import ccall unsafe "sass/functions.h" sass_callee_get_name+    :: SassCalleeEntry+    -> IO CString++foreign import ccall unsafe "sass/functions.h" sass_callee_get_path+    :: SassCalleeEntry+    -> IO CString++foreign import ccall unsafe "sass/functions.h" sass_callee_get_line+    :: SassCalleeEntry+    -> IO CSize++foreign import ccall unsafe "sass/functions.h" sass_callee_get_column+    :: SassCalleeEntry+    -> IO CSize++foreign import ccall unsafe "sass/functions.h" sass_callee_get_type+    :: SassCalleeEntry+    -> IO CInt -- ^ Returns 'SassCalleeType'++foreign import ccall unsafe "sass/functions.h" sass_callee_get_env+    :: SassCalleeEntry+    -> IO SassEnvFrame++foreign import ccall unsafe "sass/functions.h" sass_env_get_lexical+    :: SassEnvFrame+    -> CString+    -> IO (Ptr SassValue)++foreign import ccall unsafe "sass/functions.h" sass_env_set_lexical+    :: SassEnvFrame+    -> CString+    -> Ptr SassValue+    -> IO ()++foreign import ccall unsafe "sass/functions.h" sass_env_get_local+    :: SassEnvFrame+    -> CString+    -> IO (Ptr SassValue)++foreign import ccall unsafe "sass/functions.h" sass_env_set_local+    :: SassEnvFrame+    -> CString+    -> Ptr SassValue+    -> IO ()++foreign import ccall unsafe "sass/functions.h" sass_env_get_global+    :: SassEnvFrame+    -> CString+    -> IO (Ptr SassValue)++foreign import ccall unsafe "sass/functions.h" sass_env_set_global+    :: SassEnvFrame+    -> CString+    -> Ptr SassValue+    -> IO ()+ foreign import ccall unsafe "sass/functions.h" sass_import_get_imp_path     :: SassImportEntry     -> IO CString@@ -130,6 +191,14 @@     -> Ptr ()     -> IO SassFunctionEntry +foreign import ccall unsafe "sass/functions.h" sass_delete_function+    :: SassFunctionEntry+    -> IO ()++foreign import ccall unsafe "sass/functions.h" sass_delete_function_list+    :: SassFunctionList+    -> IO ()+ foreign import ccall unsafe "sass/functions.h" sass_function_get_list_entry     :: SassFunctionList     -> CSize@@ -152,3 +221,4 @@ foreign import ccall unsafe "sass/functions.h" sass_function_get_cookie     :: SassFunctionEntry     -> IO (Ptr ())+
Bindings/Libsass/Types.hs view
@@ -33,8 +33,6 @@  -- From sass/context.h -data SassCompiler-data SassOptions data SassContext data SassFileContext data SassDataContext@@ -56,10 +54,16 @@  -- From sass/functions.h +data SassEnv+data SassCallee data SassImport+data SassOptions+data SassCompiler data SassImporter data SassFunction +type SassEnvFrame = Ptr SassEnv+type SassCalleeEntry = Ptr SassCallee type SassImportEntry = Ptr SassImport type SassImportList = Ptr (Ptr SassImport) type SassImporterEntry = Ptr SassImporter@@ -81,6 +85,21 @@   -> Ptr SassOptions   -> IO (Ptr SassValue) type SassFunctionFn = FunPtr SassFunctionFnType++data SassCalleeType = SassCalleeMixin+                    | SassCalleeFunction+                    | SassCalleeCFunction+                    deriving (Eq, Show)++instance Enum SassCalleeType where+    fromEnum SassCalleeMixin = 0+    fromEnum SassCalleeFunction = 1+    fromEnum SassCalleeCFunction = 2++    toEnum 0 = SassCalleeMixin+    toEnum 1 = SassCalleeFunction+    toEnum 2 = SassCalleeCFunction+    toEnum u = error ("SassCalleeType.toEnum: Cannot match " ++ show u)  -- From sass/values.h 
Bindings/Libsass/Values.hs view
@@ -35,6 +35,7 @@ foreign import ccall unsafe "sass/values.h" sass_make_list     :: CSize     -> CInt -- ^ 'SassSeparator'+    -> Bool     -> IO (Ptr SassValue)  foreign import ccall unsafe "sass/values.h" sass_make_map@@ -201,6 +202,15 @@ foreign import ccall unsafe "sass/values.h" sass_list_set_separator     :: Ptr SassValue     -> CInt -- ^ 'SassSeparator'+    -> IO ()++foreign import ccall unsafe "sass/values.h" sass_list_get_is_bracketed+    :: Ptr SassValue+    -> IO Bool++foreign import ccall unsafe "sass/values.h" sass_list_set_is_bracketed+    :: Ptr SassValue+    -> Bool     -> IO ()  foreign import ccall unsafe "sass/values.h" sass_list_get_value
CHANGELOG.md view
@@ -1,6 +1,10 @@ # Change Log All notable changes to this project will be documented in this file. +## [0.1.7.0] - 2018-03-24+### Changed+- Update to Libsass 3.5.2+ ## [0.1.6.1] - 2017-08-06 ### Fixed - Build process (`Stack.hs`) works with Cabal 1.X and Cabal 2.X@@ -74,6 +78,7 @@ - Bindings to Libsass C API - Basic tests +[0.1.7.0]: https://github.com/jakubfijalkowski/hlibsass/compare/v0.1.6.1...v0.1.7.0 [0.1.6.1]: https://github.com/jakubfijalkowski/hlibsass/compare/v0.1.6.0...v0.1.6.1 [0.1.6.0]: https://github.com/jakubfijalkowski/hlibsass/compare/v0.1.5.2...v0.1.6.0 [0.1.5.2]: https://github.com/jakubfijalkowski/hlibsass/compare/v0.1.5.0...v0.1.5.2
Setup.hs view
@@ -16,7 +16,6 @@ import           Distribution.System import           System.Directory                   (getCurrentDirectory) -#ifdef MIN_VERSION_Cabal #if MIN_VERSION_Cabal(2, 0, 0) import           Distribution.Version               (mkVersion) #else@@ -26,7 +25,6 @@ mkFlagName :: String -> FlagName mkFlagName = FlagName #endif-#endif  main = defaultMainWithHooks hooksFix     where@@ -132,7 +130,14 @@     writeFile "libsass/VERSION" ver     return emptyHookedBuildInfo +#if MIN_VERSION_Cabal(2, 2, 0) getCabalFlag :: String -> ConfigFlags -> Bool+getCabalFlag name flags = fromMaybe False (lookupFlagAssignment (mkFlagName name') allFlags)+    where allFlags = configConfigurationsFlags flags+          name' = map toLower name+#else+getCabalFlag :: String -> ConfigFlags -> Bool getCabalFlag name flags = fromMaybe False (lookup (mkFlagName name') allFlags)     where allFlags = configConfigurationsFlags flags           name' = map toLower name+#endif
hlibsass.cabal view
@@ -1,5 +1,5 @@ name:                hlibsass-version:             0.1.6.1+version:             0.1.7.0 license:             MIT license-file:        LICENSE author:              Jakub Fijałkowski <fiolek94@gmail.com>
libsass/Makefile view
@@ -15,10 +15,14 @@ CFLAGS   ?= -Wall CXXFLAGS ?= -Wall LDFLAGS  ?= -Wall-ifneq "$(COVERAGE)" "yes"+ifeq "x$(COVERAGE)" "x"   CFLAGS   += -O2   CXXFLAGS += -O2   LDFLAGS  += -O2+else+  CFLAGS   += -O1 -fno-omit-frame-pointer+  CXXFLAGS += -O1 -fno-omit-frame-pointer+  LDFLAGS  += -O1 -fno-omit-frame-pointer endif LDFLAGS  += -Wl,-undefined,error CAT      ?= $(if $(filter $(OS),Windows_NT),type,cat)@@ -295,7 +299,7 @@                 install-headers  $(SASSC_BIN): $(BUILD)-	$(MAKE) -C $(SASS_SASSC_PATH)+	$(MAKE) -C $(SASS_SASSC_PATH) build-$(BUILD)-dev  sassc: $(SASSC_BIN) 	$(SASSC_BIN) -v@@ -305,16 +309,16 @@ 	$(SASSC_BIN) -v  test: $(SASSC_BIN)-	$(RUBY_BIN) $(SASS_SPEC_PATH)/sass-spec.rb -V 3.4 -c $(SASSC_BIN) --impl libsass $(LOG_FLAGS) $(SASS_SPEC_PATH)/$(SASS_SPEC_SPEC_DIR)+	$(RUBY_BIN) $(SASS_SPEC_PATH)/sass-spec.rb -V 3.5 -c $(SASSC_BIN) --impl libsass $(LOG_FLAGS) $(SASS_SPEC_PATH)/$(SASS_SPEC_SPEC_DIR)  test_build: $(SASSC_BIN)-	$(RUBY_BIN) $(SASS_SPEC_PATH)/sass-spec.rb -V 3.4 -c $(SASSC_BIN) --impl libsass $(LOG_FLAGS) $(SASS_SPEC_PATH)/$(SASS_SPEC_SPEC_DIR)+	$(RUBY_BIN) $(SASS_SPEC_PATH)/sass-spec.rb -V 3.5 -c $(SASSC_BIN) --impl libsass $(LOG_FLAGS) $(SASS_SPEC_PATH)/$(SASS_SPEC_SPEC_DIR)  test_full: $(SASSC_BIN)-	$(RUBY_BIN) $(SASS_SPEC_PATH)/sass-spec.rb -V 3.4 -c $(SASSC_BIN) --impl libsass --run-todo $(LOG_FLAGS) $(SASS_SPEC_PATH)/$(SASS_SPEC_SPEC_DIR)+	$(RUBY_BIN) $(SASS_SPEC_PATH)/sass-spec.rb -V 3.5 -c $(SASSC_BIN) --impl libsass --run-todo $(LOG_FLAGS) $(SASS_SPEC_PATH)/$(SASS_SPEC_SPEC_DIR)  test_probe: $(SASSC_BIN)-	$(RUBY_BIN) $(SASS_SPEC_PATH)/sass-spec.rb -V 3.4 -c $(SASSC_BIN) --impl libsass --probe-todo $(LOG_FLAGS) $(SASS_SPEC_PATH)/$(SASS_SPEC_SPEC_DIR)+	$(RUBY_BIN) $(SASS_SPEC_PATH)/sass-spec.rb -V 3.5 -c $(SASSC_BIN) --impl libsass --probe-todo $(LOG_FLAGS) $(SASS_SPEC_PATH)/$(SASS_SPEC_SPEC_DIR)  clean-objects: lib 	-$(RM) lib/*.a lib/*.so lib/*.dll lib/*.la
libsass/Makefile.conf view
@@ -13,6 +13,7 @@ 	functions.cpp \ 	color_maps.cpp \ 	environment.cpp \+	ast_fwd_decl.cpp \ 	bind.cpp \ 	file.cpp \ 	util.cpp \@@ -40,6 +41,8 @@ 	sass_context.cpp \ 	sass_functions.cpp \ 	sass2scss.cpp \+	backtrace.cpp \+	operators.cpp \ 	to_c.cpp \ 	to_value.cpp \ 	source_map.cpp \
libsass/VERSION view
@@ -1,1 +1,1 @@-3.4.3+3.5.2
libsass/include/sass/base.h view
@@ -75,9 +75,6 @@ ADDAPI char* ADDCALL sass_string_quote (const char* str, const char quote_mark); ADDAPI char* ADDCALL sass_string_unquote (const char* str); -// Resolve a file via the given include paths in the include char* array-ADDAPI char* ADDCALL sass_resolve_file (const char* path, const char* incs[]);- // Implemented sass language version // Hardcoded version 3.4 for time being ADDAPI const char* ADDCALL libsass_version(void);
libsass/include/sass/context.h view
@@ -81,8 +81,6 @@ ADDAPI const char* ADDCALL sass_option_get_linefeed (struct Sass_Options* options); ADDAPI const char* ADDCALL sass_option_get_input_path (struct Sass_Options* options); ADDAPI const char* ADDCALL sass_option_get_output_path (struct Sass_Options* options);-ADDAPI const char* ADDCALL sass_option_get_plugin_path (struct Sass_Options* options);-ADDAPI const char* ADDCALL sass_option_get_include_path (struct Sass_Options* options); ADDAPI const char* ADDCALL sass_option_get_source_map_file (struct Sass_Options* options); ADDAPI const char* ADDCALL sass_option_get_source_map_root (struct Sass_Options* options); ADDAPI Sass_Importer_List ADDCALL sass_option_get_c_headers (struct Sass_Options* options);@@ -124,6 +122,10 @@ 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);+ // Calculate the size of the stored null terminated array ADDAPI size_t ADDCALL sass_context_get_included_files_size (struct Sass_Context* ctx); @@ -143,10 +145,23 @@ 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.
libsass/include/sass/functions.h view
@@ -11,12 +11,18 @@   // 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);@@ -34,11 +40,18 @@ 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)@@ -66,6 +79,22 @@ 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);@@ -90,6 +119,8 @@ // 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);
libsass/include/sass/values.h view
@@ -50,7 +50,7 @@ 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);+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);@@ -116,6 +116,8 @@ // 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);
libsass/include/sass/version.h view
@@ -6,7 +6,7 @@ #endif  #ifndef LIBSASS_LANGUAGE_VERSION-#define LIBSASS_LANGUAGE_VERSION "3.4"+#define LIBSASS_LANGUAGE_VERSION "3.5" #endif  #endif
libsass/include/sass/version.h.in view
@@ -6,7 +6,7 @@ #endif  #ifndef LIBSASS_LANGUAGE_VERSION-#define LIBSASS_LANGUAGE_VERSION "3.4"+#define LIBSASS_LANGUAGE_VERSION "3.5" #endif  #endif
libsass/include/sass2scss.h view
@@ -37,7 +37,7 @@ #ifndef SASS2SCSS_VERSION // Hardcode once the file is copied from // https://github.com/mgreter/sass2scss-#define SASS2SCSS_VERSION "1.1.0"+#define SASS2SCSS_VERSION "1.1.1" #endif  // add namespace for c++
libsass/src/GNUmakefile.am view
@@ -34,7 +34,7 @@  libsass_la_SOURCES = ${CSOURCES} ${SOURCES} -libsass_la_LDFLAGS = $(AM_LDFLAGS) -no-undefined -version-info 0:9:0+libsass_la_LDFLAGS = $(AM_LDFLAGS) -no-undefined -version-info 1:0:0  if ENABLE_TESTS if ENABLE_COVERAGE
libsass/src/ast.cpp view
@@ -2,2450 +2,2225 @@ #include "ast.hpp" #include "context.hpp" #include "node.hpp"-#include "extend.hpp"-#include "emitter.hpp"-#include "color_maps.hpp"-#include "ast_fwd_decl.hpp"-#include <set>-#include <iomanip>-#include <iostream>-#include <algorithm>-#include <functional>-#include <cctype>-#include <locale>--namespace Sass {--  static Null sass_null(ParserState("null"));--  bool Supports_Operator::needs_parens(Supports_Condition_Obj cond) const {-    if (Supports_Operator_Obj op = SASS_MEMORY_CAST(Supports_Operator, cond)) {-      return op->operand() != operand();-    }-    return SASS_MEMORY_CAST(Supports_Negation, cond) != NULL;-  }--  bool Supports_Negation::needs_parens(Supports_Condition_Obj cond) const {-    return SASS_MEMORY_CAST(Supports_Negation, cond) ||-           SASS_MEMORY_CAST(Supports_Operator, cond);-  }--  size_t HashExpression::operator() (Expression_Obj ex) const {-    return ex ? ex->hash() : 0;-  }--  size_t HashSimpleSelector::operator() (Simple_Selector_Obj ex) const {-    return ex ? ex->hash() : 0;-  }---  bool CompareExpression::operator()(const Expression_Obj& lhs, const Expression_Obj& rhs) const {-    return lhs && rhs && lhs->eq(*rhs);-  }--  bool CompareSimpleSelector::operator()(Simple_Selector_Obj lhs, Simple_Selector_Obj rhs) const {-    return &lhs && &rhs && *lhs == *rhs;-  }--  std::string & str_ltrim(std::string & str)-  {-    auto it2 =  std::find_if( str.begin() , str.end() , [](char ch){ return !std::isspace<char>(ch , std::locale::classic() ) ; } );-    str.erase( str.begin() , it2);-    return str;-  }--  std::string & str_rtrim(std::string & str)-  {-    auto it1 =  std::find_if( str.rbegin() , str.rend() , [](char ch){ return !std::isspace<char>(ch , std::locale::classic() ) ; } );-    str.erase( it1.base() , str.end() );-    return str;-  }--  void String_Constant::rtrim()-  {-    value_ = str_rtrim(value_);-  }-  void String_Constant::ltrim()-  {-    value_ = str_ltrim(value_);-  }-  void String_Constant::trim()-  {-    rtrim();-    ltrim();-  }--  void String_Schema::rtrim()-  {-    if (!empty()) {-      if (String_Ptr str = SASS_MEMORY_CAST(String, last())) str->rtrim();-    }-  }-  void String_Schema::ltrim()-  {-    if (!empty()) {-      if (String_Ptr str = SASS_MEMORY_CAST(String, first())) str->ltrim();-    }-  }-  void String_Schema::trim()-  {-    rtrim();-    ltrim();-  }--  void Argument::set_delayed(bool delayed)-  {-    if (value_) value_->set_delayed(delayed);-    is_delayed(delayed);-  }--  void Arguments::set_delayed(bool delayed)-  {-    for (Argument_Obj arg : elements()) {-      if (arg) arg->set_delayed(delayed);-    }-    is_delayed(delayed);-  }---  bool At_Root_Query::exclude(std::string str)-  {-    bool with = feature() && unquote(feature()->to_string()).compare("with") == 0;-    List_Ptr l = static_cast<List_Ptr>(&value());-    std::string v;--    if (with)-    {-      if (!l || l->length() == 0) return str.compare("rule") != 0;-      for (size_t i = 0, L = l->length(); i < L; ++i)-      {-        v = unquote((*l)[i]->to_string());-        if (v.compare("all") == 0 || v == str) return false;-      }-      return true;-    }-    else-    {-      if (!l || !l->length()) return str.compare("rule") == 0;-      for (size_t i = 0, L = l->length(); i < L; ++i)-      {-        v = unquote((*l)[i]->to_string());-        if (v.compare("all") == 0 || v == str) return true;-      }-      return false;-    }-  }--  void AST_Node::update_pstate(const ParserState& pstate)-  {-    pstate_.offset += pstate - pstate_ + pstate.offset;-  }--  void AST_Node::set_pstate_offset(const Offset& offset)-  {-    pstate_.offset = offset;-  }--  inline bool is_ns_eq(const std::string& l, const std::string& r)-  {-    if (l.empty() && r.empty()) return true;-    else if (l.empty() && r == "*") return true;-    else if (r.empty() && l == "*") return true;-    else return l == r;-  }----  bool Compound_Selector::operator< (const Compound_Selector& rhs) const-  {-    size_t L = std::min(length(), rhs.length());-    for (size_t i = 0; i < L; ++i)-    {-      Simple_Selector_Obj l = (*this)[i];-      Simple_Selector_Obj r = rhs[i];-      if (!l && !r) return false;-      else if (!r) return false;-      else if (!l) return true;-      else if (*l != *r)-      { return *l < *r; }-    }-    // just compare the length now-    return length() < rhs.length();-  }--  bool Compound_Selector::has_parent_ref()-  {-    for (Simple_Selector_Obj s : *this) {-      if (s && s->has_parent_ref()) return true;-    }-    return false;-  }--  bool Compound_Selector::has_real_parent_ref()-  {-    for (Simple_Selector_Obj s : *this) {-      if (s && s->has_real_parent_ref()) return true;-    }-    return false;-  }--  bool Complex_Selector::has_parent_ref()-  {-    return (head() && head()->has_parent_ref()) ||-           (tail() && tail()->has_parent_ref());-  }--  bool Complex_Selector::has_real_parent_ref()-  {-    return (head() && head()->has_real_parent_ref()) ||-           (tail() && tail()->has_real_parent_ref());-  }--  bool Complex_Selector::operator< (const Complex_Selector& rhs) const-  {-    // const iterators for tails-    Complex_Selector_Ptr_Const l = this;-    Complex_Selector_Ptr_Const r = &rhs;-    Compound_Selector_Ptr l_h = l ? &l->head() : 0;-    Compound_Selector_Ptr r_h = r ? &r->head() : 0;-    // process all tails-    while (true)-    {-      // skip empty ancestor first-      if (l && l->is_empty_ancestor())-      {-        l = &l->tail();-        l_h = l ? &l->head() : 0;-        continue;-      }-      // skip empty ancestor first-      if (r && r->is_empty_ancestor())-      {-        r = &r->tail();-        r_h = r ? &r->head() : 0;-        continue;-      }-      // check for valid selectors-      if (!l) return !!r;-      if (!r) return false;-      // both are null-      else if (!l_h && !r_h)-      {-        // check combinator after heads-        if (l->combinator() != r->combinator())-        { return l->combinator() < r->combinator(); }-        // advance to next tails-        l = &l->tail();-        r = &r->tail();-        // fetch the next headers-        l_h = l ? &l->head() : 0;-        r_h = r ? &r->head() : 0;-      }-      // one side is null-      else if (!r_h) return true;-      else if (!l_h) return false;-      // heads ok and equal-      else if (*l_h == *r_h)-      {-        // check combinator after heads-        if (l->combinator() != r->combinator())-        { return l->combinator() < r->combinator(); }-        // advance to next tails-        l = &l->tail();-        r = &r->tail();-        // fetch the next headers-        l_h = l ? &l->head() : 0;-        r_h = r ? &r->head() : 0;-      }-      // heads are not equal-      else return *l_h < *r_h;-    }-    return true;-  }--  bool Complex_Selector::operator== (const Complex_Selector& rhs) const-  {-    // const iterators for tails-    Complex_Selector_Ptr_Const l = this;-    Complex_Selector_Ptr_Const r = &rhs;-    Compound_Selector_Ptr l_h = l ? &l->head() : 0;-    Compound_Selector_Ptr r_h = r ? &r->head() : 0;-    // process all tails-    while (true)-    {-      // skip empty ancestor first-      if (l && l->is_empty_ancestor())-      {-        l = &l->tail();-        l_h = l ? &l->head() : 0;-        continue;-      }-      // skip empty ancestor first-      if (r && r->is_empty_ancestor())-      {-        r = &r->tail();-        r_h = r ? &r->head() : 0;-        continue;-      }-      // check the pointers-      if (!r) return !l;-      if (!l) return !r;-      // both are null-      if (!l_h && !r_h)-      {-        // check combinator after heads-        if (l->combinator() != r->combinator())-        { return l->combinator() < r->combinator(); }-        // advance to next tails-        l = &l->tail();-        r = &r->tail();-        // fetch the next heads-        l_h = l ? &l->head() : 0;-        r_h = r ? &r->head() : 0;-      }-      // equals if other head is empty-      else if ((!l_h && !r_h) ||-               (!l_h && r_h->empty()) ||-               (!r_h && l_h->empty()) ||-               (*l_h == *r_h))-      {-        // check combinator after heads-        if (l->combinator() != r->combinator())-        { return l->combinator() == r->combinator(); }-        // advance to next tails-        l = &l->tail();-        r = &r->tail();-        // fetch the next heads-        l_h = l ? &l->head() : 0;-        r_h = r ? &r->head() : 0;-      }-      // abort-      else break;-    }-    // unreachable-    return false;-  }--  Compound_Selector_Ptr Compound_Selector::unify_with(Compound_Selector_Ptr rhs, Context& ctx)-  {-    if (empty()) return rhs;-    Compound_Selector_Obj unified = SASS_MEMORY_COPY(rhs);-    for (size_t i = 0, L = length(); i < L; ++i)-    {-      if (unified.isNull()) break;-      unified = at(i)->unify_with(&unified, ctx);-    }-    return unified.detach();-  }--  bool Selector::operator== (const Selector& rhs) const-  {-    if (Selector_List_Ptr_Const sl = dynamic_cast<Selector_List_Ptr_Const>(this)) return *sl == rhs;-    if (Simple_Selector_Ptr_Const sp = dynamic_cast<Simple_Selector_Ptr_Const>(this)) return *sp == rhs;-    throw std::runtime_error("invalid selector base classes to compare");-    return false;-  }--  bool Selector::operator< (const Selector& rhs) const-  {-    if (Selector_List_Ptr_Const sl = dynamic_cast<Selector_List_Ptr_Const>(this)) return *sl < rhs;-    if (Simple_Selector_Ptr_Const sp = dynamic_cast<Simple_Selector_Ptr_Const>(this)) return *sp < rhs;-    throw std::runtime_error("invalid selector base classes to compare");-    return false;-  }--  bool Simple_Selector::operator== (const Selector& rhs) const-  {-    if (Simple_Selector_Ptr_Const sp = dynamic_cast<Simple_Selector_Ptr_Const>(&rhs)) return *this == *sp;-    return false;-  }--  bool Simple_Selector::operator< (const Selector& rhs) const-  {-    if (Simple_Selector_Ptr_Const sp = dynamic_cast<Simple_Selector_Ptr_Const>(&rhs)) return *this < *sp;-    return false;-  }--  bool Simple_Selector::operator== (const Simple_Selector& rhs) const-  {-    Simple_Type type = simple_type();-    // dynamic cast is a bottleneck - use concrete type as types are final-    if (type == PSEUDO_SEL /* Pseudo_Selector_Ptr_Const lp = dynamic_cast<Pseudo_Selector_Ptr_Const>(this) */) {-      return *static_cast<Pseudo_Selector_Ptr_Const>(this) == rhs;-    }-    else if (type == WRAPPED_SEL /* Wrapped_Selector_Ptr_Const lw = dynamic_cast<Wrapped_Selector_Ptr_Const>(this) */) {-      return *static_cast<Wrapped_Selector_Ptr_Const>(this) == rhs;-    }-    else if (type == ATTR_SEL /* Attribute_Selector_Ptr_Const la = dynamic_cast<Attribute_Selector_Ptr_Const>(this) */) {-      return *static_cast<Attribute_Selector_Ptr_Const>(this) == rhs;-    }-    else if (name_ == rhs.name_)-    { return is_ns_eq(ns_, rhs.ns_); }-    else return false;-  }--  bool Simple_Selector::operator< (const Simple_Selector& rhs) const-  {-    Simple_Type type = simple_type();-    // dynamic cast is a bottleneck - use concrete type as types are final-    if (type == PSEUDO_SEL /* Pseudo_Selector_Ptr_Const lp = dynamic_cast<Pseudo_Selector_Ptr_Const>(this) */) {-      return *static_cast<Pseudo_Selector_Ptr_Const>(this) < rhs;-    }-    else if (type == WRAPPED_SEL /* Wrapped_Selector_Ptr_Const lw = dynamic_cast<Wrapped_Selector_Ptr_Const>(this) */) {-      return *static_cast<Wrapped_Selector_Ptr_Const>(this) < rhs;-    }-    else if (type == ATTR_SEL /* Attribute_Selector_Ptr_Const la = dynamic_cast<Attribute_Selector_Ptr_Const>(this) */) {-      return *static_cast<Attribute_Selector_Ptr_Const>(this) < rhs;-    }-    if (is_ns_eq(ns_, rhs.ns_))-    { return name_ < rhs.name_; }-    return ns_ < rhs.ns_;-  }--  bool Selector_List::operator== (const Selector& rhs) const-  {-    // solve the double dispatch problem by using RTTI information via dynamic cast-    if (Selector_List_Ptr_Const ls = dynamic_cast<Selector_List_Ptr_Const>(&rhs)) { return *this == *ls; }-    else if (Complex_Selector_Ptr_Const ls = dynamic_cast<Complex_Selector_Ptr_Const>(&rhs)) { return *this == *ls; }-    else if (Compound_Selector_Ptr_Const ls = dynamic_cast<Compound_Selector_Ptr_Const>(&rhs)) { return *this == *ls; }-    // no compare method-    return this == &rhs;-  }--  // Selector lists can be compared to comma lists-  bool Selector_List::operator==(const Expression& rhs) const-  {-    // solve the double dispatch problem by using RTTI information via dynamic cast-    if (List_Ptr_Const ls = dynamic_cast<List_Ptr_Const>(&rhs)) { return *this == *ls; }-    if (Selector_Ptr_Const ls = dynamic_cast<Selector_Ptr_Const>(&rhs)) { return *this == *ls; }-    // compare invalid (maybe we should error?)-    return false;-  }--  bool Selector_List::operator== (const Selector_List& rhs) const-  {-    // for array access-    size_t i = 0, n = 0;-    size_t iL = length();-    size_t nL = rhs.length();-    // create temporary vectors and sort them-    std::vector<Complex_Selector_Obj> l_lst = this->elements();-    std::vector<Complex_Selector_Obj> r_lst = rhs.elements();-    std::sort(l_lst.begin(), l_lst.end(), cmp_complex_selector());-    std::sort(r_lst.begin(), r_lst.end(), cmp_complex_selector());-    // process loop-    while (true)-    {-      // first check for valid index-      if (i == iL) return iL == nL;-      else if (n == nL) return iL == nL;-      // the access the vector items-      Complex_Selector_Obj l = l_lst[i];-      Complex_Selector_Obj r = r_lst[n];-      // skip nulls-      if (!l) ++i;-      else if (!r) ++n;-      // do the check-      else if (*l != *r)-      { return false; }-      // advance-      ++i; ++n;-    }-    // no mismatch-    return true;-  }--  bool Selector_List::operator< (const Selector& rhs) const-  {-    if (Selector_List_Ptr_Const sp = dynamic_cast<Selector_List_Ptr_Const>(&rhs)) return *this < *sp;-    return false;-  }--  bool Selector_List::operator< (const Selector_List& rhs) const-  {-    if (this->length() != rhs.length()) return false;-    for (size_t i = 0; i < rhs.length(); i ++) {-      if (!(*at(i) < *rhs.at(i))) return false;-    }-    return true;-  }--  Compound_Selector_Ptr Simple_Selector::unify_with(Compound_Selector_Ptr rhs, Context& ctx)-  {-    for (size_t i = 0, L = rhs->length(); i < L; ++i)-    { if (to_string(ctx.c_options) == rhs->at(i)->to_string(ctx.c_options)) return rhs; }--    // check for pseudo elements because they are always last-    size_t i, L;-    bool found = false;-    if (typeid(*this) == typeid(Pseudo_Selector) || typeid(*this) == typeid(Wrapped_Selector))-    {-      for (i = 0, L = rhs->length(); i < L; ++i)-      {-        if ((SASS_MEMORY_CAST(Pseudo_Selector, (*rhs)[i]) || SASS_MEMORY_CAST(Wrapped_Selector, (*rhs)[i])) && (*rhs)[L-1]->is_pseudo_element())-        { found = true; break; }-      }-    }-    else-    {-      for (i = 0, L = rhs->length(); i < L; ++i)-      {-        if (SASS_MEMORY_CAST(Pseudo_Selector, (*rhs)[i]) || SASS_MEMORY_CAST(Wrapped_Selector, (*rhs)[i]))-        { found = true; break; }-      }-    }-    if (!found)-    {-      rhs->append(this);-      return rhs;-    }-    rhs->elements().insert(rhs->elements().begin() + i, this);-    return rhs;-  }--  Simple_Selector_Ptr Element_Selector::unify_with(Simple_Selector_Ptr rhs, Context& ctx)-  {-    // check if ns can be extended-    // true for no ns or universal-    if (has_universal_ns())-    {-      // but dont extend with universal-      // true for valid ns and universal-      if (!rhs->is_universal_ns())-      {-        // overwrite the name if star is given as name-        if (this->name() == "*") { this->name(rhs->name()); }-        // now overwrite the namespace name and flag-        this->ns(rhs->ns()); this->has_ns(rhs->has_ns());-        // return copy-        return this;-      }-    }-    // namespace may changed, check the name now-    // overwrite star (but not with another star)-    if (name() == "*" && rhs->name() != "*")-    {-      // simply set the new name-      this->name(rhs->name());-      // return copy-      return this;-    }-    // return original-    return this;-  }--  Compound_Selector_Ptr Element_Selector::unify_with(Compound_Selector_Ptr rhs, Context& ctx)-  {-    // TODO: handle namespaces--    // if the rhs is empty, just return a copy of this-    if (rhs->length() == 0) {-      rhs->append(this);-      return rhs;-    }--    Simple_Selector_Ptr rhs_0 = &rhs->at(0);-    // otherwise, this is a tag name-    if (name() == "*")-    {-      if (typeid(*rhs_0) == typeid(Element_Selector))-      {-        // if rhs is universal, just return this tagname + rhs's qualifiers-        Element_Selector_Ptr ts = SASS_MEMORY_CAST_PTR(Element_Selector, rhs_0);-        rhs->at(0) = this->unify_with(ts, ctx);-        return rhs;-      }-      else if (SASS_MEMORY_CAST_PTR(Class_Selector, rhs_0) || SASS_MEMORY_CAST_PTR(Id_Selector, rhs_0)) {-        // qualifier is `.class`, so we can prefix with `ns|*.class`-        if (has_ns() && !rhs_0->has_ns()) {-          if (ns() != "*") rhs->elements().insert(rhs->begin(), this);-        }-        return rhs;-      }---      return rhs;-    }--    if (typeid(*rhs_0) == typeid(Element_Selector))-    {-      // if rhs is universal, just return this tagname + rhs's qualifiers-      if (rhs_0->name() != "*" && rhs_0->ns() != "*" && rhs_0->name() != name()) return 0;-      // otherwise create new compound and unify first simple selector-      rhs->at(0) = this->unify_with(rhs_0, ctx);-      return rhs;--    }-    // else it's a tag name and a bunch of qualifiers -- just append them-    if (name() != "*") rhs->elements().insert(rhs->begin(), this);-    return rhs;-  }--  Compound_Selector_Ptr Class_Selector::unify_with(Compound_Selector_Ptr rhs, Context& ctx)-  {-    rhs->has_line_break(has_line_break());-    return Simple_Selector::unify_with(rhs, ctx);-  }--  Compound_Selector_Ptr Id_Selector::unify_with(Compound_Selector_Ptr rhs, Context& ctx)-  {-    for (size_t i = 0, L = rhs->length(); i < L; ++i)-    {-      if (Id_Selector_Ptr sel = SASS_MEMORY_CAST(Id_Selector, rhs->at(i))) {-        if (sel->name() != name()) return 0;-      }-    }-    rhs->has_line_break(has_line_break());-    return Simple_Selector::unify_with(rhs, ctx);-  }--  Compound_Selector_Ptr Pseudo_Selector::unify_with(Compound_Selector_Ptr rhs, Context& ctx)-  {-    if (is_pseudo_element())-    {-      for (size_t i = 0, L = rhs->length(); i < L; ++i)-      {-        if (Pseudo_Selector_Ptr sel = SASS_MEMORY_CAST(Pseudo_Selector, rhs->at(i))) {-          if (sel->is_pseudo_element() && sel->name() != name()) return 0;-        }-      }-    }-    return Simple_Selector::unify_with(rhs, ctx);-  }--  bool Attribute_Selector::operator< (const Attribute_Selector& rhs) const-  {-    if (is_ns_eq(ns(), rhs.ns())) {-      if (name() == rhs.name()) {-        if (matcher() == rhs.matcher()) {-          bool no_lhs_val = value().isNull();-          bool no_rhs_val = rhs.value().isNull();-          if (no_lhs_val && no_rhs_val) {-            return true;-          }-          if (!no_lhs_val && !no_rhs_val) {-            return *value() < *rhs.value();-          }-        } else { return matcher() < rhs.matcher(); }-      } else { return name() < rhs.name(); }-    }-    return false;-  }--  bool Attribute_Selector::operator< (const Simple_Selector& rhs) const-  {-    if (Attribute_Selector_Ptr_Const w = dynamic_cast<Attribute_Selector_Ptr_Const>(&rhs))-    {-      return *this < *w;-    }-    if (is_ns_eq(ns(), rhs.ns()))-    { return name() < rhs.name(); }-    return ns() < rhs.ns();-  }--  bool Attribute_Selector::operator== (const Attribute_Selector& rhs) const-  {-    // get optional value state-    bool no_lhs_val = value().isNull();-    bool no_rhs_val = rhs.value().isNull();-    // both are null, therefore equal-    if (no_lhs_val && no_rhs_val) {-      return (name() == rhs.name())-        && (matcher() == rhs.matcher())-        && (is_ns_eq(ns(), rhs.ns()));-    }-    // both are defined, evaluate-    if (no_lhs_val == no_rhs_val) {-      return (name() == rhs.name())-        && (matcher() == rhs.matcher())-        && (is_ns_eq(ns(), rhs.ns()))-        && (*value() == *rhs.value());-    }-    // not equal-    return false;--  }--  bool Attribute_Selector::operator== (const Simple_Selector& rhs) const-  {-    if (Attribute_Selector_Ptr_Const w = dynamic_cast<Attribute_Selector_Ptr_Const>(&rhs))-    {-      return *this == *w;-    }-    if (is_ns_eq(ns(), rhs.ns()))-    { return name() == rhs.name(); }-    return ns() == rhs.ns();-  }--  bool Pseudo_Selector::operator== (const Pseudo_Selector& rhs) const-  {-    if (is_ns_eq(ns(), rhs.ns()) && name() == rhs.name())-    {-      String_Obj lhs_ex = expression();-      String_Obj rhs_ex = rhs.expression();-      if (rhs_ex && lhs_ex) return *lhs_ex == *rhs_ex;-      else return lhs_ex == rhs_ex;-    }-    else return false;-  }--  bool Pseudo_Selector::operator== (const Simple_Selector& rhs) const-  {-    if (Pseudo_Selector_Ptr_Const w = dynamic_cast<Pseudo_Selector_Ptr_Const>(&rhs))-    {-      return *this == *w;-    }-    if (is_ns_eq(ns(), rhs.ns()))-    { return name() == rhs.name(); }-    return ns() == rhs.ns();-  }--  bool Pseudo_Selector::operator< (const Pseudo_Selector& rhs) const-  {-    if (is_ns_eq(ns(), rhs.ns()) && name() == rhs.name())-    {-      String_Obj lhs_ex = expression();-      String_Obj rhs_ex = rhs.expression();-      if (rhs_ex && lhs_ex) return *lhs_ex < *rhs_ex;-      else return lhs_ex < rhs_ex;-    }-    if (is_ns_eq(ns(), rhs.ns()))-    { return name() < rhs.name(); }-    return ns() < rhs.ns();-  }--  bool Pseudo_Selector::operator< (const Simple_Selector& rhs) const-  {-    if (Pseudo_Selector_Ptr_Const w = dynamic_cast<Pseudo_Selector_Ptr_Const>(&rhs))-    {-      return *this < *w;-    }-    if (is_ns_eq(ns(), rhs.ns()))-    { return name() < rhs.name(); }-    return ns() < rhs.ns();-  }--  bool Wrapped_Selector::operator== (const Wrapped_Selector& rhs) const-  {-    if (is_ns_eq(ns(), rhs.ns()) && name() == rhs.name())-    { return *(selector()) == *(rhs.selector()); }-    else return false;-  }--  bool Wrapped_Selector::operator== (const Simple_Selector& rhs) const-  {-    if (Wrapped_Selector_Ptr_Const w = dynamic_cast<Wrapped_Selector_Ptr_Const>(&rhs))-    {-      return *this == *w;-    }-    if (is_ns_eq(ns(), rhs.ns()))-    { return name() == rhs.name(); }-    return ns() == rhs.ns();-  }--  bool Wrapped_Selector::operator< (const Wrapped_Selector& rhs) const-  {-    if (is_ns_eq(ns(), rhs.ns()) && name() == rhs.name())-    { return *(selector()) < *(rhs.selector()); }-    if (is_ns_eq(ns(), rhs.ns()))-    { return name() < rhs.name(); }-    return ns() < rhs.ns();-  }--  bool Wrapped_Selector::operator< (const Simple_Selector& rhs) const-  {-    if (Wrapped_Selector_Ptr_Const w = dynamic_cast<Wrapped_Selector_Ptr_Const>(&rhs))-    {-      return *this < *w;-    }-    if (is_ns_eq(ns(), rhs.ns()))-    { return name() < rhs.name(); }-    return ns() < rhs.ns();-  }--  bool Wrapped_Selector::is_superselector_of(Wrapped_Selector_Obj sub)-  {-    if (this->name() != sub->name()) return false;-    if (this->name() == ":current") return false;-    if (Selector_List_Obj rhs_list = SASS_MEMORY_CAST(Selector_List, sub->selector())) {-      if (Selector_List_Obj lhs_list = SASS_MEMORY_CAST(Selector_List, selector())) {-        return lhs_list->is_superselector_of(rhs_list);-      }-      error("is_superselector expected a Selector_List", sub->pstate());-    } else {-      error("is_superselector expected a Selector_List", sub->pstate());-    }-    return false;-  }--  bool Compound_Selector::is_superselector_of(Selector_List_Obj rhs, std::string wrapped)-  {-    for (Complex_Selector_Obj item : rhs->elements()) {-      if (is_superselector_of(&item, wrapped)) return true;-    }-    return false;-  }--  bool Compound_Selector::is_superselector_of(Complex_Selector_Obj rhs, std::string wrapped)-  {-    if (rhs->head()) return is_superselector_of(&rhs->head(), wrapped);-    return false;-  }--  bool Compound_Selector::is_superselector_of(Compound_Selector_Obj rhs, std::string wrapping)-  {-    Compound_Selector_Ptr lhs = this;-    Simple_Selector_Ptr lbase = lhs->base();-    Simple_Selector_Ptr rbase = rhs->base();--    // Check if pseudo-elements are the same between the selectors--    std::set<std::string> lpsuedoset, rpsuedoset;-    for (size_t i = 0, L = length(); i < L; ++i)-    {-      if ((*this)[i]->is_pseudo_element()) {-        std::string pseudo((*this)[i]->to_string());-        pseudo = pseudo.substr(pseudo.find_first_not_of(":")); // strip off colons to ensure :after matches ::after since ruby sass is forgiving-        lpsuedoset.insert(pseudo);-      }-    }-    for (size_t i = 0, L = rhs->length(); i < L; ++i)-    {-      if ((*rhs)[i]->is_pseudo_element()) {-        std::string pseudo((*rhs)[i]->to_string());-        pseudo = pseudo.substr(pseudo.find_first_not_of(":")); // strip off colons to ensure :after matches ::after since ruby sass is forgiving-        rpsuedoset.insert(pseudo);-      }-    }-    if (lpsuedoset != rpsuedoset) {-      return false;-    }--    std::set<std::string> lset, rset;--    if (lbase && rbase)-    {-      if (lbase->to_string() == rbase->to_string()) {-        for (size_t i = 1, L = length(); i < L; ++i)-        { lset.insert((*this)[i]->to_string()); }-        for (size_t i = 1, L = rhs->length(); i < L; ++i)-        { rset.insert((*rhs)[i]->to_string()); }-        return includes(rset.begin(), rset.end(), lset.begin(), lset.end());-      }-      return false;-    }--    for (size_t i = 0, iL = length(); i < iL; ++i)-    {-      Selector_Obj lhs = &(*this)[i];-      // very special case for wrapped matches selector-      if (Wrapped_Selector_Obj wrapped = SASS_MEMORY_CAST(Wrapped_Selector, lhs)) {-        if (wrapped->name() == ":not") {-          if (Selector_List_Obj not_list = SASS_MEMORY_CAST(Selector_List, wrapped->selector())) {-            if (not_list->is_superselector_of(rhs, wrapped->name())) return false;-          } else {-            throw std::runtime_error("wrapped not selector is not a list");-          }-        }-        if (wrapped->name() == ":matches" || wrapped->name() == ":-moz-any") {-          lhs = wrapped->selector();-          if (Selector_List_Obj list = SASS_MEMORY_CAST(Selector_List, wrapped->selector())) {-            if (Compound_Selector_Obj comp = SASS_MEMORY_CAST(Compound_Selector, rhs)) {-              if (!wrapping.empty() && wrapping != wrapped->name()) return false;-              if (wrapping.empty() || wrapping != wrapped->name()) {;-                if (list->is_superselector_of(comp, wrapped->name())) return true;-              }-            }-          }-        }-        Simple_Selector_Ptr rhs_sel = rhs->elements().size() > i ? &(*rhs)[i] : 0;-        if (Wrapped_Selector_Ptr wrapped_r = dynamic_cast<Wrapped_Selector_Ptr>(rhs_sel)) {-          if (wrapped->name() == wrapped_r->name()) {-          if (wrapped->is_superselector_of(wrapped_r)) {-             continue;-             rset.insert(lhs->to_string());--          }}-        }-      }-      // match from here on as strings-      lset.insert(lhs->to_string());-    }--    for (size_t n = 0, nL = rhs->length(); n < nL; ++n)-    {-      Selector_Obj r = &(*rhs)[n];-      if (Wrapped_Selector_Obj wrapped = SASS_MEMORY_CAST(Wrapped_Selector, r)) {-        if (wrapped->name() == ":not") {-          if (Selector_List_Obj ls = SASS_MEMORY_CAST(Selector_List, wrapped->selector())) {-            ls->remove_parent_selectors();-            if (is_superselector_of(ls, wrapped->name())) return false;-          }-        }-        if (wrapped->name() == ":matches" || wrapped->name() == ":-moz-any") {-          if (!wrapping.empty()) {-            if (wrapping != wrapped->name()) return false;-          }-          if (Selector_List_Obj ls = SASS_MEMORY_CAST(Selector_List, wrapped->selector())) {-            ls->remove_parent_selectors();-            return (is_superselector_of(ls, wrapped->name()));-          }-        }-      }-      rset.insert(r->to_string());-    }--    //for (auto l : lset) { cerr << "l: " << l << endl; }-    //for (auto r : rset) { cerr << "r: " << r << endl; }--    if (lset.empty()) return true;-    // return true if rset contains all the elements of lset-    return includes(rset.begin(), rset.end(), lset.begin(), lset.end());--  }--  // create complex selector (ancestor of) from compound selector-  Complex_Selector_Obj Compound_Selector::to_complex()-  {-    // create an intermediate complex selector-    return SASS_MEMORY_NEW(Complex_Selector,-                           pstate(),-                           Complex_Selector::ANCESTOR_OF,-                           this,-                           0);-  }--  Selector_List_Ptr Complex_Selector::unify_with(Complex_Selector_Ptr other, Context& ctx)-  {--    // get last tails (on the right side)-    Complex_Selector_Obj l_last = this->last();-    Complex_Selector_Obj r_last = other->last();--    // check valid pointers (assertion)-    SASS_ASSERT(l_last, "lhs is null");-    SASS_ASSERT(r_last, "rhs is null");--    // Not sure about this check, but closest way I could check-    // was to see if this is a ruby 'SimpleSequence' equivalent.-    // It seems to do the job correctly as some specs react to this-    if (l_last->combinator() != Combinator::ANCESTOR_OF) return 0;-    if (r_last->combinator() != Combinator::ANCESTOR_OF ) return 0;--    // get the headers for the last tails-    Compound_Selector_Obj l_last_head = l_last->head();-    Compound_Selector_Obj r_last_head = r_last->head();--    // check valid head pointers (assertion)-    SASS_ASSERT(l_last_head, "lhs head is null");-    SASS_ASSERT(r_last_head, "rhs head is null");--    // get the unification of the last compound selectors-    Compound_Selector_Obj unified = r_last_head->unify_with(&l_last_head, ctx);--    // abort if we could not unify heads-    if (unified == 0) return 0;--    // check for universal (star: `*`) selector-    bool is_universal = l_last_head->is_universal() ||-                        r_last_head->is_universal();--    if (is_universal)-    {-      // move the head-      l_last->head(0);-      r_last->head(unified);-    }--    // create nodes from both selectors-    Node lhsNode = complexSelectorToNode(this, ctx);-    Node rhsNode = complexSelectorToNode(other, ctx);--    // overwrite universal base-    if (!is_universal)-    {-      // create some temporaries to convert to node-      Complex_Selector_Obj fake = unified->to_complex();-      Node unified_node = complexSelectorToNode(&fake, ctx);-      // add to permutate the list?-      rhsNode.plus(unified_node);-    }--    // do some magic we inherit from node and extend-    Node node = Extend::subweave(lhsNode, rhsNode, ctx);-    Selector_List_Ptr result = SASS_MEMORY_NEW(Selector_List, pstate());-    NodeDequePtr col = node.collection(); // move from collection to list-    for (NodeDeque::iterator it = col->begin(), end = col->end(); it != end; it++)-    { result->append(nodeToComplexSelector(Node::naiveTrim(*it, ctx), ctx)); }--    // only return if list has some entries-    return result->length() ? result : 0;--  }--  bool Compound_Selector::operator== (const Compound_Selector& rhs) const-  {-    // for array access-    size_t i = 0, n = 0;-    size_t iL = length();-    size_t nL = rhs.length();-    // create temporary vectors and sort them-    std::vector<Simple_Selector_Obj> l_lst = this->elements();-    std::vector<Simple_Selector_Obj> r_lst = rhs.elements();-    std::sort(l_lst.begin(), l_lst.end(), cmp_simple_selector());-    std::sort(r_lst.begin(), r_lst.end(), cmp_simple_selector());-    // process loop-    while (true)-    {-      // first check for valid index-      if (i == iL) return iL == nL;-      else if (n == nL) return iL == nL;-      // the access the vector items-      Simple_Selector_Obj l = l_lst[i];-      Simple_Selector_Obj r = r_lst[n];-      // skip nulls-      if (!l) ++i;-      if (!r) ++n;-      // do the check now-      else if (*l != *r)-      { return false; }-      // advance now-      ++i; ++n;-    }-    // no mismatch-    return true;-  }--  bool Complex_Selector_Pointer_Compare::operator() (const Complex_Selector_Obj& pLeft, const Complex_Selector_Obj& pRight) const {-    return *pLeft < *pRight;-  }--  bool Complex_Selector::is_superselector_of(Compound_Selector_Obj rhs, std::string wrapping)-  {-    return last()->head() && last()->head()->is_superselector_of(rhs, wrapping);-  }--  bool Complex_Selector::is_superselector_of(Complex_Selector_Obj rhs, std::string wrapping)-  {-    Complex_Selector_Ptr lhs = this;-    // check for selectors with leading or trailing combinators-    if (!lhs->head() || !rhs->head())-    { return false; }-    Complex_Selector_Obj l_innermost = lhs->innermost();-    if (l_innermost->combinator() != Complex_Selector::ANCESTOR_OF)-    { return false; }-    Complex_Selector_Obj r_innermost = rhs->innermost();-    if (r_innermost->combinator() != Complex_Selector::ANCESTOR_OF)-    { return false; }-    // more complex (i.e., longer) selectors are always more specific-    size_t l_len = lhs->length(), r_len = rhs->length();-    if (l_len > r_len)-    { return false; }--    if (l_len == 1)-    { return lhs->head()->is_superselector_of(&rhs->last()->head(), wrapping); }--    // we have to look one tail deeper, since we cary the-    // combinator around for it (which is important here)-    if (rhs->tail() && lhs->tail() && combinator() != Complex_Selector::ANCESTOR_OF) {-      Complex_Selector_Obj lhs_tail = lhs->tail();-      Complex_Selector_Obj rhs_tail = rhs->tail();-      if (lhs_tail->combinator() != rhs_tail->combinator()) return false;-      if (lhs_tail->head() && !rhs_tail->head()) return false;-      if (!lhs_tail->head() && rhs_tail->head()) return false;-      if (lhs_tail->head() && rhs_tail->head()) {-        if (!lhs_tail->head()->is_superselector_of(&rhs_tail->head())) return false;-      }-    }--    bool found = false;-    Complex_Selector_Obj marker = rhs;-    for (size_t i = 0, L = rhs->length(); i < L; ++i) {-      if (i == L-1)-      { return false; }-      if (lhs->head() && marker->head() && lhs->head()->is_superselector_of(&marker->head(), wrapping))-      { found = true; break; }-      marker = marker->tail();-    }-    if (!found)-    { return false; }--    /*-      Hmm, I hope I have the logic right:--      if lhs has a combinator:-        if !(marker has a combinator) return false-        if !(lhs.combinator == '~' ? marker.combinator != '>' : lhs.combinator == marker.combinator) return false-        return lhs.tail-without-innermost.is_superselector_of(marker.tail-without-innermost)-      else if marker has a combinator:-        if !(marker.combinator == ">") return false-        return lhs.tail.is_superselector_of(marker.tail)-      else-        return lhs.tail.is_superselector_of(marker.tail)-    */-    if (lhs->combinator() != Complex_Selector::ANCESTOR_OF)-    {-      if (marker->combinator() == Complex_Selector::ANCESTOR_OF)-      { return false; }-      if (!(lhs->combinator() == Complex_Selector::PRECEDES ? marker->combinator() != Complex_Selector::PARENT_OF : lhs->combinator() == marker->combinator()))-      { return false; }-      return lhs->tail()->is_superselector_of(&marker->tail());-    }-    else if (marker->combinator() != Complex_Selector::ANCESTOR_OF)-    {-      if (marker->combinator() != Complex_Selector::PARENT_OF)-      { return false; }-      return lhs->tail()->is_superselector_of(&marker->tail());-    }-    else-    {-      return lhs->tail()->is_superselector_of(&marker->tail());-    }-    // catch-all-    return false;-  }--  size_t Complex_Selector::length() const-  {-    // TODO: make this iterative-    if (!tail()) return 1;-    return 1 + tail()->length();-  }--  Complex_Selector_Obj Complex_Selector::context(Context& ctx)-  {-    if (!tail()) return 0;-    if (!head()) return tail()->context(ctx);-    Complex_Selector_Obj cpy = SASS_MEMORY_NEW(Complex_Selector, pstate(), combinator(), head(), tail()->context(ctx));-    cpy->media_block(media_block());-    return cpy;-  }--  // append another complex selector at the end-  // check if we need to append some headers-  // then we need to check for the combinator-  // only then we can safely set the new tail-  void Complex_Selector::append(Context& ctx, Complex_Selector_Obj ss)-  {--    Complex_Selector_Obj t = ss->tail();-    Combinator c = ss->combinator();-    String_Obj r = ss->reference();-    Compound_Selector_Obj h = ss->head();--    if (ss->has_line_feed()) has_line_feed(true);-    if (ss->has_line_break()) has_line_break(true);--    // append old headers-    if (h && h->length()) {-      if (last()->combinator() != ANCESTOR_OF && c != ANCESTOR_OF) {-        error("Invalid parent selector", pstate_);-      } else if (last()->head_ && last()->head_->length()) {-        Compound_Selector_Obj rh = last()->head();-        size_t i = 0, L = h->length();-        if (SASS_MEMORY_CAST(Element_Selector, h->first())) {-          if (Class_Selector_Ptr sq = SASS_MEMORY_CAST(Class_Selector, rh->last())) {-            Class_Selector_Ptr sqs = SASS_MEMORY_COPY(sq);-            sqs->name(sqs->name() + (*h)[0]->name());-            sqs->pstate((*h)[0]->pstate());-            (*rh)[rh->length()-1] = sqs;-            rh->pstate(h->pstate());-            for (i = 1; i < L; ++i) rh->append(&(*h)[i]);-          } else if (Id_Selector_Ptr sq = SASS_MEMORY_CAST(Id_Selector, rh->last())) {-            Id_Selector_Ptr sqs = SASS_MEMORY_COPY(sq);-            sqs->name(sqs->name() + (*h)[0]->name());-            sqs->pstate((*h)[0]->pstate());-            (*rh)[rh->length()-1] = sqs;-            rh->pstate(h->pstate());-            for (i = 1; i < L; ++i) rh->append(&(*h)[i]);-          } else if (Element_Selector_Ptr ts = SASS_MEMORY_CAST(Element_Selector, rh->last())) {-            Element_Selector_Ptr tss = SASS_MEMORY_COPY(ts);-            tss->name(tss->name() + (*h)[0]->name());-            tss->pstate((*h)[0]->pstate());-            (*rh)[rh->length()-1] = tss;-            rh->pstate(h->pstate());-            for (i = 1; i < L; ++i) rh->append(&(*h)[i]);-          } else if (Placeholder_Selector_Ptr ps = SASS_MEMORY_CAST(Placeholder_Selector, rh->last())) {-            Placeholder_Selector_Ptr pss = SASS_MEMORY_COPY(ps);-            pss->name(pss->name() + (*h)[0]->name());-            pss->pstate((*h)[0]->pstate());-            (*rh)[rh->length()-1] = pss;-            rh->pstate(h->pstate());-            for (i = 1; i < L; ++i) rh->append(&(*h)[i]);-          } else {-            last()->head_->concat(&h);-          }-        } else {-          last()->head_->concat(&h);-        }-      } else {-        last()->head_->concat(&h);-      }-    } else {-      // std::cerr << "has no or empty head\n";-    }--    if (last()) {-      if (last()->combinator() != ANCESTOR_OF && c != ANCESTOR_OF) {-        Complex_Selector_Ptr inter = SASS_MEMORY_NEW(Complex_Selector, pstate());-        inter->reference(r);-        inter->combinator(c);-        inter->tail(t);-        last()->tail(inter);-      } else {-        if (last()->combinator() == ANCESTOR_OF) {-          last()->combinator(c);-          last()->reference(r);-        }-        last()->tail(t);-      }-    }---  }--  Selector_List_Ptr Selector_List::resolve_parent_refs(Context& ctx, std::vector<Selector_List_Obj>& pstack, bool implicit_parent)-  {-    if (!this->has_parent_ref()) return this;-    Selector_List_Ptr ss = SASS_MEMORY_NEW(Selector_List, pstate());-    Selector_List_Ptr ps = &pstack.back();-    for (size_t pi = 0, pL = ps->length(); pi < pL; ++pi) {-      for (size_t si = 0, sL = this->length(); si < sL; ++si) {-        Selector_List_Obj rv = at(si)->resolve_parent_refs(ctx, pstack, implicit_parent);-        ss->concat(&rv);-      }-    }-    return ss;-  }--  Selector_List_Ptr Complex_Selector::resolve_parent_refs(Context& ctx, std::vector<Selector_List_Obj>& pstack, bool implicit_parent)-  {-    Complex_Selector_Obj tail = this->tail();-    Compound_Selector_Obj head = this->head();-    Selector_List_Ptr parents = &pstack.back();--    if (!this->has_real_parent_ref() && !implicit_parent) {-      Selector_List_Ptr retval = SASS_MEMORY_NEW(Selector_List, pstate());-      retval->append(this);-      return retval;-    }--    // first resolve_parent_refs the tail (which may return an expanded list)-    Selector_List_Obj tails = tail ? tail->resolve_parent_refs(ctx, pstack, implicit_parent) : 0;--    if (head && head->length() > 0) {--      Selector_List_Obj retval;-      // we have a parent selector in a simple compound list-      // mix parent complex selector into the compound list-      if (SASS_MEMORY_CAST(Parent_Selector, (*head)[0])) {-        retval = SASS_MEMORY_NEW(Selector_List, pstate());--        // it turns out that real parent references reach-        // across @at-root rules, which comes unexpected-        if (parents == NULL && head->has_real_parent_ref()) {-          int i = pstack.size() - 1;-          while (!parents && i > -1) {-            parents = &pstack.at(i--);-          }-        }--        if (parents && parents->length()) {-          if (tails && tails->length() > 0) {-            for (size_t n = 0, nL = tails->length(); n < nL; ++n) {-              for (size_t i = 0, iL = parents->length(); i < iL; ++i) {-                Complex_Selector_Obj t = (*tails)[n];-                Complex_Selector_Obj parent = (*parents)[i];-                Complex_Selector_Obj s = SASS_MEMORY_CLONE(parent);-                Complex_Selector_Obj ss = SASS_MEMORY_CLONE(this);-                ss->tail(t ? SASS_MEMORY_CLONE(t) : 0);-                Compound_Selector_Obj h = SASS_MEMORY_COPY(head_);-                // remove parent selector from sequence-                if (h->length()) h->erase(h->begin());-                ss->head(h->length() ? &h : 0);-                // adjust for parent selector (1 char)-                if (h->length()) {-                  ParserState state(h->at(0)->pstate());-                  state.offset.column += 1;-                  state.column -= 1;-                  (*h)[0]->pstate(state);-                }-                // keep old parser state-                s->pstate(pstate());-                // append new tail-                s->append(ctx, ss);-                retval->append(s);-              }-            }-          }-          // have no tails but parents-          // loop above is inside out-          else {-            for (size_t i = 0, iL = parents->length(); i < iL; ++i) {-              Complex_Selector_Obj parent = (*parents)[i];-              Complex_Selector_Obj s = SASS_MEMORY_CLONE(parent);-              Complex_Selector_Obj ss = SASS_MEMORY_CLONE(this);-              // this is only if valid if the parent has no trailing op-              // otherwise we cannot append more simple selectors to head-              if (parent->last()->combinator() != ANCESTOR_OF) {-                throw Exception::InvalidParent(&parent, &ss);-              }-              ss->tail(tail ? SASS_MEMORY_CLONE(tail) : 0);-              Compound_Selector_Obj h = SASS_MEMORY_COPY(head_);-              // remove parent selector from sequence-              if (h->length()) h->erase(h->begin());-              ss->head(h->length() ? &h : 0);-              // \/ IMO ruby sass bug \/-              ss->has_line_feed(false);-              // adjust for parent selector (1 char)-              if (h->length()) {-                ParserState state(h->at(0)->pstate());-                state.offset.column += 1;-                state.column -= 1;-                (*h)[0]->pstate(state);-              }-              // keep old parser state-              s->pstate(pstate());-              // append new tail-              s->append(ctx, &ss);-              retval->append(s);-            }-          }-        }-        // have no parent but some tails-        else {-          if (tails && tails->length() > 0) {-            for (size_t n = 0, nL = tails->length(); n < nL; ++n) {-              Complex_Selector_Obj cpy = SASS_MEMORY_CLONE(this);-              cpy->tail(SASS_MEMORY_CLONE(tails->at(n)));-              cpy->head(SASS_MEMORY_NEW(Compound_Selector, head->pstate()));-              for (size_t i = 1, L = this->head()->length(); i < L; ++i)-                cpy->head()->append(&(*this->head())[i]);-              if (!cpy->head()->length()) cpy->head(0);-              retval->append(cpy->skip_empty_reference());-            }-          }-          // have no parent nor tails-          else {-            Complex_Selector_Obj cpy = SASS_MEMORY_CLONE(this);-            cpy->head(SASS_MEMORY_NEW(Compound_Selector, head->pstate()));-            for (size_t i = 1, L = this->head()->length(); i < L; ++i)-              cpy->head()->append(&(*this->head())[i]);-            if (!cpy->head()->length()) cpy->head(0);-            retval->append(cpy->skip_empty_reference());-          }-        }-      }-      // no parent selector in head-      else {-        retval = this->tails(ctx, &tails);-      }--      for (Simple_Selector_Obj ss : head->elements()) {-        if (Wrapped_Selector_Ptr ws = SASS_MEMORY_CAST(Wrapped_Selector, ss)) {-          if (Selector_List_Ptr sl = SASS_MEMORY_CAST(Selector_List, ws->selector())) {-            if (parents) ws->selector(sl->resolve_parent_refs(ctx, pstack, implicit_parent));-          }-        }-      }--      return retval.detach();--    }-    // has no head-    else {-      return this->tails(ctx, &tails);-    }--    // unreachable-    return 0;-  }--  Selector_List_Ptr Complex_Selector::tails(Context& ctx, Selector_List_Ptr tails)-  {-    Selector_List_Ptr rv = SASS_MEMORY_NEW(Selector_List, pstate_);-    if (tails && tails->length()) {-      for (size_t i = 0, iL = tails->length(); i < iL; ++i) {-        Complex_Selector_Obj pr = SASS_MEMORY_CLONE(this);-        pr->tail(tails->at(i));-        rv->append(pr);-      }-    }-    else {-      rv->append(this);-    }-    return rv;-  }--  // return the last tail that is defined-  Complex_Selector_Obj Complex_Selector::first()-  {-    // declare variables used in loop-    Complex_Selector_Obj cur = this;-    Compound_Selector_Obj head;-    // processing loop-    while (cur)-    {-      // get the head-      head = cur->head_;-      // abort (and return) if it is not a parent selector-      if (!head || head->length() != 1 || !SASS_MEMORY_CAST(Parent_Selector, (*head)[0])) {-        break;-      }-      // advance to next-      cur = cur->tail_;-    }-    // result-    return &cur;-  }--  // return the last tail that is defined-  Complex_Selector_Obj Complex_Selector::last()-  {-    // ToDo: implement with a while loop-    return tail_? tail_->last() : this;-  }--  Complex_Selector::Combinator Complex_Selector::clear_innermost()-  {-    Combinator c;-    if (!tail() || tail()->tail() == 0)-    { c = combinator(); combinator(ANCESTOR_OF); tail(0); }-    else-    { c = tail()->clear_innermost(); }-    return c;-  }--  void Complex_Selector::set_innermost(Complex_Selector_Obj val, Combinator c)-  {-    if (!tail())-    { tail(val); combinator(c); }-    else-    { tail()->set_innermost(val, c); }-  }--  void Complex_Selector::cloneChildren()-  {-    if (head()) head(SASS_MEMORY_CLONE(head()));-    if (tail()) tail(SASS_MEMORY_CLONE(tail()));-  }--  void Compound_Selector::cloneChildren()-  {-    for (size_t i = 0, l = length(); i < l; i++) {-      at(i) = SASS_MEMORY_CLONE(at(i));-    }-  }--  void Selector_List::cloneChildren()-  {-    for (size_t i = 0, l = length(); i < l; i++) {-      at(i) = SASS_MEMORY_CLONE(at(i));-    }-  }--  void Wrapped_Selector::cloneChildren()-  {-    selector(SASS_MEMORY_CLONE(selector()));-  }--  // remove parent selector references-  // basically unwraps parsed selectors-  void Selector_List::remove_parent_selectors()-  {-    // Check every rhs selector against left hand list-    for(size_t i = 0, L = length(); i < L; ++i) {-      if (!(*this)[i]->head()) continue;-      if ((*this)[i]->head()->is_empty_reference()) {-        // simply move to the next tail if we have "no" combinator-        if ((*this)[i]->combinator() == Complex_Selector::ANCESTOR_OF) {-          if ((*this)[i]->tail()) {-            if ((*this)[i]->has_line_feed()) {-              (*this)[i]->tail()->has_line_feed(true);-            }-            (*this)[i] = (*this)[i]->tail();-          }-        }-        // otherwise remove the first item from head-        else {-          (*this)[i]->head()->erase((*this)[i]->head()->begin());-        }-      }-    }-  }--  bool Selector_List::has_parent_ref()-  {-    for (Complex_Selector_Obj s : elements()) {-      if (s && s->has_parent_ref()) return true;-    }-    return false;-  }--  bool Selector_List::has_real_parent_ref()-  {-    for (Complex_Selector_Obj s : elements()) {-      if (s && s->has_real_parent_ref()) return true;-    }-    return false;-  }--  bool Selector_Schema::has_parent_ref()-  {-    if (String_Schema_Obj schema = SASS_MEMORY_CAST(String_Schema, contents())) {-      return schema->length() > 0 && SASS_MEMORY_CAST(Parent_Selector, schema->at(0)) != NULL;-    }-    return false;-  }--  bool Selector_Schema::has_real_parent_ref()-  {-    if (String_Schema_Obj schema = SASS_MEMORY_CAST(String_Schema, contents())) {-      Parent_Selector_Obj p = SASS_MEMORY_CAST(Parent_Selector, schema->at(0));-      return schema->length() > 0 && p && p->is_real_parent_ref();-    }-    return false;-  }--  void Selector_List::adjust_after_pushing(Complex_Selector_Obj c)-  {-    // if (c->has_reference())   has_reference(true);-  }--  // it's a superselector if every selector of the right side-  // list is a superselector of the given left side selector-  bool Complex_Selector::is_superselector_of(Selector_List_Obj sub, std::string wrapping)-  {-    // Check every rhs selector against left hand list-    for(size_t i = 0, L = sub->length(); i < L; ++i) {-      if (!is_superselector_of(&(*sub)[i], wrapping)) return false;-    }-    return true;-  }--  // it's a superselector if every selector of the right side-  // list is a superselector of the given left side selector-  bool Selector_List::is_superselector_of(Selector_List_Obj sub, std::string wrapping)-  {-    // Check every rhs selector against left hand list-    for(size_t i = 0, L = sub->length(); i < L; ++i) {-      if (!is_superselector_of(&(*sub)[i], wrapping)) return false;-    }-    return true;-  }--  // it's a superselector if every selector on the right side-  // is a superselector of any one of the left side selectors-  bool Selector_List::is_superselector_of(Compound_Selector_Obj sub, std::string wrapping)-  {-    // Check every lhs selector against right hand-    for(size_t i = 0, L = length(); i < L; ++i) {-      if ((*this)[i]->is_superselector_of(sub, wrapping)) return true;-    }-    return false;-  }--  // it's a superselector if every selector on the right side-  // is a superselector of any one of the left side selectors-  bool Selector_List::is_superselector_of(Complex_Selector_Obj sub, std::string wrapping)-  {-    // Check every lhs selector against right hand-    for(size_t i = 0, L = length(); i < L; ++i) {-      if ((*this)[i]->is_superselector_of(sub)) return true;-    }-    return false;-  }--  Selector_List_Ptr Selector_List::unify_with(Selector_List_Ptr rhs, Context& ctx) {-    std::vector<Complex_Selector_Obj> unified_complex_selectors;-    // Unify all of children with RHS's children, storing the results in `unified_complex_selectors`-    for (size_t lhs_i = 0, lhs_L = length(); lhs_i < lhs_L; ++lhs_i) {-      Complex_Selector_Obj seq1 = (*this)[lhs_i];-      for(size_t rhs_i = 0, rhs_L = rhs->length(); rhs_i < rhs_L; ++rhs_i) {-        Complex_Selector_Ptr seq2 = &rhs->at(rhs_i);--        Selector_List_Obj result = seq1->unify_with(seq2, ctx);-        if( result ) {-          for(size_t i = 0, L = result->length(); i < L; ++i) {-            unified_complex_selectors.push_back( &(*result)[i] );-          }-        }-      }-    }--    // Creates the final Selector_List by combining all the complex selectors-    Selector_List_Ptr final_result = SASS_MEMORY_NEW(Selector_List, pstate());-    for (auto itr = unified_complex_selectors.begin(); itr != unified_complex_selectors.end(); ++itr) {-      final_result->append(*itr);-    }-    return final_result;-  }--  void Selector_List::populate_extends(Selector_List_Obj extendee, Context& ctx, Subset_Map& extends)-  {--    Selector_List_Ptr extender = this;-    for (auto complex_sel : extendee->elements()) {-      Complex_Selector_Obj c = complex_sel;---      // Ignore any parent selectors, until we find the first non Selectorerence head-      Compound_Selector_Obj compound_sel = c->head();-      Complex_Selector_Obj pIter = complex_sel;-      while (pIter) {-        Compound_Selector_Obj pHead = pIter->head();-        if (pHead && SASS_MEMORY_CAST(Parent_Selector, pHead->elements()[0]) == NULL) {-          compound_sel = pHead;-          break;-        }--        pIter = pIter->tail();-      }--      if (!pIter->head() || pIter->tail()) {-        error("nested selectors may not be extended", c->pstate());-      }--      compound_sel->is_optional(extendee->is_optional());--      for (size_t i = 0, L = extender->length(); i < L; ++i) {-        extends.put(compound_sel, std::make_pair(&(*extender)[i], &compound_sel));-      }-    }-  };--  std::vector<std::string> Compound_Selector::to_str_vec()-  {-    std::vector<std::string> result(length());-    for (size_t i = 0, L = length(); i < L; ++i)-    { result.push_back((*this)[i]->to_string()); }-    return result;-  }--  void Compound_Selector::append(Simple_Selector_Ptr element)-  {-    Vectorized<Simple_Selector_Obj>::append(element);-    pstate_.offset += element->pstate().offset;-  }--  Compound_Selector_Ptr Compound_Selector::minus(Compound_Selector_Ptr rhs, Context& ctx)-  {-    Compound_Selector_Ptr result = SASS_MEMORY_NEW(Compound_Selector, pstate());-    // result->has_parent_reference(has_parent_reference());--    // not very efficient because it needs to preserve order-    for (size_t i = 0, L = length(); i < L; ++i)-    {-      bool found = false;-      std::string thisSelector((*this)[i]->to_string(ctx.c_options));-      for (size_t j = 0, M = rhs->length(); j < M; ++j)-      {-        if (thisSelector == (*rhs)[j]->to_string(ctx.c_options))-        {-          found = true;-          break;-        }-      }-      if (!found) result->append(&(*this)[i]);-    }--    return result;-  }--  void Compound_Selector::mergeSources(SourcesSet& sources, Context& ctx)-  {-    for (SourcesSet::iterator iterator = sources.begin(), endIterator = sources.end(); iterator != endIterator; ++iterator) {-      this->sources_.insert(SASS_MEMORY_CLONE(*iterator));-    }-  }--  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 NULL;-  }--  Argument_Obj Arguments::get_keyword_argument()-  {-    if (this->has_keyword_argument()) {-      for (Argument_Obj arg : this->elements()) {-        if (arg->is_keyword_argument()) {-          return arg;-        }-      }-    }-    return NULL;-  }--  void Arguments::adjust_after_pushing(Argument_Obj a)-  {-    if (!a->name().empty()) {-      if (/* has_rest_argument_ || */ has_keyword_argument_) {-        error("named arguments must precede variable-length argument", a->pstate());-      }-      has_named_arguments_ = true;-    }-    else if (a->is_rest_argument()) {-      if (has_rest_argument_) {-        error("functions and mixins may only be called with one variable-length argument", a->pstate());-      }-      if (has_keyword_argument_) {-        error("only keyword arguments may follow variable arguments", a->pstate());-      }-      has_rest_argument_ = true;-    }-    else if (a->is_keyword_argument()) {-      if (has_keyword_argument_) {-        error("functions and mixins may only be called with one keyword argument", a->pstate());-      }-      has_keyword_argument_ = true;-    }-    else {-      if (has_rest_argument_) {-        error("ordinal arguments must precede variable-length arguments", a->pstate());-      }-      if (has_named_arguments_) {-        error("ordinal arguments must precede named arguments", a->pstate());-      }-    }-  }--  bool Ruleset::is_invisible() const {-    if (Selector_List_Ptr sl = SASS_MEMORY_CAST(Selector_List, selector())) {-      for (size_t i = 0, L = sl->length(); i < L; ++i)-        if (!(*sl)[i]->has_placeholder()) return false;-    }-    return true;-  }--  bool Media_Block::is_invisible() const {-    for (size_t i = 0, L = block()->length(); i < L; ++i) {-      Statement_Obj stm = block()->at(i);-      if (!stm->is_invisible()) return false;-    }-    return true;-  }--  Number::Number(ParserState pstate, double val, std::string u, bool zero)-  : Value(pstate),-    value_(val),-    zero_(zero),-    numerator_units_(std::vector<std::string>()),-    denominator_units_(std::vector<std::string>()),-    hash_(0)-  {-    size_t l = 0, r = 0;-    if (!u.empty()) {-      bool nominator = true;-      while (true) {-        r = u.find_first_of("*/", l);-        std::string unit(u.substr(l, r == std::string::npos ? r : r - l));-        if (!unit.empty()) {-          if (nominator) numerator_units_.push_back(unit);-          else denominator_units_.push_back(unit);-        }-        if (r == std::string::npos) break;-        // ToDo: should error for multiple slashes-        // if (!nominator && u[r] == '/') error(...)-        if (u[r] == '/')-          nominator = false;-        l = r + 1;-      }-    }-    concrete_type(NUMBER);-  }--  std::string Number::unit() const-  {-    std::string u;-    for (size_t i = 0, S = numerator_units_.size(); i < S; ++i) {-      if (i) u += '*';-      u += numerator_units_[i];-    }-    if (!denominator_units_.empty()) u += '/';-    for (size_t i = 0, S = denominator_units_.size(); i < S; ++i) {-      if (i) u += '*';-      u += denominator_units_[i];-    }-    return u;-  }--  bool Number::is_valid_css_unit() const-  {-    return numerator_units().size() <= 1 &&-           denominator_units().size() == 0;-  }--  bool Number::is_unitless() const-  { return numerator_units_.empty() && denominator_units_.empty(); }--  void Number::normalize(const std::string& prefered, bool strict)-  {--    // 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<std::string, int> exponents;--    // initialize by summing up occurences in unit vectors-    for (size_t i = 0, S = numerator_units_.size(); i < S; ++i) ++ exponents[numerator_units_[i]];-    for (size_t i = 0, S = denominator_units_.size(); i < S; ++i) -- exponents[denominator_units_[i]];--    // the final conversion factor-    double factor = 1;--    // get the first entry of numerators-    // forward it when entry is converted-    std::vector<std::string>::iterator nom_it = numerator_units_.begin();-    std::vector<std::string>::iterator nom_end = numerator_units_.end();-    std::vector<std::string>::iterator denom_it = denominator_units_.begin();-    std::vector<std::string>::iterator denom_end = denominator_units_.end();--    // main normalization loop-    // should be close to optimal-    while (denom_it != denom_end)-    {-      // get and increment afterwards-      const std::string denom = *(denom_it ++);-      // skip already canceled out unit-      if (exponents[denom] >= 0) continue;-      // skip all units we don't know how to convert-      if (string_to_unit(denom) == UNKNOWN) continue;-      // now search for nominator-      while (nom_it != nom_end)-      {-        // get and increment afterwards-        const std::string nom = *(nom_it ++);-        // skip already canceled out unit-        if (exponents[nom] <= 0) continue;-        // skip all units we don't know how to convert-        if (string_to_unit(nom) == UNKNOWN) continue;-        // we now have two convertable units-        // add factor for current conversion-        factor *= conversion_factor(nom, denom, strict);-        // update nominator/denominator exponent-        -- exponents[nom]; ++ exponents[denom];-        // inner loop done-        break;-      }-    }--    // now we can build up the new unit arrays-    numerator_units_.clear();-    denominator_units_.clear();--    // build them by iterating over the exponents-    for (auto exp : exponents)-    {-      // maybe there is more effecient way to push-      // the same item multiple times to a vector?-      for(size_t i = 0, S = abs(exp.second); i < S; ++i)-      {-        // opted to have these switches in the inner loop-        // makes it more readable and should not cost much-        if (!exp.first.empty()) {-          if (exp.second < 0) denominator_units_.push_back(exp.first);-          else if (exp.second > 0) numerator_units_.push_back(exp.first);-        }-      }-    }--    // apply factor to value_-    // best precision this way-    value_ *= factor;--    // maybe convert to other unit-    // easier implemented on its own-    try { convert(prefered, strict); }-    catch (incompatibleUnits& err)-    { error(err.what(), pstate()); }-    catch (...) { throw; }--  }--  // this does not cover all cases (multiple prefered units)-  double Number::convert_factor(const Number& n) const-  {--    // 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 class-    // std::map<std::string, int> exponents;-    // initialize by summing up occurences in unit vectors-    // for (size_t i = 0, S = numerator_units_.size(); i < S; ++i) ++ exponents[unit_to_class(numerator_units_[i])];-    // for (size_t i = 0, S = denominator_units_.size(); i < S; ++i) -- exponents[unit_to_class(denominator_units_[i])];--    std::vector<std::string> l_miss_nums(0);-    std::vector<std::string> l_miss_dens(0);-    // create copy since we need these for state keeping-    std::vector<std::string> r_nums(n.numerator_units_);-    std::vector<std::string> r_dens(n.denominator_units_);--    std::vector<std::string>::const_iterator l_num_it = numerator_units_.begin();-    std::vector<std::string>::const_iterator l_num_end = numerator_units_.end();--    bool l_unitless = is_unitless();-    bool r_unitless = n.is_unitless();--    // overall conversion-    double factor = 1;--    // process all left numerators-    while (l_num_it != l_num_end)-    {-      // get and increment afterwards-      const std::string l_num = *(l_num_it ++);--      std::vector<std::string>::iterator r_num_it = r_nums.begin();-      std::vector<std::string>::iterator r_num_end = r_nums.end();--      bool found = false;-      // search for compatible numerator-      while (r_num_it != r_num_end)-      {-        // get and increment afterwards-        const std::string r_num = *(r_num_it);-        // get possible converstion factor for units-        double conversion = conversion_factor(l_num, r_num, false);-        // skip incompatible numerator-        if (conversion == 0) {-          ++ r_num_it;-          continue;-        }-        // apply to global factor-        factor *= conversion;-        // remove item from vector-        r_nums.erase(r_num_it);-        // found numerator-        found = true;-        break;-      }-      // maybe we did not find any-      // left numerator is leftover-      if (!found) l_miss_nums.push_back(l_num);-    }--    std::vector<std::string>::const_iterator l_den_it = denominator_units_.begin();-    std::vector<std::string>::const_iterator l_den_end = denominator_units_.end();--    // process all left denominators-    while (l_den_it != l_den_end)-    {-      // get and increment afterwards-      const std::string l_den = *(l_den_it ++);--      std::vector<std::string>::iterator r_den_it = r_dens.begin();-      std::vector<std::string>::iterator r_den_end = r_dens.end();--      bool found = false;-      // search for compatible denominator-      while (r_den_it != r_den_end)-      {-        // get and increment afterwards-        const std::string r_den = *(r_den_it);-        // get possible converstion factor for units-        double conversion = conversion_factor(l_den, r_den, false);-        // skip incompatible denominator-        if (conversion == 0) {-          ++ r_den_it;-          continue;-        }-        // apply to global factor-        factor *= conversion;-        // remove item from vector-        r_dens.erase(r_den_it);-        // found denominator-        found = true;-        break;-      }-      // maybe we did not find any-      // left denominator is leftover-      if (!found) l_miss_dens.push_back(l_den);-    }--    // check left-overs (ToDo: might cancel out)-    if (l_miss_nums.size() > 0 && !r_unitless) {-      throw Exception::IncompatibleUnits(n, *this);-    }-    if (l_miss_dens.size() > 0 && !r_unitless) {-      throw Exception::IncompatibleUnits(n, *this);-    }-    if (r_nums.size() > 0 && !l_unitless) {-      throw Exception::IncompatibleUnits(n, *this);-    }-    if (r_dens.size() > 0 && !l_unitless) {-      throw Exception::IncompatibleUnits(n, *this);-    }--    return factor;-  }--  // this does not cover all cases (multiple prefered units)-  bool Number::convert(const std::string& prefered, bool strict)-  {-    // no conversion if unit is empty-    if (prefered.empty()) return true;--    // 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<std::string, int> exponents;--    // initialize by summing up occurences in unit vectors-    for (size_t i = 0, S = numerator_units_.size(); i < S; ++i) ++ exponents[numerator_units_[i]];-    for (size_t i = 0, S = denominator_units_.size(); i < S; ++i) -- exponents[denominator_units_[i]];--    // the final conversion factor-    double factor = 1;--    std::vector<std::string>::iterator denom_it = denominator_units_.begin();-    std::vector<std::string>::iterator denom_end = denominator_units_.end();--    // main normalization loop-    // should be close to optimal-    while (denom_it != denom_end)-    {-      // get and increment afterwards-      const std::string denom = *(denom_it ++);-      // check if conversion is needed-      if (denom == prefered) continue;-      // skip already canceled out unit-      if (exponents[denom] >= 0) continue;-      // skip all units we don't know how to convert-      if (string_to_unit(denom) == UNKNOWN) continue;-      // we now have two convertable units-      // add factor for current conversion-      factor *= conversion_factor(denom, prefered, strict);-      // update nominator/denominator exponent-      ++ exponents[denom]; -- exponents[prefered];-    }--    std::vector<std::string>::iterator nom_it = numerator_units_.begin();-    std::vector<std::string>::iterator nom_end = numerator_units_.end();--    // now search for nominator-    while (nom_it != nom_end)-    {-      // get and increment afterwards-      const std::string nom = *(nom_it ++);-      // check if conversion is needed-      if (nom == prefered) continue;-      // skip already canceled out unit-      if (exponents[nom] <= 0) continue;-      // skip all units we don't know how to convert-      if (string_to_unit(nom) == UNKNOWN) continue;-      // we now have two convertable units-      // add factor for current conversion-      factor *= conversion_factor(nom, prefered, strict);-      // update nominator/denominator exponent-      -- exponents[nom]; ++ exponents[prefered];-    }--    // now we can build up the new unit arrays-    numerator_units_.clear();-    denominator_units_.clear();--    // build them by iterating over the exponents-    for (auto exp : exponents)-    {-      // maybe there is more effecient way to push-      // the same item multiple times to a vector?-      for(size_t i = 0, S = abs(exp.second); i < S; ++i)-      {-        // opted to have these switches in the inner loop-        // makes it more readable and should not cost much-        if (!exp.first.empty()) {-          if (exp.second < 0) denominator_units_.push_back(exp.first);-          else if (exp.second > 0) numerator_units_.push_back(exp.first);-        }-      }-    }--    // apply factor to value_-    // best precision this way-    value_ *= factor;--    // success?-    return true;--  }--  // useful for making one number compatible with another-  std::string Number::find_convertible_unit() const-  {-    for (size_t i = 0, S = numerator_units_.size(); i < S; ++i) {-      std::string u(numerator_units_[i]);-      if (string_to_unit(u) != UNKNOWN) return u;-    }-    for (size_t i = 0, S = denominator_units_.size(); i < S; ++i) {-      std::string u(denominator_units_[i]);-      if (string_to_unit(u) != UNKNOWN) return u;-    }-    return std::string();-  }--  bool Custom_Warning::operator== (const Expression& rhs) const-  {-    if (Custom_Warning_Ptr_Const r = dynamic_cast<Custom_Warning_Ptr_Const>(&rhs)) {-      return message() == r->message();-    }-    return false;-  }--  bool Custom_Error::operator== (const Expression& rhs) const-  {-    if (Custom_Error_Ptr_Const r = dynamic_cast<Custom_Error_Ptr_Const>(&rhs)) {-      return message() == r->message();-    }-    return false;-  }--  bool Number::eq (const Expression& rhs) const-  {-    if (Number_Ptr_Const r = dynamic_cast<Number_Ptr_Const>(&rhs)) {-      size_t lhs_units = numerator_units_.size() + denominator_units_.size();-      size_t rhs_units = r->numerator_units_.size() + r->denominator_units_.size();-      if (!lhs_units && !rhs_units) {-        return std::fabs(value() - r->value()) < NUMBER_EPSILON;-      }-      return (numerator_units_ == r->numerator_units_) &&-             (denominator_units_ == r->denominator_units_) &&-             std::fabs(value() - r->value()) < NUMBER_EPSILON;-    }-    return false;-  }--  bool Number::operator== (const Expression& rhs) const-  {-    if (Number_Ptr_Const r = dynamic_cast<Number_Ptr_Const>(&rhs)) {-      size_t lhs_units = numerator_units_.size() + denominator_units_.size();-      size_t rhs_units = r->numerator_units_.size() + r->denominator_units_.size();-      // unitless and only having one unit seems equivalent (will change in future)-      if (!lhs_units || !rhs_units) {-        return std::fabs(value() - r->value()) < NUMBER_EPSILON;-      }-      return (numerator_units_ == r->numerator_units_) &&-             (denominator_units_ == r->denominator_units_) &&-             std::fabs(value() - r->value()) < NUMBER_EPSILON;-    }-    return false;-  }--  bool Number::operator< (const Number& rhs) const-  {-    size_t lhs_units = numerator_units_.size() + denominator_units_.size();-    size_t rhs_units = rhs.numerator_units_.size() + rhs.denominator_units_.size();-    // unitless and only having one unit seems equivalent (will change in future)-    if (!lhs_units || !rhs_units) {-      return value() < rhs.value();-    }--    Number tmp_r(&rhs); // copy-    tmp_r.normalize(find_convertible_unit());-    std::string l_unit(unit());-    std::string r_unit(tmp_r.unit());-    if (unit() != tmp_r.unit()) {-      error("cannot compare numbers with incompatible units", pstate());-    }-    return value() < tmp_r.value();-  }--  bool String_Quoted::operator== (const Expression& rhs) const-  {-    if (String_Quoted_Ptr_Const qstr = dynamic_cast<String_Quoted_Ptr_Const>(&rhs)) {-      return (value() == qstr->value());-    } else if (String_Constant_Ptr_Const cstr = dynamic_cast<String_Constant_Ptr_Const>(&rhs)) {-      return (value() == cstr->value());-    }-    return false;-  }--  bool String_Constant::is_invisible() const {-    return value_.empty() && quote_mark_ == 0;-  }--  bool String_Constant::operator== (const Expression& rhs) const-  {-    if (String_Quoted_Ptr_Const qstr = dynamic_cast<String_Quoted_Ptr_Const>(&rhs)) {-      return (value() == qstr->value());-    } else if (String_Constant_Ptr_Const cstr = dynamic_cast<String_Constant_Ptr_Const>(&rhs)) {-      return (value() == cstr->value());-    }-    return false;-  }--  bool String_Schema::is_left_interpolant(void) const-  {-    return length() && first()->is_left_interpolant();-  }-  bool String_Schema::is_right_interpolant(void) const-  {-    return length() && last()->is_right_interpolant();-  }--  bool String_Schema::operator== (const Expression& rhs) const-  {-    if (String_Schema_Ptr_Const r = dynamic_cast<String_Schema_Ptr_Const>(&rhs)) {-      if (length() != r->length()) return false;-      for (size_t i = 0, L = length(); i < L; ++i) {-        Expression_Obj rv = (*r)[i];-        Expression_Obj lv = (*this)[i];-        if (!lv || !rv) return false;-        if (!(*lv == *rv)) return false;-      }-      return true;-    }-    return false;-  }--  bool Boolean::operator== (const Expression& rhs) const-  {-    if (Boolean_Ptr_Const r = dynamic_cast<Boolean_Ptr_Const>(&rhs)) {-      return (value() == r->value());-    }-    return false;-  }--  bool Color::operator== (const Expression& rhs) const-  {-    if (Color_Ptr_Const r = dynamic_cast<Color_Ptr_Const>(&rhs)) {-      return r_ == r->r() &&-             g_ == r->g() &&-             b_ == r->b() &&-             a_ == r->a();-    }-    return false;-  }--  bool List::operator== (const Expression& rhs) const-  {-    if (List_Ptr_Const r = dynamic_cast<List_Ptr_Const>(&rhs)) {-      if (length() != r->length()) return false;-      if (separator() != r->separator()) return false;-      for (size_t i = 0, L = length(); i < L; ++i) {-        Expression_Obj rv = r->at(i);-        Expression_Obj lv = this->at(i);-        if (!lv || !rv) return false;-        if (!(*lv == *rv)) return false;-      }-      return true;-    }-    return false;-  }--  bool Map::operator== (const Expression& rhs) const-  {-    if (Map_Ptr_Const r = dynamic_cast<Map_Ptr_Const>(&rhs)) {-      if (length() != r->length()) return false;-      for (auto key : keys()) {-        Expression_Obj lv = at(key);-        Expression_Obj rv = r->at(key);-        if (!rv || !lv) return false;-        if (!(*lv == *rv)) return false;-      }-      return true;-    }-    return false;-  }--  bool Null::operator== (const Expression& rhs) const-  {-    return rhs.concrete_type() == NULL_VAL;-  }--  size_t List::size() const {-    if (!is_arglist_) return length();-    // arglist expects a list of arguments-    // so we need to break before keywords-    for (size_t i = 0, L = length(); i < L; ++i) {-      Expression_Obj obj = this->at(i);-      if (Argument* arg = dynamic_cast<Argument*>(&obj)) {-        if (!arg->name().empty()) return i;-      }-    }-    return length();-  }--  Expression_Obj Hashed::at(Expression_Obj k) const-  {-    if (elements_.count(k))-    { return elements_.at(k); }-    else { return NULL; }-  }--  bool Binary_Expression::is_left_interpolant(void) const-  {-    return is_interpolant() || (left() && left()->is_left_interpolant());-  }-  bool Binary_Expression::is_right_interpolant(void) const-  {-    return is_interpolant() || (right() && right()->is_right_interpolant());-  }--  std::string AST_Node::to_string(Sass_Inspect_Options opt) const-  {-    Sass_Output_Options out(opt);-    Emitter emitter(out);-    Inspect i(emitter);-    i.in_declaration = true;-    // ToDo: inspect should be const-    const_cast<AST_Node_Ptr>(this)->perform(&i);-    return i.get_buffer();-  }--  std::string AST_Node::to_string() const-  {-    return to_string({ NESTED, 5 });-  }--  std::string String_Quoted::inspect() const-  {-    return quote(value_, '*');-  }--  std::string String_Constant::inspect() const-  {-    return quote(value_, '*');-  }--  //////////////////////////////////////////////////////////////////////////////////////////-  // Additional method on Lists to retrieve values directly or from an encompassed Argument.-  //////////////////////////////////////////////////////////////////////////////////////////-  Expression_Obj List::value_at_index(size_t i) {-    Expression_Obj obj = this->at(i);-    if (is_arglist_) {-      if (Argument* arg = dynamic_cast<Argument*>(&obj)) {-        return arg->value();-      } else {-        return &obj;-      }-    } else {-      return &obj;-    }-  }--  //////////////////////////////////////////////////////////////////////////////////////////-  // Convert map to (key, value) list.-  //////////////////////////////////////////////////////////////////////////////////////////-  List_Obj Map::to_list(Context& ctx, ParserState& pstate) {-    List_Obj ret = SASS_MEMORY_NEW(List, pstate, length(), SASS_COMMA);--    for (auto key : keys()) {-      List_Obj l = SASS_MEMORY_NEW(List, pstate, 2);-      l->append(&key);-      l->append(at(key));-      ret->append(&l);-    }--    return ret;-  }--  //////////////////////////////////////////////////////////////////////////////////////////-  // Copy implementations-  //////////////////////////////////////////////////////////////////////////////////////////--  #ifdef DEBUG_SHARED_PTR--  #define IMPLEMENT_AST_OPERATORS(klass) \-    klass##_Ptr klass::copy(std::string file, size_t line) const { \-      klass##_Ptr cpy = new klass(this); \-      cpy->trace(file, line); \-      return cpy; \-    } \-    klass##_Ptr klass::clone(std::string file, size_t line) const { \-      klass##_Ptr cpy = copy(file, line); \-      cpy->cloneChildren(); \-      return cpy; \-    } \--  #else--  #define IMPLEMENT_AST_OPERATORS(klass) \-    klass##_Ptr klass::copy() const { \-      return new klass(this); \-    } \-    klass##_Ptr klass::clone() const { \-      klass##_Ptr cpy = copy(); \-      cpy->cloneChildren(); \-      return cpy; \-    } \--  #endif--  IMPLEMENT_AST_OPERATORS(Supports_Operator);-  IMPLEMENT_AST_OPERATORS(Supports_Negation);-  IMPLEMENT_AST_OPERATORS(Compound_Selector);-  IMPLEMENT_AST_OPERATORS(Complex_Selector);-  IMPLEMENT_AST_OPERATORS(Element_Selector);-  IMPLEMENT_AST_OPERATORS(Class_Selector);-  IMPLEMENT_AST_OPERATORS(Id_Selector);-  IMPLEMENT_AST_OPERATORS(Pseudo_Selector);-  IMPLEMENT_AST_OPERATORS(Wrapped_Selector);-  IMPLEMENT_AST_OPERATORS(Selector_List);-  IMPLEMENT_AST_OPERATORS(Ruleset);-  IMPLEMENT_AST_OPERATORS(Media_Block);-  IMPLEMENT_AST_OPERATORS(Custom_Warning);-  IMPLEMENT_AST_OPERATORS(Custom_Error);-  IMPLEMENT_AST_OPERATORS(List);-  IMPLEMENT_AST_OPERATORS(Map);-  IMPLEMENT_AST_OPERATORS(Number);-  IMPLEMENT_AST_OPERATORS(Binary_Expression);-  IMPLEMENT_AST_OPERATORS(String_Schema);-  IMPLEMENT_AST_OPERATORS(String_Constant);-  IMPLEMENT_AST_OPERATORS(String_Quoted);-  IMPLEMENT_AST_OPERATORS(Boolean);-  IMPLEMENT_AST_OPERATORS(Color);-  IMPLEMENT_AST_OPERATORS(Null);-  IMPLEMENT_AST_OPERATORS(Parent_Selector);-  IMPLEMENT_AST_OPERATORS(Import);-  IMPLEMENT_AST_OPERATORS(Import_Stub);-  IMPLEMENT_AST_OPERATORS(Function_Call);-  IMPLEMENT_AST_OPERATORS(Directive);-  IMPLEMENT_AST_OPERATORS(At_Root_Block);-  IMPLEMENT_AST_OPERATORS(Supports_Block);-  IMPLEMENT_AST_OPERATORS(While);-  IMPLEMENT_AST_OPERATORS(Each);-  IMPLEMENT_AST_OPERATORS(For);-  IMPLEMENT_AST_OPERATORS(If);-  IMPLEMENT_AST_OPERATORS(Mixin_Call);-  IMPLEMENT_AST_OPERATORS(Extension);-  IMPLEMENT_AST_OPERATORS(Media_Query);-  IMPLEMENT_AST_OPERATORS(Media_Query_Expression);-  IMPLEMENT_AST_OPERATORS(Debug);-  IMPLEMENT_AST_OPERATORS(Error);-  IMPLEMENT_AST_OPERATORS(Warning);-  IMPLEMENT_AST_OPERATORS(Assignment);-  IMPLEMENT_AST_OPERATORS(Return);-  IMPLEMENT_AST_OPERATORS(At_Root_Query);-  IMPLEMENT_AST_OPERATORS(Variable);-  IMPLEMENT_AST_OPERATORS(Comment);-  IMPLEMENT_AST_OPERATORS(Attribute_Selector);-  IMPLEMENT_AST_OPERATORS(Supports_Interpolation);-  IMPLEMENT_AST_OPERATORS(Supports_Declaration);-  IMPLEMENT_AST_OPERATORS(Supports_Condition);-  IMPLEMENT_AST_OPERATORS(Parameters);-  IMPLEMENT_AST_OPERATORS(Parameter);-  IMPLEMENT_AST_OPERATORS(Arguments);-  IMPLEMENT_AST_OPERATORS(Argument);-  IMPLEMENT_AST_OPERATORS(Unary_Expression);-  IMPLEMENT_AST_OPERATORS(Function_Call_Schema);-  IMPLEMENT_AST_OPERATORS(Block);-  IMPLEMENT_AST_OPERATORS(Content);-  IMPLEMENT_AST_OPERATORS(Textual);-  IMPLEMENT_AST_OPERATORS(Trace);-  IMPLEMENT_AST_OPERATORS(Keyframe_Rule);-  IMPLEMENT_AST_OPERATORS(Bubble);-  IMPLEMENT_AST_OPERATORS(Selector_Schema);-  IMPLEMENT_AST_OPERATORS(Placeholder_Selector);-  IMPLEMENT_AST_OPERATORS(Definition);-  IMPLEMENT_AST_OPERATORS(Declaration);-+#include "eval.hpp"+#include "extend.hpp"+#include "emitter.hpp"+#include "color_maps.hpp"+#include "ast_fwd_decl.hpp"+#include <set>+#include <iomanip>+#include <iostream>+#include <algorithm>+#include <functional>+#include <cctype>+#include <locale>++namespace Sass {++  static Null sass_null(ParserState("null"));++  bool Wrapped_Selector::find ( bool (*f)(AST_Node_Obj) )+  {+    // check children first+    if (selector_) {+      if (selector_->find(f)) return true;+    }+    // execute last+    return f(this);+  }++  bool Selector_List::find ( bool (*f)(AST_Node_Obj) )+  {+    // check children first+    for (Complex_Selector_Obj sel : elements()) {+      if (sel->find(f)) return true;+    }+    // execute last+    return f(this);+  }++  bool Compound_Selector::find ( bool (*f)(AST_Node_Obj) )+  {+    // check children first+    for (Simple_Selector_Obj sel : elements()) {+      if (sel->find(f)) return true;+    }+    // execute last+    return f(this);+  }++  bool Complex_Selector::find ( bool (*f)(AST_Node_Obj) )+  {+    // check children first+    if (head_ && head_->find(f)) return true;+    if (tail_ && tail_->find(f)) return true;+    // execute last+    return f(this);+  }++  bool Supports_Operator::needs_parens(Supports_Condition_Obj cond) const {+    if (Supports_Operator_Obj op = Cast<Supports_Operator>(cond)) {+      return op->operand() != operand();+    }+    return Cast<Supports_Negation>(cond) != NULL;+  }++  bool Supports_Negation::needs_parens(Supports_Condition_Obj cond) const {+    return Cast<Supports_Negation>(cond) ||+           Cast<Supports_Operator>(cond);+  }++  void str_rtrim(std::string& str, const std::string& delimiters = " \f\n\r\t\v")+  {+    str.erase( str.find_last_not_of( delimiters ) + 1 );+  }++  void String_Constant::rtrim()+  {+    str_rtrim(value_);+  }++  void String_Schema::rtrim()+  {+    if (!empty()) {+      if (String_Ptr str = Cast<String>(last())) str->rtrim();+    }+  }++  void Argument::set_delayed(bool delayed)+  {+    if (value_) value_->set_delayed(delayed);+    is_delayed(delayed);+  }++  void Arguments::set_delayed(bool delayed)+  {+    for (Argument_Obj arg : elements()) {+      if (arg) arg->set_delayed(delayed);+    }+    is_delayed(delayed);+  }+++  bool At_Root_Query::exclude(std::string str)+  {+    bool with = feature() && unquote(feature()->to_string()).compare("with") == 0;+    List_Ptr l = static_cast<List_Ptr>(value().ptr());+    std::string v;++    if (with)+    {+      if (!l || l->length() == 0) return str.compare("rule") != 0;+      for (size_t i = 0, L = l->length(); i < L; ++i)+      {+        v = unquote((*l)[i]->to_string());+        if (v.compare("all") == 0 || v == str) return false;+      }+      return true;+    }+    else+    {+      if (!l || !l->length()) return str.compare("rule") == 0;+      for (size_t i = 0, L = l->length(); i < L; ++i)+      {+        v = unquote((*l)[i]->to_string());+        if (v.compare("all") == 0 || v == str) return true;+      }+      return false;+    }+  }++  void AST_Node::update_pstate(const ParserState& pstate)+  {+    pstate_.offset += pstate - pstate_ + pstate.offset;+  }++  bool Simple_Selector::is_ns_eq(const Simple_Selector& r) const+  {+    // https://github.com/sass/sass/issues/2229+    if ((has_ns_ == r.has_ns_) ||+        (has_ns_ && ns_.empty()) ||+        (r.has_ns_ && r.ns_.empty())+    ) {+      if (ns_.empty() && r.ns() == "*") return false;+      else if (r.ns().empty() && ns() == "*") return false;+      else return ns() == r.ns();+    }+    return false;+  }++  bool Compound_Selector::operator< (const Compound_Selector& rhs) const+  {+    size_t L = std::min(length(), rhs.length());+    for (size_t i = 0; i < L; ++i)+    {+      Simple_Selector_Obj l = (*this)[i];+      Simple_Selector_Obj r = rhs[i];+      if (!l && !r) return false;+      else if (!r) return false;+      else if (!l) return true;+      else if (*l != *r)+      { return *l < *r; }+    }+    // just compare the length now+    return length() < rhs.length();+  }++  bool Compound_Selector::has_parent_ref() const+  {+    for (Simple_Selector_Obj s : *this) {+      if (s && s->has_parent_ref()) return true;+    }+    return false;+  }++  bool Compound_Selector::has_real_parent_ref() const+  {+    for (Simple_Selector_Obj s : *this) {+      if (s && s->has_real_parent_ref()) return true;+    }+    return false;+  }++  bool Complex_Selector::has_parent_ref() const+  {+    return (head() && head()->has_parent_ref()) ||+           (tail() && tail()->has_parent_ref());+  }++  bool Complex_Selector::has_real_parent_ref() const+  {+    return (head() && head()->has_real_parent_ref()) ||+           (tail() && tail()->has_real_parent_ref());+  }++  bool Complex_Selector::operator< (const Complex_Selector& rhs) const+  {+    // const iterators for tails+    Complex_Selector_Ptr_Const l = this;+    Complex_Selector_Ptr_Const r = &rhs;+    Compound_Selector_Ptr l_h = NULL;+    Compound_Selector_Ptr r_h = NULL;+    if (l) l_h = l->head();+    if (r) r_h = r->head();+    // process all tails+    while (true)+    {+      #ifdef DEBUG+      // skip empty ancestor first+      if (l && l->is_empty_ancestor())+      {+        l_h = NULL;+        l = l->tail();+        if(l) l_h = l->head();+        continue;+      }+      // skip empty ancestor first+      if (r && r->is_empty_ancestor())+      {+        r_h = NULL;+        r = r->tail();+        if (r) r_h = r->head();+        continue;+      }+      #endif+      // check for valid selectors+      if (!l) return !!r;+      if (!r) return false;+      // both are null+      else if (!l_h && !r_h)+      {+        // check combinator after heads+        if (l->combinator() != r->combinator())+        { return l->combinator() < r->combinator(); }+        // advance to next tails+        l = l->tail();+        r = r->tail();+        // fetch the next headers+        l_h = NULL; r_h = NULL;+        if (l) l_h = l->head();+        if (r) r_h = r->head();+      }+      // one side is null+      else if (!r_h) return true;+      else if (!l_h) return false;+      // heads ok and equal+      else if (*l_h == *r_h)+      {+        // check combinator after heads+        if (l->combinator() != r->combinator())+        { return l->combinator() < r->combinator(); }+        // advance to next tails+        l = l->tail();+        r = r->tail();+        // fetch the next headers+        l_h = NULL; r_h = NULL;+        if (l) l_h = l->head();+        if (r) r_h = r->head();+      }+      // heads are not equal+      else return *l_h < *r_h;+    }+  }++  bool Complex_Selector::operator== (const Complex_Selector& rhs) const+  {+    // const iterators for tails+    Complex_Selector_Ptr_Const l = this;+    Complex_Selector_Ptr_Const r = &rhs;+    Compound_Selector_Ptr l_h = NULL;+    Compound_Selector_Ptr r_h = NULL;+    if (l) l_h = l->head();+    if (r) r_h = r->head();+    // process all tails+    while (true)+    {+      #ifdef DEBUG+      // skip empty ancestor first+      if (l && l->is_empty_ancestor())+      {+        l_h = NULL;+        l = l->tail();+        if (l) l_h = l->head();+        continue;+      }+      // skip empty ancestor first+      if (r && r->is_empty_ancestor())+      {+        r_h = NULL;+        r = r->tail();+        if (r) r_h = r->head();+        continue;+      }+      #endif+      // check the pointers+      if (!r) return !l;+      if (!l) return !r;+      // both are null+      if (!l_h && !r_h)+      {+        // check combinator after heads+        if (l->combinator() != r->combinator())+        { return l->combinator() < r->combinator(); }+        // advance to next tails+        l = l->tail();+        r = r->tail();+        // fetch the next heads+        l_h = NULL; r_h = NULL;+        if (l) l_h = l->head();+        if (r) r_h = r->head();+      }+      // equals if other head is empty+      else if ((!l_h && !r_h) ||+               (!l_h && r_h->empty()) ||+               (!r_h && l_h->empty()) ||+               (l_h && r_h && *l_h == *r_h))+      {+        // check combinator after heads+        if (l->combinator() != r->combinator())+        { return l->combinator() == r->combinator(); }+        // advance to next tails+        l = l->tail();+        r = r->tail();+        // fetch the next heads+        l_h = NULL; r_h = NULL;+        if (l) l_h = l->head();+        if (r) r_h = r->head();+      }+      // abort+      else break;+    }+    // unreachable+    return false;+  }++  Compound_Selector_Ptr Compound_Selector::unify_with(Compound_Selector_Ptr rhs)+  {+    if (empty()) return rhs;+    Compound_Selector_Obj unified = SASS_MEMORY_COPY(rhs);+    for (size_t i = 0, L = length(); i < L; ++i)+    {+      if (unified.isNull()) break;+      unified = at(i)->unify_with(unified);+    }+    return unified.detach();+  }++  bool Complex_Selector::operator== (const Selector& rhs) const+  {+    if (const Selector_List* sl = Cast<Selector_List>(&rhs)) return *this == *sl;+    if (const Simple_Selector* sp = Cast<Simple_Selector>(&rhs)) return *this == *sp;+    if (const Complex_Selector* cs = Cast<Complex_Selector>(&rhs)) return *this == *cs;+    if (const Compound_Selector* ch = Cast<Compound_Selector>(&rhs)) return *this == *ch;+    throw std::runtime_error("invalid selector base classes to compare");+  }+++  bool Complex_Selector::operator< (const Selector& rhs) const+  {+    if (const Selector_List* sl = Cast<Selector_List>(&rhs)) return *this < *sl;+    if (const Simple_Selector* sp = Cast<Simple_Selector>(&rhs)) return *this < *sp;+    if (const Complex_Selector* cs = Cast<Complex_Selector>(&rhs)) return *this < *cs;+    if (const Compound_Selector* ch = Cast<Compound_Selector>(&rhs)) return *this < *ch;+    throw std::runtime_error("invalid selector base classes to compare");+  }++  bool Compound_Selector::operator== (const Selector& rhs) const+  {+    if (const Selector_List* sl = Cast<Selector_List>(&rhs)) return *this == *sl;+    if (const Simple_Selector* sp = Cast<Simple_Selector>(&rhs)) return *this == *sp;+    if (const Complex_Selector* cs = Cast<Complex_Selector>(&rhs)) return *this == *cs;+    if (const Compound_Selector* ch = Cast<Compound_Selector>(&rhs)) return *this == *ch;+    throw std::runtime_error("invalid selector base classes to compare");+  }++  bool Compound_Selector::operator< (const Selector& rhs) const+  {+    if (const Selector_List* sl = Cast<Selector_List>(&rhs)) return *this < *sl;+    if (const Simple_Selector* sp = Cast<Simple_Selector>(&rhs)) return *this < *sp;+    if (const Complex_Selector* cs = Cast<Complex_Selector>(&rhs)) return *this < *cs;+    if (const Compound_Selector* ch = Cast<Compound_Selector>(&rhs)) return *this < *ch;+    throw std::runtime_error("invalid selector base classes to compare");+  }++  bool Selector_Schema::operator== (const Selector& rhs) const+  {+    if (const Selector_List* sl = Cast<Selector_List>(&rhs)) return *this == *sl;+    if (const Simple_Selector* sp = Cast<Simple_Selector>(&rhs)) return *this == *sp;+    if (const Complex_Selector* cs = Cast<Complex_Selector>(&rhs)) return *this == *cs;+    if (const Compound_Selector* ch = Cast<Compound_Selector>(&rhs)) return *this == *ch;+    throw std::runtime_error("invalid selector base classes to compare");+  }++  bool Selector_Schema::operator< (const Selector& rhs) const+  {+    if (const Selector_List* sl = Cast<Selector_List>(&rhs)) return *this < *sl;+    if (const Simple_Selector* sp = Cast<Simple_Selector>(&rhs)) return *this < *sp;+    if (const Complex_Selector* cs = Cast<Complex_Selector>(&rhs)) return *this < *cs;+    if (const Compound_Selector* ch = Cast<Compound_Selector>(&rhs)) return *this < *ch;+    throw std::runtime_error("invalid selector base classes to compare");+  }++  bool Simple_Selector::operator== (const Selector& rhs) const+  {+    if (Simple_Selector_Ptr_Const sp = Cast<Simple_Selector>(&rhs)) return *this == *sp;+    return false;+  }++  bool Simple_Selector::operator< (const Selector& rhs) const+  {+    if (Simple_Selector_Ptr_Const sp = Cast<Simple_Selector>(&rhs)) return *this < *sp;+    return false;+  }++  bool Simple_Selector::operator== (const Simple_Selector& rhs) const+  {+    // solve the double dispatch problem by using RTTI information via dynamic cast+    if (const Pseudo_Selector* lhs = Cast<Pseudo_Selector>(this)) {return *lhs == rhs; }+    else if (const Wrapped_Selector* lhs = Cast<Wrapped_Selector>(this)) {return *lhs == rhs; }+    else if (const Element_Selector* lhs = Cast<Element_Selector>(this)) {return *lhs == rhs; }+    else if (const Attribute_Selector* lhs = Cast<Attribute_Selector>(this)) {return *lhs == rhs; }+    else if (name_ == rhs.name_)+    { return is_ns_eq(rhs); }+    else return false;+  }++  bool Simple_Selector::operator< (const Simple_Selector& rhs) const+  {+    // solve the double dispatch problem by using RTTI information via dynamic cast+    if (const Pseudo_Selector* lhs = Cast<Pseudo_Selector>(this)) {return *lhs < rhs; }+    else if (const Wrapped_Selector* lhs = Cast<Wrapped_Selector>(this)) {return *lhs < rhs; }+    else if (const Element_Selector* lhs = Cast<Element_Selector>(this)) {return *lhs < rhs; }+    else if (const Attribute_Selector* lhs = Cast<Attribute_Selector>(this)) {return *lhs < rhs; }+    if (is_ns_eq(rhs))+    { return name_ < rhs.name_; }+    return ns_ < rhs.ns_;+  }++  bool Selector_List::operator== (const Selector& rhs) const+  {+    // solve the double dispatch problem by using RTTI information via dynamic cast+    if (Selector_List_Ptr_Const sl = Cast<Selector_List>(&rhs)) { return *this == *sl; }+    else if (Complex_Selector_Ptr_Const cpx = Cast<Complex_Selector>(&rhs)) { return *this == *cpx; }+    else if (Compound_Selector_Ptr_Const cpd = Cast<Compound_Selector>(&rhs)) { return *this == *cpd; }+    // no compare method+    return this == &rhs;+  }++  // Selector lists can be compared to comma lists+  bool Selector_List::operator== (const Expression& rhs) const+  {+    // solve the double dispatch problem by using RTTI information via dynamic cast+    if (List_Ptr_Const ls = Cast<List>(&rhs)) { return *ls == *this; }+    if (Selector_Ptr_Const ls = Cast<Selector>(&rhs)) { return *this == *ls; }+    // compare invalid (maybe we should error?)+    return false;+  }++  bool Selector_List::operator== (const Selector_List& rhs) const+  {+    // for array access+    size_t i = 0, n = 0;+    size_t iL = length();+    size_t nL = rhs.length();+    // create temporary vectors and sort them+    std::vector<Complex_Selector_Obj> l_lst = this->elements();+    std::vector<Complex_Selector_Obj> r_lst = rhs.elements();+    std::sort(l_lst.begin(), l_lst.end(), OrderNodes());+    std::sort(r_lst.begin(), r_lst.end(), OrderNodes());+    // process loop+    while (true)+    {+      // first check for valid index+      if (i == iL) return iL == nL;+      else if (n == nL) return iL == nL;+      // the access the vector items+      Complex_Selector_Obj l = l_lst[i];+      Complex_Selector_Obj r = r_lst[n];+      // skip nulls+      if (!l) ++i;+      else if (!r) ++n;+      // do the check+      else if (*l != *r)+      { return false; }+      // advance+      ++i; ++n;+    }+    // there is no break?!+  }++  bool Selector_List::operator< (const Selector& rhs) const+  {+    if (Selector_List_Ptr_Const sp = Cast<Selector_List>(&rhs)) return *this < *sp;+    return false;+  }++  bool Selector_List::operator< (const Selector_List& rhs) const+  {+    size_t l = rhs.length();+    if (length() < l) l = length();+    for (size_t i = 0; i < l; i ++) {+      if (*at(i) < *rhs.at(i)) return true;+    }+    return false;+  }++  Compound_Selector_Ptr Simple_Selector::unify_with(Compound_Selector_Ptr rhs)+  {+    for (size_t i = 0, L = rhs->length(); i < L; ++i)+    { if (to_string() == rhs->at(i)->to_string()) return rhs; }++    // check for pseudo elements because they are always last+    size_t i, L;+    bool found = false;+    if (typeid(*this) == typeid(Pseudo_Selector) || typeid(*this) == typeid(Wrapped_Selector) || typeid(*this) == typeid(Attribute_Selector))+    {+      for (i = 0, L = rhs->length(); i < L; ++i)+      {+        if ((Cast<Pseudo_Selector>((*rhs)[i]) || Cast<Wrapped_Selector>((*rhs)[i]) || Cast<Attribute_Selector>((*rhs)[i])) && (*rhs)[L-1]->is_pseudo_element())+        { found = true; break; }+      }+    }+    else+    {+      for (i = 0, L = rhs->length(); i < L; ++i)+      {+        if (Cast<Pseudo_Selector>((*rhs)[i]) || Cast<Wrapped_Selector>((*rhs)[i]) || Cast<Attribute_Selector>((*rhs)[i]))+        { found = true; break; }+      }+    }+    if (!found)+    {+      rhs->append(this);+    } else {+      rhs->elements().insert(rhs->elements().begin() + i, this);+    }+    return rhs;+  }++  Simple_Selector_Ptr Element_Selector::unify_with(Simple_Selector_Ptr rhs)+  {+    // check if ns can be extended+    // true for no ns or universal+    if (has_universal_ns())+    {+      // but dont extend with universal+      // true for valid ns and universal+      if (!rhs->is_universal_ns())+      {+        // overwrite the name if star is given as name+        if (this->name() == "*") { this->name(rhs->name()); }+        // now overwrite the namespace name and flag+        this->ns(rhs->ns()); this->has_ns(rhs->has_ns());+        // return copy+        return this;+      }+    }+    // namespace may changed, check the name now+    // overwrite star (but not with another star)+    if (name() == "*" && rhs->name() != "*")+    {+      // simply set the new name+      this->name(rhs->name());+      // return copy+      return this;+    }+    // return original+    return this;+  }++  Compound_Selector_Ptr Element_Selector::unify_with(Compound_Selector_Ptr rhs)+  {+    // TODO: handle namespaces++    // if the rhs is empty, just return a copy of this+    if (rhs->length() == 0) {+      rhs->append(this);+      return rhs;+    }++    Simple_Selector_Ptr rhs_0 = rhs->at(0);+    // otherwise, this is a tag name+    if (name() == "*")+    {+      if (typeid(*rhs_0) == typeid(Element_Selector))+      {+        // if rhs is universal, just return this tagname + rhs's qualifiers+        Element_Selector_Ptr ts = Cast<Element_Selector>(rhs_0);+        rhs->at(0) = this->unify_with(ts);+        return rhs;+      }+      else if (Cast<Class_Selector>(rhs_0) || Cast<Id_Selector>(rhs_0)) {+        // qualifier is `.class`, so we can prefix with `ns|*.class`+        if (has_ns() && !rhs_0->has_ns()) {+          if (ns() != "*") rhs->elements().insert(rhs->begin(), this);+        }+        return rhs;+      }+++      return rhs;+    }++    if (typeid(*rhs_0) == typeid(Element_Selector))+    {+      // if rhs is universal, just return this tagname + rhs's qualifiers+      if (rhs_0->name() != "*" && rhs_0->ns() != "*" && rhs_0->name() != name()) return 0;+      // otherwise create new compound and unify first simple selector+      rhs->at(0) = this->unify_with(rhs_0);+      return rhs;++    }+    // else it's a tag name and a bunch of qualifiers -- just append them+    if (name() != "*") rhs->elements().insert(rhs->begin(), this);+    return rhs;+  }++  Compound_Selector_Ptr Class_Selector::unify_with(Compound_Selector_Ptr rhs)+  {+    rhs->has_line_break(has_line_break());+    return Simple_Selector::unify_with(rhs);+  }++  Compound_Selector_Ptr Id_Selector::unify_with(Compound_Selector_Ptr rhs)+  {+    for (size_t i = 0, L = rhs->length(); i < L; ++i)+    {+      if (Id_Selector_Ptr sel = Cast<Id_Selector>(rhs->at(i))) {+        if (sel->name() != name()) return 0;+      }+    }+    rhs->has_line_break(has_line_break());+    return Simple_Selector::unify_with(rhs);+  }++  Compound_Selector_Ptr Pseudo_Selector::unify_with(Compound_Selector_Ptr rhs)+  {+    if (is_pseudo_element())+    {+      for (size_t i = 0, L = rhs->length(); i < L; ++i)+      {+        if (Pseudo_Selector_Ptr sel = Cast<Pseudo_Selector>(rhs->at(i))) {+          if (sel->is_pseudo_element() && sel->name() != name()) return 0;+        }+      }+    }+    return Simple_Selector::unify_with(rhs);+  }++  bool Attribute_Selector::operator< (const Attribute_Selector& rhs) const+  {+    if (is_ns_eq(rhs)) {+      if (name() == rhs.name()) {+        if (matcher() == rhs.matcher()) {+          bool no_lhs_val = value().isNull();+          bool no_rhs_val = rhs.value().isNull();+          if (no_lhs_val && no_rhs_val) return false; // equal+          else if (no_lhs_val) return true; // lhs is null+          else if (no_rhs_val) return false; // rhs is null+          return *value() < *rhs.value(); // both are given+        } else { return matcher() < rhs.matcher(); }+      } else { return name() < rhs.name(); }+    } else { return ns() < rhs.ns(); }+  }++  bool Attribute_Selector::operator< (const Simple_Selector& rhs) const+  {+    if (Attribute_Selector_Ptr_Const w = Cast<Attribute_Selector>(&rhs))+    {+      return *this < *w;+    }+    if (is_ns_eq(rhs))+    { return name() < rhs.name(); }+    return ns() < rhs.ns();+  }++  bool Attribute_Selector::operator== (const Attribute_Selector& rhs) const+  {+    // get optional value state+    bool no_lhs_val = value().isNull();+    bool no_rhs_val = rhs.value().isNull();+    // both are null, therefore equal+    if (no_lhs_val && no_rhs_val) {+      return (name() == rhs.name())+        && (matcher() == rhs.matcher())+        && (is_ns_eq(rhs));+    }+    // both are defined, evaluate+    if (no_lhs_val == no_rhs_val) {+      return (name() == rhs.name())+        && (matcher() == rhs.matcher())+        && (is_ns_eq(rhs))+        && (*value() == *rhs.value());+    }+    // not equal+    return false;++  }++  bool Attribute_Selector::operator== (const Simple_Selector& rhs) const+  {+    if (Attribute_Selector_Ptr_Const w = Cast<Attribute_Selector>(&rhs))+    {+      return is_ns_eq(rhs) &&+             name() == rhs.name() &&+             *this == *w;+    }+    return false;+  }++  bool Element_Selector::operator< (const Element_Selector& rhs) const+  {+    if (is_ns_eq(rhs))+    { return name() < rhs.name(); }+    return ns() < rhs.ns();+  }++  bool Element_Selector::operator< (const Simple_Selector& rhs) const+  {+    if (Element_Selector_Ptr_Const w = Cast<Element_Selector>(&rhs))+    {+      return *this < *w;+    }+    if (is_ns_eq(rhs))+    { return name() < rhs.name(); }+    return ns() < rhs.ns();+  }++  bool Element_Selector::operator== (const Element_Selector& rhs) const+  {+    return is_ns_eq(rhs) &&+           name() == rhs.name();+  }++  bool Element_Selector::operator== (const Simple_Selector& rhs) const+  {+    if (Element_Selector_Ptr_Const w = Cast<Element_Selector>(&rhs))+    {+      return is_ns_eq(rhs) &&+             name() == rhs.name() &&+             *this == *w;+    }+    return false;+  }++  bool Pseudo_Selector::operator== (const Pseudo_Selector& rhs) const+  {+    if (is_ns_eq(rhs) && name() == rhs.name())+    {+      String_Obj lhs_ex = expression();+      String_Obj rhs_ex = rhs.expression();+      if (rhs_ex && lhs_ex) return *lhs_ex == *rhs_ex;+      else return lhs_ex.ptr() == rhs_ex.ptr();+    }+    else return false;+  }++  bool Pseudo_Selector::operator== (const Simple_Selector& rhs) const+  {+    if (Pseudo_Selector_Ptr_Const w = Cast<Pseudo_Selector>(&rhs))+    {+      return *this == *w;+    }+    return is_ns_eq(rhs) &&+           name() == rhs.name();+  }++  bool Pseudo_Selector::operator< (const Pseudo_Selector& rhs) const+  {+    if (is_ns_eq(rhs) && name() == rhs.name())+    {+      String_Obj lhs_ex = expression();+      String_Obj rhs_ex = rhs.expression();+      if (rhs_ex && lhs_ex) return *lhs_ex < *rhs_ex;+      else return lhs_ex.ptr() < rhs_ex.ptr();+    }+    if (is_ns_eq(rhs))+    { return name() < rhs.name(); }+    return ns() < rhs.ns();+  }++  bool Pseudo_Selector::operator< (const Simple_Selector& rhs) const+  {+    if (Pseudo_Selector_Ptr_Const w = Cast<Pseudo_Selector>(&rhs))+    {+      return *this < *w;+    }+    if (is_ns_eq(rhs))+    { return name() < rhs.name(); }+    return ns() < rhs.ns();+  }++  bool Wrapped_Selector::operator== (const Wrapped_Selector& rhs) const+  {+    if (is_ns_eq(rhs) && name() == rhs.name())+    { return *(selector()) == *(rhs.selector()); }+    else return false;+  }++  bool Wrapped_Selector::operator== (const Simple_Selector& rhs) const+  {+    if (Wrapped_Selector_Ptr_Const w = Cast<Wrapped_Selector>(&rhs))+    {+      return *this == *w;+    }+    return is_ns_eq(rhs) &&+           name() == rhs.name();+  }++  bool Wrapped_Selector::operator< (const Wrapped_Selector& rhs) const+  {+    if (is_ns_eq(rhs) && name() == rhs.name())+    { return *(selector()) < *(rhs.selector()); }+    if (is_ns_eq(rhs))+    { return name() < rhs.name(); }+    return ns() < rhs.ns();+  }++  bool Wrapped_Selector::operator< (const Simple_Selector& rhs) const+  {+    if (Wrapped_Selector_Ptr_Const w = Cast<Wrapped_Selector>(&rhs))+    {+      return *this < *w;+    }+    if (is_ns_eq(rhs))+    { return name() < rhs.name(); }+    return ns() < rhs.ns();+  }++  bool Wrapped_Selector::is_superselector_of(Wrapped_Selector_Obj sub)+  {+    if (this->name() != sub->name()) return false;+    if (this->name() == ":current") return false;+    if (Selector_List_Obj rhs_list = Cast<Selector_List>(sub->selector())) {+      if (Selector_List_Obj lhs_list = Cast<Selector_List>(selector())) {+        return lhs_list->is_superselector_of(rhs_list);+      }+    }+    coreError("is_superselector expected a Selector_List", sub->pstate());+    return false;+  }++  bool Compound_Selector::is_superselector_of(Selector_List_Obj rhs, std::string wrapped)+  {+    for (Complex_Selector_Obj item : rhs->elements()) {+      if (is_superselector_of(item, wrapped)) return true;+    }+    return false;+  }++  bool Compound_Selector::is_superselector_of(Complex_Selector_Obj rhs, std::string wrapped)+  {+    if (rhs->head()) return is_superselector_of(rhs->head(), wrapped);+    return false;+  }++  bool Compound_Selector::is_superselector_of(Compound_Selector_Obj rhs, std::string wrapping)+  {+    Compound_Selector_Ptr lhs = this;+    Simple_Selector_Ptr lbase = lhs->base();+    Simple_Selector_Ptr rbase = rhs->base();++    // Check if pseudo-elements are the same between the selectors++    std::set<std::string> lpsuedoset, rpsuedoset;+    for (size_t i = 0, L = length(); i < L; ++i)+    {+      if ((*this)[i]->is_pseudo_element()) {+        std::string pseudo((*this)[i]->to_string());+        pseudo = pseudo.substr(pseudo.find_first_not_of(":")); // strip off colons to ensure :after matches ::after since ruby sass is forgiving+        lpsuedoset.insert(pseudo);+      }+    }+    for (size_t i = 0, L = rhs->length(); i < L; ++i)+    {+      if ((*rhs)[i]->is_pseudo_element()) {+        std::string pseudo((*rhs)[i]->to_string());+        pseudo = pseudo.substr(pseudo.find_first_not_of(":")); // strip off colons to ensure :after matches ::after since ruby sass is forgiving+        rpsuedoset.insert(pseudo);+      }+    }+    if (lpsuedoset != rpsuedoset) {+      return false;+    }++    // would like to replace this without stringification+    // https://github.com/sass/sass/issues/2229+    // SimpleSelectorSet lset, rset;+    std::set<std::string> lset, rset;++    if (lbase && rbase)+    {+      if (lbase->to_string() == rbase->to_string()) {+        for (size_t i = 1, L = length(); i < L; ++i)+        { lset.insert((*this)[i]->to_string()); }+        for (size_t i = 1, L = rhs->length(); i < L; ++i)+        { rset.insert((*rhs)[i]->to_string()); }+        return includes(rset.begin(), rset.end(), lset.begin(), lset.end());+      }+      return false;+    }++    for (size_t i = 0, iL = length(); i < iL; ++i)+    {+      Selector_Obj wlhs = (*this)[i];+      // very special case for wrapped matches selector+      if (Wrapped_Selector_Obj wrapped = Cast<Wrapped_Selector>(wlhs)) {+        if (wrapped->name() == ":not") {+          if (Selector_List_Obj not_list = Cast<Selector_List>(wrapped->selector())) {+            if (not_list->is_superselector_of(rhs, wrapped->name())) return false;+          } else {+            throw std::runtime_error("wrapped not selector is not a list");+          }+        }+        if (wrapped->name() == ":matches" || wrapped->name() == ":-moz-any") {+          wlhs = wrapped->selector();+          if (Selector_List_Obj list = Cast<Selector_List>(wrapped->selector())) {+            if (Compound_Selector_Obj comp = Cast<Compound_Selector>(rhs)) {+              if (!wrapping.empty() && wrapping != wrapped->name()) return false;+              if (wrapping.empty() || wrapping != wrapped->name()) {;+                if (list->is_superselector_of(comp, wrapped->name())) return true;+              }+            }+          }+        }+        Simple_Selector_Ptr rhs_sel = NULL;+        if (rhs->elements().size() > i) rhs_sel = (*rhs)[i];+        if (Wrapped_Selector_Ptr wrapped_r = Cast<Wrapped_Selector>(rhs_sel)) {+          if (wrapped->name() == wrapped_r->name()) {+          if (wrapped->is_superselector_of(wrapped_r)) {+             continue;+          }}+        }+      }+      // match from here on as strings+      lset.insert(wlhs->to_string());+    }++    for (size_t n = 0, nL = rhs->length(); n < nL; ++n)+    {+      Selector_Obj r = (*rhs)[n];+      if (Wrapped_Selector_Obj wrapped = Cast<Wrapped_Selector>(r)) {+        if (wrapped->name() == ":not") {+          if (Selector_List_Obj ls = Cast<Selector_List>(wrapped->selector())) {+            ls->remove_parent_selectors();+            if (is_superselector_of(ls, wrapped->name())) return false;+          }+        }+        if (wrapped->name() == ":matches" || wrapped->name() == ":-moz-any") {+          if (!wrapping.empty()) {+            if (wrapping != wrapped->name()) return false;+          }+          if (Selector_List_Obj ls = Cast<Selector_List>(wrapped->selector())) {+            ls->remove_parent_selectors();+            return (is_superselector_of(ls, wrapped->name()));+          }+        }+      }+      rset.insert(r->to_string());+    }++    //for (auto l : lset) { cerr << "l: " << l << endl; }+    //for (auto r : rset) { cerr << "r: " << r << endl; }++    if (lset.empty()) return true;+    // return true if rset contains all the elements of lset+    return includes(rset.begin(), rset.end(), lset.begin(), lset.end());++  }++  // create complex selector (ancestor of) from compound selector+  Complex_Selector_Obj Compound_Selector::to_complex()+  {+    // create an intermediate complex selector+    return SASS_MEMORY_NEW(Complex_Selector,+                           pstate(),+                           Complex_Selector::ANCESTOR_OF,+                           this,+                           0);+  }++  Selector_List_Ptr Complex_Selector::unify_with(Complex_Selector_Ptr other)+  {++    // get last tails (on the right side)+    Complex_Selector_Obj l_last = this->last();+    Complex_Selector_Obj r_last = other->last();++    // check valid pointers (assertion)+    SASS_ASSERT(l_last, "lhs is null");+    SASS_ASSERT(r_last, "rhs is null");++    // Not sure about this check, but closest way I could check+    // was to see if this is a ruby 'SimpleSequence' equivalent.+    // It seems to do the job correctly as some specs react to this+    if (l_last->combinator() != Combinator::ANCESTOR_OF) return 0;+    if (r_last->combinator() != Combinator::ANCESTOR_OF ) return 0;++    // get the headers for the last tails+    Compound_Selector_Obj l_last_head = l_last->head();+    Compound_Selector_Obj r_last_head = r_last->head();++    // check valid head pointers (assertion)+    SASS_ASSERT(l_last_head, "lhs head is null");+    SASS_ASSERT(r_last_head, "rhs head is null");++    // get the unification of the last compound selectors+    Compound_Selector_Obj unified = r_last_head->unify_with(l_last_head);++    // abort if we could not unify heads+    if (unified == 0) return 0;++    // check for universal (star: `*`) selector+    bool is_universal = l_last_head->is_universal() ||+                        r_last_head->is_universal();++    if (is_universal)+    {+      // move the head+      l_last->head(0);+      r_last->head(unified);+    }++    // create nodes from both selectors+    Node lhsNode = complexSelectorToNode(this);+    Node rhsNode = complexSelectorToNode(other);++    // overwrite universal base+    if (!is_universal)+    {+      // create some temporaries to convert to node+      Complex_Selector_Obj fake = unified->to_complex();+      Node unified_node = complexSelectorToNode(fake);+      // add to permutate the list?+      rhsNode.plus(unified_node);+    }++    // do some magic we inherit from node and extend+    Node node = subweave(lhsNode, rhsNode);+    Selector_List_Obj result = SASS_MEMORY_NEW(Selector_List, pstate());+    NodeDequePtr col = node.collection(); // move from collection to list+    for (NodeDeque::iterator it = col->begin(), end = col->end(); it != end; it++)+    { result->append(nodeToComplexSelector(Node::naiveTrim(*it))); }++    // only return if list has some entries+    return result->length() ? result.detach() : 0;++  }++  bool Compound_Selector::operator== (const Compound_Selector& rhs) const+  {+    // for array access+    size_t i = 0, n = 0;+    size_t iL = length();+    size_t nL = rhs.length();+    // create temporary vectors and sort them+    std::vector<Simple_Selector_Obj> l_lst = this->elements();+    std::vector<Simple_Selector_Obj> r_lst = rhs.elements();+    std::sort(l_lst.begin(), l_lst.end(), OrderNodes());+    std::sort(r_lst.begin(), r_lst.end(), OrderNodes());+    // process loop+    while (true)+    {+      // first check for valid index+      if (i == iL) return iL == nL;+      else if (n == nL) return iL == nL;+      // the access the vector items+      Simple_Selector_Obj l = l_lst[i];+      Simple_Selector_Obj r = r_lst[n];+      // skip nulls+      if (!l) ++i;+      if (!r) ++n;+      // do the check now+      else if (*l != *r)+      { return false; }+      // advance now+      ++i; ++n;+    }+    // there is no break?!+  }++  bool Complex_Selector::is_superselector_of(Compound_Selector_Obj rhs, std::string wrapping)+  {+    return last()->head() && last()->head()->is_superselector_of(rhs, wrapping);+  }++  bool Complex_Selector::is_superselector_of(Complex_Selector_Obj rhs, std::string wrapping)+  {+    Complex_Selector_Ptr lhs = this;+    // check for selectors with leading or trailing combinators+    if (!lhs->head() || !rhs->head())+    { return false; }+    Complex_Selector_Obj l_innermost = lhs->innermost();+    if (l_innermost->combinator() != Complex_Selector::ANCESTOR_OF)+    { return false; }+    Complex_Selector_Obj r_innermost = rhs->innermost();+    if (r_innermost->combinator() != Complex_Selector::ANCESTOR_OF)+    { return false; }+    // more complex (i.e., longer) selectors are always more specific+    size_t l_len = lhs->length(), r_len = rhs->length();+    if (l_len > r_len)+    { return false; }++    if (l_len == 1)+    { return lhs->head()->is_superselector_of(rhs->last()->head(), wrapping); }++    // we have to look one tail deeper, since we cary the+    // combinator around for it (which is important here)+    if (rhs->tail() && lhs->tail() && combinator() != Complex_Selector::ANCESTOR_OF) {+      Complex_Selector_Obj lhs_tail = lhs->tail();+      Complex_Selector_Obj rhs_tail = rhs->tail();+      if (lhs_tail->combinator() != rhs_tail->combinator()) return false;+      if (lhs_tail->head() && !rhs_tail->head()) return false;+      if (!lhs_tail->head() && rhs_tail->head()) return false;+      if (lhs_tail->head() && rhs_tail->head()) {+        if (!lhs_tail->head()->is_superselector_of(rhs_tail->head())) return false;+      }+    }++    bool found = false;+    Complex_Selector_Obj marker = rhs;+    for (size_t i = 0, L = rhs->length(); i < L; ++i) {+      if (i == L-1)+      { return false; }+      if (lhs->head() && marker->head() && lhs->head()->is_superselector_of(marker->head(), wrapping))+      { found = true; break; }+      marker = marker->tail();+    }+    if (!found)+    { return false; }++    /*+      Hmm, I hope I have the logic right:++      if lhs has a combinator:+        if !(marker has a combinator) return false+        if !(lhs.combinator == '~' ? marker.combinator != '>' : lhs.combinator == marker.combinator) return false+        return lhs.tail-without-innermost.is_superselector_of(marker.tail-without-innermost)+      else if marker has a combinator:+        if !(marker.combinator == ">") return false+        return lhs.tail.is_superselector_of(marker.tail)+      else+        return lhs.tail.is_superselector_of(marker.tail)+    */+    if (lhs->combinator() != Complex_Selector::ANCESTOR_OF)+    {+      if (marker->combinator() == Complex_Selector::ANCESTOR_OF)+      { return false; }+      if (!(lhs->combinator() == Complex_Selector::PRECEDES ? marker->combinator() != Complex_Selector::PARENT_OF : lhs->combinator() == marker->combinator()))+      { return false; }+      return lhs->tail()->is_superselector_of(marker->tail());+    }+    else if (marker->combinator() != Complex_Selector::ANCESTOR_OF)+    {+      if (marker->combinator() != Complex_Selector::PARENT_OF)+      { return false; }+      return lhs->tail()->is_superselector_of(marker->tail());+    }+    return lhs->tail()->is_superselector_of(marker->tail());+  }++  size_t Complex_Selector::length() const+  {+    // TODO: make this iterative+    if (!tail()) return 1;+    return 1 + tail()->length();+  }++  // append another complex selector at the end+  // check if we need to append some headers+  // then we need to check for the combinator+  // only then we can safely set the new tail+  void Complex_Selector::append(Complex_Selector_Obj ss, Backtraces& traces)+  {++    Complex_Selector_Obj t = ss->tail();+    Combinator c = ss->combinator();+    String_Obj r = ss->reference();+    Compound_Selector_Obj h = ss->head();++    if (ss->has_line_feed()) has_line_feed(true);+    if (ss->has_line_break()) has_line_break(true);++    // append old headers+    if (h && h->length()) {+      if (last()->combinator() != ANCESTOR_OF && c != ANCESTOR_OF) {+        traces.push_back(Backtrace(pstate()));+        throw Exception::InvalidParent(this, traces, ss);+      } else if (last()->head_ && last()->head_->length()) {+        Compound_Selector_Obj rh = last()->head();+        size_t i;+        size_t L = h->length();+        if (Cast<Element_Selector>(h->first())) {+          if (Class_Selector_Ptr cs = Cast<Class_Selector>(rh->last())) {+            Class_Selector_Ptr sqs = SASS_MEMORY_COPY(cs);+            sqs->name(sqs->name() + (*h)[0]->name());+            sqs->pstate((*h)[0]->pstate());+            (*rh)[rh->length()-1] = sqs;+            rh->pstate(h->pstate());+            for (i = 1; i < L; ++i) rh->append((*h)[i]);+          } else if (Id_Selector_Ptr is = Cast<Id_Selector>(rh->last())) {+            Id_Selector_Ptr sqs = SASS_MEMORY_COPY(is);+            sqs->name(sqs->name() + (*h)[0]->name());+            sqs->pstate((*h)[0]->pstate());+            (*rh)[rh->length()-1] = sqs;+            rh->pstate(h->pstate());+            for (i = 1; i < L; ++i) rh->append((*h)[i]);+          } else if (Element_Selector_Ptr ts = Cast<Element_Selector>(rh->last())) {+            Element_Selector_Ptr tss = SASS_MEMORY_COPY(ts);+            tss->name(tss->name() + (*h)[0]->name());+            tss->pstate((*h)[0]->pstate());+            (*rh)[rh->length()-1] = tss;+            rh->pstate(h->pstate());+            for (i = 1; i < L; ++i) rh->append((*h)[i]);+          } else if (Placeholder_Selector_Ptr ps = Cast<Placeholder_Selector>(rh->last())) {+            Placeholder_Selector_Ptr pss = SASS_MEMORY_COPY(ps);+            pss->name(pss->name() + (*h)[0]->name());+            pss->pstate((*h)[0]->pstate());+            (*rh)[rh->length()-1] = pss;+            rh->pstate(h->pstate());+            for (i = 1; i < L; ++i) rh->append((*h)[i]);+          } else {+            last()->head_->concat(h);+          }+        } else {+          last()->head_->concat(h);+        }+      } else if (last()->head_) {+        last()->head_->concat(h);+      }+    } else {+      // std::cerr << "has no or empty head\n";+    }++    if (last()) {+      if (last()->combinator() != ANCESTOR_OF && c != ANCESTOR_OF) {+        Complex_Selector_Ptr inter = SASS_MEMORY_NEW(Complex_Selector, pstate());+        inter->reference(r);+        inter->combinator(c);+        inter->tail(t);+        last()->tail(inter);+      } else {+        if (last()->combinator() == ANCESTOR_OF) {+          last()->combinator(c);+          last()->reference(r);+        }+        last()->tail(t);+      }+    }++  }++  Selector_List_Obj Selector_List::eval(Eval& eval)+  {+    Selector_List_Obj list = schema() ?+      eval(schema()) : eval(this);+    list->schema(schema());+    return list;+  }++  Selector_List_Ptr Selector_List::resolve_parent_refs(std::vector<Selector_List_Obj>& pstack, Backtraces& traces, bool implicit_parent)+  {+    if (!this->has_parent_ref()) return this;+    Selector_List_Ptr ss = SASS_MEMORY_NEW(Selector_List, pstate());+    Selector_List_Ptr ps = pstack.back();+    for (size_t pi = 0, pL = ps->length(); pi < pL; ++pi) {+      for (size_t si = 0, sL = this->length(); si < sL; ++si) {+        Selector_List_Obj rv = at(si)->resolve_parent_refs(pstack, traces, implicit_parent);+        ss->concat(rv);+      }+    }+    return ss;+  }++  Selector_List_Ptr Complex_Selector::resolve_parent_refs(std::vector<Selector_List_Obj>& pstack, Backtraces& traces, bool implicit_parent)+  {+    Complex_Selector_Obj tail = this->tail();+    Compound_Selector_Obj head = this->head();+    Selector_List_Ptr parents = pstack.back();++    if (!this->has_real_parent_ref() && !implicit_parent) {+      Selector_List_Ptr retval = SASS_MEMORY_NEW(Selector_List, pstate());+      retval->append(this);+      return retval;+    }++    // first resolve_parent_refs the tail (which may return an expanded list)+    Selector_List_Obj tails = tail ? tail->resolve_parent_refs(pstack, traces, implicit_parent) : 0;++    if (head && head->length() > 0) {++      Selector_List_Obj retval;+      // we have a parent selector in a simple compound list+      // mix parent complex selector into the compound list+      if (Cast<Parent_Selector>((*head)[0])) {+        retval = SASS_MEMORY_NEW(Selector_List, pstate());++        // it turns out that real parent references reach+        // across @at-root rules, which comes unexpected+        if (parents == NULL && head->has_real_parent_ref()) {+          int i = pstack.size() - 1;+          while (!parents && i > -1) {+            parents = pstack.at(i--);+          }+        }++        if (parents && parents->length()) {+          if (tails && tails->length() > 0) {+            for (size_t n = 0, nL = tails->length(); n < nL; ++n) {+              for (size_t i = 0, iL = parents->length(); i < iL; ++i) {+                Complex_Selector_Obj t = (*tails)[n];+                Complex_Selector_Obj parent = (*parents)[i];+                Complex_Selector_Obj s = SASS_MEMORY_CLONE(parent);+                Complex_Selector_Obj ss = SASS_MEMORY_CLONE(this);+                ss->tail(t ? SASS_MEMORY_CLONE(t) : NULL);+                Compound_Selector_Obj h = SASS_MEMORY_COPY(head_);+                // remove parent selector from sequence+                if (h->length()) {+                  h->erase(h->begin());+                  ss->head(h);+                } else {+                  ss->head(NULL);+                }+                // adjust for parent selector (1 char)+                // if (h->length()) {+                //   ParserState state(h->at(0)->pstate());+                //   state.offset.column += 1;+                //   state.column -= 1;+                //   (*h)[0]->pstate(state);+                // }+                // keep old parser state+                s->pstate(pstate());+                // append new tail+                s->append(ss, traces);+                retval->append(s);+              }+            }+          }+          // have no tails but parents+          // loop above is inside out+          else {+            for (size_t i = 0, iL = parents->length(); i < iL; ++i) {+              Complex_Selector_Obj parent = (*parents)[i];+              Complex_Selector_Obj s = SASS_MEMORY_CLONE(parent);+              Complex_Selector_Obj ss = SASS_MEMORY_CLONE(this);+              // this is only if valid if the parent has no trailing op+              // otherwise we cannot append more simple selectors to head+              if (parent->last()->combinator() != ANCESTOR_OF) {+                traces.push_back(Backtrace(pstate()));+                throw Exception::InvalidParent(parent, traces, ss);+              }+              ss->tail(tail ? SASS_MEMORY_CLONE(tail) : NULL);+              Compound_Selector_Obj h = SASS_MEMORY_COPY(head_);+              // remove parent selector from sequence+              if (h->length()) {+                h->erase(h->begin());+                ss->head(h);+              } else {+                ss->head(NULL);+              }+              // \/ IMO ruby sass bug \/+              ss->has_line_feed(false);+              // adjust for parent selector (1 char)+              // if (h->length()) {+              //   ParserState state(h->at(0)->pstate());+              //   state.offset.column += 1;+              //   state.column -= 1;+              //   (*h)[0]->pstate(state);+              // }+              // keep old parser state+              s->pstate(pstate());+              // append new tail+              s->append(ss, traces);+              retval->append(s);+            }+          }+        }+        // have no parent but some tails+        else {+          if (tails && tails->length() > 0) {+            for (size_t n = 0, nL = tails->length(); n < nL; ++n) {+              Complex_Selector_Obj cpy = SASS_MEMORY_CLONE(this);+              cpy->tail(SASS_MEMORY_CLONE(tails->at(n)));+              cpy->head(SASS_MEMORY_NEW(Compound_Selector, head->pstate()));+              for (size_t i = 1, L = this->head()->length(); i < L; ++i)+                cpy->head()->append((*this->head())[i]);+              if (!cpy->head()->length()) cpy->head(0);+              retval->append(cpy->skip_empty_reference());+            }+          }+          // have no parent nor tails+          else {+            Complex_Selector_Obj cpy = SASS_MEMORY_CLONE(this);+            cpy->head(SASS_MEMORY_NEW(Compound_Selector, head->pstate()));+            for (size_t i = 1, L = this->head()->length(); i < L; ++i)+              cpy->head()->append((*this->head())[i]);+            if (!cpy->head()->length()) cpy->head(0);+            retval->append(cpy->skip_empty_reference());+          }+        }+      }+      // no parent selector in head+      else {+        retval = this->tails(tails);+      }++      for (Simple_Selector_Obj ss : head->elements()) {+        if (Wrapped_Selector_Ptr ws = Cast<Wrapped_Selector>(ss)) {+          if (Selector_List_Ptr sl = Cast<Selector_List>(ws->selector())) {+            if (parents) ws->selector(sl->resolve_parent_refs(pstack, traces, implicit_parent));+          }+        }+      }++      return retval.detach();++    }+    // has no head+    return this->tails(tails);+  }++  Selector_List_Ptr Complex_Selector::tails(Selector_List_Ptr tails)+  {+    Selector_List_Ptr rv = SASS_MEMORY_NEW(Selector_List, pstate_);+    if (tails && tails->length()) {+      for (size_t i = 0, iL = tails->length(); i < iL; ++i) {+        Complex_Selector_Obj pr = SASS_MEMORY_CLONE(this);+        pr->tail(tails->at(i));+        rv->append(pr);+      }+    }+    else {+      rv->append(this);+    }+    return rv;+  }++  // return the last tail that is defined+  Complex_Selector_Obj Complex_Selector::first()+  {+    // declare variables used in loop+    Complex_Selector_Obj cur = this;+    Compound_Selector_Obj head;+    // processing loop+    while (cur)+    {+      // get the head+      head = cur->head_;+      // abort (and return) if it is not a parent selector+      if (!head || head->length() != 1 || !Cast<Parent_Selector>((*head)[0])) {+        break;+      }+      // advance to next+      cur = cur->tail_;+    }+    // result+    return cur;+  }++  // return the last tail that is defined+  Complex_Selector_Obj Complex_Selector::last()+  {+    Complex_Selector_Ptr cur = this;+    Complex_Selector_Ptr nxt = cur;+    // loop until last+    while (nxt) {+      cur = nxt;+      nxt = cur->tail();+    }+    return cur;+  }++  Complex_Selector::Combinator Complex_Selector::clear_innermost()+  {+    Combinator c;+    if (!tail() || tail()->tail() == 0)+    { c = combinator(); combinator(ANCESTOR_OF); tail(0); }+    else+    { c = tail()->clear_innermost(); }+    return c;+  }++  void Complex_Selector::set_innermost(Complex_Selector_Obj val, Combinator c)+  {+    if (!tail())+    { tail(val); combinator(c); }+    else+    { tail()->set_innermost(val, c); }+  }++  void Complex_Selector::cloneChildren()+  {+    if (head()) head(SASS_MEMORY_CLONE(head()));+    if (tail()) tail(SASS_MEMORY_CLONE(tail()));+  }++  void Compound_Selector::cloneChildren()+  {+    for (size_t i = 0, l = length(); i < l; i++) {+      at(i) = SASS_MEMORY_CLONE(at(i));+    }+  }++  void Selector_List::cloneChildren()+  {+    for (size_t i = 0, l = length(); i < l; i++) {+      at(i) = SASS_MEMORY_CLONE(at(i));+    }+  }++  void Wrapped_Selector::cloneChildren()+  {+    selector(SASS_MEMORY_CLONE(selector()));+  }++  // remove parent selector references+  // basically unwraps parsed selectors+  void Selector_List::remove_parent_selectors()+  {+    // Check every rhs selector against left hand list+    for(size_t i = 0, L = length(); i < L; ++i) {+      if (!(*this)[i]->head()) continue;+      if ((*this)[i]->head()->is_empty_reference()) {+        // simply move to the next tail if we have "no" combinator+        if ((*this)[i]->combinator() == Complex_Selector::ANCESTOR_OF) {+          if ((*this)[i]->tail()) {+            if ((*this)[i]->has_line_feed()) {+              (*this)[i]->tail()->has_line_feed(true);+            }+            (*this)[i] = (*this)[i]->tail();+          }+        }+        // otherwise remove the first item from head+        else {+          (*this)[i]->head()->erase((*this)[i]->head()->begin());+        }+      }+    }+  }++  size_t Wrapped_Selector::hash()+  {+    if (hash_ == 0) {+      hash_combine(hash_, Simple_Selector::hash());+      if (selector_) hash_combine(hash_, selector_->hash());+    }+    return hash_;+  }+  bool Wrapped_Selector::has_parent_ref() const {+    // if (has_reference()) return true;+    if (!selector()) return false;+    return selector()->has_parent_ref();+  }+  bool Wrapped_Selector::has_real_parent_ref() const {+    // if (has_reference()) return true;+    if (!selector()) return false;+    return selector()->has_real_parent_ref();+  }+  unsigned long Wrapped_Selector::specificity() const+  {+    return selector_ ? selector_->specificity() : 0;+  }+++  bool Selector_List::has_parent_ref() const+  {+    for (Complex_Selector_Obj s : elements()) {+      if (s && s->has_parent_ref()) return true;+    }+    return false;+  }++  bool Selector_List::has_real_parent_ref() const+  {+    for (Complex_Selector_Obj s : elements()) {+      if (s && s->has_real_parent_ref()) return true;+    }+    return false;+  }++  bool Selector_Schema::has_parent_ref() const+  {+    if (String_Schema_Obj schema = Cast<String_Schema>(contents())) {+      return schema->length() > 0 && Cast<Parent_Selector>(schema->at(0)) != NULL;+    }+    return false;+  }++  bool Selector_Schema::has_real_parent_ref() const+  {+    if (String_Schema_Obj schema = Cast<String_Schema>(contents())) {+      Parent_Selector_Obj p = Cast<Parent_Selector>(schema->at(0));+      return schema->length() > 0 && p && p->is_real_parent_ref();+    }+    return false;+  }++  void Selector_List::adjust_after_pushing(Complex_Selector_Obj c)+  {+    // if (c->has_reference())   has_reference(true);+  }++  // it's a superselector if every selector of the right side+  // list is a superselector of the given left side selector+  bool Complex_Selector::is_superselector_of(Selector_List_Obj sub, std::string wrapping)+  {+    // Check every rhs selector against left hand list+    for(size_t i = 0, L = sub->length(); i < L; ++i) {+      if (!is_superselector_of((*sub)[i], wrapping)) return false;+    }+    return true;+  }++  // it's a superselector if every selector of the right side+  // list is a superselector of the given left side selector+  bool Selector_List::is_superselector_of(Selector_List_Obj sub, std::string wrapping)+  {+    // Check every rhs selector against left hand list+    for(size_t i = 0, L = sub->length(); i < L; ++i) {+      if (!is_superselector_of((*sub)[i], wrapping)) return false;+    }+    return true;+  }++  // it's a superselector if every selector on the right side+  // is a superselector of any one of the left side selectors+  bool Selector_List::is_superselector_of(Compound_Selector_Obj sub, std::string wrapping)+  {+    // Check every lhs selector against right hand+    for(size_t i = 0, L = length(); i < L; ++i) {+      if ((*this)[i]->is_superselector_of(sub, wrapping)) return true;+    }+    return false;+  }++  // it's a superselector if every selector on the right side+  // is a superselector of any one of the left side selectors+  bool Selector_List::is_superselector_of(Complex_Selector_Obj sub, std::string wrapping)+  {+    // Check every lhs selector against right hand+    for(size_t i = 0, L = length(); i < L; ++i) {+      if ((*this)[i]->is_superselector_of(sub)) return true;+    }+    return false;+  }++  Selector_List_Ptr Selector_List::unify_with(Selector_List_Ptr rhs) {+    std::vector<Complex_Selector_Obj> unified_complex_selectors;+    // Unify all of children with RHS's children, storing the results in `unified_complex_selectors`+    for (size_t lhs_i = 0, lhs_L = length(); lhs_i < lhs_L; ++lhs_i) {+      Complex_Selector_Obj seq1 = (*this)[lhs_i];+      for(size_t rhs_i = 0, rhs_L = rhs->length(); rhs_i < rhs_L; ++rhs_i) {+        Complex_Selector_Ptr seq2 = rhs->at(rhs_i);++        Selector_List_Obj result = seq1->unify_with(seq2);+        if( result ) {+          for(size_t i = 0, L = result->length(); i < L; ++i) {+            unified_complex_selectors.push_back( (*result)[i] );+          }+        }+      }+    }++    // Creates the final Selector_List by combining all the complex selectors+    Selector_List_Ptr final_result = SASS_MEMORY_NEW(Selector_List, pstate());+    for (auto itr = unified_complex_selectors.begin(); itr != unified_complex_selectors.end(); ++itr) {+      final_result->append(*itr);+    }+    return final_result;+  }++  void Selector_List::populate_extends(Selector_List_Obj extendee, Subset_Map& extends)+  {++    Selector_List_Ptr extender = this;+    for (auto complex_sel : extendee->elements()) {+      Complex_Selector_Obj c = complex_sel;+++      // Ignore any parent selectors, until we find the first non Selectorerence head+      Compound_Selector_Obj compound_sel = c->head();+      Complex_Selector_Obj pIter = complex_sel;+      while (pIter) {+        Compound_Selector_Obj pHead = pIter->head();+        if (pHead && Cast<Parent_Selector>(pHead->elements()[0]) == NULL) {+          compound_sel = pHead;+          break;+        }++        pIter = pIter->tail();+      }++      if (!pIter->head() || pIter->tail()) {+        coreError("nested selectors may not be extended", c->pstate());+      }++      compound_sel->is_optional(extendee->is_optional());++      for (size_t i = 0, L = extender->length(); i < L; ++i) {+        extends.put(compound_sel, std::make_pair((*extender)[i], compound_sel));+      }+    }+  };++  void Compound_Selector::append(Simple_Selector_Ptr element)+  {+    Vectorized<Simple_Selector_Obj>::append(element);+    pstate_.offset += element->pstate().offset;+  }++  Compound_Selector_Ptr Compound_Selector::minus(Compound_Selector_Ptr rhs)+  {+    Compound_Selector_Ptr result = SASS_MEMORY_NEW(Compound_Selector, pstate());+    // result->has_parent_reference(has_parent_reference());++    // not very efficient because it needs to preserve order+    for (size_t i = 0, L = length(); i < L; ++i)+    {+      bool found = false;+      std::string thisSelector((*this)[i]->to_string());+      for (size_t j = 0, M = rhs->length(); j < M; ++j)+      {+        if (thisSelector == (*rhs)[j]->to_string())+        {+          found = true;+          break;+        }+      }+      if (!found) result->append((*this)[i]);+    }++    return result;+  }++  void Compound_Selector::mergeSources(ComplexSelectorSet& sources)+  {+    for (ComplexSelectorSet::iterator iterator = sources.begin(), endIterator = sources.end(); iterator != endIterator; ++iterator) {+      this->sources_.insert(SASS_MEMORY_CLONE(*iterator));+    }+  }++  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 NULL;+  }++  Argument_Obj Arguments::get_keyword_argument()+  {+    if (this->has_keyword_argument()) {+      for (Argument_Obj arg : this->elements()) {+        if (arg->is_keyword_argument()) {+          return arg;+        }+      }+    }+    return NULL;+  }++  void Arguments::adjust_after_pushing(Argument_Obj a)+  {+    if (!a->name().empty()) {+      if (has_keyword_argument()) {+        coreError("named arguments must precede variable-length argument", a->pstate());+      }+      has_named_arguments(true);+    }+    else if (a->is_rest_argument()) {+      if (has_rest_argument()) {+        coreError("functions and mixins may only be called with one variable-length argument", a->pstate());+      }+      if (has_keyword_argument_) {+        coreError("only keyword arguments may follow variable arguments", a->pstate());+      }+      has_rest_argument(true);+    }+    else if (a->is_keyword_argument()) {+      if (has_keyword_argument()) {+        coreError("functions and mixins may only be called with one keyword argument", a->pstate());+      }+      has_keyword_argument(true);+    }+    else {+      if (has_rest_argument()) {+        coreError("ordinal arguments must precede variable-length arguments", a->pstate());+      }+      if (has_named_arguments()) {+        coreError("ordinal arguments must precede named arguments", a->pstate());+      }+    }+  }++  bool Ruleset::is_invisible() const {+    if (Selector_List_Ptr sl = Cast<Selector_List>(selector())) {+      for (size_t i = 0, L = sl->length(); i < L; ++i)+        if (!(*sl)[i]->has_placeholder()) return false;+    }+    return true;+  }++  bool Media_Block::is_invisible() const {+    for (size_t i = 0, L = block()->length(); i < L; ++i) {+      Statement_Obj stm = block()->at(i);+      if (!stm->is_invisible()) return false;+    }+    return true;+  }++  Number::Number(ParserState pstate, double val, std::string u, bool zero)+  : Value(pstate),+    Units(),+    value_(val),+    zero_(zero),+    hash_(0)+  {+    size_t l = 0;+    size_t r;+    if (!u.empty()) {+      bool nominator = true;+      while (true) {+        r = u.find_first_of("*/", l);+        std::string unit(u.substr(l, r == std::string::npos ? r : r - l));+        if (!unit.empty()) {+          if (nominator) numerators.push_back(unit);+          else denominators.push_back(unit);+        }+        if (r == std::string::npos) break;+        // ToDo: should error for multiple slashes+        // if (!nominator && u[r] == '/') error(...)+        if (u[r] == '/')+          nominator = false;+        // strange math parsing?+        // else if (u[r] == '*')+        //  nominator = true;+        l = r + 1;+      }+    }+    concrete_type(NUMBER);+  }++  // cancel out unnecessary units+  void Number::reduce()+  {+    // apply conversion factor+    value_ *= this->Units::reduce();+  }++  void Number::normalize()+  {+    // apply conversion factor+    value_ *= this->Units::normalize();+  }++  bool Custom_Warning::operator== (const Expression& rhs) const+  {+    if (Custom_Warning_Ptr_Const r = Cast<Custom_Warning>(&rhs)) {+      return message() == r->message();+    }+    return false;+  }++  bool Custom_Error::operator== (const Expression& rhs) const+  {+    if (Custom_Error_Ptr_Const r = Cast<Custom_Error>(&rhs)) {+      return message() == r->message();+    }+    return false;+  }++  bool Number::operator== (const Expression& rhs) const+  {+    if (auto rhsnr = Cast<Number>(&rhs)) {+      return *this == *rhsnr;+    }+    return false;+  }++  bool Number::operator== (const Number& rhs) const+  {+    Number l(*this), r(rhs); l.reduce(); r.reduce();+    size_t lhs_units = l.numerators.size() + l.denominators.size();+    size_t rhs_units = r.numerators.size() + r.denominators.size();+    // unitless and only having one unit seems equivalent (will change in future)+    if (!lhs_units || !rhs_units) {+      return NEAR_EQUAL(l.value(), r.value());+    }+    l.normalize(); r.normalize();+    Units &lhs_unit = l, &rhs_unit = r;+    return lhs_unit == rhs_unit &&+      NEAR_EQUAL(l.value(), r.value());+  }++  bool Number::operator< (const Number& rhs) const+  {+    Number l(*this), r(rhs); l.reduce(); r.reduce();+    size_t lhs_units = l.numerators.size() + l.denominators.size();+    size_t rhs_units = r.numerators.size() + r.denominators.size();+    // unitless and only having one unit seems equivalent (will change in future)+    if (!lhs_units || !rhs_units) {+      return l.value() < r.value();+    }+    l.normalize(); r.normalize();+    Units &lhs_unit = l, &rhs_unit = r;+    if (!(lhs_unit == rhs_unit)) {+      /* ToDo: do we always get usefull backtraces? */+      throw Exception::IncompatibleUnits(rhs, *this);+    }+    return lhs_unit < rhs_unit ||+           l.value() < r.value();+  }++  bool String_Quoted::operator== (const Expression& rhs) const+  {+    if (String_Quoted_Ptr_Const qstr = Cast<String_Quoted>(&rhs)) {+      return (value() == qstr->value());+    } else if (String_Constant_Ptr_Const cstr = Cast<String_Constant>(&rhs)) {+      return (value() == cstr->value());+    }+    return false;+  }++  bool String_Constant::is_invisible() const {+    return value_.empty() && quote_mark_ == 0;+  }++  bool String_Constant::operator== (const Expression& rhs) const+  {+    if (String_Quoted_Ptr_Const qstr = Cast<String_Quoted>(&rhs)) {+      return (value() == qstr->value());+    } else if (String_Constant_Ptr_Const cstr = Cast<String_Constant>(&rhs)) {+      return (value() == cstr->value());+    }+    return false;+  }++  bool String_Schema::is_left_interpolant(void) const+  {+    return length() && first()->is_left_interpolant();+  }+  bool String_Schema::is_right_interpolant(void) const+  {+    return length() && last()->is_right_interpolant();+  }++  bool String_Schema::operator== (const Expression& rhs) const+  {+    if (String_Schema_Ptr_Const r = Cast<String_Schema>(&rhs)) {+      if (length() != r->length()) return false;+      for (size_t i = 0, L = length(); i < L; ++i) {+        Expression_Obj rv = (*r)[i];+        Expression_Obj lv = (*this)[i];+        if (!lv || !rv) return false;+        if (!(*lv == *rv)) return false;+      }+      return true;+    }+    return false;+  }++  bool Boolean::operator== (const Expression& rhs) const+  {+    if (Boolean_Ptr_Const r = Cast<Boolean>(&rhs)) {+      return (value() == r->value());+    }+    return false;+  }++  bool Color::operator== (const Expression& rhs) const+  {+    if (Color_Ptr_Const r = Cast<Color>(&rhs)) {+      return r_ == r->r() &&+             g_ == r->g() &&+             b_ == r->b() &&+             a_ == r->a();+    }+    return false;+  }++  bool List::operator== (const Expression& rhs) const+  {+    if (List_Ptr_Const r = Cast<List>(&rhs)) {+      if (length() != r->length()) return false;+      if (separator() != r->separator()) return false;+      if (is_bracketed() != r->is_bracketed()) return false;+      for (size_t i = 0, L = length(); i < L; ++i) {+        Expression_Obj rv = r->at(i);+        Expression_Obj lv = this->at(i);+        if (!lv || !rv) return false;+        if (!(*lv == *rv)) return false;+      }+      return true;+    }+    return false;+  }++  bool Map::operator== (const Expression& rhs) const+  {+    if (Map_Ptr_Const r = Cast<Map>(&rhs)) {+      if (length() != r->length()) return false;+      for (auto key : keys()) {+        Expression_Obj lv = at(key);+        Expression_Obj rv = r->at(key);+        if (!rv || !lv) return false;+        if (!(*lv == *rv)) return false;+      }+      return true;+    }+    return false;+  }++  bool Null::operator== (const Expression& rhs) const+  {+    return rhs.concrete_type() == NULL_VAL;+  }++  bool Function::operator== (const Expression& rhs) const+  {+    if (Function_Ptr_Const r = Cast<Function>(&rhs)) {+      Definition_Ptr_Const d1 = Cast<Definition>(definition());+      Definition_Ptr_Const d2 = Cast<Definition>(r->definition());+      return d1 && d2 && d1 == d2 && is_css() == r->is_css();+    }+    return false;+  }++  size_t List::size() const {+    if (!is_arglist_) return length();+    // arglist expects a list of arguments+    // so we need to break before keywords+    for (size_t i = 0, L = length(); i < L; ++i) {+      Expression_Obj obj = this->at(i);+      if (Argument_Ptr arg = Cast<Argument>(obj)) {+        if (!arg->name().empty()) return i;+      }+    }+    return length();+  }++  Expression_Obj Hashed::at(Expression_Obj k) const+  {+    if (elements_.count(k))+    { return elements_.at(k); }+    else { return NULL; }+  }++  bool Binary_Expression::is_left_interpolant(void) const+  {+    return is_interpolant() || (left() && left()->is_left_interpolant());+  }+  bool Binary_Expression::is_right_interpolant(void) const+  {+    return is_interpolant() || (right() && right()->is_right_interpolant());+  }++  const std::string AST_Node::to_string(Sass_Inspect_Options opt) const+  {+    Sass_Output_Options out(opt);+    Emitter emitter(out);+    Inspect i(emitter);+    i.in_declaration = true;+    // ToDo: inspect should be const+    const_cast<AST_Node_Ptr>(this)->perform(&i);+    return i.get_buffer();+  }++  const std::string AST_Node::to_string() const+  {+    return to_string({ NESTED, 5 });+  }++  std::string String_Quoted::inspect() const+  {+    return quote(value_, '*');+  }++  std::string String_Constant::inspect() const+  {+    return quote(value_, '*');+  }++  bool Declaration::is_invisible() const+  {+    if (is_custom_property()) return false;++    return !(value_ && value_->concrete_type() != Expression::NULL_VAL);+  }++  //////////////////////////////////////////////////////////////////////////////////////////+  // Additional method on Lists to retrieve values directly or from an encompassed Argument.+  //////////////////////////////////////////////////////////////////////////////////////////+  Expression_Obj List::value_at_index(size_t i) {+    Expression_Obj obj = this->at(i);+    if (is_arglist_) {+      if (Argument_Ptr arg = Cast<Argument>(obj)) {+        return arg->value();+      } else {+        return obj;+      }+    } else {+      return obj;+    }+  }++  //////////////////////////////////////////////////////////////////////////////////////////+  // Convert map to (key, value) list.+  //////////////////////////////////////////////////////////////////////////////////////////+  List_Obj Map::to_list(ParserState& pstate) {+    List_Obj ret = SASS_MEMORY_NEW(List, pstate, length(), SASS_COMMA);++    for (auto key : keys()) {+      List_Obj l = SASS_MEMORY_NEW(List, pstate, 2);+      l->append(key);+      l->append(at(key));+      ret->append(l);+    }++    return ret;+  }++  //////////////////////////////////////////////////////////////////////////////////////////+  // Copy implementations+  //////////////////////////////////////////////////////////////////////////////////////////++  #ifdef DEBUG_SHARED_PTR++  #define IMPLEMENT_AST_OPERATORS(klass) \+    klass##_Ptr klass::copy(std::string file, size_t line) const { \+      klass##_Ptr cpy = new klass(this); \+      cpy->trace(file, line); \+      return cpy; \+    } \+    klass##_Ptr klass::clone(std::string file, size_t line) const { \+      klass##_Ptr cpy = copy(file, line); \+      cpy->cloneChildren(); \+      return cpy; \+    } \++  #else++  #define IMPLEMENT_AST_OPERATORS(klass) \+    klass##_Ptr klass::copy() const { \+      return new klass(this); \+    } \+    klass##_Ptr klass::clone() const { \+      klass##_Ptr cpy = copy(); \+      cpy->cloneChildren(); \+      return cpy; \+    } \++  #endif++  IMPLEMENT_AST_OPERATORS(Supports_Operator);+  IMPLEMENT_AST_OPERATORS(Supports_Negation);+  IMPLEMENT_AST_OPERATORS(Compound_Selector);+  IMPLEMENT_AST_OPERATORS(Complex_Selector);+  IMPLEMENT_AST_OPERATORS(Element_Selector);+  IMPLEMENT_AST_OPERATORS(Class_Selector);+  IMPLEMENT_AST_OPERATORS(Id_Selector);+  IMPLEMENT_AST_OPERATORS(Pseudo_Selector);+  IMPLEMENT_AST_OPERATORS(Wrapped_Selector);+  IMPLEMENT_AST_OPERATORS(Selector_List);+  IMPLEMENT_AST_OPERATORS(Ruleset);+  IMPLEMENT_AST_OPERATORS(Media_Block);+  IMPLEMENT_AST_OPERATORS(Custom_Warning);+  IMPLEMENT_AST_OPERATORS(Custom_Error);+  IMPLEMENT_AST_OPERATORS(List);+  IMPLEMENT_AST_OPERATORS(Map);+  IMPLEMENT_AST_OPERATORS(Function);+  IMPLEMENT_AST_OPERATORS(Number);+  IMPLEMENT_AST_OPERATORS(Binary_Expression);+  IMPLEMENT_AST_OPERATORS(String_Schema);+  IMPLEMENT_AST_OPERATORS(String_Constant);+  IMPLEMENT_AST_OPERATORS(String_Quoted);+  IMPLEMENT_AST_OPERATORS(Boolean);+  IMPLEMENT_AST_OPERATORS(Color);+  IMPLEMENT_AST_OPERATORS(Null);+  IMPLEMENT_AST_OPERATORS(Parent_Selector);+  IMPLEMENT_AST_OPERATORS(Import);+  IMPLEMENT_AST_OPERATORS(Import_Stub);+  IMPLEMENT_AST_OPERATORS(Function_Call);+  IMPLEMENT_AST_OPERATORS(Directive);+  IMPLEMENT_AST_OPERATORS(At_Root_Block);+  IMPLEMENT_AST_OPERATORS(Supports_Block);+  IMPLEMENT_AST_OPERATORS(While);+  IMPLEMENT_AST_OPERATORS(Each);+  IMPLEMENT_AST_OPERATORS(For);+  IMPLEMENT_AST_OPERATORS(If);+  IMPLEMENT_AST_OPERATORS(Mixin_Call);+  IMPLEMENT_AST_OPERATORS(Extension);+  IMPLEMENT_AST_OPERATORS(Media_Query);+  IMPLEMENT_AST_OPERATORS(Media_Query_Expression);+  IMPLEMENT_AST_OPERATORS(Debug);+  IMPLEMENT_AST_OPERATORS(Error);+  IMPLEMENT_AST_OPERATORS(Warning);+  IMPLEMENT_AST_OPERATORS(Assignment);+  IMPLEMENT_AST_OPERATORS(Return);+  IMPLEMENT_AST_OPERATORS(At_Root_Query);+  IMPLEMENT_AST_OPERATORS(Variable);+  IMPLEMENT_AST_OPERATORS(Comment);+  IMPLEMENT_AST_OPERATORS(Attribute_Selector);+  IMPLEMENT_AST_OPERATORS(Supports_Interpolation);+  IMPLEMENT_AST_OPERATORS(Supports_Declaration);+  IMPLEMENT_AST_OPERATORS(Supports_Condition);+  IMPLEMENT_AST_OPERATORS(Parameters);+  IMPLEMENT_AST_OPERATORS(Parameter);+  IMPLEMENT_AST_OPERATORS(Arguments);+  IMPLEMENT_AST_OPERATORS(Argument);+  IMPLEMENT_AST_OPERATORS(Unary_Expression);+  IMPLEMENT_AST_OPERATORS(Function_Call_Schema);+  IMPLEMENT_AST_OPERATORS(Block);+  IMPLEMENT_AST_OPERATORS(Content);+  IMPLEMENT_AST_OPERATORS(Trace);+  IMPLEMENT_AST_OPERATORS(Keyframe_Rule);+  IMPLEMENT_AST_OPERATORS(Bubble);+  IMPLEMENT_AST_OPERATORS(Selector_Schema);+  IMPLEMENT_AST_OPERATORS(Placeholder_Selector);+  IMPLEMENT_AST_OPERATORS(Definition);+  IMPLEMENT_AST_OPERATORS(Declaration); }
libsass/src/ast.hpp view
@@ -1,6 +1,7 @@ #ifndef SASS_AST_H #define SASS_AST_H +#include "sass.hpp" #include <set> #include <deque> #include <vector>@@ -74,6 +75,9 @@   // Note: most methods follow precision option   const double NUMBER_EPSILON = 0.00000000000001; +  // macro to test if numbers are equal within a small error margin+  #define NEAR_EQUAL(lhs, rhs) std::fabs(lhs - rhs) < NUMBER_EPSILON+   // ToDo: where does this fit best?   // We don't share this with C-API?   class Operand {@@ -120,12 +124,14 @@     ATTACH_VIRTUAL_AST_OPERATIONS(AST_Node);     virtual std::string inspect() const { return to_string({ INSPECT, 5 }); }     virtual std::string to_sass() const { return to_string({ TO_SASS, 5 }); }-    virtual std::string to_string(Sass_Inspect_Options opt) const;-    virtual std::string to_string() const;+    virtual const std::string to_string(Sass_Inspect_Options opt) const;+    virtual const std::string to_string() const;     virtual void cloneChildren() {};+    // generic find function (not fully implemented yet)+    // ToDo: add specific implementions to all children+    virtual bool find ( bool (*f)(AST_Node_Obj) ) { return f(this); };   public:     void update_pstate(const ParserState& pstate);-    void set_pstate_offset(const Offset& offset);   public:     Offset off() { return pstate(); }     Position pos() { return pstate(); }@@ -133,7 +139,22 @@   };   inline AST_Node::~AST_Node() { } +  //////////////////////////////////////////////////////////////////////+  // define cast template now (need complete type)+  ////////////////////////////////////////////////////////////////////// +  template<class T>+  T* Cast(AST_Node* ptr) {+    return ptr && typeid(T) == typeid(*ptr) ?+           static_cast<T*>(ptr) : NULL;+  };++  template<class T>+  const T* Cast(const AST_Node* ptr) {+    return ptr && typeid(T) == typeid(*ptr) ?+           static_cast<const T*>(ptr) : NULL;+  };+   //////////////////////////////////////////////////////////////////////   // Abstract base class for expressions. This side of the AST hierarchy   // represents elements in value contexts, which exist primarily to be@@ -151,9 +172,11 @@       MAP,       SELECTOR,       NULL_VAL,+      FUNCTION_VAL,       C_WARNING,       C_ERROR,       FUNCTION,+      VARIABLE,       NUM_TYPES     };     enum Simple_Type {@@ -186,7 +209,7 @@     { }     virtual operator bool() { return true; }     virtual ~Expression() { }-    virtual std::string type() { return ""; /* TODO: raise an error? */ }+    virtual std::string type() const { return ""; /* TODO: raise an error? */ }     virtual bool is_invisible() const { return false; }     static std::string type_name() { return ""; }     virtual bool is_false() { return false; }@@ -343,8 +366,11 @@     void reset_duplicate_key() { duplicate_key_ = 0; }     virtual void adjust_after_pushing(std::pair<Expression_Obj, Expression_Obj> p) { }   public:-    Hashed(size_t s = 0) : elements_(ExpressionMap(s)), list_(std::vector<Expression_Obj>())-    { elements_.reserve(s); list_.reserve(s); reset_duplicate_key(); }+    Hashed(size_t s = 0)+    : elements_(ExpressionMap(s)),+      list_(std::vector<Expression_Obj>()),+      hash_(0), duplicate_key_(NULL)+    { elements_.reserve(s); list_.reserve(s); }     virtual ~Hashed();     size_t length() const                  { return list_.size(); }     bool empty() const                     { return list_.empty(); }@@ -454,7 +480,6 @@   ////////////////////////   class Block : public Statement, public Vectorized<Statement_Obj> {     ADD_PROPERTY(bool, is_root)-    ADD_PROPERTY(bool, is_at_root);     // needed for properly formatted CSS emission   protected:     void adjust_after_pushing(Statement_Obj s)@@ -464,14 +489,12 @@     Block(ParserState pstate, size_t s = 0, bool r = false)     : Statement(pstate),       Vectorized<Statement_Obj>(s),-      is_root_(r),-      is_at_root_(false)+      is_root_(r)     { }     Block(const Block* ptr)     : Statement(ptr),       Vectorized<Statement_Obj>(*ptr),-      is_root_(ptr->is_root_),-      is_at_root_(ptr->is_at_root_)+      is_root_(ptr->is_root_)     { }     virtual bool has_content()     {@@ -509,17 +532,15 @@   // of style declarations.   /////////////////////////////////////////////////////////////////////////////   class Ruleset : public Has_Block {-    ADD_PROPERTY(Selector_Obj, selector)-    ADD_PROPERTY(bool, at_root);+    ADD_PROPERTY(Selector_List_Obj, selector)     ADD_PROPERTY(bool, is_root);   public:-    Ruleset(ParserState pstate, Selector_Obj s = 0, Block_Obj b = 0)-    : Has_Block(pstate, b), selector_(s), at_root_(false), is_root_(false)+    Ruleset(ParserState pstate, Selector_List_Obj s = 0, Block_Obj b = 0)+    : Has_Block(pstate, b), selector_(s), is_root_(false)     { statement_type(RULESET); }     Ruleset(const Ruleset* ptr)     : Has_Block(ptr),       selector_(ptr->selector_),-      at_root_(ptr->at_root_),       is_root_(ptr->is_root_)     { statement_type(RULESET); }     bool is_invisible() const;@@ -551,13 +572,15 @@   // Trace.   /////////////////   class Trace : public Has_Block {-    ADD_PROPERTY(std::string, name)+    ADD_CONSTREF(char, type)+    ADD_CONSTREF(std::string, name)   public:-    Trace(ParserState pstate, std::string n, Block_Obj b = 0)-    : Has_Block(pstate, b), name_(n)+    Trace(ParserState pstate, std::string n, Block_Obj b = 0, char type = 'm')+    : Has_Block(pstate, b), type_(type), name_(n)     { }     Trace(const Trace* ptr)     : Has_Block(ptr),+      type_(ptr->type_),       name_(ptr->name_)     { }     ATTACH_AST_OPERATIONS(Trace)@@ -573,9 +596,6 @@     Media_Block(ParserState pstate, List_Obj mqs, Block_Obj b)     : Has_Block(pstate, b), media_queries_(mqs)     { statement_type(MEDIA); }-    Media_Block(ParserState pstate, List_Obj mqs, Block_Obj b, Selector_Obj s)-    : Has_Block(pstate, b), media_queries_(mqs)-    { statement_type(MEDIA); }     Media_Block(const Media_Block* ptr)     : Has_Block(ptr), media_queries_(ptr->media_queries_)     { statement_type(MEDIA); }@@ -590,11 +610,11 @@   // optional statement block.   ///////////////////////////////////////////////////////////////////////   class Directive : public Has_Block {-    ADD_PROPERTY(std::string, keyword)-    ADD_PROPERTY(Selector_Obj, selector)+    ADD_CONSTREF(std::string, keyword)+    ADD_PROPERTY(Selector_List_Obj, selector)     ADD_PROPERTY(Expression_Obj, value)   public:-    Directive(ParserState pstate, std::string kwd, Selector_Obj sel = 0, Block_Obj b = 0, Expression_Obj val = 0)+    Directive(ParserState pstate, std::string kwd, Selector_List_Obj sel = 0, Block_Obj b = 0, Expression_Obj val = 0)     : Has_Block(pstate, b), keyword_(kwd), selector_(sel), value_(val) // set value manually if needed     { statement_type(DIRECTIVE); }     Directive(const Directive* ptr)@@ -626,7 +646,7 @@   class Keyframe_Rule : public Has_Block {     // according to css spec, this should be <keyframes-name>     // <keyframes-name> = <custom-ident> | <string>-    ADD_PROPERTY(Selector_Obj, name)+    ADD_PROPERTY(Selector_List_Obj, name)   public:     Keyframe_Rule(ParserState pstate, Block_Obj b)     : Has_Block(pstate, b), name_()@@ -645,19 +665,22 @@     ADD_PROPERTY(String_Obj, property)     ADD_PROPERTY(Expression_Obj, value)     ADD_PROPERTY(bool, is_important)+    ADD_PROPERTY(bool, is_custom_property)     ADD_PROPERTY(bool, is_indented)   public:     Declaration(ParserState pstate,-                String_Obj prop, Expression_Obj val, bool i = false, Block_Obj b = 0)-    : Has_Block(pstate, b), property_(prop), value_(val), is_important_(i), is_indented_(false)+                String_Obj prop, Expression_Obj val, bool i = false, bool c = false, Block_Obj b = 0)+    : Has_Block(pstate, b), property_(prop), value_(val), is_important_(i), is_custom_property_(c), is_indented_(false)     { statement_type(DECLARATION); }     Declaration(const Declaration* ptr)     : Has_Block(ptr),       property_(ptr->property_),       value_(ptr->value_),       is_important_(ptr->is_important_),+      is_custom_property_(ptr->is_custom_property_),       is_indented_(ptr->is_indented_)     { statement_type(DECLARATION); }+    virtual bool is_invisible() const;     ATTACH_AST_OPERATIONS(Declaration)     ATTACH_OPERATIONS()   };@@ -666,7 +689,7 @@   // Assignments -- variable and value.   /////////////////////////////////////   class Assignment : public Statement {-    ADD_PROPERTY(std::string, variable)+    ADD_CONSTREF(std::string, variable)     ADD_PROPERTY(Expression_Obj, value)     ADD_PROPERTY(bool, is_default)     ADD_PROPERTY(bool, is_global)@@ -811,7 +834,7 @@     ADD_PROPERTY(Block_Obj, alternative)   public:     If(ParserState pstate, Expression_Obj pred, Block_Obj con, Block_Obj alt = 0)-    : Has_Block(pstate, &con), predicate_(pred), alternative_(alt)+    : Has_Block(pstate, con), predicate_(pred), alternative_(alt)     { statement_type(IF); }     If(const If* ptr)     : Has_Block(ptr),@@ -830,7 +853,7 @@   // The Sass `@for` control directive.   /////////////////////////////////////   class For : public Has_Block {-    ADD_PROPERTY(std::string, variable)+    ADD_CONSTREF(std::string, variable)     ADD_PROPERTY(Expression_Obj, lower_bound)     ADD_PROPERTY(Expression_Obj, upper_bound)     ADD_PROPERTY(bool, is_inclusive)@@ -904,9 +927,9 @@   // The Sass `@extend` directive.   ////////////////////////////////   class Extension : public Statement {-    ADD_PROPERTY(Selector_Obj, selector)+    ADD_PROPERTY(Selector_List_Obj, selector)   public:-    Extension(ParserState pstate, Selector_Obj s)+    Extension(ParserState pstate, Selector_List_Obj s)     : Statement(pstate), selector_(s)     { statement_type(EXTEND); }     Extension(const Extension* ptr)@@ -921,13 +944,12 @@   // by a type tag.   /////////////////////////////////////////////////////////////////////////////   struct Backtrace;-  typedef Environment<AST_Node_Obj> Env;   typedef const char* Signature;-  typedef Expression_Ptr (*Native_Function)(Env&, Env&, Context&, Signature, ParserState, Backtrace*, std::vector<Selector_List_Obj>);+  typedef Expression_Ptr (*Native_Function)(Env&, Env&, Context&, Signature, ParserState, Backtraces, std::vector<Selector_List_Obj>);   class Definition : public Has_Block {   public:     enum Type { MIXIN, FUNCTION };-    ADD_PROPERTY(std::string, name)+    ADD_CONSTREF(std::string, name)     ADD_PROPERTY(Parameters_Obj, parameters)     ADD_PROPERTY(Env*, environment)     ADD_PROPERTY(Type, type)@@ -1009,7 +1031,7 @@   // Mixin calls (i.e., `@include ...`).   //////////////////////////////////////   class Mixin_Call : public Has_Block {-    ADD_PROPERTY(std::string, name)+    ADD_CONSTREF(std::string, name)     ADD_PROPERTY(Arguments_Obj, arguments)   public:     Mixin_Call(ParserState pstate, std::string n, Arguments_Obj args, Block_Obj b = 0)@@ -1028,11 +1050,15 @@   // The @content directive for mixin content blocks.   ///////////////////////////////////////////////////   class Content : public Statement {-    ADD_PROPERTY(Media_Block_Obj, media_block)+    ADD_PROPERTY(Media_Block_Ptr, media_block)   public:-    Content(ParserState pstate) : Statement(pstate)+    Content(ParserState pstate)+    : Statement(pstate),+      media_block_(NULL)     { statement_type(CONTENT); }-    Content(const Content* ptr) : Statement(ptr)+    Content(const Content* ptr)+    : Statement(ptr),+      media_block_(ptr->media_block_)     { statement_type(CONTENT); }     ATTACH_AST_OPERATIONS(Content)     ATTACH_OPERATIONS()@@ -1047,14 +1073,16 @@   private:     ADD_PROPERTY(enum Sass_Separator, separator)     ADD_PROPERTY(bool, is_arglist)+    ADD_PROPERTY(bool, is_bracketed)     ADD_PROPERTY(bool, from_selector)   public:     List(ParserState pstate,-         size_t size = 0, enum Sass_Separator sep = SASS_SPACE, bool argl = false)+         size_t size = 0, enum Sass_Separator sep = SASS_SPACE, bool argl = false, bool bracket = false)     : Value(pstate),       Vectorized<Expression_Obj>(size),       separator_(sep),       is_arglist_(argl),+      is_bracketed_(bracket),       from_selector_(false)     { concrete_type(LIST); }     List(const List* ptr)@@ -1062,15 +1090,16 @@       Vectorized<Expression_Obj>(*ptr),       separator_(ptr->separator_),       is_arglist_(ptr->is_arglist_),+      is_bracketed_(ptr->is_bracketed_),       from_selector_(ptr->from_selector_)     { concrete_type(LIST); }-    std::string type() { return is_arglist_ ? "arglist" : "list"; }+    std::string type() const { return is_arglist_ ? "arglist" : "list"; }     static std::string type_name() { return "list"; }     const char* sep_string(bool compressed = false) const {       return separator() == SASS_SPACE ?         " " : (compressed ? "," : ", ");     }-    bool is_invisible() const { return empty(); }+    bool is_invisible() const { return empty() && !is_bracketed(); }     Expression_Obj value_at_index(size_t i);      virtual size_t size() const;@@ -1079,6 +1108,7 @@     {       if (hash_ == 0) {         hash_ = std::hash<std::string>()(sep_string());+        hash_combine(hash_, std::hash<bool>()(is_bracketed()));         for (size_t i = 0, L = length(); i < L; ++i)           hash_combine(hash_, (elements()[i])->hash());       }@@ -1112,10 +1142,10 @@     : Value(ptr),       Hashed(*ptr)     { concrete_type(MAP); }-    std::string type() { return "map"; }+    std::string type() const { return "map"; }     static std::string type_name() { return "map"; }     bool is_invisible() const { return empty(); }-    List_Obj to_list(Context& ctx, ParserState& pstate);+    List_Obj to_list(ParserState& pstate);      virtual size_t hash()     {@@ -1137,25 +1167,46 @@    inline static const std::string sass_op_to_name(enum Sass_OP op) {     switch (op) {-      case AND: return "and"; break;-      case OR: return "or"; break;-      case EQ: return "eq"; break;-      case NEQ: return "neq"; break;-      case GT: return "gt"; break;-      case GTE: return "gte"; break;-      case LT: return "lt"; break;-      case LTE: return "lte"; break;-      case ADD: return "plus"; break;-      case SUB: return "sub"; break;-      case MUL: return "times"; break;-      case DIV: return "div"; break;-      case MOD: return "mod"; break;+      case AND: return "and";+      case OR: return "or";+      case EQ: return "eq";+      case NEQ: return "neq";+      case GT: return "gt";+      case GTE: return "gte";+      case LT: return "lt";+      case LTE: return "lte";+      case ADD: return "plus";+      case SUB: return "sub";+      case MUL: return "times";+      case DIV: return "div";+      case MOD: return "mod";       // this is only used internally!-      case NUM_OPS: return "[OPS]"; break;-      default: return "invalid"; break;+      case NUM_OPS: return "[OPS]";+      default: return "invalid";     }   } +  inline static const std::string sass_op_separator(enum Sass_OP op) {+    switch (op) {+      case AND: return "&&";+      case OR: return "||";+      case EQ: return "==";+      case NEQ: return "!=";+      case GT: return ">";+      case GTE: return ">=";+      case LT: return "<";+      case LTE: return "<=";+      case ADD: return "+";+      case SUB: return "-";+      case MUL: return "*";+      case DIV: return "/";+      case MOD: return "%";+      // this is only used internally!+      case NUM_OPS: return "[OPS]";+      default: return "invalid";+    }+  }+   //////////////////////////////////////////////////////////////////////////   // Binary expressions. Represents logical, relational, and arithmetic   // operations. Templatized to avoid large switch statements and repetitive@@ -1163,9 +1214,9 @@   //////////////////////////////////////////////////////////////////////////   class Binary_Expression : public PreValue {   private:-    ADD_HASHED(Operand, op)-    ADD_HASHED(Expression_Obj, left)-    ADD_HASHED(Expression_Obj, right)+    HASH_PROPERTY(Operand, op)+    HASH_PROPERTY(Expression_Obj, left)+    HASH_PROPERTY(Expression_Obj, right)     size_t hash_;   public:     Binary_Expression(ParserState pstate,@@ -1180,44 +1231,10 @@       hash_(ptr->hash_)     { }     const std::string type_name() {-      switch (type()) {-        case AND: return "and"; break;-        case OR: return "or"; break;-        case EQ: return "eq"; break;-        case NEQ: return "neq"; break;-        case GT: return "gt"; break;-        case GTE: return "gte"; break;-        case LT: return "lt"; break;-        case LTE: return "lte"; break;-        case ADD: return "add"; break;-        case SUB: return "sub"; break;-        case MUL: return "mul"; break;-        case DIV: return "div"; break;-        case MOD: return "mod"; break;-        // this is only used internally!-        case NUM_OPS: return "[OPS]"; break;-        default: return "invalid"; break;-      }+      return sass_op_to_name(optype());     }     const std::string separator() {-      switch (type()) {-        case AND: return "&&"; break;-        case OR: return "||"; break;-        case EQ: return "=="; break;-        case NEQ: return "!="; break;-        case GT: return ">"; break;-        case GTE: return ">="; break;-        case LT: return "<"; break;-        case LTE: return "<="; break;-        case ADD: return "+"; break;-        case SUB: return "-"; break;-        case MUL: return "*"; break;-        case DIV: return "/"; break;-        case MOD: return "%"; break;-        // this is only used internally!-        case NUM_OPS: return "[OPS]"; break;-        default: return "invalid"; break;-      }+      return sass_op_separator(optype());     }     bool is_left_interpolant(void) const;     bool is_right_interpolant(void) const;@@ -1236,11 +1253,11 @@     {       try       {-        Binary_Expression_Ptr_Const m = dynamic_cast<Binary_Expression_Ptr_Const>(&rhs);+        Binary_Expression_Ptr_Const m = Cast<Binary_Expression>(&rhs);         if (m == 0) return false;         return type() == m->type() &&-               left() == m->left() &&-               right() == m->right();+               *left() == *m->left() &&+               *right() == *m->right();       }       catch (std::bad_cast&)       {@@ -1251,13 +1268,13 @@     virtual size_t hash()     {       if (hash_ == 0) {-        hash_ = std::hash<size_t>()(type());+        hash_ = std::hash<size_t>()(optype());         hash_combine(hash_, left()->hash());         hash_combine(hash_, right()->hash());       }       return hash_;     }-    enum Sass_OP type() const { return op_.operand; }+    enum Sass_OP optype() const { return op_.operand; }     ATTACH_AST_OPERATIONS(Binary_Expression)     ATTACH_OPERATIONS()   };@@ -1267,37 +1284,38 @@   ////////////////////////////////////////////////////////////////////////////   class Unary_Expression : public Expression {   public:-    enum Type { PLUS, MINUS, NOT };+    enum Type { PLUS, MINUS, NOT, SLASH };   private:-    ADD_HASHED(Type, type)-    ADD_HASHED(Expression_Obj, operand)+    HASH_PROPERTY(Type, optype)+    HASH_PROPERTY(Expression_Obj, operand)     size_t hash_;   public:     Unary_Expression(ParserState pstate, Type t, Expression_Obj o)-    : Expression(pstate), type_(t), operand_(o), hash_(0)+    : Expression(pstate), optype_(t), operand_(o), hash_(0)     { }     Unary_Expression(const Unary_Expression* ptr)     : Expression(ptr),-      type_(ptr->type_),+      optype_(ptr->optype_),       operand_(ptr->operand_),       hash_(ptr->hash_)     { }     const std::string type_name() {-      switch (type_) {-        case PLUS: return "plus"; break;-        case MINUS: return "minus"; break;-        case NOT: return "not"; break;-        default: return "invalid"; break;+      switch (optype_) {+        case PLUS: return "plus";+        case MINUS: return "minus";+        case SLASH: return "slash";+        case NOT: return "not";+        default: return "invalid";       }     }     virtual bool operator==(const Expression& rhs) const     {       try       {-        Unary_Expression_Ptr_Const m = dynamic_cast<Unary_Expression_Ptr_Const>(&rhs);+        Unary_Expression_Ptr_Const m = Cast<Unary_Expression>(&rhs);         if (m == 0) return false;         return type() == m->type() &&-               operand() == m->operand();+               *operand() == *m->operand();       }       catch (std::bad_cast&)       {@@ -1308,7 +1326,7 @@     virtual size_t hash()     {       if (hash_ == 0) {-        hash_ = std::hash<size_t>()(type_);+        hash_ = std::hash<size_t>()(optype_);         hash_combine(hash_, operand()->hash());       };       return hash_;@@ -1321,8 +1339,8 @@   // Individual argument objects for mixin and function calls.   ////////////////////////////////////////////////////////////   class Argument : public Expression {-    ADD_HASHED(Expression_Obj, value)-    ADD_HASHED(std::string, name)+    HASH_PROPERTY(Expression_Obj, value)+    HASH_CONSTREF(std::string, name)     ADD_PROPERTY(bool, is_rest_argument)     ADD_PROPERTY(bool, is_keyword_argument)     size_t hash_;@@ -1331,7 +1349,7 @@     : Expression(pstate), value_(val), name_(n), is_rest_argument_(rest), is_keyword_argument_(keyword), hash_(0)     {       if (!name_.empty() && is_rest_argument_) {-        error("variable-length argument may not be passed by name", pstate_);+        coreError("variable-length argument may not be passed by name", pstate_);       }     }     Argument(const Argument* ptr)@@ -1343,7 +1361,7 @@       hash_(ptr->hash_)     {       if (!name_.empty() && is_rest_argument_) {-        error("variable-length argument may not be passed by name", pstate_);+        coreError("variable-length argument may not be passed by name", pstate_);       }     } @@ -1352,7 +1370,7 @@     {       try       {-        Argument_Ptr_Const m = dynamic_cast<Argument_Ptr_Const>(&rhs);+        Argument_Ptr_Const m = Cast<Argument>(&rhs);         if (!(m && name() == m->name())) return false;         return *value() == *m->value();       }@@ -1412,19 +1430,55 @@     ATTACH_OPERATIONS()   }; +  ////////////////////////////////////////////////////+  // Function reference.+  ////////////////////////////////////////////////////+  class Function : public Value {+  public:+    ADD_PROPERTY(Definition_Obj, definition)+    ADD_PROPERTY(bool, is_css)+  public:+    Function(ParserState pstate, Definition_Obj def, bool css)+    : Value(pstate), definition_(def), is_css_(css)+    { concrete_type(FUNCTION_VAL); }+    Function(const Function* ptr)+    : Value(ptr), definition_(ptr->definition_), is_css_(ptr->is_css_)+    { concrete_type(FUNCTION_VAL); }++    std::string type() const { return "function"; }+    static std::string type_name() { return "function"; }+    bool is_invisible() const { return true; }++    std::string name() {+      if (definition_) {+        return definition_->name();+      }+      return "";+    }++    virtual bool operator== (const Expression& rhs) const;++    ATTACH_AST_OPERATIONS(Function)+    ATTACH_OPERATIONS()+  };+   //////////////////   // Function calls.   //////////////////   class Function_Call : public PreValue {-    ADD_HASHED(std::string, name)-    ADD_HASHED(Arguments_Obj, arguments)+    HASH_CONSTREF(std::string, name)+    HASH_PROPERTY(Arguments_Obj, arguments)+    HASH_PROPERTY(Function_Obj, func)     ADD_PROPERTY(bool, via_call)     ADD_PROPERTY(void*, cookie)     size_t hash_;   public:     Function_Call(ParserState pstate, std::string n, Arguments_Obj args, void* cookie)-    : PreValue(pstate), name_(n), arguments_(args), via_call_(false), cookie_(cookie), hash_(0)+    : PreValue(pstate), name_(n), arguments_(args), func_(0), via_call_(false), cookie_(cookie), hash_(0)     { concrete_type(FUNCTION); }+    Function_Call(ParserState pstate, std::string n, Arguments_Obj args, Function_Obj func)+    : PreValue(pstate), name_(n), arguments_(args), func_(func), via_call_(false), cookie_(0), hash_(0)+    { concrete_type(FUNCTION); }     Function_Call(ParserState pstate, std::string n, Arguments_Obj args)     : PreValue(pstate), name_(n), arguments_(args), via_call_(false), cookie_(0), hash_(0)     { concrete_type(FUNCTION); }@@ -1432,20 +1486,26 @@     : PreValue(ptr),       name_(ptr->name_),       arguments_(ptr->arguments_),+      func_(ptr->func_),       via_call_(ptr->via_call_),       cookie_(ptr->cookie_),       hash_(ptr->hash_)     { concrete_type(FUNCTION); } +    bool is_css() {+      if (func_) return func_->is_css();+      return false;+    }+     virtual bool operator==(const Expression& rhs) const     {       try       {-        Function_Call_Ptr_Const m = dynamic_cast<Function_Call_Ptr_Const>(&rhs);+        Function_Call_Ptr_Const m = Cast<Function_Call>(&rhs);         if (!(m && name() == m->name())) return false;         if (!(m && arguments()->length() == m->arguments()->length())) return false;         for (size_t i =0, L = arguments()->length(); i < L; ++i)-          if (!((*arguments())[i] == (*m->arguments())[i])) return false;+          if (!(*(*arguments())[i] == *(*m->arguments())[i])) return false;         return true;       }       catch (std::bad_cast&)@@ -1491,20 +1551,20 @@   // Variable references.   ///////////////////////   class Variable : public PreValue {-    ADD_PROPERTY(std::string, name)+    ADD_CONSTREF(std::string, name)   public:     Variable(ParserState pstate, std::string n)     : PreValue(pstate), name_(n)-    { }+    { concrete_type(VARIABLE); }     Variable(const Variable* ptr)     : PreValue(ptr), name_(ptr->name_)-    { }+    { concrete_type(VARIABLE); }      virtual bool operator==(const Expression& rhs) const     {       try       {-        Variable_Ptr_Const e = dynamic_cast<Variable_Ptr_Const>(&rhs);+        Variable_Ptr_Const e = Cast<Variable>(&rhs);         return e && name() == e->name();       }       catch (std::bad_cast&)@@ -1523,108 +1583,45 @@     ATTACH_OPERATIONS()   }; -  ////////////////////////////////////////////////////////////////////////////-  // Textual (i.e., unevaluated) numeric data. Variants are distinguished with-  // a type tag.-  ////////////////////////////////////////////////////////////////////////////-  class Textual : public Expression {-  public:-    enum Type { NUMBER, PERCENTAGE, DIMENSION, HEX };-  private:-    ADD_HASHED(Type, type)-    ADD_HASHED(std::string, value)-    size_t hash_;-  public:-    Textual(ParserState pstate, Type t, std::string val)-    : Expression(pstate, DELAYED), type_(t), value_(val),-      hash_(0)-    { }-    Textual(const Textual* ptr)-    : Expression(ptr),-      type_(ptr->type_),-      value_(ptr->value_),-      hash_(ptr->hash_)-    { }--    virtual bool operator==(const Expression& rhs) const-    {-      try-      {-        Textual_Ptr_Const e = dynamic_cast<Textual_Ptr_Const>(&rhs);-        return e && value() == e->value() && type() == e->type();-      }-      catch (std::bad_cast&)-      {-        return false;-      }-      catch (...) { throw; }-    }--    virtual size_t hash()-    {-      if (hash_ == 0) {-        hash_ = std::hash<std::string>()(value_);-        hash_combine(hash_, std::hash<int>()(type_));-      }-      return hash_;-    }--    ATTACH_AST_OPERATIONS(Textual)-    ATTACH_OPERATIONS()-  };-   ////////////////////////////////////////////////   // Numbers, percentages, dimensions, and colors.   ////////////////////////////////////////////////-  class Number : public Value {-    ADD_HASHED(double, value)+  class Number : public Value, public Units {+    HASH_PROPERTY(double, value)     ADD_PROPERTY(bool, zero)-    std::vector<std::string> numerator_units_;-    std::vector<std::string> denominator_units_;     size_t hash_;   public:     Number(ParserState pstate, double val, std::string u = "", bool zero = true);      Number(const Number* ptr)     : Value(ptr),+      Units(ptr),       value_(ptr->value_), zero_(ptr->zero_),-      numerator_units_(ptr->numerator_units_),-      denominator_units_(ptr->denominator_units_),       hash_(ptr->hash_)     { concrete_type(NUMBER); }      bool zero() { return zero_; }-    bool is_valid_css_unit() const;-    std::vector<std::string>& numerator_units()   { return numerator_units_; }-    std::vector<std::string>& denominator_units() { return denominator_units_; }-    const std::vector<std::string>& numerator_units() const   { return numerator_units_; }-    const std::vector<std::string>& denominator_units() const { return denominator_units_; }-    std::string type() { return "number"; }+    std::string type() const { return "number"; }     static std::string type_name() { return "number"; }-    std::string unit() const; -    bool is_unitless() const;-    double convert_factor(const Number&) const;-    bool convert(const std::string& unit = "", bool strict = false);-    void normalize(const std::string& unit = "", bool strict = false);-    // useful for making one number compatible with another-    std::string find_convertible_unit() const;+    void reduce();+    void normalize();      virtual size_t hash()     {       if (hash_ == 0) {         hash_ = std::hash<double>()(value_);-        for (const auto numerator : numerator_units())+        for (const auto numerator : numerators)           hash_combine(hash_, std::hash<std::string>()(numerator));-        for (const auto denominator : denominator_units())+        for (const auto denominator : denominators)           hash_combine(hash_, std::hash<std::string>()(denominator));       }       return hash_;     }      virtual bool operator< (const Number& rhs) const;+    virtual bool operator== (const Number& rhs) const;     virtual bool operator== (const Expression& rhs) const;-    virtual bool eq(const Expression& rhs) const;     ATTACH_AST_OPERATIONS(Number)     ATTACH_OPERATIONS()   };@@ -1633,11 +1630,11 @@   // Colors.   //////////   class Color : public Value {-    ADD_HASHED(double, r)-    ADD_HASHED(double, g)-    ADD_HASHED(double, b)-    ADD_HASHED(double, a)-    ADD_PROPERTY(std::string, disp)+    HASH_PROPERTY(double, r)+    HASH_PROPERTY(double, g)+    HASH_PROPERTY(double, b)+    HASH_PROPERTY(double, a)+    ADD_CONSTREF(std::string, disp)     size_t hash_;   public:     Color(ParserState pstate, double r, double g, double b, double a = 1, const std::string disp = "")@@ -1653,7 +1650,7 @@       disp_(ptr->disp_),       hash_(ptr->hash_)     { concrete_type(COLOR); }-    std::string type() { return "color"; }+    std::string type() const { return "color"; }     static std::string type_name() { return "color"; }      virtual size_t hash()@@ -1677,7 +1674,7 @@   // Errors from Sass_Values.   //////////////////////////////   class Custom_Error : public Value {-    ADD_PROPERTY(std::string, message)+    ADD_CONSTREF(std::string, message)   public:     Custom_Error(ParserState pstate, std::string msg)     : Value(pstate), message_(msg)@@ -1694,7 +1691,7 @@   // Warnings from Sass_Values.   //////////////////////////////   class Custom_Warning : public Value {-    ADD_PROPERTY(std::string, message)+    ADD_CONSTREF(std::string, message)   public:     Custom_Warning(ParserState pstate, std::string msg)     : Value(pstate), message_(msg)@@ -1711,7 +1708,7 @@   // Booleans.   ////////////   class Boolean : public Value {-    ADD_HASHED(bool, value)+    HASH_PROPERTY(bool, value)     size_t hash_;   public:     Boolean(ParserState pstate, bool val)@@ -1724,7 +1721,7 @@       hash_(ptr->hash_)     { concrete_type(BOOLEAN); }     virtual operator bool() { return value_; }-    std::string type() { return "bool"; }+    std::string type() const { return "bool"; }     static std::string type_name() { return "bool"; }     virtual bool is_false() { return !value_; } @@ -1757,8 +1754,6 @@     static std::string type_name() { return "string"; }     virtual ~String() = 0;     virtual void rtrim() = 0;-    virtual void ltrim() = 0;-    virtual void trim() = 0;     virtual bool operator==(const Expression& rhs) const = 0;     virtual bool operator<(const Expression& rhs) const {       return this->to_string() < rhs.to_string();@@ -1773,19 +1768,20 @@   // evaluation phase.   ///////////////////////////////////////////////////////////////////////   class String_Schema : public String, public Vectorized<Expression_Obj> {-    // ADD_PROPERTY(bool, has_interpolants)+    ADD_PROPERTY(bool, css)     size_t hash_;   public:-    String_Schema(ParserState pstate, size_t size = 0, bool has_interpolants = false)-    : String(pstate), Vectorized<Expression_Obj>(size), hash_(0)+    String_Schema(ParserState pstate, size_t size = 0, bool css = true)+    : String(pstate), Vectorized<Expression_Obj>(size), css_(css), hash_(0)     { concrete_type(STRING); }     String_Schema(const String_Schema* ptr)     : String(ptr),       Vectorized<Expression_Obj>(*ptr),+      css_(ptr->css_),       hash_(ptr->hash_)     { concrete_type(STRING); } -    std::string type() { return "string"; }+    std::string type() const { return "string"; }     static std::string type_name() { return "string"; }      bool is_left_interpolant(void) const;@@ -1798,8 +1794,6 @@       return false;     }     virtual void rtrim();-    virtual void ltrim();-    virtual void trim();      virtual size_t hash()     {@@ -1825,7 +1819,7 @@   class String_Constant : public String {     ADD_PROPERTY(char, quote_mark)     ADD_PROPERTY(bool, can_compress_whitespace)-    ADD_HASHED(std::string, value)+    HASH_CONSTREF(std::string, value)   protected:     size_t hash_;   public:@@ -1836,24 +1830,22 @@       value_(ptr->value_),       hash_(ptr->hash_)     { }-    String_Constant(ParserState pstate, std::string val)-    : String(pstate), quote_mark_(0), can_compress_whitespace_(false), value_(read_css_string(val)), hash_(0)+    String_Constant(ParserState pstate, std::string val, bool css = true)+    : String(pstate), quote_mark_(0), can_compress_whitespace_(false), value_(read_css_string(val, css)), hash_(0)     { }-    String_Constant(ParserState pstate, const char* beg)-    : String(pstate), quote_mark_(0), can_compress_whitespace_(false), value_(read_css_string(std::string(beg))), hash_(0)+    String_Constant(ParserState pstate, const char* beg, bool css = true)+    : String(pstate), quote_mark_(0), can_compress_whitespace_(false), value_(read_css_string(std::string(beg), css)), hash_(0)     { }-    String_Constant(ParserState pstate, const char* beg, const char* end)-    : String(pstate), quote_mark_(0), can_compress_whitespace_(false), value_(read_css_string(std::string(beg, end-beg))), hash_(0)+    String_Constant(ParserState pstate, const char* beg, const char* end, bool css = true)+    : String(pstate), quote_mark_(0), can_compress_whitespace_(false), value_(read_css_string(std::string(beg, end-beg), css)), hash_(0)     { }-    String_Constant(ParserState pstate, const Token& tok)-    : String(pstate), quote_mark_(0), can_compress_whitespace_(false), value_(read_css_string(std::string(tok.begin, tok.end))), hash_(0)+    String_Constant(ParserState pstate, const Token& tok, bool css = true)+    : String(pstate), quote_mark_(0), can_compress_whitespace_(false), value_(read_css_string(std::string(tok.begin, tok.end), css)), hash_(0)     { }-    std::string type() { return "string"; }+    std::string type() const { return "string"; }     static std::string type_name() { return "string"; }     virtual bool is_invisible() const;     virtual void rtrim();-    virtual void ltrim();-    virtual void trim();      virtual size_t hash()     {@@ -1881,8 +1873,8 @@   public:     String_Quoted(ParserState pstate, std::string val, char q = 0,       bool keep_utf8_escapes = false, bool skip_unquoting = false,-      bool strict_unquoting = true)-    : String_Constant(pstate, val)+      bool strict_unquoting = true, bool css = true)+    : String_Constant(pstate, val, css)     {       if (skip_unquoting == false) {         value_ = unquote(value_, &quote_mark_, keep_utf8_escapes, strict_unquoting);@@ -2103,7 +2095,7 @@        if (s->statement_type() == Statement::DIRECTIVE)       {-        if (Directive_Obj dir = SASS_MEMORY_CAST(Directive, s))+        if (Directive_Obj dir = Cast<Directive>(s))         {           std::string keyword(dir->keyword());           if (keyword.length() > 0) keyword.erase(0, 1);@@ -2122,7 +2114,7 @@       {         return expression()->exclude("supports");       }-      if (Directive_Obj dir = SASS_MEMORY_CAST(Directive, s))+      if (Directive_Obj dir = Cast<Directive>(s))       {         if (dir->is_keyframes()) return expression()->exclude("keyframes");       }@@ -2139,7 +2131,7 @@   public:     Null(ParserState pstate) : Value(pstate) { concrete_type(NULL_VAL); }     Null(const Null* ptr) : Value(ptr) { concrete_type(NULL_VAL); }-    std::string type() { return "null"; }+    std::string type() const { return "null"; }     static std::string type_name() { return "null"; }     bool is_invisible() const { return true; }     operator bool() { return false; }@@ -2172,7 +2164,7 @@   // Individual parameter objects for mixins and functions.   /////////////////////////////////////////////////////////   class Parameter : public AST_Node {-    ADD_PROPERTY(std::string, name)+    ADD_CONSTREF(std::string, name)     ADD_PROPERTY(Expression_Obj, default_value)     ADD_PROPERTY(bool, is_rest_parameter)   public:@@ -2180,9 +2172,11 @@               std::string n, Expression_Obj def = 0, bool rest = false)     : AST_Node(pstate), name_(n), default_value_(def), is_rest_parameter_(rest)     {-      if (default_value_ && is_rest_parameter_) {-        error("variable-length parameter may not have a default value", pstate_);-      }+      // tried to come up with a spec test for this, but it does no longer+      // get  past the parser (it error out earlier). A spec test was added!+      // if (default_value_ && is_rest_parameter_) {+      //   error("variable-length parameter may not have a default value", pstate_);+      // }     }     Parameter(const Parameter* ptr)     : AST_Node(ptr),@@ -2190,9 +2184,11 @@       default_value_(ptr->default_value_),       is_rest_parameter_(ptr->is_rest_parameter_)     {-      if (default_value_ && is_rest_parameter_) {-        error("variable-length parameter may not have a default value", pstate_);-      }+      // tried to come up with a spec test for this, but it does no longer+      // get  past the parser (it error out earlier). A spec test was added!+      // if (default_value_ && is_rest_parameter_) {+      //   error("variable-length parameter may not have a default value", pstate_);+      // }     }     ATTACH_AST_OPERATIONS(Parameter)     ATTACH_OPERATIONS()@@ -2210,23 +2206,23 @@     void adjust_after_pushing(Parameter_Obj p)     {       if (p->default_value()) {-        if (has_rest_parameter_) {-          error("optional parameters may not be combined with variable-length parameters", p->pstate());+        if (has_rest_parameter()) {+          coreError("optional parameters may not be combined with variable-length parameters", p->pstate());         }-        has_optional_parameters_ = true;+        has_optional_parameters(true);       }       else if (p->is_rest_parameter()) {-        if (has_rest_parameter_) {-          error("functions and mixins cannot have more than one variable-length parameter", p->pstate());+        if (has_rest_parameter()) {+          coreError("functions and mixins cannot have more than one variable-length parameter", p->pstate());         }-        has_rest_parameter_ = true;+        has_rest_parameter(true);       }       else {-        if (has_rest_parameter_) {-          error("required parameters must precede variable-length parameters", p->pstate());+        if (has_rest_parameter()) {+          coreError("required parameters must precede variable-length parameters", p->pstate());         }-        if (has_optional_parameters_) {-          error("required parameters must precede optional parameters", p->pstate());+        if (has_optional_parameters()) {+          coreError("required parameters must precede optional parameters", p->pstate());         }       }     }@@ -2266,9 +2262,8 @@   protected:     size_t hash_;   public:-    Selector(ParserState pstate, bool r = false, bool h = false)+    Selector(ParserState pstate)     : Expression(pstate),-      // has_reference_(r),       has_line_feed_(false),       has_line_break_(false),       is_optional_(false),@@ -2286,21 +2281,19 @@     { concrete_type(SELECTOR); }     virtual ~Selector() = 0;     virtual size_t hash() = 0;-    virtual unsigned long specificity() {-      return 0;-    }+    virtual unsigned long specificity() const = 0;     virtual void set_media_block(Media_Block_Ptr mb) {       media_block(mb);     }-    virtual bool has_parent_ref() {+    virtual bool has_parent_ref() const {       return false;     }-    virtual bool has_real_parent_ref() {+    virtual bool has_real_parent_ref() const {       return false;     }     // dispatch to correct handlers-    virtual bool operator<(const Selector& rhs) const;-    virtual bool operator==(const Selector& rhs) const;+    virtual bool operator<(const Selector& rhs) const = 0;+    virtual bool operator==(const Selector& rhs) const = 0;     ATTACH_VIRTUAL_AST_OPERATIONS(Selector);   };   inline Selector::~Selector() { }@@ -2309,20 +2302,36 @@   // Interpolated selectors -- the interpolated String will be expanded and   // re-parsed into a normal selector class.   /////////////////////////////////////////////////////////////////////////-  class Selector_Schema : public Selector {+  class Selector_Schema : public AST_Node {     ADD_PROPERTY(String_Obj, contents)-    ADD_PROPERTY(bool, at_root);+    ADD_PROPERTY(bool, connect_parent);+    // must not be a reference counted object+    // otherwise we create circular references+    ADD_PROPERTY(Media_Block_Ptr, media_block)+    // store computed hash+    size_t hash_;   public:     Selector_Schema(ParserState pstate, String_Obj c)-    : Selector(pstate), contents_(c), at_root_(false)+    : AST_Node(pstate),+      contents_(c),+      connect_parent_(true),+      media_block_(NULL),+      hash_(0)     { }     Selector_Schema(const Selector_Schema* ptr)-    : Selector(ptr),+    : AST_Node(ptr),       contents_(ptr->contents_),-      at_root_(ptr->at_root_)+      connect_parent_(ptr->connect_parent_),+      media_block_(ptr->media_block_),+      hash_(ptr->hash_)     { }-    virtual bool has_parent_ref();-    virtual bool has_real_parent_ref();+    virtual bool has_parent_ref() const;+    virtual bool has_real_parent_ref() const;+    virtual bool operator<(const Selector& rhs) const;+    virtual bool operator==(const Selector& rhs) const;+    // selector schema is not yet a final selector, so we do not+    // have a specificity for it yet. We need to+    virtual unsigned long specificity() const { return 0; }     virtual size_t hash() {       if (hash_ == 0) {         hash_combine(hash_, contents_->hash());@@ -2337,8 +2346,8 @@   // Abstract base class for simple selectors.   ////////////////////////////////////////////   class Simple_Selector : public Selector {-    ADD_PROPERTY(std::string, ns)-    ADD_PROPERTY(std::string, name)+    ADD_CONSTREF(std::string, ns)+    ADD_CONSTREF(std::string, name)     ADD_PROPERTY(Simple_Type, simple_type)     ADD_PROPERTY(bool, has_ns)   public:@@ -2360,10 +2369,6 @@       name_(ptr->name_),       has_ns_(ptr->has_ns_)     { simple_type(SIMPLE); }-    virtual bool unique() const-    {-      return false;-    }     virtual std::string ns_name() const     {       std::string name("");@@ -2380,6 +2385,8 @@       }       return hash_;     }+    // namespace compare functions+    bool is_ns_eq(const Simple_Selector& r) const;     // namespace query functions     bool is_universal_ns() const     {@@ -2412,11 +2419,10 @@     }      virtual ~Simple_Selector() = 0;-    virtual Compound_Selector_Ptr unify_with(Compound_Selector_Ptr, Context&);-    virtual bool has_parent_ref() { return false; };-    virtual bool has_real_parent_ref() { return false; };-    virtual bool is_pseudo_element() { return false; }-    virtual bool is_pseudo_class() { return false; }+    virtual Compound_Selector_Ptr unify_with(Compound_Selector_Ptr);+    virtual bool has_parent_ref() const { return false; };+    virtual bool has_real_parent_ref() const  { return false; };+    virtual bool is_pseudo_element() const { return false; }      virtual bool is_superselector_of(Compound_Selector_Obj sub) { return false; } @@ -2448,14 +2454,14 @@     Parent_Selector(const Parent_Selector* ptr)     : Simple_Selector(ptr), real_(ptr->real_)     { /* has_reference(true); */ }-    bool is_real_parent_ref() { return real(); };-    virtual bool has_parent_ref() { return true; };-    virtual bool has_real_parent_ref() { return is_real_parent_ref(); };-    virtual unsigned long specificity()+    bool is_real_parent_ref() const { return real(); };+    virtual bool has_parent_ref() const { return true; };+    virtual bool has_real_parent_ref() const { return is_real_parent_ref(); };+    virtual unsigned long specificity() const     {       return 0;     }-    std::string type() { return "selector"; }+    std::string type() const { return "selector"; }     static std::string type_name() { return "selector"; }     ATTACH_AST_OPERATIONS(Parent_Selector)     ATTACH_OPERATIONS()@@ -2472,7 +2478,7 @@     Placeholder_Selector(const Placeholder_Selector* ptr)     : Simple_Selector(ptr)     { }-    virtual unsigned long specificity()+    virtual unsigned long specificity() const     {       return Constants::Specificity_Base;     }@@ -2495,13 +2501,17 @@     Element_Selector(const Element_Selector* ptr)     : Simple_Selector(ptr)     { }-    virtual unsigned long specificity()+    virtual unsigned long specificity() const     {       if (name() == "*") return 0;       else               return Constants::Specificity_Element;     }-    virtual Simple_Selector_Ptr unify_with(Simple_Selector_Ptr, Context&);-    virtual Compound_Selector_Ptr unify_with(Compound_Selector_Ptr, Context&);+    virtual Simple_Selector_Ptr unify_with(Simple_Selector_Ptr);+    virtual Compound_Selector_Ptr unify_with(Compound_Selector_Ptr);+    virtual bool operator==(const Simple_Selector& rhs) const;+    virtual bool operator==(const Element_Selector& rhs) const;+    virtual bool operator<(const Simple_Selector& rhs) const;+    virtual bool operator<(const Element_Selector& rhs) const;     ATTACH_AST_OPERATIONS(Element_Selector)     ATTACH_OPERATIONS()   };@@ -2517,15 +2527,11 @@     Class_Selector(const Class_Selector* ptr)     : Simple_Selector(ptr)     { }-    virtual bool unique() const-    {-      return false;-    }-    virtual unsigned long specificity()+    virtual unsigned long specificity() const     {       return Constants::Specificity_Class;     }-    virtual Compound_Selector_Ptr unify_with(Compound_Selector_Ptr, Context&);+    virtual Compound_Selector_Ptr unify_with(Compound_Selector_Ptr);     ATTACH_AST_OPERATIONS(Class_Selector)     ATTACH_OPERATIONS()   };@@ -2541,15 +2547,11 @@     Id_Selector(const Id_Selector* ptr)     : Simple_Selector(ptr)     { }-    virtual bool unique() const-    {-      return true;-    }-    virtual unsigned long specificity()+    virtual unsigned long specificity() const     {       return Constants::Specificity_ID;     }-    virtual Compound_Selector_Ptr unify_with(Compound_Selector_Ptr, Context&);+    virtual Compound_Selector_Ptr unify_with(Compound_Selector_Ptr);     ATTACH_AST_OPERATIONS(Id_Selector)     ATTACH_OPERATIONS()   };@@ -2558,17 +2560,19 @@   // Attribute selectors -- e.g., [src*=".jpg"], etc.   ///////////////////////////////////////////////////   class Attribute_Selector : public Simple_Selector {-    ADD_PROPERTY(std::string, matcher)+    ADD_CONSTREF(std::string, matcher)     // this cannot be changed to obj atm!!!!!!????!!!!!!!     ADD_PROPERTY(String_Obj, value) // might be interpolated+    ADD_PROPERTY(char, modifier);   public:-    Attribute_Selector(ParserState pstate, std::string n, std::string m, String_Obj v)-    : Simple_Selector(pstate, n), matcher_(m), value_(v)+    Attribute_Selector(ParserState pstate, std::string n, std::string m, String_Obj v, char o = 0)+    : Simple_Selector(pstate, n), matcher_(m), value_(v), modifier_(o)     { simple_type(ATTR_SEL); }     Attribute_Selector(const Attribute_Selector* ptr)     : Simple_Selector(ptr),       matcher_(ptr->matcher_),-      value_(ptr->value_)+      value_(ptr->value_),+      modifier_(ptr->modifier_)     { simple_type(ATTR_SEL); }     virtual size_t hash()     {@@ -2579,7 +2583,7 @@       }       return hash_;     }-    virtual unsigned long specificity()+    virtual unsigned long specificity() const     {       return Constants::Specificity_Attr;     }@@ -2617,14 +2621,6 @@     : Simple_Selector(ptr), expression_(ptr->expression_)     { simple_type(PSEUDO_SEL); } -    // A pseudo-class always consists of a "colon" (:) followed by the name-    // of the pseudo-class and optionally by a value between parentheses.-    virtual bool is_pseudo_class()-    {-      return (name_[0] == ':' && name_[1] != ':')-             && ! is_pseudo_class_element(name_);-    }-     // 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.@@ -2633,7 +2629,7 @@     // in CSS levels 1 and 2 (namely, :first-line, :first-letter, :before and     // :after). This compatibility is not allowed for the new pseudo-elements     // introduced in this specification.-    virtual bool is_pseudo_element()+    virtual bool is_pseudo_element() const     {       return (name_[0] == ':' && name_[1] == ':')              || is_pseudo_class_element(name_);@@ -2646,7 +2642,7 @@       }       return hash_;     }-    virtual unsigned long specificity()+    virtual unsigned long specificity() const     {       if (is_pseudo_element())         return Constants::Specificity_Element;@@ -2656,7 +2652,7 @@     virtual bool operator==(const Pseudo_Selector& rhs) const;     virtual bool operator<(const Simple_Selector& rhs) const;     virtual bool operator<(const Pseudo_Selector& rhs) const;-    virtual Compound_Selector_Ptr unify_with(Compound_Selector_Ptr, Context&);+    virtual Compound_Selector_Ptr unify_with(Compound_Selector_Ptr);     ATTACH_AST_OPERATIONS(Pseudo_Selector)     ATTACH_OPERATIONS()   };@@ -2665,9 +2661,9 @@   // Wrapped selector -- pseudo selector that takes a list of selectors as argument(s) e.g., :not(:first-of-type), :-moz-any(ol p.blah, ul, menu, dir)   /////////////////////////////////////////////////   class Wrapped_Selector : public Simple_Selector {-    ADD_PROPERTY(Selector_Obj, selector)+    ADD_PROPERTY(Selector_List_Obj, selector)   public:-    Wrapped_Selector(ParserState pstate, std::string n, Selector_Obj sel)+    Wrapped_Selector(ParserState pstate, std::string n, Selector_List_Obj sel)     : Simple_Selector(pstate, n), selector_(sel)     { simple_type(WRAPPED_SEL); }     Wrapped_Selector(const Wrapped_Selector* ptr)@@ -2676,28 +2672,11 @@     virtual bool is_superselector_of(Wrapped_Selector_Obj sub);     // Selectors inside the negation pseudo-class are counted like any     // other, but the negation itself does not count as a pseudo-class.-    virtual size_t hash()-    {-      if (hash_ == 0) {-        hash_combine(hash_, Simple_Selector::hash());-        if (selector_) hash_combine(hash_, selector_->hash());-      }-      return hash_;-    }-    virtual bool has_parent_ref() {-      // if (has_reference()) return true;-      if (!selector()) return false;-      return selector()->has_parent_ref();-    }-    virtual bool has_real_parent_ref() {-      // if (has_reference()) return true;-      if (!selector()) return false;-      return selector()->has_real_parent_ref();-    }-    virtual unsigned long specificity()-    {-      return selector_ ? selector_->specificity() : 0;-    }+    virtual size_t hash();+    virtual bool has_parent_ref() const;+    virtual bool has_real_parent_ref() const;+    virtual unsigned long specificity() const;+    virtual bool find ( bool (*f)(AST_Node_Obj) );     virtual bool operator==(const Simple_Selector& rhs) const;     virtual bool operator==(const Wrapped_Selector& rhs) const;     virtual bool operator<(const Simple_Selector& rhs) const;@@ -2707,18 +2686,13 @@     ATTACH_OPERATIONS()   }; -  struct Complex_Selector_Pointer_Compare {-    bool operator() (const Complex_Selector_Obj& pLeft, const Complex_Selector_Obj& pRight) const;-  };-   ////////////////////////////////////////////////////////////////////////////   // Simple selector sequences. Maintains flags indicating whether it contains   // any parent references or placeholders, to simplify expansion.   ////////////////////////////////////////////////////////////////////////////-  typedef std::set<Complex_Selector_Obj, Complex_Selector_Pointer_Compare> SourcesSet;   class Compound_Selector : public Selector, public Vectorized<Simple_Selector_Obj> {   private:-    SourcesSet sources_;+    ComplexSelectorSet sources_;     ADD_PROPERTY(bool, extended);     ADD_PROPERTY(bool, has_parent_reference);   protected:@@ -2755,20 +2729,15 @@     }      Complex_Selector_Obj to_complex();-    Compound_Selector_Ptr unify_with(Compound_Selector_Ptr rhs, Context& ctx);+    Compound_Selector_Ptr unify_with(Compound_Selector_Ptr rhs);     // virtual Placeholder_Selector_Ptr find_placeholder();-    virtual bool has_parent_ref();-    virtual bool has_real_parent_ref();-    Simple_Selector_Ptr base()-    {-      // Implement non-const in terms of const. Safe to const_cast since this method is non-const-      return const_cast<Simple_Selector_Ptr>(static_cast<Compound_Selector_Ptr_Const>(this)->base());-    }-    Simple_Selector_Ptr_Const base() const {+    virtual bool has_parent_ref() const;+    virtual bool has_real_parent_ref() const;+    Simple_Selector_Ptr base() const {       if (length() == 0) return 0;       // ToDo: why is this needed?-      if (SASS_MEMORY_CAST(Element_Selector, (*this)[0]))-        return &(*this)[0];+      if (Cast<Element_Selector>((*this)[0]))+        return (*this)[0];       return 0;     }     virtual bool is_superselector_of(Compound_Selector_Obj sub, std::string wrapped = "");@@ -2782,7 +2751,7 @@       }       return Selector::hash_;     }-    virtual unsigned long specificity()+    virtual unsigned long specificity() const     {       int sum = 0;       for (size_t i = 0, L = length(); i < L; ++i)@@ -2802,19 +2771,21 @@     bool is_empty_reference()     {       return length() == 1 &&-             SASS_MEMORY_CAST(Parent_Selector, (*this)[0]);+             Cast<Parent_Selector>((*this)[0]);     }-    std::vector<Subset_Map_Key> to_str_vec(); // sometimes need to convert to a flat "by-value" data structure +    virtual bool find ( bool (*f)(AST_Node_Obj) );+    virtual bool operator<(const Selector& rhs) const;+    virtual bool operator==(const Selector& rhs) const;     virtual bool operator<(const Compound_Selector& rhs) const;     virtual bool operator==(const Compound_Selector& rhs) const;     inline bool operator!=(const Compound_Selector& rhs) const { return !(*this == rhs); } -    SourcesSet& sources() { return sources_; }+    ComplexSelectorSet& sources() { return sources_; }     void clearSources() { sources_.clear(); }-    void mergeSources(SourcesSet& sources, Context& ctx);+    void mergeSources(ComplexSelectorSet& sources); -    Compound_Selector_Ptr minus(Compound_Selector_Ptr rhs, Context& ctx);+    Compound_Selector_Ptr minus(Compound_Selector_Ptr rhs);     virtual void cloneChildren();     ATTACH_AST_OPERATIONS(Compound_Selector)     ATTACH_OPERATIONS()@@ -2829,10 +2800,10 @@   public:     enum Combinator { ANCESTOR_OF, PARENT_OF, PRECEDES, ADJACENT_TO, REFERENCE };   private:-    ADD_PROPERTY(Combinator, combinator)-    ADD_PROPERTY(Compound_Selector_Obj, head)-    ADD_PROPERTY(Complex_Selector_Obj, tail)-    ADD_PROPERTY(String_Obj, reference);+    HASH_CONSTREF(Combinator, combinator)+    HASH_PROPERTY(Compound_Selector_Obj, head)+    HASH_PROPERTY(Complex_Selector_Obj, tail)+    HASH_PROPERTY(String_Obj, reference);   public:     bool contains_placeholder() {       if (head() && head()->contains_placeholder()) return true;@@ -2855,8 +2826,8 @@       head_(ptr->head_), tail_(ptr->tail_),       reference_(ptr->reference_)     {};-    virtual bool has_parent_ref();-    virtual bool has_real_parent_ref();+    virtual bool has_parent_ref() const;+    virtual bool has_real_parent_ref() const;      Complex_Selector_Obj skip_empty_reference()     {@@ -2878,10 +2849,7 @@              combinator() == Combinator::ANCESTOR_OF;     } -    Complex_Selector_Obj context(Context&);---    Selector_List_Ptr tails(Context& ctx, Selector_List_Ptr tails);+    Selector_List_Ptr tails(Selector_List_Ptr tails);      // front returns the first real tail     // skips over parent and empty ones@@ -2893,13 +2861,13 @@     Complex_Selector_Obj innermost() { return last(); };      size_t length() const;-    Selector_List_Ptr resolve_parent_refs(Context& ctx, std::vector<Selector_List_Obj>& pstack, bool implicit_parent = true);+    Selector_List_Ptr resolve_parent_refs(std::vector<Selector_List_Obj>& pstack, Backtraces& traces, bool implicit_parent = true);     virtual bool is_superselector_of(Compound_Selector_Obj sub, std::string wrapping = "");     virtual bool is_superselector_of(Complex_Selector_Obj sub, std::string wrapping = "");     virtual bool is_superselector_of(Selector_List_Obj sub, std::string wrapping = "");-    Selector_List_Ptr unify_with(Complex_Selector_Ptr rhs, Context& ctx);+    Selector_List_Ptr unify_with(Complex_Selector_Ptr rhs);     Combinator clear_innermost();-    void append(Context&, Complex_Selector_Obj);+    void append(Complex_Selector_Obj, Backtraces& traces);     void set_innermost(Complex_Selector_Obj, Combinator);     virtual size_t hash()     {@@ -2928,55 +2896,58 @@       if (tail_ && tail_->has_placeholder()) return true;       return false;     }+    virtual bool find ( bool (*f)(AST_Node_Obj) );+    virtual bool operator<(const Selector& rhs) const;+    virtual bool operator==(const Selector& rhs) const;     virtual bool operator<(const Complex_Selector& rhs) const;     virtual bool operator==(const Complex_Selector& rhs) const;     inline bool operator!=(const Complex_Selector& rhs) const { return !(*this == rhs); }-    SourcesSet sources()+    const ComplexSelectorSet sources()     {       //s = Set.new       //seq.map {|sseq_or_op| s.merge sseq_or_op.sources if sseq_or_op.is_a?(SimpleSequence)}       //s -      SourcesSet srcs;+      ComplexSelectorSet srcs;        Compound_Selector_Obj pHead = head();       Complex_Selector_Obj  pTail = tail();        if (pHead) {-        SourcesSet& headSources = pHead->sources();+        const ComplexSelectorSet& headSources = pHead->sources();         srcs.insert(headSources.begin(), headSources.end());       }        if (pTail) {-        SourcesSet tailSources = pTail->sources();+        const ComplexSelectorSet& tailSources = pTail->sources();         srcs.insert(tailSources.begin(), tailSources.end());       }        return srcs;     }-    void addSources(SourcesSet& sources, Context& ctx) {+    void addSources(ComplexSelectorSet& sources) {       // members.map! {|m| m.is_a?(SimpleSequence) ? m.with_more_sources(sources) : m}       Complex_Selector_Ptr pIter = this;       while (pIter) {-        Compound_Selector_Ptr pHead = &pIter->head();+        Compound_Selector_Ptr pHead = pIter->head();          if (pHead) {-          pHead->mergeSources(sources, ctx);+          pHead->mergeSources(sources);         } -        pIter = &pIter->tail();+        pIter = pIter->tail();       }     }     void clearSources() {       Complex_Selector_Ptr pIter = this;       while (pIter) {-        Compound_Selector_Ptr pHead = &pIter->head();+        Compound_Selector_Ptr pHead = pIter->head();          if (pHead) {           pHead->clearSources();         } -        pIter = &pIter->tail();+        pIter = pIter->tail();       }     } @@ -2985,36 +2956,40 @@     ATTACH_OPERATIONS()   }; -  typedef std::deque<Complex_Selector_Obj> ComplexSelectorDeque;-   ///////////////////////////////////   // Comma-separated selector groups.   ///////////////////////////////////   class Selector_List : public Selector, public Vectorized<Complex_Selector_Obj> {-    ADD_PROPERTY(std::vector<std::string>, wspace)+    ADD_PROPERTY(Selector_Schema_Obj, schema)+    ADD_CONSTREF(std::vector<std::string>, wspace)   protected:     void adjust_after_pushing(Complex_Selector_Obj c);   public:     Selector_List(ParserState pstate, size_t s = 0)-    : Selector(pstate), Vectorized<Complex_Selector_Obj>(s), wspace_(0)+    : Selector(pstate),+      Vectorized<Complex_Selector_Obj>(s),+      schema_(NULL),+      wspace_(0)     { }     Selector_List(const Selector_List* ptr)     : Selector(ptr),       Vectorized<Complex_Selector_Obj>(*ptr),+      schema_(ptr->schema_),       wspace_(ptr->wspace_)     { }-    std::string type() { return "list"; }+    std::string type() const { return "list"; }     // remove parent selector references     // basically unwraps parsed selectors-    virtual bool has_parent_ref();-    virtual bool has_real_parent_ref();+    virtual bool has_parent_ref() const;+    virtual bool has_real_parent_ref() const;     void remove_parent_selectors();-    Selector_List_Ptr resolve_parent_refs(Context& ctx, std::vector<Selector_List_Obj>& pstack, bool implicit_parent = true);+    Selector_List_Ptr resolve_parent_refs(std::vector<Selector_List_Obj>& pstack, Backtraces& traces, bool implicit_parent = true);     virtual bool is_superselector_of(Compound_Selector_Obj sub, std::string wrapping = "");     virtual bool is_superselector_of(Complex_Selector_Obj sub, std::string wrapping = "");     virtual bool is_superselector_of(Selector_List_Obj sub, std::string wrapping = "");-    Selector_List_Ptr unify_with(Selector_List_Ptr, Context&);-    void populate_extends(Selector_List_Obj, Context&, Subset_Map&);+    Selector_List_Ptr unify_with(Selector_List_Ptr);+    void populate_extends(Selector_List_Obj, Subset_Map&);+    Selector_List_Obj eval(Eval& eval);     virtual size_t hash()     {       if (Selector::hash_ == 0) {@@ -3023,10 +2998,10 @@       }       return Selector::hash_;     }-    virtual unsigned long specificity()+    virtual unsigned long specificity() const     {       unsigned long sum = 0;-      unsigned long specificity = 0;+      unsigned long specificity;       for (size_t i = 0, L = length(); i < L; ++i)       {         specificity = (*this)[i]->specificity();@@ -3046,6 +3021,7 @@       }       return false;     }+    virtual bool find ( bool (*f)(AST_Node_Obj) );     virtual bool operator<(const Selector& rhs) const;     virtual bool operator==(const Selector& rhs) const;     virtual bool operator<(const Selector_List& rhs) const;@@ -3056,24 +3032,6 @@     ATTACH_AST_OPERATIONS(Selector_List)     ATTACH_OPERATIONS()   };--  template<typename SelectorType>-  bool selectors_equal(const SelectorType& one, const SelectorType& two, bool simpleSelectorOrderDependent) {-    // Test for equality among selectors while differentiating between checks that demand the underlying Simple_Selector-    // ordering to be the same or not. This works because operator< (which doesn't make a whole lot of sense for selectors, but-    // is required for proper stl collection ordering) is implemented using string comparision. This gives stable sorting-    // behavior, and can be used to determine if the selectors would have exactly idential output. operator== matches the-    // ruby sass implementations for eql, which sometimes perform order independent comparisions (like set comparisons of the-    // members of a SimpleSequence (Compound_Selector)).-    //-    // Due to the reliance on operator== and operater< behavior, this templated method is currently only intended for-    // use with Compound_Selector and Complex_Selector objects.-    if (simpleSelectorOrderDependent) {-      return !(one < two) && !(two < one);-    } else {-      return one == two;-    }-  }    // compare function for sorting and probably other other uses   struct cmp_complex_selector { inline bool operator() (const Complex_Selector_Obj l, const Complex_Selector_Obj r) { return (*l < *r); } };
libsass/src/ast_def_macros.hpp view
@@ -19,13 +19,22 @@       this->orig = var;       *(this->var) = orig;     }+    void reset()+    {+      *(this->var) = this->orig;+    }     ~LocalOption() {       *(this->var) = this->orig;     } };  #define LOCAL_FLAG(name,opt) LocalOption<bool> flag_##name(name, opt)+#define LOCAL_COUNT(name,opt) LocalOption<size_t> cnt_##name(name, opt) +#define NESTING_GUARD(name) \+  LocalOption<size_t> cnt_##name(name, name + 1); \+  if (name > MAX_NESTING) throw Exception::NestingLimitError(pstate, traces); \+ #define ATTACH_OPERATIONS()\ virtual void perform(Operation<void>* op) { (*op)(this); }\ virtual AST_Node_Ptr perform(Operation<AST_Node_Ptr>* op) { return (*op)(this); }\@@ -44,12 +53,28 @@   type name(type name##__) { return name##_ = name##__; }\ private: -#define ADD_HASHED(type, name)\+#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) \+protected: \+  type name##_; \+public: \+  const type& name() const { return name##_; } \+  void name(type name##__) { name##_ = name##__; } \+private:++#define HASH_CONSTREF(type, name) \+protected: \+  type name##_; \+public: \+  const type& name() const { return name##_; } \+  void name(type name##__) { hash_ = 0; name##_ = name##__; } \ private:  #endif
+ libsass/src/ast_fwd_decl.cpp view
@@ -0,0 +1,29 @@+#include "ast.hpp"++namespace Sass {++  #define IMPLEMENT_BASE_CAST(T) \+  template<> \+  T* Cast(AST_Node* ptr) { \+    return dynamic_cast<T*>(ptr); \+  }; \+  \+  template<> \+  const T* Cast(const AST_Node* ptr) { \+    return dynamic_cast<const T*>(ptr); \+  }; \++  IMPLEMENT_BASE_CAST(AST_Node)+  IMPLEMENT_BASE_CAST(Expression)+  IMPLEMENT_BASE_CAST(Statement)+  IMPLEMENT_BASE_CAST(Has_Block)+  IMPLEMENT_BASE_CAST(PreValue)+  IMPLEMENT_BASE_CAST(Value)+  IMPLEMENT_BASE_CAST(List)+  IMPLEMENT_BASE_CAST(String)+  IMPLEMENT_BASE_CAST(String_Constant)+  IMPLEMENT_BASE_CAST(Supports_Condition)+  IMPLEMENT_BASE_CAST(Selector)+  IMPLEMENT_BASE_CAST(Simple_Selector)++}
libsass/src/ast_fwd_decl.hpp view
@@ -1,12 +1,17 @@ #ifndef SASS_AST_FWD_DECL_H #define SASS_AST_FWD_DECL_H +#include <map>+#include <set>+#include <deque> #include <vector>+#include <typeinfo> #include <iostream> #include <algorithm> #include <unordered_map> #include <unordered_set> #include "memory/SharedPtr.hpp"+#include "sass/functions.h"  ///////////////////////////////////////////// // Forward declarations for the AST visitors.@@ -128,6 +133,9 @@   class Map;   typedef Map* Map_Ptr;   typedef Map const* Map_Ptr_Const;+  class Function;+  typedef Function* Function_Ptr;+  typedef Function const* Function_Ptr_Const;    class Mixin_Call;   typedef Mixin_Call* Mixin_Call_Ptr;@@ -154,9 +162,6 @@   class Variable;   typedef Variable* Variable_Ptr;   typedef Variable const* Variable_Ptr_Const;-  class Textual;-  typedef Textual* Textual_Ptr;-  typedef Textual const* Textual_Ptr_Const;   class Number;   typedef Number* Number_Ptr;   typedef Number const* Number_Ptr_Const;@@ -310,6 +315,7 @@   IMPL_MEM_OBJ(Expression);   IMPL_MEM_OBJ(List);   IMPL_MEM_OBJ(Map);+  IMPL_MEM_OBJ(Function);   IMPL_MEM_OBJ(Binary_Expression);   IMPL_MEM_OBJ(Unary_Expression);   IMPL_MEM_OBJ(Function_Call);@@ -317,7 +323,6 @@   IMPL_MEM_OBJ(Custom_Warning);   IMPL_MEM_OBJ(Custom_Error);   IMPL_MEM_OBJ(Variable);-  IMPL_MEM_OBJ(Textual);   IMPL_MEM_OBJ(Number);   IMPL_MEM_OBJ(Color);   IMPL_MEM_OBJ(Boolean);@@ -353,37 +358,105 @@   IMPL_MEM_OBJ(Complex_Selector);   IMPL_MEM_OBJ(Selector_List); +  // ###########################################################################+  // Implement compare, order and hashing operations for AST Nodes+  // ########################################################################### -  struct HashExpression {-    size_t operator() (Expression_Obj ex) const;+  struct HashNodes {+    template <class T>+    size_t operator() (const T& ex) const {+      return ex.isNull() ? 0 : ex->hash();+    }   };-  struct CompareExpression {-    bool operator()(const Expression_Obj& lhs, const Expression_Obj& rhs) const;+  struct OrderNodes {+    template <class T>+    bool operator() (const T& lhs, const T& rhs) const {+      return !lhs.isNull() && !rhs.isNull() && *lhs < *rhs;+    }   };--  struct HashSimpleSelector {-    size_t operator() (Simple_Selector_Obj ex) const;+  struct CompareNodes {+    template <class T>+    bool operator() (const T& lhs, const T& rhs) const {+      // code around sass logic issue. 1px == 1 is true+      // but both items are still different keys in maps+      if (dynamic_cast<Number*>(lhs.ptr()))+        if (dynamic_cast<Number*>(rhs.ptr()))+          return lhs->hash() == rhs->hash();+      return !lhs.isNull() && !rhs.isNull() && *lhs == *rhs;+    }   }; -  struct CompareSimpleSelector {-    bool operator()(Simple_Selector_Obj lhs, Simple_Selector_Obj rhs) const;-  };+  // ###########################################################################+  // some often used typedefs+  // ###########################################################################    typedef std::unordered_map<     Expression_Obj, // key     Expression_Obj, // value-    HashExpression, // hasher-    CompareExpression // compare+    HashNodes, // hasher+    CompareNodes // compare   > ExpressionMap;   typedef std::unordered_set<     Expression_Obj, // value-    HashExpression, // hasher-    CompareExpression // compare+    HashNodes, // hasher+    CompareNodes // compare   > ExpressionSet; -  typedef std::string Subset_Map_Key;-  typedef std::vector<size_t> Subset_Map_Arr;-  typedef std::pair<Complex_Selector_Obj, Compound_Selector_Obj> Subset_Map_Val;+  typedef std::string SubSetMapKey;+  typedef std::vector<std::string> SubSetMapKeys;++  typedef std::pair<Complex_Selector_Obj, Compound_Selector_Obj> SubSetMapPair;+  typedef std::pair<Compound_Selector_Obj, Complex_Selector_Obj> SubSetMapLookup;+  typedef std::vector<SubSetMapPair> SubSetMapPairs;+  typedef std::vector<SubSetMapLookup> SubSetMapLookups;++  typedef std::pair<Complex_Selector_Obj, SubSetMapPairs> SubSetMapResult;+  typedef std::vector<SubSetMapResult> SubSetMapResults;++  typedef std::deque<Complex_Selector_Obj> ComplexSelectorDeque;+  typedef std::set<Simple_Selector_Obj, OrderNodes> SimpleSelectorSet;+  typedef std::set<Complex_Selector_Obj, OrderNodes> ComplexSelectorSet;+  typedef std::set<Compound_Selector_Obj, OrderNodes> CompoundSelectorSet;+  typedef std::unordered_set<Simple_Selector_Obj, HashNodes, CompareNodes> SimpleSelectorDict;++  typedef std::vector<Sass_Import_Entry>* ImporterStack;++  // only to switch implementations for testing+  #define environment_map std::map++  // ###########################################################################+  // explicit type conversion functions+  // ###########################################################################++  template<class T>+  T* Cast(AST_Node* ptr);++  template<class T>+  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(Has_Block)+  DECLARE_BASE_CAST(PreValue)+  DECLARE_BASE_CAST(Value)+  DECLARE_BASE_CAST(List)+  DECLARE_BASE_CAST(String)+  DECLARE_BASE_CAST(String_Constant)+  DECLARE_BASE_CAST(Supports_Condition)+  DECLARE_BASE_CAST(Selector)+  DECLARE_BASE_CAST(Simple_Selector)  } 
libsass/src/b64/encode.h view
@@ -24,7 +24,9 @@  		encoder(int buffersize_in = BUFFERSIZE) 		: _buffersize(buffersize_in)-		{}+		{+			base64_init_encodestate(&_state);+		}  		int encode(char value_in) 		{
+ libsass/src/backtrace.cpp view
@@ -0,0 +1,46 @@+#include "backtrace.hpp"++namespace Sass {++  const std::string traces_to_string(Backtraces traces, std::string indent) {++    std::stringstream ss;+    std::string cwd(File::get_cwd());++    bool first = true;+    size_t i_beg = traces.size() - 1;+    size_t i_end = std::string::npos;+    for (size_t i = i_beg; i != i_end; i --) {++      const Backtrace& trace = traces[i];++      // make path relative to the current directory+      std::string rel_path(File::abs2rel(trace.pstate.path, 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.line + 1;+        ss << " of " << rel_path;+        // ss << trace.caller;+        first = false;+      } else {+        ss << trace.caller;+        ss << std::endl;+        ss << indent;+        ss << "from line ";+        ss << trace.pstate.line + 1;+        ss << " of " << rel_path;+      }++    }++    ss << std::endl;+    return ss.str();++  }++};
libsass/src/backtrace.hpp view
@@ -1,75 +1,28 @@ #ifndef SASS_BACKTRACE_H #define SASS_BACKTRACE_H +#include <vector> #include <sstream>- #include "file.hpp" #include "position.hpp"  namespace Sass { -   struct Backtrace { -    Backtrace*  parent;     ParserState pstate;-    std::string      caller;+    std::string caller; -    Backtrace(Backtrace* prn, ParserState pstate, std::string c)-    : parent(prn),-      pstate(pstate),+    Backtrace(ParserState pstate, std::string c = "")+    : pstate(pstate),       caller(c)     { } -    std::string to_string(bool warning = false)-    {-      size_t i = -1;-      std::stringstream ss;-      std::string cwd(Sass::File::get_cwd());-      Backtrace* this_point = this;--      if (!warning) ss << std::endl << "Backtrace:";-      // the first tracepoint (which is parent-less) is an empty placeholder-      while (this_point->parent) {--        // make path relative to the current directory-        std::string rel_path(Sass::File::abs2rel(this_point->pstate.path, cwd, cwd));--        if (warning) {-          ss << std::endl-             << "\t"-             << (++i == 0 ? "on" : "from")-             << " line "-             << this_point->pstate.line + 1-             << " of "-             << rel_path;-        } else {-          ss << std::endl-             << "\t"-             << rel_path-             << ":"-             << this_point->pstate.line + 1-             << this_point->parent->caller;-        }--        this_point = this_point->parent;-      }--      return ss.str();-    }+  }; -    size_t depth()-    {-      size_t d = 0;-      Backtrace* p = parent;-      while (p) {-        ++d;-        p = p->parent;-      }-      return d-1;-    }+  typedef std::vector<Backtrace> Backtraces; -  };+  const std::string traces_to_string(Backtraces traces, std::string indent = "\t");  } 
libsass/src/bind.cpp view
@@ -2,6 +2,7 @@ #include "bind.hpp" #include "ast.hpp" #include "context.hpp"+#include "expand.hpp" #include "eval.hpp" #include <map> #include <iostream>@@ -14,9 +15,11 @@     std::string callee(type + " " + name);      std::map<std::string, Parameter_Obj> 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 = SASS_MEMORY_CAST(String_Quoted, (*as)[i]->value())) {+      if (auto str = Cast<String_Quoted>((*as)[i]->value())) {         // force optional quotes (only if needed)         if (str->quote_mark()) {           str->quote_mark('*');@@ -42,7 +45,7 @@       if (ip >= LP) {         // skip empty rest arguments         if (a->is_rest_argument()) {-          if (List_Obj l = SASS_MEMORY_CAST(List, a->value())) {+          if (List_Obj l = Cast<List>(a->value())) {             if (l->length() == 0) {               ++ ia; continue;             }@@ -51,7 +54,7 @@         std::stringstream msg;         msg << "wrong number of arguments (" << LA << " for " << LP << ")";         msg << " for `" << name << "'";-        return error(msg.str(), as->pstate());+        return error(msg.str(), as->pstate(), eval->exp.traces);       }       Parameter_Obj p = ps->at(ip); @@ -61,7 +64,7 @@         if (a->is_rest_argument()) {            // We should always get a list for rest arguments-          if (List_Obj rest = SASS_MEMORY_CAST(List, a->value())) {+          if (List_Obj rest = Cast<List>(a->value())) {               // create a new list object for wrapped items               List_Ptr arglist = SASS_MEMORY_NEW(List,                                               p->pstate(),@@ -70,12 +73,12 @@                                               true);               // wrap each item from list as an argument               for (Expression_Obj item : rest->elements()) {-                if (Argument_Obj arg = SASS_MEMORY_CAST(Argument, item)) {+                if (Argument_Obj arg = Cast<Argument>(item)) {                   arglist->append(SASS_MEMORY_COPY(arg)); // copy                 } else {                   arglist->append(SASS_MEMORY_NEW(Argument,                                                   item->pstate(),-                                                  &item,+                                                  item,                                                   "",                                                   false,                                                   false));@@ -93,15 +96,20 @@           // expand keyword arguments into their parameters           List_Ptr arglist = SASS_MEMORY_NEW(List, p->pstate(), 0, SASS_COMMA, true);           env->local_frame()[p->name()] = arglist;-          Map_Obj argmap = SASS_MEMORY_CAST(Map, a->value());+          Map_Obj argmap = Cast<Map>(a->value());           for (auto key : argmap->keys()) {-            std::string name = unquote(SASS_MEMORY_CAST(String_Constant, key)->value());-            arglist->append(SASS_MEMORY_NEW(Argument,-                                            key->pstate(),-                                            argmap->at(key),-                                            "$" + name,-                                            false,-                                            false));+            if (String_Constant_Obj str = Cast<String_Constant>(key)) {+              std::string param = unquote(str->value());+              arglist->append(SASS_MEMORY_NEW(Argument,+                                              key->pstate(),+                                              argmap->at(key),+                                              "$" + param,+                                              false,+                                              false));+            } else {+              eval->exp.traces.push_back(Backtrace(key->pstate()));+              throw Exception::InvalidVarKwdType(key->pstate(), eval->exp.traces, key->inspect(), a);+            }           }          } else {@@ -117,25 +125,25 @@             // get and post inc             a = (*as)[ia++];             // maybe we have another list as argument-            List_Obj ls = SASS_MEMORY_CAST(List, a->value());+            List_Obj ls = Cast<List>(a->value());             // skip any list completely if empty             if (ls && ls->empty() && a->is_rest_argument()) continue;              Expression_Obj value = a->value();-            if (Argument_Obj arg = SASS_MEMORY_CAST(Argument, value)) {-              arglist->append(&arg);+            if (Argument_Obj arg = Cast<Argument>(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 = SASS_MEMORY_CAST(List, a->value())) {+              if (List_Obj rest = Cast<List>(a->value())) {                 arglist->separator(rest->separator()); -                for (size_t i = 0, L = rest->size(); i < L; ++i) {-                  Expression_Obj obj = rest->at(i);+                for (size_t i = 0, L = rest->length(); i < L; ++i) {+                  Expression_Obj obj = rest->value_at_index(i);                   arglist->append(SASS_MEMORY_NEW(Argument,                                                 obj->pstate(),-                                                &obj,+                                                obj,                                                 "",                                                 false,                                                 false));@@ -155,7 +163,7 @@             }           }           // assign new arglist to environment-          env->local_frame()[p->name()] = &arglist;+          env->local_frame()[p->name()] = arglist;         }         // consumed parameter         ++ip;@@ -166,9 +174,16 @@       // 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 = SASS_MEMORY_CAST(List, a->value());+        List_Obj arglist = Cast<List>(a->value());+        if (!arglist) {+          if (Expression_Obj arg = Cast<Expression>(a->value())) {+            arglist = SASS_MEMORY_NEW(List, a->pstate(), 1);+            arglist->append(arg);+          }+        }+         // empty rest arg - treat all args as default values-        if (!arglist->length()) {+        if (!arglist || !arglist->length()) {           break;         } else {           if (arglist->length() > LP - ip && !ps->has_rest_parameter()) {@@ -187,8 +202,8 @@         }         // otherwise move one of the rest args into the param, converting to argument if necessary         Expression_Obj obj = arglist->at(0);-        if (!(a = SASS_MEMORY_CAST(Argument, obj))) {-          Expression_Ptr a_to_convert = &obj;+        if (!(a = Cast<Argument>(obj))) {+          Expression_Ptr a_to_convert = obj;           a = SASS_MEMORY_NEW(Argument,                               a_to_convert->pstate(),                               a_to_convert,@@ -202,17 +217,22 @@         }        } else if (a->is_keyword_argument()) {-        Map_Obj argmap = SASS_MEMORY_CAST(Map, a->value());+        Map_Obj argmap = Cast<Map>(a->value());          for (auto key : argmap->keys()) {-          std::string name = "$" + unquote(SASS_MEMORY_CAST(String_Constant, key)->value());+          String_Constant_Ptr val = Cast<String_Constant>(key);+          if (val == NULL) {+            eval->exp.traces.push_back(Backtrace(key->pstate()));+            throw Exception::InvalidVarKwdType(key->pstate(), eval->exp.traces, key->inspect(), a);+          }+          std::string param = "$" + unquote(val->value()); -          if (!param_map.count(name)) {+          if (!param_map.count(param)) {             std::stringstream msg;-            msg << callee << " has no parameter named " << name;-            error(msg.str(), a->pstate());+            msg << callee << " has no parameter named " << param;+            error(msg.str(), a->pstate(), eval->exp.traces);           }-          env->local_frame()[name] = &argmap->at(&key);+          env->local_frame()[param] = argmap->at(key);         }         ++ia;         continue;@@ -225,32 +245,38 @@           std::stringstream msg;           msg << "parameter " << p->name()           << " provided more than once in call to " << callee;-          error(msg.str(), a->pstate());+          error(msg.str(), a->pstate(), eval->exp.traces);         }         // ordinal arg -- bind it to the next param-        env->local_frame()[p->name()] = &a->value();+        env->local_frame()[p->name()] = a->value();         ++ip;       }       else {         // named arg -- bind it to the appropriately named param         if (!param_map.count(a->name())) {-          std::stringstream msg;-          msg << callee << " has no parameter named " << a->name();-          error(msg.str(), a->pstate());+          if (ps->has_rest_parameter()) {+            varargs->append(a);+          } else {+            std::stringstream msg;+            msg << callee << " has no parameter named " << a->name();+            error(msg.str(), a->pstate(), eval->exp.traces);+          }         }-        if (param_map[a->name()]->is_rest_parameter()) {-          std::stringstream msg;-          msg << "argument " << a->name() << " of " << callee-              << "cannot be used as named argument";-          error(msg.str(), a->pstate());+        if (param_map[a->name()]) {+          if (param_map[a->name()]->is_rest_parameter()) {+            std::stringstream msg;+            msg << "argument " << a->name() << " of " << callee+                << "cannot be used as named argument";+            error(msg.str(), a->pstate(), eval->exp.traces);+          }         }         if (env->has_local(a->name())) {           std::stringstream msg;           msg << "parameter " << p->name()               << "provided more than once in call to " << callee;-          error(msg.str(), a->pstate());+          error(msg.str(), a->pstate(), eval->exp.traces);         }-        env->local_frame()[a->name()] = &a->value();+        env->local_frame()[a->name()] = a->value();       }     }     // EO while ia@@ -265,11 +291,7 @@       // cerr << "********" << endl;       if (!env->has_local(leftover->name())) {         if (leftover->is_rest_parameter()) {-          env->local_frame()[leftover->name()] = SASS_MEMORY_NEW(List,-                                                                   leftover->pstate(),-                                                                   0,-                                                                   SASS_COMMA,-                                                                   true);+          env->local_frame()[leftover->name()] = varargs;         }         else if (leftover->default_value()) {           Expression_Ptr dv = leftover->default_value()->perform(eval);@@ -277,7 +299,7 @@         }         else {           // param is unbound and has no default value -- error-          throw Exception::MissingArgument(as->pstate(), name, leftover->name(), type);+          throw Exception::MissingArgument(as->pstate(), eval->exp.traces, name, leftover->name(), type);         }       }     }
libsass/src/bind.hpp view
@@ -6,7 +6,6 @@ #include "ast_fwd_decl.hpp"  namespace Sass {-  typedef Environment<AST_Node_Obj> Env;    void bind(std::string type, std::string name, Parameters_Obj, Arguments_Obj, Context*, Env*, Eval*); }
libsass/src/cencode.c view
@@ -46,6 +46,9 @@ 			result = (fragment & 0x0fc) >> 2; 			*codechar++ = base64_encode_value(result); 			result = (fragment & 0x003) << 4;+			#ifndef _MSC_VER+				__attribute__ ((fallthrough));+			#endif 	case step_B: 			if (plainchar == plaintextend) 			{@@ -57,6 +60,9 @@ 			result |= (fragment & 0x0f0) >> 4; 			*codechar++ = base64_encode_value(result); 			result = (fragment & 0x00f) << 2;+			#ifndef _MSC_VER+				__attribute__ ((fallthrough));+			#endif 	case step_C: 			if (plainchar == plaintextend) 			{
libsass/src/check_nesting.cpp view
@@ -7,15 +7,20 @@    CheckNesting::CheckNesting()   : parents(std::vector<Statement_Ptr>()),-    parent(0),-    current_mixin_definition(0)+    traces(std::vector<Backtrace>()),+    parent(0), current_mixin_definition(0)   { } +  void error(AST_Node_Ptr node, Backtraces traces, std::string msg) {+    traces.push_back(Backtrace(node->pstate()));+    throw Exception::InvalidSass(node->pstate(), traces, msg);+  }+   Statement_Ptr CheckNesting::visit_children(Statement_Ptr parent)   {     Statement_Ptr old_parent = this->parent; -    if (At_Root_Block_Ptr root = SASS_MEMORY_CAST_PTR(At_Root_Block, parent)) {+    if (At_Root_Block_Ptr root = Cast<At_Root_Block>(parent)) {       std::vector<Statement_Ptr> old_parents = this->parents;       std::vector<Statement_Ptr> new_parents; @@ -39,9 +44,15 @@         }       } -      At_Root_Block_Ptr ar = SASS_MEMORY_CAST_PTR(At_Root_Block, parent);-      Statement_Ptr ret = this->visit_children(&ar->block());+      At_Root_Block_Ptr ar = Cast<At_Root_Block>(parent);+      Block_Ptr ret = ar->block(); +      if (ret != NULL) {+        for (auto n : ret->elements()) {+          n->perform(this);+        }+      }+       this->parent = old_parent;       this->parents = old_parents; @@ -54,11 +65,17 @@      this->parents.push_back(parent); -    Block_Ptr b = SASS_MEMORY_CAST_PTR(Block, parent);+    Block_Ptr b = Cast<Block>(parent); +    if (Trace_Ptr trace = Cast<Trace>(parent)) {+      if (trace->type() == 'i') {+        this->traces.push_back(Backtrace(trace->pstate()));+      }+    }+     if (!b) {-      if (Has_Block_Ptr bb = SASS_MEMORY_CAST(Has_Block, *parent)) {-        b = &bb->block();+      if (Has_Block_Ptr bb = Cast<Has_Block>(parent)) {+        b = bb->block();       }     } @@ -67,9 +84,16 @@         n->perform(this);       }     }+     this->parent = old_parent;     this->parents.pop_back(); +    if (Trace_Ptr trace = Cast<Trace>(parent)) {+      if (trace->type() == 'i') {+        this->traces.pop_back();+      }+    }+     return b;   } @@ -97,10 +121,21 @@     return n;   } +  Statement_Ptr CheckNesting::operator()(If_Ptr i)+  {+    this->visit_children(i);++    if (Block_Ptr b = Cast<Block>(i->alternative())) {+      for (auto n : b->elements()) n->perform(this);+    }++    return i;+  }+   Statement_Ptr CheckNesting::fallback_impl(Statement_Ptr s)   {-    Block_Ptr b1 = SASS_MEMORY_CAST_PTR(Block, s);-    Has_Block_Ptr b2 = SASS_MEMORY_CAST_PTR(Has_Block, s);+    Block_Ptr b1 = Cast<Block>(s);+    Has_Block_Ptr b2 = Cast<Has_Block>(s);     return b1 || b2 ? visit_children(s) : s;   } @@ -108,91 +143,82 @@   {     if (!this->parent) return true; -    if (SASS_MEMORY_CAST_PTR(Content, node))-    { this->invalid_content_parent(this->parent); }+    if (Cast<Content>(node))+    { this->invalid_content_parent(this->parent, node); }      if (is_charset(node))-    { this->invalid_charset_parent(this->parent); }+    { this->invalid_charset_parent(this->parent, node); } -    if (SASS_MEMORY_CAST_PTR(Extension, node))-    { this->invalid_extend_parent(this->parent); }+    if (Cast<Extension>(node))+    { this->invalid_extend_parent(this->parent, node); } -    // if (SASS_MEMORY_CAST(Import, node))+    // if (Cast<Import>(node))     // { this->invalid_import_parent(this->parent); }      if (this->is_mixin(node))-    { this->invalid_mixin_definition_parent(this->parent); }+    { this->invalid_mixin_definition_parent(this->parent, node); }      if (this->is_function(node))-    { this->invalid_function_parent(this->parent); }+    { this->invalid_function_parent(this->parent, node); }      if (this->is_function(this->parent))     { this->invalid_function_child(node); } -    if (SASS_MEMORY_CAST_PTR(Declaration, node))-    { this->invalid_prop_parent(this->parent); }+    if (Declaration_Ptr d = Cast<Declaration>(node))+    {+      this->invalid_prop_parent(this->parent, node);+      this->invalid_value_child(d->value());+    } -    if (SASS_MEMORY_CAST_PTR(Declaration, this->parent))+    if (Cast<Declaration>(this->parent))     { this->invalid_prop_child(node); } -    if (SASS_MEMORY_CAST_PTR(Return, node))-    { this->invalid_return_parent(this->parent); }+    if (Cast<Return>(node))+    { this->invalid_return_parent(this->parent, node); }      return true;   } -  void CheckNesting::invalid_content_parent(Statement_Ptr parent)+  void CheckNesting::invalid_content_parent(Statement_Ptr parent, AST_Node_Ptr node)   {     if (!this->current_mixin_definition) {-      throw Exception::InvalidSass(-        parent->pstate(),-        "@content may only be used within a mixin."-      );+      error(node, traces, "@content may only be used within a mixin.");     }   } -  void CheckNesting::invalid_charset_parent(Statement_Ptr parent)+  void CheckNesting::invalid_charset_parent(Statement_Ptr parent, AST_Node_Ptr node)   {     if (!(         is_root_node(parent)     )) {-      throw Exception::InvalidSass(-        parent->pstate(),-        "@charset may only be used at the root of a document."-      );+      error(node, traces, "@charset may only be used at the root of a document.");     }   } -  void CheckNesting::invalid_extend_parent(Statement_Ptr parent)+  void CheckNesting::invalid_extend_parent(Statement_Ptr parent, AST_Node_Ptr node)   {     if (!(-        SASS_MEMORY_CAST_PTR(Ruleset, parent) ||-        SASS_MEMORY_CAST_PTR(Mixin_Call, parent) ||+        Cast<Ruleset>(parent) ||+        Cast<Mixin_Call>(parent) ||         is_mixin(parent)     )) {-      throw Exception::InvalidSass(-        parent->pstate(),-        "Extend directives may only be used within rules."-      );+      error(node, traces, "Extend directives may only be used within rules.");     }   } -  // void CheckNesting::invalid_import_parent(Statement_Ptr parent)+  // void CheckNesting::invalid_import_parent(Statement_Ptr parent, AST_Node_Ptr node)   // {   //   for (auto pp : this->parents) {   //     if (-  //         SASS_MEMORY_CAST(Each, pp) ||-  //         SASS_MEMORY_CAST(For, pp) ||-  //         SASS_MEMORY_CAST(If, pp) ||-  //         SASS_MEMORY_CAST(While, pp) ||-  //         SASS_MEMORY_CAST(Trace, pp) ||-  //         SASS_MEMORY_CAST(Mixin_Call, pp) ||+  //         Cast<Each>(pp) ||+  //         Cast<For>(pp) ||+  //         Cast<If>(pp) ||+  //         Cast<While>(pp) ||+  //         Cast<Trace>(pp) ||+  //         Cast<Mixin_Call>(pp) ||   //         is_mixin(pp)   //     ) {-  //       throw Exception::InvalidSass(-  //         parent->pstate(),-  //         "Import directives may not be defined within control directives or other mixins."-  //       );+  //       error(node, traces, "Import directives may not be defined within control directives or other mixins.");   //     }   //   } @@ -201,49 +227,40 @@   //   }    //   if (false/*n.css_import?*/) {-  //     throw Exception::InvalidSass(-  //       parent->pstate(),-  //       "CSS import directives may only be used at the root of a document."-  //     );+  //     error(node, traces, "CSS import directives may only be used at the root of a document.");   //   }   // } -  void CheckNesting::invalid_mixin_definition_parent(Statement_Ptr parent)+  void CheckNesting::invalid_mixin_definition_parent(Statement_Ptr parent, AST_Node_Ptr node)   {     for (Statement_Ptr pp : this->parents) {       if (-          SASS_MEMORY_CAST_PTR(Each, pp) ||-          SASS_MEMORY_CAST_PTR(For, pp) ||-          SASS_MEMORY_CAST_PTR(If, pp) ||-          SASS_MEMORY_CAST_PTR(While, pp) ||-          SASS_MEMORY_CAST_PTR(Trace, pp) ||-          SASS_MEMORY_CAST_PTR(Mixin_Call, pp) ||+          Cast<Each>(pp) ||+          Cast<For>(pp) ||+          Cast<If>(pp) ||+          Cast<While>(pp) ||+          Cast<Trace>(pp) ||+          Cast<Mixin_Call>(pp) ||           is_mixin(pp)       ) {-        throw Exception::InvalidSass(-          parent->pstate(),-          "Mixins may not be defined within control directives or other mixins."-        );+        error(node, traces, "Mixins may not be defined within control directives or other mixins.");       }     }   } -  void CheckNesting::invalid_function_parent(Statement_Ptr parent)+  void CheckNesting::invalid_function_parent(Statement_Ptr parent, AST_Node_Ptr node)   {     for (Statement_Ptr pp : this->parents) {       if (-          SASS_MEMORY_CAST_PTR(Each, pp) ||-          SASS_MEMORY_CAST_PTR(For, pp) ||-          SASS_MEMORY_CAST_PTR(If, pp) ||-          SASS_MEMORY_CAST_PTR(While, pp) ||-          SASS_MEMORY_CAST_PTR(Trace, pp) ||-          SASS_MEMORY_CAST_PTR(Mixin_Call, pp) ||+          Cast<Each>(pp) ||+          Cast<For>(pp) ||+          Cast<If>(pp) ||+          Cast<While>(pp) ||+          Cast<Trace>(pp) ||+          Cast<Mixin_Call>(pp) ||           is_mixin(pp)       ) {-        throw Exception::InvalidSass(-          parent->pstate(),-          "Functions may not be defined within control directives or other mixins."-        );+        error(node, traces, "Functions may not be defined within control directives or other mixins.");       }     }   }@@ -251,70 +268,75 @@   void CheckNesting::invalid_function_child(Statement_Ptr child)   {     if (!(-        SASS_MEMORY_CAST_PTR(Each, child) ||-        SASS_MEMORY_CAST_PTR(For, child) ||-        SASS_MEMORY_CAST_PTR(If, child) ||-        SASS_MEMORY_CAST_PTR(While, child) ||-        SASS_MEMORY_CAST_PTR(Trace, child) ||-        SASS_MEMORY_CAST_PTR(Comment, child) ||-        SASS_MEMORY_CAST_PTR(Debug, child) ||-        SASS_MEMORY_CAST_PTR(Return, child) ||-        SASS_MEMORY_CAST_PTR(Variable, child) ||+        Cast<Each>(child) ||+        Cast<For>(child) ||+        Cast<If>(child) ||+        Cast<While>(child) ||+        Cast<Trace>(child) ||+        Cast<Comment>(child) ||+        Cast<Debug>(child) ||+        Cast<Return>(child) ||+        Cast<Variable>(child) ||         // Ruby Sass doesn't distinguish variables and assignments-        SASS_MEMORY_CAST_PTR(Assignment, child) ||-        SASS_MEMORY_CAST_PTR(Warning, child) ||-        SASS_MEMORY_CAST_PTR(Error, child)+        Cast<Assignment>(child) ||+        Cast<Warning>(child) ||+        Cast<Error>(child)     )) {-      throw Exception::InvalidSass(-        child->pstate(),-        "Functions can only contain variable declarations and control directives."-      );+      error(child, traces, "Functions can only contain variable declarations and control directives.");     }   }    void CheckNesting::invalid_prop_child(Statement_Ptr child)   {     if (!(-        SASS_MEMORY_CAST_PTR(Each, child) ||-        SASS_MEMORY_CAST_PTR(For, child) ||-        SASS_MEMORY_CAST_PTR(If, child) ||-        SASS_MEMORY_CAST_PTR(While, child) ||-        SASS_MEMORY_CAST_PTR(Trace, child) ||-        SASS_MEMORY_CAST_PTR(Comment, child) ||-        SASS_MEMORY_CAST_PTR(Declaration, child) ||-        SASS_MEMORY_CAST_PTR(Mixin_Call, child)+        Cast<Each>(child) ||+        Cast<For>(child) ||+        Cast<If>(child) ||+        Cast<While>(child) ||+        Cast<Trace>(child) ||+        Cast<Comment>(child) ||+        Cast<Declaration>(child) ||+        Cast<Mixin_Call>(child)     )) {-      throw Exception::InvalidSass(-        child->pstate(),-        "Illegal nesting: Only properties may be nested beneath properties."-      );+      error(child, traces, "Illegal nesting: Only properties may be nested beneath properties.");     }   } -  void CheckNesting::invalid_prop_parent(Statement_Ptr parent)+  void CheckNesting::invalid_prop_parent(Statement_Ptr parent, AST_Node_Ptr node)   {     if (!(         is_mixin(parent) ||         is_directive_node(parent) ||-        SASS_MEMORY_CAST_PTR(Ruleset, parent) ||-        SASS_MEMORY_CAST_PTR(Keyframe_Rule, parent) ||-        SASS_MEMORY_CAST_PTR(Declaration, parent) ||-        SASS_MEMORY_CAST_PTR(Mixin_Call, parent)+        Cast<Ruleset>(parent) ||+        Cast<Keyframe_Rule>(parent) ||+        Cast<Declaration>(parent) ||+        Cast<Mixin_Call>(parent)     )) {-      throw Exception::InvalidSass(-        parent->pstate(),-        "Properties are only allowed within rules, directives, mixin includes, or other properties."-      );+      error(node, traces, "Properties are only allowed within rules, directives, mixin includes, or other properties.");     }   } -  void CheckNesting::invalid_return_parent(Statement_Ptr parent)+  void CheckNesting::invalid_value_child(AST_Node_Ptr d)   {+    if (Map_Ptr m = Cast<Map>(d)) {+      traces.push_back(Backtrace(m->pstate()));+      throw Exception::InvalidValue(traces, *m);+    }+    if (Number_Ptr n = Cast<Number>(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_Ptr parent, AST_Node_Ptr node)+  {     if (!this->is_function(parent)) {-      throw Exception::InvalidSass(-        parent->pstate(),-        "@return may only be used within a function."-      );+      error(node, traces, "@return may only be used within a function.");     }   } @@ -326,51 +348,51 @@                              !is_root_node(grandparent) &&                              !is_at_root_node(grandparent); -    return SASS_MEMORY_CAST_PTR(Import, parent) ||-           SASS_MEMORY_CAST_PTR(Each, parent) ||-           SASS_MEMORY_CAST_PTR(For, parent) ||-           SASS_MEMORY_CAST_PTR(If, parent) ||-           SASS_MEMORY_CAST_PTR(While, parent) ||-           SASS_MEMORY_CAST_PTR(Trace, parent) ||+    return Cast<Import>(parent) ||+           Cast<Each>(parent) ||+           Cast<For>(parent) ||+           Cast<If>(parent) ||+           Cast<While>(parent) ||+           Cast<Trace>(parent) ||            valid_bubble_node;   }    bool CheckNesting::is_charset(Statement_Ptr n)   {-    Directive_Ptr d = SASS_MEMORY_CAST_PTR(Directive, n);+    Directive_Ptr d = Cast<Directive>(n);     return d && d->keyword() == "charset";   }    bool CheckNesting::is_mixin(Statement_Ptr n)   {-    Definition_Ptr def = SASS_MEMORY_CAST_PTR(Definition, n);+    Definition_Ptr def = Cast<Definition>(n);     return def && def->type() == Definition::MIXIN;   }    bool CheckNesting::is_function(Statement_Ptr n)   {-    Definition_Ptr def = SASS_MEMORY_CAST_PTR(Definition, n);+    Definition_Ptr def = Cast<Definition>(n);     return def && def->type() == Definition::FUNCTION;   }    bool CheckNesting::is_root_node(Statement_Ptr n)   {-    if (SASS_MEMORY_CAST_PTR(Ruleset, n)) return false;+    if (Cast<Ruleset>(n)) return false; -    Block_Ptr b = SASS_MEMORY_CAST_PTR(Block, n);+    Block_Ptr b = Cast<Block>(n);     return b && b->is_root();   }    bool CheckNesting::is_at_root_node(Statement_Ptr n)   {-    return SASS_MEMORY_CAST_PTR(At_Root_Block, n) != NULL;+    return Cast<At_Root_Block>(n) != NULL;   }    bool CheckNesting::is_directive_node(Statement_Ptr n)   {-    return SASS_MEMORY_CAST_PTR(Directive, n) ||-           SASS_MEMORY_CAST_PTR(Import, n) ||-           SASS_MEMORY_CAST_PTR(Media_Block, n) ||-           SASS_MEMORY_CAST_PTR(Supports_Block, n);+    return Cast<Directive>(n) ||+           Cast<Import>(n) ||+           Cast<Media_Block>(n) ||+           Cast<Supports_Block>(n);   } }
libsass/src/check_nesting.hpp view
@@ -6,11 +6,10 @@  namespace Sass { -  typedef Environment<AST_Node_Obj> Env;-   class CheckNesting : public Operation_CRTP<Statement_Ptr, CheckNesting> {      std::vector<Statement_Ptr>  parents;+    Backtraces                  traces;     Statement_Ptr               parent;     Definition_Ptr              current_mixin_definition; @@ -24,10 +23,11 @@      Statement_Ptr operator()(Block_Ptr);     Statement_Ptr operator()(Definition_Ptr);+    Statement_Ptr operator()(If_Ptr);      template <typename U>     Statement_Ptr fallback(U x) {-      Statement_Ptr n = SASS_MEMORY_CAST_PTR(Statement, x);+      Statement_Ptr n = Cast<Statement>(x);       if (this->should_visit(n)) {         return fallback_impl(n);       }@@ -35,17 +35,18 @@     }    private:-    void invalid_content_parent(Statement_Ptr);-    void invalid_charset_parent(Statement_Ptr);-    void invalid_extend_parent(Statement_Ptr);+    void invalid_content_parent(Statement_Ptr, AST_Node_Ptr);+    void invalid_charset_parent(Statement_Ptr, AST_Node_Ptr);+    void invalid_extend_parent(Statement_Ptr, AST_Node_Ptr);     // void invalid_import_parent(Statement_Ptr);-    void invalid_mixin_definition_parent(Statement_Ptr);-    void invalid_function_parent(Statement_Ptr);+    void invalid_mixin_definition_parent(Statement_Ptr, AST_Node_Ptr);+    void invalid_function_parent(Statement_Ptr, AST_Node_Ptr);      void invalid_function_child(Statement_Ptr);     void invalid_prop_child(Statement_Ptr);-    void invalid_prop_parent(Statement_Ptr);-    void invalid_return_parent(Statement_Ptr);+    void invalid_prop_parent(Statement_Ptr, AST_Node_Ptr);+    void invalid_return_parent(Statement_Ptr, AST_Node_Ptr);+    void invalid_value_child(AST_Node_Ptr);      bool is_transparent_parent(Statement_Ptr, Statement_Ptr); 
libsass/src/color_maps.cpp view
@@ -607,16 +607,20 @@    Color_Ptr_Const name_to_color(const char* key)   {-    auto p = names_to_colors.find(key);-    if (p != names_to_colors.end()) {-      return p->second;-    }-    return 0;+    return name_to_color(std::string(key));   }    Color_Ptr_Const name_to_color(const std::string& key)   {-    return name_to_color(key.c_str());+    // case insensitive lookup.  See #2462+    std::string lower{key};+    std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower);++    auto p = names_to_colors.find(lower.c_str());+    if (p != names_to_colors.end()) {+      return p->second;+    }+    return 0;   }    const char* color_to_name(const int key)
libsass/src/color_maps.hpp view
@@ -1,3 +1,4 @@+ #ifndef SASS_COLOR_MAPS_H #define SASS_COLOR_MAPS_H @@ -319,14 +320,11 @@     extern const Color transparent;   } -  extern const std::map<const int, const char*> colors_to_names;-  extern const std::map<const char*, Color_Ptr_Const, map_cmp_str> names_to_colors;--  extern Color_Ptr_Const name_to_color(const char*);-  extern Color_Ptr_Const name_to_color(const std::string&);-  extern const char* color_to_name(const int);-  extern const char* color_to_name(const Color&);-  extern const char* color_to_name(const double);+  Color_Ptr_Const name_to_color(const char*);+  Color_Ptr_Const name_to_color(const std::string&);+  const char* color_to_name(const int);+  const char* color_to_name(const Color&);+  const char* color_to_name(const double);  } 
libsass/src/constants.cpp view
@@ -79,11 +79,12 @@     extern const char supports_kwd[]     = "@supports";     extern const char keyframes_kwd[]    = "keyframes";     extern const char only_kwd[]         = "only";-    extern const char rgb_kwd[]          = "rgb(";+    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_kwd[]   = "url-prefix(";+    // extern const char url_prefix_fn_kwd[] = "url-prefix(";     extern const char important_kwd[]    = "important";-    extern const char pseudo_not_kwd[]   = ":not(";+    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";
libsass/src/constants.hpp view
@@ -79,11 +79,12 @@     extern const char supports_kwd[];     extern const char keyframes_kwd[];     extern const char only_kwd[];-    extern const char rgb_kwd[];+    extern const char rgb_fn_kwd[];+    extern const char url_fn_kwd[];     extern const char url_kwd[];-    // extern const char url_prefix_kwd[];+    // extern const char url_prefix_fn_kwd[];     extern const char important_kwd[];-    extern const char pseudo_not_kwd[];+    extern const char pseudo_not_fn_kwd[];     extern const char even_kwd[];     extern const char odd_kwd[];     extern const char progid_kwd[];
libsass/src/context.cpp view
@@ -67,11 +67,15 @@     plugins(),     emitter(c_options), +    ast_gc(),     strings(),     resources(),     sheets(),     subset_map(),     import_stack(),+    callee_stack(),+    traces(),+    c_compiler(NULL),      c_headers               (std::vector<Sass_Importer_Entry>()),     c_importers             (std::vector<Sass_Importer_Entry>()),@@ -87,8 +91,9 @@    { -    // add cwd to include paths-    include_paths.push_back(CWD);+    // 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);@@ -129,7 +134,7 @@    Context::~Context()   {-    // resources were allocated by strdup or malloc+    // resources were allocated by malloc     for (size_t i = 0; i < resources.size(); ++i) {       free(resources[i].contents);       free(resources[i].srcmap);@@ -246,10 +251,9 @@     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, ParserState* prstate)+  void Context::register_resource(const Include& inc, const Resource& res)   {      // do not parse same resource twice@@ -297,21 +301,22 @@     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) {+        std::string cwd(File::get_cwd());+        // make path relative to the current directory         std::string stack("An @import loop has been found:");         for (size_t n = 1; n < i + 2; ++n) {-          stack += "\n    " + std::string(import_stack[n]->imp_path) +-            " imports " + std::string(import_stack[n+1]->imp_path);+          stack += "\n    " + std::string(File::abs2rel(import_stack[n]->abs_path, cwd, cwd)) ++            " imports " + std::string(File::abs2rel(import_stack[n+1]->abs_path, cwd, cwd));         }         // implement error throw directly until we         // decided how to handle full stack traces-        ParserState state = prstate ? *prstate : pstate;-        throw Exception::InvalidSyntax(state, stack, &import_stack);+        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(Parser::from_c_str(contents, *this, pstate));+    Parser p(Parser::from_c_str(contents, *this, traces, pstate));     // do not yet dispose these buffers     sass_import_take_source(import);     sass_import_take_srcmap(import);@@ -326,7 +331,15 @@       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, ParserState& prstate)+  {+    traces.push_back(Backtrace(prstate));+    register_resource(inc, res);+    traces.pop_back();   }    // Add a new import to the context (called from `import_url`)@@ -346,7 +359,7 @@       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);+      error(msg_stream.str(), pstate, traces);     }      // process the resolved entry@@ -358,7 +371,7 @@       // 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);+        register_resource(resolved[0], { contents, 0 }, pstate);         // return resolved entry         return resolved[0];       }@@ -391,15 +404,15 @@       String_Constant_Ptr loc = SASS_MEMORY_NEW(String_Constant, pstate, unquote(load_path));       Argument_Obj loc_arg = SASS_MEMORY_NEW(Argument, pstate, loc);       Arguments_Obj loc_args = SASS_MEMORY_NEW(Arguments, pstate);-      loc_args->append(&loc_arg);-      Function_Call_Ptr new_url = SASS_MEMORY_NEW(Function_Call, pstate, "url", &loc_args);+      loc_args->append(loc_arg);+      Function_Call_Ptr new_url = SASS_MEMORY_NEW(Function_Call, pstate, "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 + ".\nParent style sheet: " + ctx_path, pstate);+        error("File to import not found or unreadable: " + imp_path + ".", pstate, traces);       }       imp->incs().push_back(include);     }@@ -415,12 +428,12 @@     // need one correct import     bool has_import = false;     // process all custom importers (or custom headers)-    for (Sass_Importer_Entry& importer : importers) {+    for (Sass_Importer_Entry& importer_ent : importers) {       // int priority = sass_importer_get_priority(importer);-      Sass_Importer_Fn fn = sass_importer_get_function(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, c_compiler)+          fn(load_path.c_str(), importer_ent, c_compiler)       ) {         // get c pointer copy to iterate over         Sass_Import_List it_includes = includes;@@ -435,18 +448,18 @@           // create the importer struct           Importer importer(uniq_path, ctx_path);           // query data from the current include-          Sass_Import_Entry include = *it_includes;-          char* source = sass_import_take_source(include);-          char* srcmap = sass_import_take_srcmap(include);-          size_t line = sass_import_get_error_line(include);-          size_t column = sass_import_get_error_column(include);-          const char *abs_path = sass_import_get_abs_path(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)) {-            if (source || srcmap) register_resource({ importer, uniq_path }, { source, srcmap }, &pstate);-            if (line == std::string::npos && column == std::string::npos) error(err_message, pstate);-            else error(err_message, ParserState(ctx_path, source, Position(line, column)));+          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 == std::string::npos && column == std::string::npos) error(err_message, pstate, traces);+            else error(err_message, ParserState(ctx_path, source, Position(line, column)), traces);           }           // content for import was set           else if (source) {@@ -458,7 +471,7 @@             // attach information to AST node             imp->incs().push_back(include);             // register the resource buffers-            register_resource(include, { source, srcmap }, &pstate);+            register_resource(include, { source, srcmap }, pstate);           }           // only a path was retuned           // try to load it like normal@@ -528,11 +541,11 @@     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);+    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);+    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]));@@ -644,30 +657,33 @@     for (size_t i = 0, S = c_functions.size(); i < S; ++i)     { register_c_function(*this, &global, c_functions[i]); }     // create initial backtrace entry-    Backtrace backtrace(0, ParserState("", 0), "");     // create crtp visitor objects-    Expand expand(*this, &global, &backtrace);-    Cssize cssize(*this, &backtrace);+    Expand expand(*this, &global);+    Cssize cssize(*this);     CheckNesting check_nesting;-    // check nesting-    check_nesting(&root);+    // 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);+    root = expand(root);     // check nesting-    check_nesting(&root);+    check_nesting(root);     // merge and bubble certain rules-    root = cssize(&root);+    root = cssize(root);     // should we extend something?     if (!subset_map.empty()) {       // create crtp visitor object-      Extend extend(*this, subset_map);+      Extend extend(subset_map);+      extend.setEval(expand.eval);       // extend tree nodes-      extend(&root);+      extend(root);     }      // clean up by removing empty placeholders     // ToDo: maybe we can do this somewhere else?-    Remove_Placeholders remove_placeholders(*this);+    Remove_Placeholders remove_placeholders;     root->perform(&remove_placeholders);     // return processed tree     return root;@@ -695,10 +711,8 @@   char* Context::render_srcmap()   {     if (source_map_file == "") return 0;-    char* result = 0;     std::string map = emitter.render_srcmap(*this);-    result = sass_copy_c_string(map.c_str());-    return result;+    return sass_copy_c_string(map.c_str());   }  @@ -810,6 +824,7 @@     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);@@ -829,6 +844,8 @@     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);
libsass/src/context.hpp view
@@ -15,6 +15,7 @@ #include "environment.hpp" #include "source_map.hpp" #include "subset_map.hpp"+#include "backtrace.hpp" #include "output.hpp" #include "plugins.hpp" #include "file.hpp"@@ -43,6 +44,9 @@     Plugins plugins;     Output emitter; +    // generic ast node garbage container+    // used to avoid possible circular refs+    std::vector<AST_Node_Obj> ast_gc;     // resources add under our control     // these are guaranteed to be freed     std::vector<char*> strings;@@ -50,6 +54,8 @@     std::map<const std::string, StyleSheet> sheets;     Subset_Map subset_map;     std::vector<Sass_Import_Entry> import_stack;+    std::vector<Sass_Callee> callee_stack;+    std::vector<Backtrace> traces;      struct Sass_Compiler* c_compiler; @@ -90,7 +96,8 @@     virtual char* render(Block_Obj root);     virtual char* render_srcmap(); -    void register_resource(const Include&, const Resource&, ParserState* = 0);+    void register_resource(const Include&, const Resource&);+    void register_resource(const Include&, const Resource&, ParserState&);     std::vector<Include> find_includes(const Importer& import);     Include load_import(const Importer&, ParserState pstate); 
libsass/src/cssize.cpp view
@@ -5,15 +5,14 @@  #include "cssize.hpp" #include "context.hpp"-#include "backtrace.hpp"  namespace Sass { -  Cssize::Cssize(Context& ctx, Backtrace* bt)+  Cssize::Cssize(Context& ctx)   : ctx(ctx),+    traces(ctx.traces),     block_stack(std::vector<Block_Ptr>()),-    p_stack(std::vector<Statement_Ptr>()),-    backtrace(bt)+    p_stack(std::vector<Statement_Ptr>())   { }    Statement_Ptr Cssize::parent()@@ -23,25 +22,28 @@    Block_Ptr Cssize::operator()(Block_Ptr b)   {-    Block_Ptr bb = SASS_MEMORY_NEW(Block, b->pstate(), b->length(), b->is_root());+    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;+    return bb.detach();   }    Statement_Ptr Cssize::operator()(Trace_Ptr t)   {-    return t->block()->perform(this);+    traces.push_back(Backtrace(t->pstate()));+    auto result = t->block()->perform(this);+    traces.pop_back();+    return result;   }    Statement_Ptr Cssize::operator()(Declaration_Ptr d)   {-    String_Obj property = SASS_MEMORY_CAST(String, d->property());+    String_Obj property = Cast<String>(d->property()); -    if (Declaration_Ptr dd = dynamic_cast<Declaration_Ptr>(parent())) {-      String_Obj parent_property = SASS_MEMORY_CAST(String, dd->property());+    if (Declaration_Ptr dd = Cast<Declaration>(parent())) {+      String_Obj parent_property = Cast<String>(dd->property());       property = SASS_MEMORY_NEW(String_Constant,                                  d->property()->pstate(),                                  parent_property->to_string() + "-" + property->to_string());@@ -54,17 +56,18 @@                                       d->pstate(),                                       property,                                       d->value(),-                                      d->is_important());+                                      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.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);+        bb->unshift(dd);       }       return bb.detach();     }@@ -89,7 +92,7 @@                                   r->pstate(),                                   r->keyword(),                                   r->selector(),-                                  r->block() ? operator()(&r->block()) : 0);+                                  r->block() ? operator()(r->block()) : 0);     if (r->value()) rr->value(r->value());     p_stack.pop_back(); @@ -99,10 +102,10 @@       Statement_Obj s = r->block()->at(i);       if (s->statement_type() != Statement::BUBBLE) directive_exists = true;       else {-        Bubble_Obj s_obj = SASS_MEMORY_CAST(Bubble, s);+        Bubble_Obj s_obj = Cast<Bubble>(s);         s = s_obj->node();         if (s->statement_type() != Statement::DIRECTIVE) directive_exists = false;-        else directive_exists = (static_cast<Directive_Ptr>(&s)->keyword() == rr->keyword());+        else directive_exists = (Cast<Directive>(s)->keyword() == rr->keyword());       }      }@@ -110,12 +113,14 @@     Block_Ptr result = SASS_MEMORY_NEW(Block, rr->pstate());     if (!(directive_exists || rr->is_keyframes()))     {-      Directive_Ptr empty_node = SASS_MEMORY_CAST(Directive, rr);+      Directive_Ptr empty_node = Cast<Directive>(rr);       empty_node->block(SASS_MEMORY_NEW(Block, rr->block() ? rr->block()->pstate() : rr->pstate()));       result->append(empty_node);     } -    Block_Obj ss = debubble(rr->block() ? &rr->block() : SASS_MEMORY_NEW(Block, rr->pstate()), &rr);+    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));     }@@ -129,10 +134,10 @@      Keyframe_Rule_Obj rr = SASS_MEMORY_NEW(Keyframe_Rule,                                         r->pstate(),-                                        operator()(&r->block()));-    if (&r->name()) rr->name(r->name());+                                        operator()(r->block()));+    if (!r->name().isNull()) rr->name(r->name()); -    return debubble(&rr->block(), &rr);+    return debubble(rr->block(), rr);   }    Statement_Ptr Cssize::operator()(Ruleset_Ptr r)@@ -142,11 +147,11 @@     // string schema is not a statement!     // r->block() is already a string schema     // and that is comming from propset expand-    Block_Ptr bb = operator()(&r->block());+    Block_Ptr 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 (dynamic_cast<Statement_Ptr>(bb) == NULL) {-      error("Illegal nesting: Only properties may be nested beneath properties.", r->block()->pstate());+    if (Cast<Statement>(bb) == NULL) {+      error("Illegal nesting: Only properties may be nested beneath properties.", r->block()->pstate(), traces);     }     Ruleset_Obj rr = SASS_MEMORY_NEW(Ruleset,                                   r->pstate(),@@ -158,31 +163,31 @@     p_stack.pop_back();      if (!rr->block()) {-      error("Illegal nesting: Only properties may be nested beneath properties.", r->block()->pstate());+      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_Ptr rules = SASS_MEMORY_NEW(Block, rr->block()->pstate());     for (size_t i = 0, L = rr->block()->length(); i < L; i++)     {-      Statement_Ptr s = &rr->block()->at(i);+      Statement_Ptr s = rr->block()->at(i);       if (bubblable(s)) rules->append(s);       if (!bubblable(s)) props->append(s);     }      if (props->length())     {-      Block_Obj bb = SASS_MEMORY_NEW(Block, rr->block()->pstate());-      bb->concat(&props);-      rr->block(bb);+      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_Ptr stm = &rules->at(i);+        Statement_Ptr stm = rules->at(i);         stm->tabs(stm->tabs() + 1);       } -      rules->unshift(&rr);+      rules->unshift(rr);     }      Block_Ptr ptr = rules;@@ -194,7 +199,7 @@     }      if (!(!rules->length() ||-          !bubblable(&rules->last()) ||+          !bubblable(rules->last()) ||           parent()->statement_type() == Statement::RULESET))     {       rules->last()->group_end(true);@@ -219,13 +224,13 @@      Media_Block_Obj mm = SASS_MEMORY_NEW(Media_Block,                                       m->pstate(),-                                      &m->media_queries(),-                                      operator()(&m->block()));+                                      m->media_queries(),+                                      operator()(m->block()));     mm->tabs(m->tabs());      p_stack.pop_back(); -    return debubble(&mm->block(), &mm);+    return debubble(mm->block(), mm);   }    Statement_Ptr Cssize::operator()(Supports_Block_Ptr m)@@ -240,13 +245,13 @@      Supports_Block_Obj mm = SASS_MEMORY_NEW(Supports_Block,                                        m->pstate(),-                                       &m->condition(),-                                       operator()(&m->block()));+                                       m->condition(),+                                       operator()(m->block()));     mm->tabs(m->tabs());      p_stack.pop_back(); -    return debubble(&mm->block(), &mm);+    return debubble(mm->block(), mm);   }    Statement_Ptr Cssize::operator()(At_Root_Block_Ptr m)@@ -257,15 +262,15 @@       tmp |= m->exclude_node(s);     } -    if (!tmp)+    if (!tmp && m->block())     {-      Block_Ptr bb = operator()(&m->block());+      Block_Ptr 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 (bubblable(stm)) stm->tabs(stm->tabs() + m->tabs());       }-      if (bb->length() && bubblable(&bb->last())) bb->last()->group_end(m->group_end());+      if (bb->length() && bubblable(bb->last())) bb->last()->group_end(m->group_end());       return bb;     } @@ -280,13 +285,13 @@   Statement_Ptr Cssize::bubble(Directive_Ptr m)   {     Block_Ptr bb = SASS_MEMORY_NEW(Block, this->parent()->pstate());-    Has_Block_Obj new_rule = static_cast<Has_Block_Ptr>(SASS_MEMORY_COPY(this->parent()));+    Has_Block_Obj new_rule = Cast<Has_Block>(SASS_MEMORY_COPY(this->parent()));     new_rule->block(bb);     new_rule->tabs(this->parent()->tabs());-    new_rule->block()->concat(&m->block());+    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);+    wrapper_block->append(new_rule);     Directive_Obj mm = SASS_MEMORY_NEW(Directive,                                   m->pstate(),                                   m->keyword(),@@ -294,20 +299,23 @@                                   wrapper_block);     if (m->value()) mm->value(m->value()); -    Bubble_Ptr bubble = SASS_MEMORY_NEW(Bubble, mm->pstate(), &mm);+    Bubble_Ptr bubble = SASS_MEMORY_NEW(Bubble, mm->pstate(), mm);     return bubble;   }    Statement_Ptr Cssize::bubble(At_Root_Block_Ptr m)   {+    if (!m || !m->block()) return NULL;     Block_Ptr bb = SASS_MEMORY_NEW(Block, this->parent()->pstate());-    Has_Block_Obj new_rule = static_cast<Has_Block_Ptr>(SASS_MEMORY_COPY(this->parent()));-    new_rule->block(bb);-    new_rule->tabs(this->parent()->tabs());-    new_rule->block()->concat(&m->block());-+    Has_Block_Obj new_rule = Cast<Has_Block>(SASS_MEMORY_COPY(this->parent()));     Block_Ptr wrapper_block = SASS_MEMORY_NEW(Block, m->block()->pstate());-    wrapper_block->append(&new_rule);+    if (new_rule) {+      new_rule->block(bb);+      new_rule->tabs(this->parent()->tabs());+      new_rule->block()->concat(m->block());+      wrapper_block->append(new_rule);+    }+     At_Root_Block_Ptr mm = SASS_MEMORY_NEW(At_Root_Block,                                         m->pstate(),                                         wrapper_block,@@ -318,7 +326,7 @@    Statement_Ptr Cssize::bubble(Supports_Block_Ptr m)   {-    Ruleset_Obj parent = static_cast<Ruleset_Ptr>(SASS_MEMORY_COPY(this->parent()));+    Ruleset_Obj parent = Cast<Ruleset>(SASS_MEMORY_COPY(this->parent()));      Block_Ptr bb = SASS_MEMORY_NEW(Block, parent->block()->pstate());     Ruleset_Ptr new_rule = SASS_MEMORY_NEW(Ruleset,@@ -326,13 +334,13 @@                                         parent->selector(),                                         bb);     new_rule->tabs(parent->tabs());-    new_rule->block()->concat(&m->block());+    new_rule->block()->concat(m->block());      Block_Ptr wrapper_block = SASS_MEMORY_NEW(Block, m->block()->pstate());     wrapper_block->append(new_rule);     Supports_Block_Ptr mm = SASS_MEMORY_NEW(Supports_Block,                                        m->pstate(),-                                       &m->condition(),+                                       m->condition(),                                        wrapper_block);      mm->tabs(m->tabs());@@ -343,7 +351,7 @@    Statement_Ptr Cssize::bubble(Media_Block_Ptr m)   {-    Ruleset_Obj parent = static_cast<Ruleset_Ptr>(SASS_MEMORY_COPY(this->parent()));+    Ruleset_Obj parent = Cast<Ruleset>(SASS_MEMORY_COPY(this->parent()));      Block_Ptr bb = SASS_MEMORY_NEW(Block, parent->block()->pstate());     Ruleset_Ptr new_rule = SASS_MEMORY_NEW(Ruleset,@@ -351,32 +359,31 @@                                         parent->selector(),                                         bb);     new_rule->tabs(parent->tabs());-    new_rule->block()->concat(&m->block());+    new_rule->block()->concat(m->block());      Block_Ptr wrapper_block = SASS_MEMORY_NEW(Block, m->block()->pstate());     wrapper_block->append(new_rule);     Media_Block_Obj mm = SASS_MEMORY_NEW(Media_Block,                                       m->pstate(),-                                      &m->media_queries(),-                                      wrapper_block,-                                      0);+                                      m->media_queries(),+                                      wrapper_block);      mm->tabs(m->tabs()); -    return SASS_MEMORY_NEW(Bubble, mm->pstate(), &mm);+    return SASS_MEMORY_NEW(Bubble, mm->pstate(), mm);   }    bool Cssize::bubblable(Statement_Ptr s)   {-    return dynamic_cast<Ruleset_Ptr>(s) || s->bubbles();+    return Cast<Ruleset>(s) || s->bubbles();   }    Block_Ptr Cssize::flatten(Block_Ptr b)   {     Block_Ptr result = SASS_MEMORY_NEW(Block, b->pstate(), 0, b->is_root());     for (size_t i = 0, L = b->length(); i < L; ++i) {-      Statement_Ptr ss = &b->at(i);-      if (Block_Ptr bb = SASS_MEMORY_CAST_PTR(Block, ss)) {+      Statement_Ptr ss = b->at(i);+      if (Block_Ptr bb = Cast<Block>(ss)) {         Block_Obj bs = flatten(bb);         for (size_t j = 0, K = bs->length(); j < K; ++j) {           result->append(bs->at(j));@@ -395,7 +402,7 @@      for (size_t i = 0, L = b->length(); i < L; ++i) {       Statement_Obj value = b->at(i);-      bool key = dynamic_cast<Bubble_Ptr>(&value) != NULL;+      bool key = Cast<Bubble>(value) != NULL;        if (!results.empty() && results.back().first == key)       {@@ -424,45 +431,50 @@        if (!is_bubble) {         if (!parent) {-          result->append(&slice);+          result->append(slice);         }         else if (previous_parent) {-          previous_parent->block()->concat(&slice);+          previous_parent->block()->concat(slice);         }         else {-          previous_parent = static_cast<Has_Block_Ptr>(SASS_MEMORY_COPY(parent));+          previous_parent = Cast<Has_Block>(SASS_MEMORY_COPY(parent));           previous_parent->block(slice);           previous_parent->tabs(parent->tabs()); -          result->append(&previous_parent);+          result->append(previous_parent);         }         continue;       }        for (size_t j = 0, K = slice->length(); j < K; ++j)       {-        Statement_Ptr ss = NULL;+        Statement_Ptr ss;         Statement_Obj stm = slice->at(j);         // this has to go now here (too bad)-        Bubble_Obj node = SASS_MEMORY_CAST(Bubble, stm);+        Bubble_Obj node = Cast<Bubble>(stm);         Media_Block_Ptr m1 = NULL;         Media_Block_Ptr m2 = NULL;-        if (parent) m1 = SASS_MEMORY_CAST(Media_Block, *parent);-        if (node) m2 = SASS_MEMORY_CAST(Media_Block, node->node());+        if (parent) m1 = Cast<Media_Block>(parent);+        if (node) m2 = Cast<Media_Block>(node->node());         if (!parent ||             parent->statement_type() != Statement::MEDIA ||             node->node()->statement_type() != Statement::MEDIA ||-            (m1 && m2 && &m1->media_queries() == &m2->media_queries())+            (m1 && m2 && *m1->media_queries() == *m2->media_queries())           )         {-          ss = &node->node();+          ss = node->node();         }         else         {-          List_Obj mq = merge_media_queries(static_cast<Media_Block_Ptr>(&node->node()), static_cast<Media_Block_Ptr>(parent));+          List_Obj mq = merge_media_queries(+            Cast<Media_Block>(node->node()),+            Cast<Media_Block>(parent)+          );           if (!mq->length()) continue;-          static_cast<Media_Block_Ptr>(&node->node())->media_queries(mq);-          ss = &node->node();+          if (Media_Block* b = Cast<Media_Block>(node->node())) {+            b->media_queries(mq);+          }+          ss = node->node();         }          if (!ss) continue;@@ -470,8 +482,6 @@         ss->tabs(ss->tabs() + node->tabs());         ss->group_end(node->group_end()); -        if (!ss) continue;-         Block_Obj bb = SASS_MEMORY_NEW(Block,                                     children->pstate(),                                     children->length(),@@ -483,7 +493,7 @@                                               children->length(),                                               children->is_root()); -        Block_Ptr wrapper = flatten(&bb);+        Block_Ptr wrapper = flatten(bb);         wrapper_block->append(wrapper);          if (wrapper->length()) {@@ -491,12 +501,12 @@         }          if (wrapper_block) {-          result->append(&wrapper_block);+          result->append(wrapper_block);         }       }     } -    return flatten(&result);+    return flatten(result);   }    Statement_Ptr Cssize::fallback_impl(AST_Node_Ptr n)@@ -508,7 +518,7 @@   {     for (size_t i = 0, L = b->length(); i < L; ++i) {       Statement_Obj ith = b->at(i)->perform(this);-      if (Block_Ptr bb = SASS_MEMORY_CAST(Block, ith)) {+      if (Block_Ptr bb = Cast<Block>(ith)) {         for (size_t j = 0, K = bb->length(); j < K; ++j) {           cur->append(bb->at(j));         }@@ -530,8 +540,8 @@       for (size_t j = 0, K = m2->media_queries()->length(); j < K; j++) {         Expression_Obj l1 = m1->media_queries()->at(i);         Expression_Obj l2 = m2->media_queries()->at(j);-        Media_Query_Ptr mq1 = SASS_MEMORY_CAST(Media_Query, l1);-        Media_Query_Ptr mq2 = SASS_MEMORY_CAST(Media_Query, l2);+        Media_Query_Ptr mq1 = Cast<Media_Query>(l1);+        Media_Query_Ptr mq2 = Cast<Media_Query>(l2);         Media_Query_Ptr mq = merge_media_query(mq1, mq2);         if (mq) qq->append(mq);       }@@ -548,9 +558,9 @@     std::string mod;      std::string m1 = std::string(mq1->is_restricted() ? "only" : mq1->is_negated() ? "not" : "");-    std::string t1 = &mq1->media_type() ? mq1->media_type()->to_string(ctx.c_options) : "";+    std::string t1 = mq1->media_type() ? mq1->media_type()->to_string(ctx.c_options) : "";     std::string m2 = std::string(mq2->is_restricted() ? "only" : mq1->is_negated() ? "not" : "");-    std::string t2 = &mq2->media_type() ? mq2->media_type()->to_string(ctx.c_options) : "";+    std::string t2 = mq2->media_type() ? mq2->media_type()->to_string(ctx.c_options) : "";       if (t1.empty()) t1 = t2;@@ -578,10 +588,11 @@     }      Media_Query_Ptr mm = SASS_MEMORY_NEW(Media_Query,--mq1->pstate(), 0,-mq1->length() + mq2->length(), mod == "not", mod == "only"-);+                                         mq1->pstate(),+                                         0,+                                         mq1->length() + mq2->length(),+                                         mod == "not",+                                         mod == "only");      if (!type.empty()) {       mm->media_type(SASS_MEMORY_NEW(String_Quoted, mq1->pstate(), type));
libsass/src/cssize.hpp view
@@ -8,20 +8,19 @@  namespace Sass { -  typedef Environment<AST_Node_Obj> Env;   struct Backtrace;    class Cssize : public Operation_CRTP<Statement_Ptr, Cssize> {      Context&                    ctx;-    std::vector<Block_Ptr>         block_stack;-    std::vector<Statement_Ptr>     p_stack;-    Backtrace*                  backtrace;+    Backtraces&                 traces;+    std::vector<Block_Ptr>      block_stack;+    std::vector<Statement_Ptr>  p_stack;      Statement_Ptr fallback_impl(AST_Node_Ptr n);    public:-    Cssize(Context&, Backtrace*);+    Cssize(Context&);     ~Cssize() { }      Selector_List_Ptr selector();
libsass/src/debugger.hpp view
@@ -10,11 +10,15 @@  inline void debug_ast(AST_Node_Ptr node, std::string ind = "", Env* env = 0); -inline void debug_sources_set(SourcesSet& set, std::string ind = "")+inline void debug_ast(const AST_Node* node, std::string ind = "", Env* env = 0) {+  debug_ast(const_cast<AST_Node*>(node), ind, env);+}++inline void debug_sources_set(ComplexSelectorSet& set, std::string ind = "") {   if (ind == "") std::cerr << "#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n";   for(auto const &pair : set) {-    debug_ast(&pair, ind + "");+    debug_ast(pair, ind + "");     // debug_ast(set[pair], ind + "first: ");   }   if (ind == "") std::cerr << "#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n";@@ -64,30 +68,30 @@ {   if (node == 0) return;   if (ind == "") std::cerr << "####################################################################\n";-  if (dynamic_cast<Bubble_Ptr>(node)) {-    Bubble_Ptr bubble = dynamic_cast<Bubble_Ptr>(node);+  if (Cast<Bubble>(node)) {+    Bubble_Ptr bubble = Cast<Bubble>(node);     std::cerr << ind << "Bubble " << bubble;     std::cerr << " (" << pstate_source_position(node) << ")";     std::cerr << " " << bubble->tabs();     std::cerr << std::endl;-    debug_ast(&bubble->node(), ind + " ", env);-  } else if (dynamic_cast<Trace_Ptr>(node)) {-    Trace_Ptr trace = dynamic_cast<Trace_Ptr>(node);+    debug_ast(bubble->node(), ind + " ", env);+  } else if (Cast<Trace>(node)) {+    Trace_Ptr trace = Cast<Trace>(node);     std::cerr << ind << "Trace " << trace;     std::cerr << " (" << pstate_source_position(node) << ")"-    << " [name:" << trace->name() << "]"+    << " [name:" << trace->name() << ", type: " << trace->type() << "]"     << std::endl;-    debug_ast(&trace->block(), ind + " ", env);-  } else if (dynamic_cast<At_Root_Block_Ptr>(node)) {-    At_Root_Block_Ptr root_block = dynamic_cast<At_Root_Block_Ptr>(node);+    debug_ast(trace->block(), ind + " ", env);+  } else if (Cast<At_Root_Block>(node)) {+    At_Root_Block_Ptr root_block = Cast<At_Root_Block>(node);     std::cerr << ind << "At_Root_Block " << root_block;     std::cerr << " (" << pstate_source_position(node) << ")";     std::cerr << " " << root_block->tabs();     std::cerr << std::endl;-    debug_ast(&root_block->expression(), ind + ":", env);-    debug_ast(&root_block->block(), ind + " ", env);-  } else if (dynamic_cast<Selector_List_Ptr>(node)) {-    Selector_List_Ptr selector = dynamic_cast<Selector_List_Ptr>(node);+    debug_ast(root_block->expression(), ind + ":", env);+    debug_ast(root_block->block(), ind + " ", env);+  } else if (Cast<Selector_List>(node)) {+    Selector_List_Ptr selector = Cast<Selector_List>(node);     std::cerr << ind << "Selector_List " << selector;     std::cerr << " (" << pstate_source_position(node) << ")";     std::cerr << " <" << selector->hash() << ">";@@ -99,15 +103,16 @@     std::cerr << (selector->has_line_break() ? " [line-break]": " -");     std::cerr << (selector->has_line_feed() ? " [line-feed]": " -");     std::cerr << std::endl;+    debug_ast(selector->schema(), ind + "#{} "); -    for(const Complex_Selector_Obj& i : selector->elements()) { debug_ast(&i, ind + " ", env); }+    for(const Complex_Selector_Obj& i : selector->elements()) { debug_ast(i, ind + " ", env); } -//  } else if (dynamic_cast<Expression_Ptr>(node)) {-//    Expression_Ptr expression = dynamic_cast<Expression_Ptr>(node);+//  } else if (Cast<Expression>(node)) {+//    Expression_Ptr expression = Cast<Expression>(node); //    std::cerr << ind << "Expression " << expression << " " << expression->concrete_type() << std::endl; -  } else if (dynamic_cast<Parent_Selector_Ptr>(node)) {-    Parent_Selector_Ptr selector = dynamic_cast<Parent_Selector_Ptr>(node);+  } else if (Cast<Parent_Selector>(node)) {+    Parent_Selector_Ptr selector = Cast<Parent_Selector>(node);     std::cerr << ind << "Parent_Selector " << selector; //    if (selector->not_selector()) cerr << " [in_declaration]";     std::cerr << " (" << pstate_source_position(node) << ")";@@ -116,8 +121,8 @@     std::cerr << " <" << prettyprint(selector->pstate().token.ws_before()) << ">" << std::endl; //    debug_ast(selector->selector(), ind + "->", env); -  } else if (dynamic_cast<Complex_Selector_Ptr>(node)) {-    Complex_Selector_Ptr selector = dynamic_cast<Complex_Selector_Ptr>(node);+  } else if (Cast<Complex_Selector>(node)) {+    Complex_Selector_Ptr selector = Cast<Complex_Selector>(node);     std::cerr << ind << "Complex_Selector " << selector       << " (" << pstate_source_position(node) << ")"       << " <" << selector->hash() << ">"@@ -141,16 +146,16 @@       }       // if (del = "/") del += selector->reference()->perform(&to_string) + "/";     std::cerr << " <" << prettyprint(selector->pstate().token.ws_before()) << ">" << std::endl;-    debug_ast(&selector->head(), ind + " " /* + "[" + del + "]" */, env);+    debug_ast(selector->head(), ind + " " /* + "[" + del + "]" */, env);     if (selector->tail()) {-      debug_ast(&selector->tail(), ind + "{" + del + "}", env);+      debug_ast(selector->tail(), ind + "{" + del + "}", env);     } else if(del != " ") {       std::cerr << ind << " |" << del << "| {trailing op}" << std::endl;     }-    SourcesSet set = selector->sources();+    ComplexSelectorSet set = selector->sources();     // debug_sources_set(set, ind + "  @--> ");-  } else if (dynamic_cast<Compound_Selector_Ptr>(node)) {-    Compound_Selector_Ptr selector = dynamic_cast<Compound_Selector_Ptr>(node);+  } else if (Cast<Compound_Selector>(node)) {+    Compound_Selector_Ptr selector = Cast<Compound_Selector>(node);     std::cerr << ind << "Compound_Selector " << selector;     std::cerr << " (" << pstate_source_position(node) << ")";     std::cerr << " <" << selector->hash() << ">";@@ -162,9 +167,9 @@     std::cerr << (selector->has_line_break() ? " [line-break]": " -");     std::cerr << (selector->has_line_feed() ? " [line-feed]": " -");     std::cerr << " <" << prettyprint(selector->pstate().token.ws_before()) << ">" << std::endl;-    for(const Simple_Selector_Obj& i : selector->elements()) { debug_ast(&i, ind + " ", env); }-  } else if (dynamic_cast<Wrapped_Selector_Ptr>(node)) {-    Wrapped_Selector_Ptr selector = dynamic_cast<Wrapped_Selector_Ptr>(node);+    for(const Simple_Selector_Obj& i : selector->elements()) { debug_ast(i, ind + " ", env); }+  } else if (Cast<Wrapped_Selector>(node)) {+    Wrapped_Selector_Ptr selector = Cast<Wrapped_Selector>(node);     std::cerr << ind << "Wrapped_Selector " << selector;     std::cerr << " (" << pstate_source_position(node) << ")";     std::cerr << " <" << selector->hash() << ">";@@ -174,9 +179,9 @@     std::cerr << (selector->has_line_break() ? " [line-break]": " -");     std::cerr << (selector->has_line_feed() ? " [line-feed]": " -");     std::cerr << std::endl;-    debug_ast(&selector->selector(), ind + " () ", env);-  } else if (dynamic_cast<Pseudo_Selector_Ptr>(node)) {-    Pseudo_Selector_Ptr selector = dynamic_cast<Pseudo_Selector_Ptr>(node);+    debug_ast(selector->selector(), ind + " () ", env);+  } else if (Cast<Pseudo_Selector>(node)) {+    Pseudo_Selector_Ptr selector = Cast<Pseudo_Selector>(node);     std::cerr << ind << "Pseudo_Selector " << selector;     std::cerr << " (" << pstate_source_position(node) << ")";     std::cerr << " <" << selector->hash() << ">";@@ -186,9 +191,9 @@     std::cerr << (selector->has_line_break() ? " [line-break]": " -");     std::cerr << (selector->has_line_feed() ? " [line-feed]": " -");     std::cerr << std::endl;-    debug_ast(&selector->expression(), ind + " <= ", env);-  } else if (dynamic_cast<Attribute_Selector_Ptr>(node)) {-    Attribute_Selector_Ptr selector = dynamic_cast<Attribute_Selector_Ptr>(node);+    debug_ast(selector->expression(), ind + " <= ", env);+  } else if (Cast<Attribute_Selector>(node)) {+    Attribute_Selector_Ptr selector = Cast<Attribute_Selector>(node);     std::cerr << ind << "Attribute_Selector " << selector;     std::cerr << " (" << pstate_source_position(node) << ")";     std::cerr << " <" << selector->hash() << ">";@@ -198,9 +203,9 @@     std::cerr << (selector->has_line_break() ? " [line-break]": " -");     std::cerr << (selector->has_line_feed() ? " [line-feed]": " -");     std::cerr << std::endl;-    debug_ast(&selector->value(), ind + "[" + selector->matcher() + "] ", env);-  } else if (dynamic_cast<Class_Selector_Ptr>(node)) {-    Class_Selector_Ptr selector = dynamic_cast<Class_Selector_Ptr>(node);+    debug_ast(selector->value(), ind + "[" + selector->matcher() + "] ", env);+  } else if (Cast<Class_Selector>(node)) {+    Class_Selector_Ptr selector = Cast<Class_Selector>(node);     std::cerr << ind << "Class_Selector " << selector;     std::cerr << " (" << pstate_source_position(node) << ")";     std::cerr << " <" << selector->hash() << ">";@@ -210,8 +215,8 @@     std::cerr << (selector->has_line_break() ? " [line-break]": " -");     std::cerr << (selector->has_line_feed() ? " [line-feed]": " -");     std::cerr << std::endl;-  } else if (dynamic_cast<Id_Selector_Ptr>(node)) {-    Id_Selector_Ptr selector = dynamic_cast<Id_Selector_Ptr>(node);+  } else if (Cast<Id_Selector>(node)) {+    Id_Selector_Ptr selector = Cast<Id_Selector>(node);     std::cerr << ind << "Id_Selector " << selector;     std::cerr << " (" << pstate_source_position(node) << ")";     std::cerr << " <" << selector->hash() << ">";@@ -221,8 +226,8 @@     std::cerr << (selector->has_line_break() ? " [line-break]": " -");     std::cerr << (selector->has_line_feed() ? " [line-feed]": " -");     std::cerr << std::endl;-  } else if (dynamic_cast<Element_Selector_Ptr>(node)) {-    Element_Selector_Ptr selector = dynamic_cast<Element_Selector_Ptr>(node);+  } else if (Cast<Element_Selector>(node)) {+    Element_Selector_Ptr selector = Cast<Element_Selector>(node);     std::cerr << ind << "Element_Selector " << selector;     std::cerr << " (" << pstate_source_position(node) << ")";     std::cerr << " <" << selector->hash() << ">";@@ -233,9 +238,9 @@     std::cerr << (selector->has_line_feed() ? " [line-feed]": " -");     std::cerr << " <" << prettyprint(selector->pstate().token.ws_before()) << ">";     std::cerr << std::endl;-  } else if (dynamic_cast<Placeholder_Selector_Ptr>(node)) {+  } else if (Cast<Placeholder_Selector>(node)) { -    Placeholder_Selector_Ptr selector = dynamic_cast<Placeholder_Selector_Ptr>(node);+    Placeholder_Selector_Ptr selector = Cast<Placeholder_Selector>(node);     std::cerr << ind << "Placeholder_Selector [" << selector->ns_name() << "] " << selector;     std::cerr << " (" << pstate_source_position(selector) << ")"       << " <" << selector->hash() << ">"@@ -245,212 +250,211 @@       << (selector->has_line_feed() ? " [line-feed]": " -")     << std::endl; -  } else if (dynamic_cast<Simple_Selector*>(node)) {-    Simple_Selector* selector = dynamic_cast<Simple_Selector*>(node);+  } else if (Cast<Simple_Selector>(node)) {+    Simple_Selector* selector = Cast<Simple_Selector>(node);     std::cerr << ind << "Simple_Selector " << selector;     std::cerr << " (" << pstate_source_position(node) << ")";     std::cerr << (selector->has_line_break() ? " [line-break]": " -") << (selector->has_line_feed() ? " [line-feed]": " -") << std::endl; -  } else if (dynamic_cast<Selector_Schema_Ptr>(node)) {-    Selector_Schema_Ptr selector = dynamic_cast<Selector_Schema_Ptr>(node);+  } else if (Cast<Selector_Schema>(node)) {+    Selector_Schema_Ptr selector = Cast<Selector_Schema>(node);     std::cerr << ind << "Selector_Schema " << selector;     std::cerr << " (" << pstate_source_position(node) << ")"-      << (selector->at_root() && selector->at_root() ? " [@ROOT]" : "")       << " [@media:" << selector->media_block() << "]"-      << (selector->has_line_break() ? " [line-break]": " -")-      << (selector->has_line_feed() ? " [line-feed]": " -")+      << (selector->connect_parent() ? " [connect-parent]": " -")     << std::endl; -    debug_ast(&selector->contents(), ind + " ");+    debug_ast(selector->contents(), ind + " ");     // for(auto i : selector->elements()) { debug_ast(i, ind + " ", env); } -  } else if (dynamic_cast<Selector_Ptr>(node)) {-    Selector_Ptr selector = dynamic_cast<Selector_Ptr>(node);+  } else if (Cast<Selector>(node)) {+    Selector_Ptr selector = Cast<Selector>(node);     std::cerr << ind << "Selector " << selector;     std::cerr << " (" << pstate_source_position(node) << ")";     std::cerr << (selector->has_line_break() ? " [line-break]": " -")       << (selector->has_line_feed() ? " [line-feed]": " -")     << std::endl; -  } else if (dynamic_cast<Media_Query_Expression_Ptr>(node)) {-    Media_Query_Expression_Ptr block = dynamic_cast<Media_Query_Expression_Ptr>(node);+  } else if (Cast<Media_Query_Expression>(node)) {+    Media_Query_Expression_Ptr block = Cast<Media_Query_Expression>(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) ");+    debug_ast(block->feature(), ind + " feature) ");+    debug_ast(block->value(), ind + " value) "); -  } else if (dynamic_cast<Media_Query_Ptr>(node)) {-    Media_Query_Ptr block = dynamic_cast<Media_Query_Ptr>(node);+  } else if (Cast<Media_Query>(node)) {+    Media_Query_Ptr block = Cast<Media_Query>(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); }+    debug_ast(block->media_type(), ind + " ");+    for(const auto& i : block->elements()) { debug_ast(i, ind + " ", env); } -  } else if (dynamic_cast<Media_Block_Ptr>(node)) {-    Media_Block_Ptr block = dynamic_cast<Media_Block_Ptr>(node);+  } else if (Cast<Media_Block>(node)) {+    Media_Block_Ptr block = Cast<Media_Block>(node);     std::cerr << ind << "Media_Block " << block;     std::cerr << " (" << pstate_source_position(node) << ")";     std::cerr << " " << block->tabs() << std::endl;-    debug_ast(&block->media_queries(), ind + " =@ ");-    if (block->block()) for(const Statement_Obj& i : block->block()->elements()) { debug_ast(&i, ind + " ", env); }-  } else if (dynamic_cast<Supports_Block_Ptr>(node)) {-    Supports_Block_Ptr block = dynamic_cast<Supports_Block_Ptr>(node);+    debug_ast(block->media_queries(), ind + " =@ ");+    if (block->block()) for(const Statement_Obj& i : block->block()->elements()) { debug_ast(i, ind + " ", env); }+  } else if (Cast<Supports_Block>(node)) {+    Supports_Block_Ptr block = Cast<Supports_Block>(node);     std::cerr << ind << "Supports_Block " << block;     std::cerr << " (" << pstate_source_position(node) << ")";     std::cerr << " " << block->tabs() << std::endl;-    debug_ast(&block->condition(), ind + " =@ ");-    debug_ast(&block->block(), ind + " <>");-  } else if (dynamic_cast<Supports_Operator_Ptr>(node)) {-    Supports_Operator_Ptr block = dynamic_cast<Supports_Operator_Ptr>(node);+    debug_ast(block->condition(), ind + " =@ ");+    debug_ast(block->block(), ind + " <>");+  } else if (Cast<Supports_Operator>(node)) {+    Supports_Operator_Ptr block = Cast<Supports_Operator>(node);     std::cerr << ind << "Supports_Operator " << block;     std::cerr << " (" << pstate_source_position(node) << ")"     << std::endl;-    debug_ast(&block->left(), ind + " left) ");-    debug_ast(&block->right(), ind + " right) ");-  } else if (dynamic_cast<Supports_Negation_Ptr>(node)) {-    Supports_Negation_Ptr block = dynamic_cast<Supports_Negation_Ptr>(node);+    debug_ast(block->left(), ind + " left) ");+    debug_ast(block->right(), ind + " right) ");+  } else if (Cast<Supports_Negation>(node)) {+    Supports_Negation_Ptr block = Cast<Supports_Negation>(node);     std::cerr << ind << "Supports_Negation " << block;     std::cerr << " (" << pstate_source_position(node) << ")"     << std::endl;-    debug_ast(&block->condition(), ind + " condition) ");-  } else if (dynamic_cast<At_Root_Query_Ptr>(node)) {-    At_Root_Query_Ptr block = dynamic_cast<At_Root_Query_Ptr>(node);+    debug_ast(block->condition(), ind + " condition) ");+  } else if (Cast<At_Root_Query>(node)) {+    At_Root_Query_Ptr block = Cast<At_Root_Query>(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 (dynamic_cast<Supports_Declaration_Ptr>(node)) {-    Supports_Declaration_Ptr block = dynamic_cast<Supports_Declaration_Ptr>(node);+    debug_ast(block->feature(), ind + " feature) ");+    debug_ast(block->value(), ind + " value) ");+  } else if (Cast<Supports_Declaration>(node)) {+    Supports_Declaration_Ptr block = Cast<Supports_Declaration>(node);     std::cerr << ind << "Supports_Declaration " << block;     std::cerr << " (" << pstate_source_position(node) << ")"     << std::endl;-    debug_ast(&block->feature(), ind + " feature) ");-    debug_ast(&block->value(), ind + " value) ");-  } else if (dynamic_cast<Block_Ptr>(node)) {-    Block_Ptr root_block = dynamic_cast<Block_Ptr>(node);+    debug_ast(block->feature(), ind + " feature) ");+    debug_ast(block->value(), ind + " value) ");+  } else if (Cast<Block>(node)) {+    Block_Ptr root_block = Cast<Block>(node);     std::cerr << ind << "Block " << root_block;     std::cerr << " (" << pstate_source_position(node) << ")";     if (root_block->is_root()) std::cerr << " [root]";     std::cerr << " " << root_block->tabs() << std::endl;-    for(const Statement_Obj& i : root_block->elements()) { debug_ast(&i, ind + " ", env); }-  } else if (dynamic_cast<Warning_Ptr>(node)) {-    Warning_Ptr block = dynamic_cast<Warning_Ptr>(node);+    for(const Statement_Obj& i : root_block->elements()) { debug_ast(i, ind + " ", env); }+  } else if (Cast<Warning>(node)) {+    Warning_Ptr block = Cast<Warning>(node);     std::cerr << ind << "Warning " << block;     std::cerr << " (" << pstate_source_position(node) << ")";     std::cerr << " " << block->tabs() << std::endl;-    debug_ast(&block->message(), ind + " : ");-  } else if (dynamic_cast<Error_Ptr>(node)) {-    Error_Ptr block = dynamic_cast<Error_Ptr>(node);+    debug_ast(block->message(), ind + " : ");+  } else if (Cast<Error>(node)) {+    Error_Ptr block = Cast<Error>(node);     std::cerr << ind << "Error " << block;     std::cerr << " (" << pstate_source_position(node) << ")";     std::cerr << " " << block->tabs() << std::endl;-  } else if (dynamic_cast<Debug_Ptr>(node)) {-    Debug_Ptr block = dynamic_cast<Debug_Ptr>(node);+  } else if (Cast<Debug>(node)) {+    Debug_Ptr block = Cast<Debug>(node);     std::cerr << ind << "Debug " << block;     std::cerr << " (" << pstate_source_position(node) << ")";     std::cerr << " " << block->tabs() << std::endl;-    debug_ast(&block->value(), ind + " ");-  } else if (dynamic_cast<Comment_Ptr>(node)) {-    Comment_Ptr block = dynamic_cast<Comment_Ptr>(node);+    debug_ast(block->value(), ind + " ");+  } else if (Cast<Comment>(node)) {+    Comment_Ptr block = Cast<Comment>(node);     std::cerr << ind << "Comment " << block;     std::cerr << " (" << pstate_source_position(node) << ")";     std::cerr << " " << block->tabs() <<       " <" << prettyprint(block->pstate().token.ws_before()) << ">" << std::endl;-    debug_ast(&block->text(), ind + "// ", env);-  } else if (dynamic_cast<If_Ptr>(node)) {-    If_Ptr block = dynamic_cast<If_Ptr>(node);+    debug_ast(block->text(), ind + "// ", env);+  } else if (Cast<If>(node)) {+    If_Ptr block = Cast<If>(node);     std::cerr << ind << "If " << block;     std::cerr << " (" << pstate_source_position(node) << ")";     std::cerr << " " << block->tabs() << std::endl;-    debug_ast(&block->predicate(), ind + " = ");-    debug_ast(&block->block(), ind + " <>");-    debug_ast(&block->alternative(), ind + " ><");-  } else if (dynamic_cast<Return_Ptr>(node)) {-    Return_Ptr block = dynamic_cast<Return_Ptr>(node);+    debug_ast(block->predicate(), ind + " = ");+    debug_ast(block->block(), ind + " <>");+    debug_ast(block->alternative(), ind + " ><");+  } else if (Cast<Return>(node)) {+    Return_Ptr block = Cast<Return>(node);     std::cerr << ind << "Return " << block;     std::cerr << " (" << pstate_source_position(node) << ")";     std::cerr << " " << block->tabs() << std::endl;-  } else if (dynamic_cast<Extension_Ptr>(node)) {-    Extension_Ptr block = dynamic_cast<Extension_Ptr>(node);+  } else if (Cast<Extension>(node)) {+    Extension_Ptr block = Cast<Extension>(node);     std::cerr << ind << "Extension " << block;     std::cerr << " (" << pstate_source_position(node) << ")";     std::cerr << " " << block->tabs() << std::endl;-    debug_ast(&block->selector(), ind + "-> ", env);-  } else if (dynamic_cast<Content_Ptr>(node)) {-    Content_Ptr block = dynamic_cast<Content_Ptr>(node);+    debug_ast(block->selector(), ind + "-> ", env);+  } else if (Cast<Content>(node)) {+    Content_Ptr block = Cast<Content>(node);     std::cerr << ind << "Content " << block;     std::cerr << " (" << pstate_source_position(node) << ")";     std::cerr << " [@media:" << block->media_block() << "]";     std::cerr << " " << block->tabs() << std::endl;-  } else if (dynamic_cast<Import_Stub_Ptr>(node)) {-    Import_Stub_Ptr block = dynamic_cast<Import_Stub_Ptr>(node);+  } else if (Cast<Import_Stub>(node)) {+    Import_Stub_Ptr block = Cast<Import_Stub>(node);     std::cerr << ind << "Import_Stub " << block;     std::cerr << " (" << pstate_source_position(node) << ")";     std::cerr << " [" << block->imp_path() << "] ";     std::cerr << " " << block->tabs() << std::endl;-  } else if (dynamic_cast<Import_Ptr>(node)) {-    Import_Ptr block = dynamic_cast<Import_Ptr>(node);+  } else if (Cast<Import>(node)) {+    Import_Ptr block = Cast<Import>(node);     std::cerr << ind << "Import " << block;     std::cerr << " (" << pstate_source_position(node) << ")";     std::cerr << " " << block->tabs() << std::endl;     // std::vector<std::string>         files_;-    for (auto imp : block->urls()) debug_ast(&imp, ind + "@: ", env);-    debug_ast(&block->import_queries(), ind + "@@ ");-  } else if (dynamic_cast<Assignment_Ptr>(node)) {-    Assignment_Ptr block = dynamic_cast<Assignment_Ptr>(node);+    for (auto imp : block->urls()) debug_ast(imp, ind + "@: ", env);+    debug_ast(block->import_queries(), ind + "@@ ");+  } else if (Cast<Assignment>(node)) {+    Assignment_Ptr block = Cast<Assignment>(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 (dynamic_cast<Declaration_Ptr>(node)) {-    Declaration_Ptr block = dynamic_cast<Declaration_Ptr>(node);+    debug_ast(block->value(), ind + "=", env);+  } else if (Cast<Declaration>(node)) {+    Declaration_Ptr block = Cast<Declaration>(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 (dynamic_cast<Keyframe_Rule_Ptr>(node)) {-    Keyframe_Rule_Ptr has_block = dynamic_cast<Keyframe_Rule_Ptr>(node);+    debug_ast(block->property(), ind + " prop: ", env);+    debug_ast(block->value(), ind + " value: ", env);+    debug_ast(block->block(), ind + " ", env);+  } else if (Cast<Keyframe_Rule>(node)) {+    Keyframe_Rule_Ptr has_block = Cast<Keyframe_Rule>(node);     std::cerr << ind << "Keyframe_Rule " << has_block;     std::cerr << " (" << pstate_source_position(node) << ")";     std::cerr << " " << has_block->tabs() << std::endl;-    if (has_block->name()) debug_ast(&has_block->name(), ind + "@");-    if (has_block->block()) for(const Statement_Obj& i : has_block->block()->elements()) { debug_ast(&i, ind + " ", env); }-  } else if (dynamic_cast<Directive_Ptr>(node)) {-    Directive_Ptr block = dynamic_cast<Directive_Ptr>(node);+    if (has_block->name()) debug_ast(has_block->name(), ind + "@");+    if (has_block->block()) for(const Statement_Obj& i : has_block->block()->elements()) { debug_ast(i, ind + " ", env); }+  } else if (Cast<Directive>(node)) {+    Directive_Ptr block = Cast<Directive>(node);     std::cerr << ind << "Directive " << 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 (dynamic_cast<Each_Ptr>(node)) {-    Each_Ptr block = dynamic_cast<Each_Ptr>(node);+    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<Each>(node)) {+    Each_Ptr block = Cast<Each>(node);     std::cerr << ind << "Each " << block;     std::cerr << " (" << pstate_source_position(node) << ")";     std::cerr << " " << block->tabs() << std::endl;-    if (block->block()) for(const Statement_Obj& i : block->block()->elements()) { debug_ast(&i, ind + " ", env); }-  } else if (dynamic_cast<For_Ptr>(node)) {-    For_Ptr block = dynamic_cast<For_Ptr>(node);+    if (block->block()) for(const Statement_Obj& i : block->block()->elements()) { debug_ast(i, ind + " ", env); }+  } else if (Cast<For>(node)) {+    For_Ptr block = Cast<For>(node);     std::cerr << ind << "For " << block;     std::cerr << " (" << pstate_source_position(node) << ")";     std::cerr << " " << block->tabs() << std::endl;-    if (block->block()) for(const Statement_Obj& i : block->block()->elements()) { debug_ast(&i, ind + " ", env); }-  } else if (dynamic_cast<While_Ptr>(node)) {-    While_Ptr block = dynamic_cast<While_Ptr>(node);+    if (block->block()) for(const Statement_Obj& i : block->block()->elements()) { debug_ast(i, ind + " ", env); }+  } else if (Cast<While>(node)) {+    While_Ptr block = Cast<While>(node);     std::cerr << ind << "While " << block;     std::cerr << " (" << pstate_source_position(node) << ")";     std::cerr << " " << block->tabs() << std::endl;-    if (block->block()) for(const Statement_Obj& i : block->block()->elements()) { debug_ast(&i, ind + " ", env); }-  } else if (dynamic_cast<Definition_Ptr>(node)) {-    Definition_Ptr block = dynamic_cast<Definition_Ptr>(node);+    if (block->block()) for(const Statement_Obj& i : block->block()->elements()) { debug_ast(i, ind + " ", env); }+  } else if (Cast<Definition>(node)) {+    Definition_Ptr block = Cast<Definition>(node);     std::cerr << ind << "Definition " << block;     std::cerr << " (" << pstate_source_position(node) << ")";     std::cerr << " [name: " << block->name() << "] ";@@ -459,73 +463,69 @@     // 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 (dynamic_cast<Mixin_Call_Ptr>(node)) {-    Mixin_Call_Ptr block = dynamic_cast<Mixin_Call_Ptr>(node);+    debug_ast(block->parameters(), ind + " params: ", env);+    if (block->block()) debug_ast(block->block(), ind + " ", env);+  } else if (Cast<Mixin_Call>(node)) {+    Mixin_Call_Ptr block = Cast<Mixin_Call>(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: ");-    if (block->block()) debug_ast(&block->block(), ind + " ", env);-  } else if (Ruleset_Ptr ruleset = dynamic_cast<Ruleset_Ptr>(node)) {+    debug_ast(block->arguments(), ind + " args: ");+    if (block->block()) debug_ast(block->block(), ind + " ", env);+  } else if (Ruleset_Ptr ruleset = Cast<Ruleset>(node)) {     std::cerr << ind << "Ruleset " << ruleset;     std::cerr << " (" << pstate_source_position(node) << ")";     std::cerr << " [indent: " << ruleset->tabs() << "]";     std::cerr << (ruleset->is_invisible() ? " [INVISIBLE]" : "");-    std::cerr << (ruleset->at_root() ? " [@ROOT]" : "");     std::cerr << (ruleset->is_root() ? " [root]" : "");     std::cerr << std::endl;-    debug_ast(&ruleset->selector(), ind + ">");-    debug_ast(&ruleset->block(), ind + " ");-  } else if (dynamic_cast<Block_Ptr>(node)) {-    Block_Ptr block = dynamic_cast<Block_Ptr>(node);+    debug_ast(ruleset->selector(), ind + ">");+    debug_ast(ruleset->block(), ind + " ");+  } else if (Cast<Block>(node)) {+    Block_Ptr block = Cast<Block>(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 (dynamic_cast<Textual_Ptr>(node)) {-    Textual_Ptr expression = dynamic_cast<Textual_Ptr>(node);-    std::cerr << ind << "Textual " << expression;-    std::cerr << " (" << pstate_source_position(node) << ")";-    if (expression->type() == Textual::NUMBER) std::cerr << " [NUMBER]";-    else if (expression->type() == Textual::PERCENTAGE) std::cerr << " [PERCENTAGE]";-    else if (expression->type() == Textual::DIMENSION) std::cerr << " [DIMENSION]";-    else if (expression->type() == Textual::HEX) std::cerr << " [HEX]";-    std::cerr << " [" << expression->value() << "]";-    std::cerr << " [interpolant: " << expression->is_interpolant() << "] ";-    if (expression->is_delayed()) std::cerr << " [delayed]";-    std::cerr << std::endl;-  } else if (dynamic_cast<Variable_Ptr>(node)) {-    Variable_Ptr expression = dynamic_cast<Variable_Ptr>(node);+    for(const Statement_Obj& i : block->elements()) { debug_ast(i, ind + " ", env); }+  } else if (Cast<Variable>(node)) {+    Variable_Ptr expression = Cast<Variable>(node);     std::cerr << ind << "Variable " << expression;     std::cerr << " [interpolant: " << expression->is_interpolant() << "] ";     std::cerr << " (" << pstate_source_position(node) << ")";     std::cerr << " [" << expression->name() << "]" << std::endl;     std::string name(expression->name());-    if (env && env->has(name)) debug_ast(SASS_MEMORY_CAST(Expression, (*env)[name]), ind + " -> ", env);-  } else if (dynamic_cast<Function_Call_Schema_Ptr>(node)) {-    Function_Call_Schema_Ptr expression = dynamic_cast<Function_Call_Schema_Ptr>(node);+    if (env && env->has(name)) debug_ast(Cast<Expression>((*env)[name]), ind + " -> ", env);+  } else if (Cast<Function_Call_Schema>(node)) {+    Function_Call_Schema_Ptr expression = Cast<Function_Call_Schema>(node);     std::cerr << ind << "Function_Call_Schema " << expression;     std::cerr << " [interpolant: " << expression->is_interpolant() << "] ";     std::cerr << " (" << pstate_source_position(node) << ")";     std::cerr << "" << std::endl;-    debug_ast(&expression->name(), ind + "name: ", env);-    debug_ast(&expression->arguments(), ind + " args: ", env);-  } else if (dynamic_cast<Function_Call_Ptr>(node)) {-    Function_Call_Ptr expression = dynamic_cast<Function_Call_Ptr>(node);+    debug_ast(expression->name(), ind + "name: ", env);+    debug_ast(expression->arguments(), ind + " args: ", env);+  } else if (Cast<Function_Call>(node)) {+    Function_Call_Ptr expression = Cast<Function_Call>(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);-  } else if (dynamic_cast<Arguments_Ptr>(node)) {-    Arguments_Ptr expression = dynamic_cast<Arguments_Ptr>(node);+    debug_ast(expression->arguments(), ind + " args: ", env);+    debug_ast(expression->func(), ind + " func: ", env);+  } else if (Cast<Function>(node)) {+    Function_Ptr expression = Cast<Function>(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<Arguments>(node)) {+    Arguments_Ptr expression = Cast<Arguments>(node);     std::cerr << ind << "Arguments " << expression;     if (expression->is_delayed()) std::cerr << " [delayed]";     std::cerr << " (" << pstate_source_position(node) << ")";@@ -533,41 +533,41 @@     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 (dynamic_cast<Argument_Ptr>(node)) {-    Argument_Ptr expression = dynamic_cast<Argument_Ptr>(node);+    for(const Argument_Obj& i : expression->elements()) { debug_ast(i, ind + " ", env); }+  } else if (Cast<Argument>(node)) {+    Argument_Ptr expression = Cast<Argument>(node);     std::cerr << ind << "Argument " << expression;     std::cerr << " (" << pstate_source_position(node) << ")";-    std::cerr << " [" << expression->value() << "]";+    std::cerr << " [" << expression->value().ptr() << "]";     std::cerr << " [name: " << expression->name() << "] ";     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 (dynamic_cast<Parameters_Ptr>(node)) {-    Parameters_Ptr expression = dynamic_cast<Parameters_Ptr>(node);+    debug_ast(expression->value(), ind + " value: ", env);+  } else if (Cast<Parameters>(node)) {+    Parameters_Ptr expression = Cast<Parameters>(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 (dynamic_cast<Parameter_Ptr>(node)) {-    Parameter_Ptr expression = dynamic_cast<Parameter_Ptr>(node);+    for(const Parameter_Obj& i : expression->elements()) { debug_ast(i, ind + " ", env); }+  } else if (Cast<Parameter>(node)) {+    Parameter_Ptr expression = Cast<Parameter>(node);     std::cerr << ind << "Parameter " << expression;     std::cerr << " (" << pstate_source_position(node) << ")";     std::cerr << " [name: " << expression->name() << "] ";-    std::cerr << " [default: " << expression->default_value() << "] ";+    std::cerr << " [default: " << expression->default_value().ptr() << "] ";     std::cerr << " [rest: " << expression->is_rest_parameter() << "] " << std::endl;-  } else if (dynamic_cast<Unary_Expression_Ptr>(node)) {-    Unary_Expression_Ptr expression = dynamic_cast<Unary_Expression_Ptr>(node);+  } else if (Cast<Unary_Expression>(node)) {+    Unary_Expression_Ptr expression = Cast<Unary_Expression>(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 (dynamic_cast<Binary_Expression_Ptr>(node)) {-    Binary_Expression_Ptr expression = dynamic_cast<Binary_Expression_Ptr>(node);+    debug_ast(expression->operand(), ind + " operand: ", env);+  } else if (Cast<Binary_Expression>(node)) {+    Binary_Expression_Ptr expression = Cast<Binary_Expression>(node);     std::cerr << ind << "Binary_Expression " << expression;     if (expression->is_interpolant()) std::cerr << " [is interpolant] ";     if (expression->is_left_interpolant()) std::cerr << " [left interpolant] ";@@ -577,67 +577,69 @@     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 (dynamic_cast<Map_Ptr>(node)) {-    Map_Ptr expression = dynamic_cast<Map_Ptr>(node);+    debug_ast(expression->left(), ind + " left:  ", env);+    debug_ast(expression->right(), ind + " right: ", env);+  } else if (Cast<Map>(node)) {+    Map_Ptr expression = Cast<Map>(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: ");+      debug_ast(i.first, ind + " key: ");+      debug_ast(i.second, ind + " val: ");     }-  } else if (dynamic_cast<List_Ptr>(node)) {-    List_Ptr expression = dynamic_cast<List_Ptr>(node);+  } else if (Cast<List>(node)) {+    List_Ptr expression = Cast<List>(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 ") <<+      (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() << "] " <<       " [hash: " << expression->hash() << "] " <<       std::endl;-    for(const auto& i : expression->elements()) { debug_ast(&i, ind + " ", env); }-  } else if (dynamic_cast<Content_Ptr>(node)) {-    Content_Ptr expression = dynamic_cast<Content_Ptr>(node);+    for(const auto& i : expression->elements()) { debug_ast(i, ind + " ", env); }+  } else if (Cast<Content>(node)) {+    Content_Ptr expression = Cast<Content>(node);     std::cerr << ind << "Content " << expression;     std::cerr << " (" << pstate_source_position(node) << ")";     std::cerr << " [@media:" << expression->media_block() << "]";     std::cerr << " [Statement]" << std::endl;-  } else if (dynamic_cast<Boolean_Ptr>(node)) {-    Boolean_Ptr expression = dynamic_cast<Boolean_Ptr>(node);+  } else if (Cast<Boolean>(node)) {+    Boolean_Ptr expression = Cast<Boolean>(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 (dynamic_cast<Color_Ptr>(node)) {-    Color_Ptr expression = dynamic_cast<Color_Ptr>(node);+  } else if (Cast<Color>(node)) {+    Color_Ptr expression = Cast<Color>(node);     std::cerr << ind << "Color " << expression;     std::cerr << " (" << pstate_source_position(node) << ")";     std::cerr << " [delayed: " << expression->is_delayed() << "] ";     std::cerr << " [interpolant: " << expression->is_interpolant() << "] ";     std::cerr << " [" << expression->r() << ":"  << expression->g() << ":" << expression->b() << "@" << expression->a() << "]" << std::endl;-  } else if (dynamic_cast<Number_Ptr>(node)) {-    Number_Ptr expression = dynamic_cast<Number_Ptr>(node);+  } else if (Cast<Number>(node)) {+    Number_Ptr expression = Cast<Number>(node);     std::cerr << ind << "Number " << expression;     std::cerr << " (" << pstate_source_position(node) << ")";     std::cerr << " [interpolant: " << expression->is_interpolant() << "] ";     std::cerr << " [" << expression->value() << expression->unit() << "]" <<       " [hash: " << expression->hash() << "] " <<       std::endl;-  } else if (dynamic_cast<Null_Ptr>(node)) {-    Null_Ptr expression = dynamic_cast<Null_Ptr>(node);+  } else if (Cast<Null>(node)) {+    Null_Ptr expression = Cast<Null>(node);     std::cerr << ind << "Null " << expression;     std::cerr << " (" << pstate_source_position(node) << ")";     std::cerr << " [interpolant: " << expression->is_interpolant() << "] "       // " [hash: " << expression->hash() << "] "       << std::endl;-  } else if (dynamic_cast<String_Quoted_Ptr>(node)) {-    String_Quoted_Ptr expression = dynamic_cast<String_Quoted_Ptr>(node);+  } else if (Cast<String_Quoted>(node)) {+    String_Quoted_Ptr expression = Cast<String_Quoted>(node);     std::cerr << ind << "String_Quoted " << expression;     std::cerr << " (" << pstate_source_position(node) << ")";     std::cerr << " [" << prettyprint(expression->value()) << "]";@@ -645,8 +647,8 @@     if (expression->is_interpolant()) std::cerr << " [interpolant]";     if (expression->quote_mark()) std::cerr << " [quote_mark: " << expression->quote_mark() << "]";     std::cerr << " <" << prettyprint(expression->pstate().token.ws_before()) << ">" << std::endl;-  } else if (dynamic_cast<String_Constant_Ptr>(node)) {-    String_Constant_Ptr expression = dynamic_cast<String_Constant_Ptr>(node);+  } else if (Cast<String_Constant>(node)) {+    String_Constant_Ptr expression = Cast<String_Constant>(node);     std::cerr << ind << "String_Constant " << expression;     if (expression->concrete_type()) {       std::cerr << " " << expression->concrete_type();@@ -656,28 +658,29 @@     if (expression->is_delayed()) std::cerr << " [delayed]";     if (expression->is_interpolant()) std::cerr << " [interpolant]";     std::cerr << " <" << prettyprint(expression->pstate().token.ws_before()) << ">" << std::endl;-  } else if (dynamic_cast<String_Schema_Ptr>(node)) {-    String_Schema_Ptr expression = dynamic_cast<String_Schema_Ptr>(node);+  } else if (Cast<String_Schema>(node)) {+    String_Schema_Ptr expression = Cast<String_Schema>(node);     std::cerr << ind << "String_Schema " << expression;     std::cerr << " (" << pstate_source_position(expression) << ")";     std::cerr << " " << expression->concrete_type();     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->pstate().token.ws_before()) << ">" << std::endl;-    for(const auto& i : expression->elements()) { debug_ast(&i, ind + " ", env); }-  } else if (dynamic_cast<String_Ptr>(node)) {-    String_Ptr expression = dynamic_cast<String_Ptr>(node);+    for(const auto& i : expression->elements()) { debug_ast(i, ind + " ", env); }+  } else if (Cast<String>(node)) {+    String_Ptr expression = Cast<String>(node);     std::cerr << ind << "String " << expression;     std::cerr << " " << expression->concrete_type();     std::cerr << " (" << pstate_source_position(node) << ")";     if (expression->is_interpolant()) std::cerr << " [interpolant]";     std::cerr << " <" << prettyprint(expression->pstate().token.ws_before()) << ">" << std::endl;-  } else if (dynamic_cast<Expression_Ptr>(node)) {-    Expression_Ptr expression = dynamic_cast<Expression_Ptr>(node);+  } else if (Cast<Expression>(node)) {+    Expression_Ptr expression = Cast<Expression>(node);     std::cerr << ind << "Expression " << expression;     std::cerr << " (" << pstate_source_position(node) << ")";     switch (expression->concrete_type()) {@@ -694,16 +697,18 @@       case Expression::Concrete_Type::C_ERROR: std::cerr << " [C_ERROR]"; break;       case Expression::Concrete_Type::FUNCTION: std::cerr << " [FUNCTION]"; break;       case Expression::Concrete_Type::NUM_TYPES: std::cerr << " [NUM_TYPES]"; break;+      case Expression::Concrete_Type::VARIABLE: std::cerr << " [VARIABLE]"; break;+      case Expression::Concrete_Type::FUNCTION_VAL: std::cerr << " [FUNCTION_VAL]"; break;     }     std::cerr << std::endl;-  } else if (dynamic_cast<Has_Block_Ptr>(node)) {-    Has_Block_Ptr has_block = dynamic_cast<Has_Block_Ptr>(node);+  } else if (Cast<Has_Block>(node)) {+    Has_Block_Ptr has_block = Cast<Has_Block>(node);     std::cerr << ind << "Has_Block " << has_block;     std::cerr << " (" << pstate_source_position(node) << ")";     std::cerr << " " << has_block->tabs() << std::endl;-    if (has_block->block()) for(const Statement_Obj& i : has_block->block()->elements()) { debug_ast(&i, ind + " ", env); }-  } else if (dynamic_cast<Statement_Ptr>(node)) {-    Statement_Ptr statement = dynamic_cast<Statement_Ptr>(node);+    if (has_block->block()) for(const Statement_Obj& i : has_block->block()->elements()) { debug_ast(i, ind + " ", env); }+  } else if (Cast<Statement>(node)) {+    Statement_Ptr statement = Cast<Statement>(node);     std::cerr << ind << "Statement " << statement;     std::cerr << " (" << pstate_source_position(node) << ")";     std::cerr << " " << statement->tabs() << std::endl;@@ -735,7 +740,7 @@     std::cerr << node << " ";     if (node->got_line_feed) std::cerr << "[LF] ";     std::cerr << std::endl;-    debug_ast(&node->selector(), ind + "  ");+    debug_ast(node->selector(), ind + "  ");   } else if (node->isCollection()) {     std::cerr << ind;     std::cerr << "Collection ";@@ -776,21 +781,18 @@ {   if (ind == "") std::cerr << "#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n";   for(auto const &it : map.values()) {-    debug_ast(&it.first, ind + "first: ");-    debug_ast(&it.second, ind + "second: ");+    debug_ast(it.first, ind + "first: ");+    debug_ast(it.second, ind + "second: ");   }   if (ind == "") std::cerr << "#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; } -typedef std::pair<Complex_Selector_Obj, Compound_Selector_Obj> ExtensionPair;-typedef std::vector<ExtensionPair> SubsetMapEntries;--inline void debug_subset_entries(SubsetMapEntries* entries, std::string ind = "")+inline void debug_subset_entries(SubSetMapPairs* entries, std::string ind = "") {   if (ind == "") std::cerr << "#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n";   for(auto const &pair : *entries) {-    debug_ast(&pair.first, ind + "first: ");-    debug_ast(&pair.second, ind + "second: ");+    debug_ast(pair.first, ind + "first: ");+    debug_ast(pair.second, ind + "second: ");   }   if (ind == "") std::cerr << "#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; }
libsass/src/emitter.cpp view
@@ -14,7 +14,9 @@     scheduled_space(0),     scheduled_linefeed(0),     scheduled_delimiter(false),+    scheduled_crutch(0),     scheduled_mapping(0),+    in_custom_property(false),     in_comment(false),     in_wrapped(false),     in_media_block(false),@@ -101,10 +103,30 @@   // prepend some text or token to the buffer   void Emitter::prepend_string(const std::string& text)   {-    wbuf.smap.prepend(Offset(text));+    // 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));+    }     wbuf.buffer = text + wbuf.buffer;   } +  char Emitter::last_char()+  {+    return wbuf.buffer.back();+  }++  // append a single char to the buffer+  void Emitter::append_char(const char chr)+  {+    // write space/lf+    flush_schedules();+    // add to buffer+    wbuf.buffer += chr;+    // account for data in source-maps+    wbuf.smap.append(Offset(chr));+  }+   // append some text or token to the buffer   void Emitter::append_string(const std::string& text)   {@@ -145,9 +167,9 @@     add_open_mapping(node);     // hotfix for browser issues     // this is pretty ugly indeed-    if (scheduled_mapping) {-      add_open_mapping(scheduled_mapping);-      scheduled_mapping = 0;+    if (scheduled_crutch) {+      add_open_mapping(scheduled_crutch);+      scheduled_crutch = 0;     }     append_string(text);     add_close_mapping(node);@@ -193,7 +215,7 @@   {     scheduled_space = 0;     append_string(":");-    append_optional_space();+    if (!in_custom_property) append_optional_space();   }    void Emitter::append_mandatory_space()@@ -206,7 +228,9 @@     if ((output_style() != COMPRESSED) && buffer().size()) {       unsigned char lst = buffer().at(buffer().length() - 1);       if (!isspace(lst) || scheduled_delimiter) {-        append_mandatory_space();+        if (last_char() != '(') {+          append_mandatory_space();+        }       }     }   }
libsass/src/emitter.hpp view
@@ -37,9 +37,12 @@       size_t scheduled_space;       size_t scheduled_linefeed;       bool scheduled_delimiter;+      AST_Node_Ptr scheduled_crutch;       AST_Node_Ptr 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@@ -66,11 +69,15 @@       void prepend_output(const OutputBuffer& out);       // append some text or token to the buffer       void append_string(const std::string& text);+      // append a single character to buffer+      void append_char(const char chr);       // append some white-space only text       void append_wspace(const std::string& text);       // append some text or token to the buffer       // this adds source-mappings for node start and end       void append_token(const std::string& text, const AST_Node_Ptr node);+      // query last appended character+      char last_char();      public: // syntax sugar       void append_indentation();
libsass/src/environment.cpp view
@@ -6,17 +6,17 @@    template <typename T>   Environment<T>::Environment(bool is_shadow)-  : local_frame_(std::map<std::string, T>()),+  : local_frame_(environment_map<std::string, T>()),     parent_(0), is_shadow_(false)   { }   template <typename T>   Environment<T>::Environment(Environment<T>* env, bool is_shadow)-  : local_frame_(std::map<std::string, T>()),+  : local_frame_(environment_map<std::string, T>()),     parent_(env), is_shadow_(is_shadow)   { }   template <typename T>   Environment<T>::Environment(Environment<T>& env, bool is_shadow)-  : local_frame_(std::map<std::string, T>()),+  : local_frame_(environment_map<std::string, T>()),     parent_(&env), is_shadow_(is_shadow)   { } @@ -45,7 +45,7 @@   }    template <typename T>-  std::map<std::string, T>& Environment<T>::local_frame() {+  environment_map<std::string, T>& Environment<T>::local_frame() {     return local_frame_;   } @@ -53,15 +53,28 @@   bool Environment<T>::has_local(const std::string& key) const   { return local_frame_.find(key) != local_frame_.end(); } +  template <typename T> EnvResult+  Environment<T>::find_local(const std::string& key)+  {+    auto end = local_frame_.end();+    auto it = local_frame_.find(key);+    return EnvResult(it, it != end);+  }+   template <typename T>   T& Environment<T>::get_local(const std::string& key)   { return local_frame_[key]; }    template <typename T>-  void Environment<T>::set_local(const std::string& key, T val)+  void Environment<T>::set_local(const std::string& key, const T& val)   {     local_frame_[key] = val;   }+  template <typename T>+  void Environment<T>::set_local(const std::string& key, T&& val)+  {+    local_frame_[key] = val;+  }    template <typename T>   void Environment<T>::del_local(const std::string& key)@@ -86,10 +99,15 @@   { return (*global_env())[key]; }    template <typename T>-  void Environment<T>::set_global(const std::string& key, T val)+  void Environment<T>::set_global(const std::string& key, const T& val)   {     global_env()->local_frame_[key] = val;   }+  template <typename T>+  void Environment<T>::set_global(const std::string& key, T&& val)+  {+    global_env()->local_frame_[key] = val;+  }    template <typename T>   void Environment<T>::del_global(const std::string& key)@@ -126,12 +144,14 @@   // either update already existing lexical value   // or if flag is set, we create one if no lexical found   template <typename T>-  void Environment<T>::set_lexical(const std::string& key, T val)+  void Environment<T>::set_lexical(const std::string& key, const T& val)   {-    auto cur = this; bool shadow = false;-    while (cur->is_lexical() || shadow) {-      if (cur->has_local(key)) {-        cur->set_local(key, val);+    Environment<T>* 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();@@ -139,6 +159,23 @@     }     set_local(key, val);   }+  // this one moves the value+  template <typename T>+  void Environment<T>::set_lexical(const std::string& key, T&& val)+  {+    Environment<T>* cur = this;+    bool shadow = false;+    while ((cur && cur->is_lexical()) || shadow) {+      EnvResult rv(cur->find_local(key));+      if (rv.found) {+        rv.it->second = val;+        return;+      }+      shadow = cur->is_shadow();+      cur = cur->parent_;+    }+    set_local(key, val);+  }    // look on the full stack for key   // include all scopes available@@ -155,6 +192,20 @@     return false;   } +  // look on the full stack for key+  // include all scopes available+  template <typename T> EnvResult+  Environment<T>::find(const std::string& key)+  {+    auto cur = this;+    while (true) {+      EnvResult rv(cur->find_local(key));+      if (rv.found) return rv;+      cur = cur->parent_;+      if (!cur) return rv;+    }+  };+   // use array access for getter and setter functions   template <typename T>   T& Environment<T>::operator[](const std::string& key)@@ -168,7 +219,7 @@     }     return get_local(key);   }-+/*   #ifdef DEBUG   template <typename T>   size_t Environment<T>::print(std::string prefix)@@ -176,10 +227,10 @@     size_t indent = 0;     if (parent_) indent = parent_->print(prefix) + 1;     std::cerr << prefix << std::string(indent, ' ') << "== " << this << std::endl;-    for (typename std::map<std::string, T>::iterator i = local_frame_.begin(); i != local_frame_.end(); ++i) {+    for (typename environment_map<std::string, T>::iterator i = local_frame_.begin(); i != local_frame_.end(); ++i) {       if (!ends_with(i->first, "[f]") && !ends_with(i->first, "[f]4") && !ends_with(i->first, "[f]2")) {-        std::cerr << prefix << std::string(indent, ' ') << i->first << " "  << i->second;-        if (Value_Ptr val = SASS_MEMORY_CAST_PTR(Value, i->second))+        std::cerr << prefix << std::string(indent, ' ') << i->first << " " << i->second;+        if (Value_Ptr val = Cast<Value>(i->second))         { std::cerr << " : " << val->to_string(); }         std::cerr << std::endl;       }@@ -187,7 +238,7 @@     return indent ;   }   #endif-+*/   // compile implementation for AST_Node   template class Environment<AST_Node_Obj>; 
libsass/src/environment.hpp view
@@ -2,17 +2,26 @@ #define SASS_ENVIRONMENT_H  #include <string>-#include <map>- #include "ast_fwd_decl.hpp" #include "ast_def_macros.hpp"  namespace Sass { +  typedef environment_map<std::string, AST_Node_Obj>::iterator EnvIter;++  class EnvResult {+    public:+      EnvIter it;+      bool found;+    public:+      EnvResult(EnvIter it, bool found)+      : it(it), found(found) {}+  };+   template <typename T>   class Environment {     // TODO: test with map-    std::map<std::string, T> local_frame_;+    environment_map<std::string, T> local_frame_;     ADD_PROPERTY(Environment*, parent)     ADD_PROPERTY(bool, is_shadow) @@ -37,14 +46,17 @@      // scope operates on the current frame -    std::map<std::string, T>& local_frame();+    environment_map<std::string, T>& local_frame();      bool has_local(const std::string& key) const; +    EnvResult find_local(const std::string& key);+     T& get_local(const std::string& key);      // set variable on the current frame-    void set_local(const std::string& key, T val);+    void set_local(const std::string& key, const T& val);+    void set_local(const std::string& key, T&& val);      void del_local(const std::string& key); @@ -60,7 +72,8 @@     T& get_global(const std::string& key);      // set a variable on the global frame-    void set_global(const std::string& key, T val);+    void set_global(const std::string& key, const T& val);+    void set_global(const std::string& key, T&& val);      void del_global(const std::string& key); @@ -72,12 +85,17 @@     // 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 std::string& key, T val);+    void set_lexical(const std::string& key, T&& val);+    void set_lexical(const std::string& key, const T& val);      // look on the full stack for key     // include all scopes available     bool has(const std::string& key) const; +    // look on the full stack for key+    // include all scopes available+    EnvResult find(const std::string& key);+     // use array access for getter and setter functions     T& operator[](const std::string& key); @@ -86,6 +104,9 @@     #endif    };++  // define typedef for our use case+  typedef Environment<AST_Node_Obj> Env;  } 
libsass/src/error_handling.cpp view
@@ -10,19 +10,18 @@    namespace Exception { -    Base::Base(ParserState pstate, std::string msg, std::vector<Sass_Import_Entry>* import_stack)+    Base::Base(ParserState pstate, std::string msg, Backtraces traces)     : std::runtime_error(msg), msg(msg),-      prefix("Error"), pstate(pstate),-      import_stack(import_stack)+      prefix("Error"), pstate(pstate), traces(traces)     { } -    InvalidSass::InvalidSass(ParserState pstate, std::string msg)-    : Base(pstate, msg)+    InvalidSass::InvalidSass(ParserState pstate, Backtraces traces, std::string msg)+    : Base(pstate, msg, traces)     { }  -    InvalidParent::InvalidParent(Selector_Ptr parent, Selector_Ptr selector)-    : Base(selector->pstate()), parent(parent), selector(selector)+    InvalidParent::InvalidParent(Selector_Ptr parent, Backtraces traces, Selector_Ptr selector)+    : Base(selector->pstate(), def_msg, traces), parent(parent), selector(selector)     {       msg = "Invalid parent selector for \"";       msg += selector->to_string(Sass_Inspect_Options());@@ -31,55 +30,40 @@       msg += "\"";     } -    InvalidArgumentType::InvalidArgumentType(ParserState pstate, std::string fn, std::string arg, std::string type, const Value_Ptr value)-    : Base(pstate), fn(fn), arg(arg), type(type), value(value)+    InvalidVarKwdType::InvalidVarKwdType(ParserState pstate, Backtraces traces, std::string name, const Argument_Ptr arg)+    : Base(pstate, def_msg, traces), name(name), arg(arg)     {+      msg = "Variable keyword argument map must have string keys.\n";+      msg += name + " is not a string in " + arg->to_string() + ".";+    }++    InvalidArgumentType::InvalidArgumentType(ParserState pstate, Backtraces traces, std::string fn, std::string arg, std::string type, const Value_Ptr 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;       msg += " for `" + fn + "'";     } -    MissingArgument::MissingArgument(ParserState pstate, std::string fn, std::string arg, std::string fntype)-    : Base(pstate), fn(fn), arg(arg), fntype(fntype)+    MissingArgument::MissingArgument(ParserState pstate, Backtraces traces, std::string fn, std::string arg, std::string fntype)+    : Base(pstate, def_msg, traces), fn(fn), arg(arg), fntype(fntype)     {       msg  = fntype + " " + fn;       msg += " is missing argument ";       msg += arg + ".";     } -    InvalidSyntax::InvalidSyntax(ParserState pstate, std::string msg, std::vector<Sass_Import_Entry>* import_stack)-    : Base(pstate, msg, import_stack)+    InvalidSyntax::InvalidSyntax(ParserState pstate, Backtraces traces, std::string msg)+    : Base(pstate, msg, traces)     { } -    UndefinedOperation::UndefinedOperation(Expression_Ptr_Const lhs, Expression_Ptr_Const rhs, const std::string& op)-    : lhs(lhs), rhs(rhs), op(op)-    {-      msg  = def_op_msg + ": \"";-      msg += lhs->to_string({ NESTED, 5 });-      msg += " " + op + " ";-      msg += rhs->to_string({ TO_SASS, 5 });-      msg += "\".";-    }--    InvalidNullOperation::InvalidNullOperation(Expression_Ptr_Const lhs, Expression_Ptr_Const rhs, const std::string& op)-    : UndefinedOperation(lhs, rhs, op)-    {-      msg  = def_op_null_msg + ": \"";-      msg += lhs->inspect();-      msg += " " + op + " ";-      msg += rhs->inspect();-      msg += "\".";-    }--    ZeroDivisionError::ZeroDivisionError(const Expression& lhs, const Expression& rhs)-    : lhs(lhs), rhs(rhs)-    {-      msg  = "divided by 0";-    }+    NestingLimitError::NestingLimitError(ParserState pstate, Backtraces traces, std::string msg)+    : Base(pstate, msg, traces)+    { } -    DuplicateKeyError::DuplicateKeyError(const Map& dup, const Expression& org)-    : Base(org.pstate()), dup(dup), org(org)+    DuplicateKeyError::DuplicateKeyError(Backtraces traces, const Map& dup, const Expression& org)+    : Base(org.pstate(), def_msg, traces), dup(dup), org(org)     {       msg  = "Duplicate key ";       msg += dup.get_duplicate_key()->inspect();@@ -88,8 +72,8 @@       msg += ").";     } -    TypeMismatch::TypeMismatch(const Expression& var, const std::string type)-    : Base(var.pstate()), var(var), type(type)+    TypeMismatch::TypeMismatch(Backtraces traces, const Expression& var, const std::string type)+    : Base(var.pstate(), def_msg, traces), var(var), type(type)     {       msg  = var.to_string();       msg += " is not an ";@@ -97,21 +81,20 @@       msg += ".";     } -    InvalidValue::InvalidValue(const Expression& val)-    : Base(val.pstate()), val(val)+    InvalidValue::InvalidValue(Backtraces traces, const Expression& val)+    : Base(val.pstate(), def_msg, traces), val(val)     {       msg  = val.to_string();       msg += " isn't a valid CSS value.";     } -    StackError::StackError(const AST_Node& node)-    : Base(node.pstate()), node(node)+    StackError::StackError(Backtraces traces, const AST_Node& node)+    : Base(node.pstate(), def_msg, traces), node(node)     {       msg  = "stack level too deep";     } -    IncompatibleUnits::IncompatibleUnits(const Number& lhs, const Number& rhs)-    : lhs(lhs), rhs(rhs)+    IncompatibleUnits::IncompatibleUnits(const Units& lhs, const Units& rhs)     {       msg  = "Incompatible units: '";       msg += rhs.unit();@@ -120,20 +103,54 @@       msg += "'.";     } -    AlphaChannelsNotEqual::AlphaChannelsNotEqual(Expression_Ptr_Const lhs, Expression_Ptr_Const rhs, const std::string& op)-    : lhs(lhs), rhs(rhs), op(op)+    IncompatibleUnits::IncompatibleUnits(const UnitType lhs, const UnitType rhs)     {+      msg  = "Incompatible units: '";+      msg += unit_to_string(rhs);+      msg += "' and '";+      msg += unit_to_string(lhs);+      msg += "'.";+    }++    AlphaChannelsNotEqual::AlphaChannelsNotEqual(Expression_Ptr_Const lhs, Expression_Ptr_Const rhs, enum Sass_OP op)+    : OperationError(), lhs(lhs), rhs(rhs), op(op)+    {       msg  = "Alpha channels must be equal: ";       msg += lhs->to_string({ NESTED, 5 });-      msg += " " + op + " ";+      msg += " " + sass_op_to_name(op) + " ";       msg += rhs->to_string({ NESTED, 5 });       msg += ".";     } +    ZeroDivisionError::ZeroDivisionError(const Expression& lhs, const Expression& rhs)+    : OperationError(), lhs(lhs), rhs(rhs)+    {+      msg  = "divided by 0";+    } -    SassValueError::SassValueError(ParserState pstate, OperationError& err)-    : Base(pstate, err.what())+    UndefinedOperation::UndefinedOperation(Expression_Ptr_Const lhs, Expression_Ptr_Const rhs, enum Sass_OP op)+    : OperationError(), lhs(lhs), rhs(rhs), op(op)     {+      msg  = def_op_msg + ": \"";+      msg += lhs->to_string({ NESTED, 5 });+      msg += " " + sass_op_to_name(op) + " ";+      msg += rhs->to_string({ TO_SASS, 5 });+      msg += "\".";+    }++    InvalidNullOperation::InvalidNullOperation(Expression_Ptr_Const lhs, Expression_Ptr_Const rhs, enum Sass_OP op)+    : UndefinedOperation(lhs, rhs, op)+    {+      msg  = def_op_null_msg + ": \"";+      msg += lhs->inspect();+      msg += " " + sass_op_to_name(op) + " ";+      msg += rhs->inspect();+      msg += "\".";+    }++    SassValueError::SassValueError(Backtraces traces, ParserState pstate, OperationError& err)+    : Base(pstate, err.what(), traces)+    {       msg = err.what();       prefix = err.errtype();     }@@ -143,13 +160,22 @@    void warn(std::string msg, ParserState pstate)   {-    std::cerr << "Warning: " << msg<< std::endl;+    std::cerr << "Warning: " << msg << std::endl;   } +  void warning(std::string msg, ParserState pstate)+  {+    std::string cwd(Sass::File::get_cwd());+    std::string abs_path(Sass::File::rel2abs(pstate.path, cwd, cwd));+    std::string rel_path(Sass::File::abs2rel(pstate.path, cwd, cwd));+    std::string output_path(Sass::File::path_for_console(rel_path, abs_path, pstate.path));++    std::cerr << "WARNING on line " << pstate.line+1 << ", column " << pstate.column+1 << " of " << output_path << ":" << std::endl;+    std::cerr << msg << std::endl << std::endl;+  }+   void warn(std::string msg, ParserState pstate, Backtrace* bt)   {-    Backtrace top(bt, pstate, "");-    msg += top.to_string();     warn(msg, pstate);   } @@ -165,7 +191,7 @@     std::cerr << "        on line " << pstate.line+1 << " of " << output_path << std::endl;   } -  void deprecated(std::string msg, std::string msg2, ParserState pstate)+  void deprecated(std::string msg, std::string msg2, bool with_column, ParserState pstate)   {     std::string cwd(Sass::File::get_cwd());     std::string abs_path(Sass::File::rel2abs(pstate.path, cwd, cwd));@@ -173,9 +199,10 @@     std::string output_path(Sass::File::path_for_console(rel_path, pstate.path, pstate.path));      std::cerr << "DEPRECATION WARNING on line " << pstate.line + 1;+    if (with_column) std::cerr << ", column " << pstate.column + pstate.offset.column + 1;     if (output_path.length()) std::cerr << " of " << output_path;     std::cerr << ":" << std::endl;-    std::cerr << msg << " and will be an error in future versions of Sass." << std::endl;+    std::cerr << msg << std::endl;     if (msg2.length()) std::cerr << msg2 << std::endl;     std::cerr << std::endl;   }@@ -192,16 +219,17 @@     std::cerr << "This will be an error in future versions of Sass." << std::endl;   } -  void error(std::string msg, ParserState pstate)+  // should be replaced with error with backtraces+  void coreError(std::string msg, ParserState pstate)   {-    throw Exception::InvalidSyntax(pstate, msg);+    Backtraces traces;+    throw Exception::InvalidSyntax(pstate, traces, msg);   } -  void error(std::string msg, ParserState pstate, Backtrace* bt)+  void error(std::string msg, ParserState pstate, Backtraces& traces)   {-    Backtrace top(bt, pstate, "");-    msg += "\n" + top.to_string();-    error(msg, pstate);+    traces.push_back(Backtrace(pstate));+    throw Exception::InvalidSyntax(pstate, traces, msg);   }  }
libsass/src/error_handling.hpp view
@@ -5,6 +5,9 @@ #include <sstream> #include <stdexcept> #include "position.hpp"+#include "backtrace.hpp"+#include "ast_fwd_decl.hpp"+#include "sass/functions.h"  namespace Sass { @@ -15,6 +18,7 @@     const std::string def_msg = "Invalid sass detected";     const std::string def_op_msg = "Undefined operation";     const std::string def_op_null_msg = "Invalid null operation";+    const std::string def_nesting_limit = "Code too deeply neested";      class Base : public std::runtime_error {       protected:@@ -22,9 +26,9 @@         std::string prefix;       public:         ParserState pstate;-        std::vector<Sass_Import_Entry>* import_stack;+        Backtraces traces;       public:-        Base(ParserState pstate, std::string msg = def_msg, std::vector<Sass_Import_Entry>* import_stack = 0);+        Base(ParserState pstate, std::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() {};@@ -32,7 +36,7 @@      class InvalidSass : public Base {       public:-        InvalidSass(ParserState pstate, std::string msg);+        InvalidSass(ParserState pstate, Backtraces traces, std::string msg);         virtual ~InvalidSass() throw() {};     }; @@ -41,7 +45,7 @@         Selector_Ptr parent;         Selector_Ptr selector;       public:-        InvalidParent(Selector_Ptr parent, Selector_Ptr selector);+        InvalidParent(Selector_Ptr parent, Backtraces traces, Selector_Ptr selector);         virtual ~InvalidParent() throw() {};     }; @@ -51,7 +55,7 @@         std::string arg;         std::string fntype;       public:-        MissingArgument(ParserState pstate, std::string fn, std::string arg, std::string fntype);+        MissingArgument(ParserState pstate, Backtraces traces, std::string fn, std::string arg, std::string fntype);         virtual ~MissingArgument() throw() {};     }; @@ -62,38 +66,29 @@         std::string type;         const Value_Ptr value;       public:-        InvalidArgumentType(ParserState pstate, std::string fn, std::string arg, std::string type, const Value_Ptr value = 0);+        InvalidArgumentType(ParserState pstate, Backtraces traces, std::string fn, std::string arg, std::string type, const Value_Ptr value = 0);         virtual ~InvalidArgumentType() throw() {};     }; -    class InvalidSyntax : public Base {+    class InvalidVarKwdType : public Base {+      protected:+        std::string name;+        const Argument_Ptr arg;       public:-        InvalidSyntax(ParserState pstate, std::string msg, std::vector<Sass_Import_Entry>* import_stack = 0);-        virtual ~InvalidSyntax() throw() {};+        InvalidVarKwdType(ParserState pstate, Backtraces traces, std::string name, const Argument_Ptr arg = 0);+        virtual ~InvalidVarKwdType() throw() {};     }; -    /* common virtual base class (has no pstate) */-    class OperationError : public std::runtime_error {-      protected:-        std::string msg;-      public:-        OperationError(std::string msg = def_op_msg)-        : std::runtime_error(msg), msg(msg)-        {};+    class InvalidSyntax : public Base {       public:-        virtual const char* errtype() const { return "Error"; }-        virtual const char* what() const throw() { return msg.c_str(); }-        virtual ~OperationError() throw() {};+        InvalidSyntax(ParserState pstate, Backtraces traces, std::string msg);+        virtual ~InvalidSyntax() throw() {};     }; -    class ZeroDivisionError : public OperationError {-      protected:-        const Expression& lhs;-        const Expression& rhs;+    class NestingLimitError : public Base {       public:-        ZeroDivisionError(const Expression& lhs, const Expression& rhs);-        virtual const char* errtype() const { return "ZeroDivisionError"; }-        virtual ~ZeroDivisionError() throw() {};+        NestingLimitError(ParserState pstate, Backtraces traces, std::string msg = def_nesting_limit);+        virtual ~NestingLimitError() throw() {};     };      class DuplicateKeyError : public Base {@@ -101,7 +96,7 @@         const Map& dup;         const Expression& org;       public:-        DuplicateKeyError(const Map& dup, const Expression& org);+        DuplicateKeyError(Backtraces traces, const Map& dup, const Expression& org);         virtual const char* errtype() const { return "Error"; }         virtual ~DuplicateKeyError() throw() {};     };@@ -111,7 +106,7 @@         const Expression& var;         const std::string type;       public:-        TypeMismatch(const Expression& var, const std::string type);+        TypeMismatch(Backtraces traces, const Expression& var, const std::string type);         virtual const char* errtype() const { return "Error"; }         virtual ~TypeMismatch() throw() {};     };@@ -120,7 +115,7 @@       protected:         const Expression& val;       public:-        InvalidValue(const Expression& val);+        InvalidValue(Backtraces traces, const Expression& val);         virtual const char* errtype() const { return "Error"; }         virtual ~InvalidValue() throw() {};     };@@ -129,17 +124,42 @@       protected:         const AST_Node& node;       public:-        StackError(const AST_Node& node);+        StackError(Backtraces traces, const AST_Node& node);         virtual const char* errtype() const { return "SystemStackError"; }         virtual ~StackError() throw() {};     }; +    /* common virtual base class (has no pstate or trace) */+    class OperationError : public std::runtime_error {+      protected:+        std::string msg;+      public:+        OperationError(std::string msg = def_op_msg)+        : std::runtime_error(msg), 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 Number& lhs;-        const Number& rhs;+        // const Sass::UnitType lhs;+        // const Sass::UnitType rhs;       public:-        IncompatibleUnits(const Number& lhs, const Number& rhs);+        IncompatibleUnits(const Units& lhs, const Units& rhs);+        IncompatibleUnits(const UnitType lhs, const UnitType rhs);         virtual ~IncompatibleUnits() throw() {};     }; @@ -147,16 +167,16 @@       protected:         Expression_Ptr_Const lhs;         Expression_Ptr_Const rhs;-        const std::string op;+        const Sass_OP op;       public:-        UndefinedOperation(Expression_Ptr_Const lhs, Expression_Ptr_Const rhs, const std::string& op);+        UndefinedOperation(Expression_Ptr_Const lhs, Expression_Ptr_Const rhs, enum Sass_OP op);         // virtual const char* errtype() const { return "Error"; }         virtual ~UndefinedOperation() throw() {};     };      class InvalidNullOperation : public UndefinedOperation {       public:-        InvalidNullOperation(Expression_Ptr_Const lhs, Expression_Ptr_Const rhs, const std::string& op);+        InvalidNullOperation(Expression_Ptr_Const lhs, Expression_Ptr_Const rhs, enum Sass_OP op);         virtual ~InvalidNullOperation() throw() {};     }; @@ -164,16 +184,16 @@       protected:         Expression_Ptr_Const lhs;         Expression_Ptr_Const rhs;-        const std::string op;+        const Sass_OP op;       public:-        AlphaChannelsNotEqual(Expression_Ptr_Const lhs, Expression_Ptr_Const rhs, const std::string& op);+        AlphaChannelsNotEqual(Expression_Ptr_Const lhs, Expression_Ptr_Const rhs, enum Sass_OP op);         // virtual const char* errtype() const { return "Error"; }         virtual ~AlphaChannelsNotEqual() throw() {};     };      class SassValueError : public Base {       public:-        SassValueError(ParserState pstate, OperationError& err);+        SassValueError(Backtraces traces, ParserState pstate, OperationError& err);         virtual ~SassValueError() throw() {};     }; @@ -181,14 +201,15 @@    void warn(std::string msg, ParserState pstate);   void warn(std::string msg, ParserState pstate, Backtrace* bt);+  void warning(std::string msg, ParserState pstate);    void deprecated_function(std::string msg, ParserState pstate);-  void deprecated(std::string msg, std::string msg2, ParserState pstate);+  void deprecated(std::string msg, std::string msg2, bool with_column, ParserState pstate);   void deprecated_bind(std::string msg, ParserState pstate);   // void deprecated(std::string msg, ParserState pstate, Backtrace* bt); -  void error(std::string msg, ParserState pstate);-  void error(std::string msg, ParserState pstate, Backtrace* bt);+  void coreError(std::string msg, ParserState pstate);+  void error(std::string msg, ParserState pstate, Backtraces& traces);  } 
libsass/src/eval.cpp view
@@ -12,1702 +12,1667 @@ #include "bind.hpp" #include "util.hpp" #include "inspect.hpp"-#include "environment.hpp"-#include "position.hpp"-#include "sass/values.h"-#include "to_value.hpp"-#include "to_c.hpp"-#include "context.hpp"-#include "backtrace.hpp"-#include "lexer.hpp"-#include "prelexer.hpp"-#include "parser.hpp"-#include "expand.hpp"-#include "color_maps.hpp"--namespace Sass {--  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-  };--  Eval::Eval(Expand& exp)-  : exp(exp),-    ctx(exp.ctx),-    force(false),-    is_in_comment(false)-  { }-  Eval::~Eval() { }--  Env* Eval::environment()-  {-    return exp.environment();-  }--  Selector_List_Obj Eval::selector()-  {-    return exp.selector();-  }--  Backtrace* Eval::backtrace()-  {-    return exp.backtrace();-  }--  Expression_Ptr Eval::operator()(Block_Ptr b)-  {-    Expression_Ptr 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;-  }--  Expression_Ptr Eval::operator()(Assignment_Ptr a)-  {-    Env* env = exp.environment();-    std::string var(a->variable());-    if (a->is_global()) {-      if (a->is_default()) {-        if (env->has_global(var)) {-          Expression_Ptr e = SASS_MEMORY_CAST(Expression, 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));-        }-      }-      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_Ptr e = SASS_MEMORY_CAST(Expression, 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");-      }-      else if (env->has_global(var)) {-        if (AST_Node_Obj node = env->get_global(var)) {-          Expression_Ptr e = SASS_MEMORY_CAST(Expression, node);-          if (!e || e->concrete_type() == Expression::NULL_VAL) {-            env->set_global(var, a->value()->perform(this));-          }-        }-      }-      else if (env->is_lexical()) {-        env->set_local(var, a->value()->perform(this));-      }-      else {-        env->set_local(var, a->value()->perform(this));-      }-    }-    else {-      env->set_lexical(var, a->value()->perform(this));-    }-    return 0;-  }--  Expression_Ptr Eval::operator()(If_Ptr i)-  {-    Expression_Obj rv = 0;-    Env env(exp.environment());-    exp.env_stack.push_back(&env);-    Expression_Obj cond = i->predicate()->perform(this);-    if (!cond->is_false()) {-      rv = i->block()->perform(this);-    }-    else {-      Block_Obj alt = i->alternative();-      if (alt) rv = alt->perform(this);-    }-    exp.env_stack.pop_back();-    return rv.detach();-  }--  // For does not create a new env scope-  // But iteration vars are reset afterwards-  Expression_Ptr Eval::operator()(For_Ptr f)-  {-    std::string variable(f->variable());-    Expression_Obj low = f->lower_bound()->perform(this);-    if (low->concrete_type() != Expression::NUMBER) {-      throw Exception::TypeMismatch(*low, "integer");-    }-    Expression_Obj high = f->upper_bound()->perform(this);-    if (high->concrete_type() != Expression::NUMBER) {-      throw Exception::TypeMismatch(*high, "integer");-    }-    Number_Obj sass_start = SASS_MEMORY_CAST(Number, low);-    Number_Obj sass_end = SASS_MEMORY_CAST(Number, high);-    // check if units are valid for sequence-    if (sass_start->unit() != sass_end->unit()) {-      std::stringstream msg; msg << "Incompatible units: '"-        << sass_end->unit() << "' and '"-        << sass_start->unit() << "'.";-      error(msg.str(), low->pstate(), backtrace());-    }-    double start = sass_start->value();-    double end = sass_end->value();-    // only create iterator once in this environment-    Env env(environment(), true);-    exp.env_stack.push_back(&env);-    Number_Ptr it = SASS_MEMORY_NEW(Number, low->pstate(), start, sass_end->unit());-    env.set_local(variable, it);-    Block_Obj body = f->block();-    Expression_Ptr val = 0;-    if (start < end) {-      if (f->is_inclusive()) ++end;-      for (double i = start;-           i < end;-           ++i) {-        it->value(i);-        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) {-        it->value(i);-        env.set_local(variable, it);-        val = body->perform(this);-        if (val) break;-      }-    }-    exp.env_stack.pop_back();-    return val;-  }--  // Eval does not create a new env scope-  // But iteration vars are reset afterwards-  Expression_Ptr Eval::operator()(Each_Ptr e)-  {-    std::vector<std::string> variables(e->variables());-    Expression_Obj expr = e->list()->perform(this);-    Env env(environment(), true);-    exp.env_stack.push_back(&env);-    List_Obj list = 0;-    Map_Ptr map = 0;-    if (expr->concrete_type() == Expression::MAP) {-      map = SASS_MEMORY_CAST(Map, expr);-    }-    else if (Selector_List_Ptr ls = SASS_MEMORY_CAST(Selector_List, expr)) {-      Listize listize;-      Expression_Obj rv = ls->perform(&listize);-      list = dynamic_cast<List_Ptr>(&rv);-    }-    else if (expr->concrete_type() != Expression::LIST) {-      list = SASS_MEMORY_NEW(List, expr->pstate(), 1, SASS_COMMA);-      list->append(expr);-    }-    else {-      list = SASS_MEMORY_CAST(List, expr);-    }--    Block_Obj body = e->block();-    Expression_Obj val = 0;--    if (map) {-      for (Expression_Obj key : map->keys()) {-        Expression_Obj value = map->at(key);--        if (variables.size() == 1) {-          List_Ptr variable = SASS_MEMORY_NEW(List, map->pstate(), 2, SASS_SPACE);-          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;-      }-    }-    else {-      if (list->length() == 1 && SASS_MEMORY_CAST(Selector_List, list)) {-        list = SASS_MEMORY_CAST(List, list);-      }-      for (size_t i = 0, L = list->length(); i < L; ++i) {-        Expression_Ptr e = &list->at(i);-        // unwrap value if the expression is an argument-        if (Argument_Ptr arg = SASS_MEMORY_CAST_PTR(Argument, e)) e = &arg->value();-        // check if we got passed a list of args (investigate)-        if (List_Ptr scalars = SASS_MEMORY_CAST_PTR(List, e)) {-          if (variables.size() == 1) {-            Expression_Ptr var = scalars;-            env.set_local(variables[0], var);-          } else {-            // XXX: this is never hit via spec tests-            for (size_t j = 0, K = variables.size(); j < K; ++j) {-              Expression_Ptr res = j >= scalars->length()-                ? SASS_MEMORY_NEW(Null, expr->pstate())-                : &scalars->at(j);-              env.set_local(variables[j], res);-            }-          }-        } else {-          if (variables.size() > 0) {-            env.set_local(variables.at(0), e);-            for (size_t j = 1, K = variables.size(); j < K; ++j) {-              // XXX: this is never hit via spec tests-              Expression_Ptr res = SASS_MEMORY_NEW(Null, expr->pstate());-              env.set_local(variables[j], res);-            }-          }-        }-        val = body->perform(this);-        if (val) break;-      }-    }-    exp.env_stack.pop_back();-    return val.detach();-  }--  Expression_Ptr Eval::operator()(While_Ptr w)-  {-    Expression_Obj pred = w->predicate();-    Block_Obj body = w->block();-    Env env(environment(), true);-    exp.env_stack.push_back(&env);-    Expression_Obj cond = pred->perform(this);-    while (!cond->is_false()) {-      Expression_Obj val = body->perform(this);-      if (val) {-        exp.env_stack.pop_back();-        return val.detach();-      }-      cond = pred->perform(this);-    }-    exp.env_stack.pop_back();-    return 0;-  }--  Expression_Ptr Eval::operator()(Return_Ptr r)-  {-    return r->value()->perform(this);-  }--  Expression_Ptr Eval::operator()(Warning_Ptr w)-  {-    Sass_Output_Style outstyle = ctx.c_options.output_style;-    ctx.c_options.output_style = NESTED;-    Expression_Obj message = w->message()->perform(this);-    Env* env = exp.environment();--    // try to use generic function-    if (env->has("@warn[f]")) {--      Definition_Ptr def = SASS_MEMORY_CAST(Definition, (*env)["@warn[f]"]);-      // Block_Obj          body   = def->block();-      // Native_Function func   = def->native_function();-      Sass_Function_Entry c_function = def->c_function();-      Sass_Function_Fn c_func = sass_function_get_function(c_function);--      To_C to_c;-      union Sass_Value* c_args = sass_make_list(1, SASS_COMMA);-      sass_list_set_value(c_args, 0, message->perform(&to_c));-      union Sass_Value* c_val = c_func(c_args, c_function, ctx.c_compiler);-      ctx.c_options.output_style = outstyle;-      sass_delete_value(c_args);-      sass_delete_value(c_val);-      return 0;--    }--    std::string result(unquote(message->to_sass()));-    Backtrace top(backtrace(), w->pstate(), "");-    std::cerr << "WARNING: " << result;-    std::cerr << top.to_string();-    std::cerr << std::endl << std::endl;-    ctx.c_options.output_style = outstyle;-    return 0;-  }--  Expression_Ptr Eval::operator()(Error_Ptr e)-  {-    Sass_Output_Style outstyle = ctx.c_options.output_style;-    ctx.c_options.output_style = NESTED;-    Expression_Obj message = e->message()->perform(this);-    Env* env = exp.environment();--    // try to use generic function-    if (env->has("@error[f]")) {--      Definition_Ptr def = SASS_MEMORY_CAST(Definition, (*env)["@error[f]"]);-      // Block_Obj          body   = def->block();-      // Native_Function func   = def->native_function();-      Sass_Function_Entry c_function = def->c_function();-      Sass_Function_Fn c_func = sass_function_get_function(c_function);--      To_C to_c;-      union Sass_Value* c_args = sass_make_list(1, SASS_COMMA);-      sass_list_set_value(c_args, 0, message->perform(&to_c));-      union Sass_Value* c_val = c_func(c_args, c_function, ctx.c_compiler);-      ctx.c_options.output_style = outstyle;-      sass_delete_value(c_args);-      sass_delete_value(c_val);-      return 0;--    }--    std::string result(unquote(message->to_sass()));-    ctx.c_options.output_style = outstyle;-    error(result, e->pstate());-    return 0;-  }--  Expression_Ptr Eval::operator()(Debug_Ptr d)-  {-    Sass_Output_Style outstyle = ctx.c_options.output_style;-    ctx.c_options.output_style = NESTED;-    Expression_Obj message = d->value()->perform(this);-    Env* env = exp.environment();--    // try to use generic function-    if (env->has("@debug[f]")) {--      Definition_Ptr def = SASS_MEMORY_CAST(Definition, (*env)["@debug[f]"]);-      // Block_Obj          body   = def->block();-      // Native_Function func   = def->native_function();-      Sass_Function_Entry c_function = def->c_function();-      Sass_Function_Fn c_func = sass_function_get_function(c_function);--      To_C to_c;-      union Sass_Value* c_args = sass_make_list(1, SASS_COMMA);-      sass_list_set_value(c_args, 0, message->perform(&to_c));-      union Sass_Value* c_val = c_func(c_args, c_function, ctx.c_compiler);-      ctx.c_options.output_style = outstyle;-      sass_delete_value(c_args);-      sass_delete_value(c_val);-      return 0;--    }--    std::string cwd(ctx.cwd());-    std::string result(unquote(message->to_sass()));-    std::string abs_path(Sass::File::rel2abs(d->pstate().path, cwd, cwd));-    std::string rel_path(Sass::File::abs2rel(d->pstate().path, cwd, cwd));-    std::string output_path(Sass::File::path_for_console(rel_path, abs_path, d->pstate().path));-    ctx.c_options.output_style = outstyle;--    std::cerr << output_path << ":" << d->pstate().line+1 << " DEBUG: " << result;-    std::cerr << std::endl;-    return 0;-  }--  Expression_Ptr Eval::operator()(List_Ptr l)-  {-    // 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)-      {-        Expression_Ptr key = (*l)[i+0]->perform(this);-        Expression_Ptr 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()) {-        throw Exception::DuplicateKeyError(*lm, *l);-      }--      lm->is_interpolant(l->is_interpolant());-      return lm->perform(this);-    }-    // check if we should expand it-    if (l->is_expanded()) return l;-    // regular case for unevaluated lists-    List_Obj ll = SASS_MEMORY_NEW(List,-                               l->pstate(),-                               l->length(),-                               l->separator(),-                               l->is_arglist());-    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_Ptr Eval::operator()(Map_Ptr m)-  {-    if (m->is_expanded()) return m;--    // make sure we're not starting with duplicate keys.-    // the duplicate key state will have been set in the parser phase.-    if (m->has_duplicate_key()) {-      throw Exception::DuplicateKeyError(*m, *m);-    }--    Map_Obj mm = SASS_MEMORY_NEW(Map,-                                m->pstate(),-                                m->length());-    for (auto key : m->keys()) {-      Expression_Ptr ex_key = key->perform(this);-      Expression_Ptr ex_val = m->at(key)->perform(this);-      *mm << std::make_pair(ex_key, ex_val);-    }--    // check the evaluated keys aren't duplicates.-    if (mm->has_duplicate_key()) {-      throw Exception::DuplicateKeyError(*mm, *m);-    }--    mm->is_expanded(true);-    return mm.detach();-  }--  Expression_Ptr Eval::operator()(Binary_Expression_Ptr b_in)-  {--    String_Schema_Obj ret_schema;-    Binary_Expression_Obj b = b_in;-    enum Sass_OP op_type = b->type();--    // only the last item will be used to eval the binary expression-    if (String_Schema_Ptr s_l = SASS_MEMORY_CAST(String_Schema, b->left())) {-      if (!s_l->has_interpolant() && (!s_l->is_right_interpolant())) {-        ret_schema = SASS_MEMORY_NEW(String_Schema, b->pstate());-        Binary_Expression_Obj bin_ex = SASS_MEMORY_NEW(Binary_Expression, b->pstate(),-                                                    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_Ptr s_r = SASS_MEMORY_CAST(String_Schema, b->right())) {--      if (!s_r->has_interpolant() && (!s_r->is_left_interpolant() || op_type == Sass_OP::DIV)) {-        ret_schema = SASS_MEMORY_NEW(String_Schema, b->pstate());-        Binary_Expression_Obj 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);-      }-    }--    // 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();-    }--    Expression_Obj lhs = b->left();-    Expression_Obj rhs = b->right();--    // 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);-    }--    Binary_Expression_Obj u3 = b;-    switch (op_type) {-      case Sass_OP::AND: {-        return *lhs ? b->right()->perform(this) : lhs.detach();-      } break;--      case Sass_OP::OR: {-        return *lhs ? lhs.detach() : b->right()->perform(this);-      } break;--      default:-        break;-    }-    // 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;--    Expression::Concrete_Type l_type = lhs->concrete_type();-    Expression::Concrete_Type r_type = rhs->concrete_type();--    // Is one of the operands an interpolant?-    String_Schema_Obj s1 = SASS_MEMORY_CAST(String_Schema, b->left());-    String_Schema_Obj s2 = SASS_MEMORY_CAST(String_Schema, b->right());-    Binary_Expression_Obj b1 = SASS_MEMORY_CAST(Binary_Expression, b->left());-    Binary_Expression_Obj b2 = SASS_MEMORY_CAST(Binary_Expression, b->right());--    bool schema_op = false;--    bool force_delay = (s2 && s2->is_left_interpolant()) ||-                       (s1 && s1->is_right_interpolant()) ||-                       (b1 && b1->is_right_interpolant()) ||-                       (b2 && b2->is_left_interpolant());--    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_Ptr str = SASS_MEMORY_CAST(String_Constant, lhs)) {-          std::string value(str->value());-          const char* start = value.c_str();-          if (Prelexer::sequence < Prelexer::dimension, Prelexer::end_of_file >(start) != 0) {-            Textual_Obj l = SASS_MEMORY_NEW(Textual, b->pstate(), Textual::DIMENSION, str->value());-            lhs = l->perform(this);-          }-        }-        // If possible upgrade RHS to a number (for string to number compare)-        if (String_Constant_Ptr str = SASS_MEMORY_CAST(String_Constant, rhs)) {-          std::string value(str->value());-          const char* start = value.c_str();-          if (Prelexer::sequence < Prelexer::dimension, Prelexer::number >(start) != 0) {-            Textual_Obj r = SASS_MEMORY_NEW(Textual, b->pstate(), Textual::DIMENSION, str->value());-            rhs = r->perform(this);-          }-        }-      }--      To_Value to_value(ctx);-      Value_Obj v_l = dynamic_cast<Value_Ptr>(lhs->perform(&to_value));-      Value_Obj v_r = dynamic_cast<Value_Ptr>(rhs->perform(&to_value));-      l_type = lhs->concrete_type();-      r_type = rhs->concrete_type();--      if (s2 && s2->has_interpolants() && s2->length()) {-        Textual_Obj front = SASS_MEMORY_CAST(Textual, s2->elements().front());-        if (front && !front->is_interpolant())-        {-          // XXX: this is never hit via spec tests-          schema_op = true;-          rhs = front->perform(this);-        }-      }--      if (force_delay) {-        std::string str("");-        str += v_l->to_string(ctx.c_options);-        if (b->op().ws_before) str += " ";-        str += b->separator();-        if (b->op().ws_after) str += " ";-        str += v_r->to_string(ctx.c_options);-        String_Constant_Ptr val = SASS_MEMORY_NEW(String_Constant, b->pstate(), str);-        val->is_interpolant(b->left()->has_interpolant());-        return val;-      }-    }--    // see if it's a relational expression-    try {-      switch(op_type) {-        case Sass_OP::EQ:  return SASS_MEMORY_NEW(Boolean, b->pstate(), eq(lhs, rhs));-        case Sass_OP::NEQ: return SASS_MEMORY_NEW(Boolean, b->pstate(), !eq(lhs, rhs));-        case Sass_OP::GT:  return SASS_MEMORY_NEW(Boolean, b->pstate(), !lt(lhs, rhs, "gt") && !eq(lhs, rhs));-        case Sass_OP::GTE: return SASS_MEMORY_NEW(Boolean, b->pstate(), !lt(lhs, rhs, "gte"));-        case Sass_OP::LT:  return SASS_MEMORY_NEW(Boolean, b->pstate(), lt(lhs, rhs, "lt"));-        case Sass_OP::LTE: return SASS_MEMORY_NEW(Boolean, b->pstate(), lt(lhs, rhs, "lte") || eq(lhs, rhs));-        default:                     break;-      }-    }-    catch (Exception::OperationError& err)-    {-      // throw Exception::Base(b->pstate(), err.what());-      throw Exception::SassValueError(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-    Expression_Obj rv = 0;-    try {-      ParserState pstate(b->pstate());-      if (l_type == Expression::NUMBER && r_type == Expression::NUMBER) {-        Number_Ptr l_n = SASS_MEMORY_CAST(Number, lhs);-        Number_Ptr r_n = SASS_MEMORY_CAST(Number, rhs);-        rv = op_numbers(op_type, *l_n, *r_n, ctx.c_options, &pstate);-      }-      else if (l_type == Expression::NUMBER && r_type == Expression::COLOR) {-        Number_Ptr l_n = SASS_MEMORY_CAST(Number, lhs);-        Color_Ptr r_c = SASS_MEMORY_CAST(Color, rhs);-        rv = op_number_color(op_type, *l_n, *r_c, ctx.c_options, &pstate);-      }-      else if (l_type == Expression::COLOR && r_type == Expression::NUMBER) {-        Color_Ptr l_c = SASS_MEMORY_CAST(Color, lhs);-        Number_Ptr r_n = SASS_MEMORY_CAST(Number, rhs);-        rv = op_color_number(op_type, *l_c, *r_n, ctx.c_options, &pstate);-      }-      else if (l_type == Expression::COLOR && r_type == Expression::COLOR) {-        Color_Ptr l_c = SASS_MEMORY_CAST(Color, lhs);-        Color_Ptr r_c = SASS_MEMORY_CAST(Color, rhs);-        rv = op_colors(op_type, *l_c, *r_c, ctx.c_options, &pstate);-      }-      else {-        To_Value to_value(ctx);-        // this will leak if perform does not return a value!-        Value_Obj v_l = SASS_MEMORY_CAST_PTR(Value, lhs->perform(&to_value));-        Value_Obj v_r = SASS_MEMORY_CAST_PTR(Value, 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) {-          throw Exception::InvalidValue(*v_l);-        }-        if (r_type == Expression::MAP) {-          throw Exception::InvalidValue(*v_r);-        }-        Value_Ptr ex = op_strings(b->op(), *v_l, *v_r, ctx.c_options, &pstate, !interpolant); // pass true to compress-        if (String_Constant_Ptr str = dynamic_cast<String_Constant_Ptr>(ex))-        {-          if (str->concrete_type() == Expression::STRING)-          {-            String_Constant_Ptr lstr = SASS_MEMORY_CAST(String_Constant, lhs);-            String_Constant_Ptr rstr = SASS_MEMORY_CAST(String_Constant, rhs);-            if (op_type != Sass_OP::SUB) {-              if (String_Constant_Ptr org = lstr ? lstr : rstr)-              { str->quote_mark(org->quote_mark()); }-            }-          }-        }-        ex->is_interpolant(b->is_interpolant());-        rv = ex;-      }-    }-    catch (Exception::OperationError& err)-    {-      // throw Exception::Base(b->pstate(), err.what());-      throw Exception::SassValueError(b->pstate(), err);-    }--    if (rv) {-      if (schema_op) {-        // XXX: this is never hit via spec tests-        (*s2)[0] = rv;-        rv = s2->perform(this);-      }-    }--    return rv.detach();--  }--  Expression_Ptr Eval::operator()(Unary_Expression_Ptr u)-  {-    Expression_Obj operand = u->operand()->perform(this);-    if (u->type() == Unary_Expression::NOT) {-      Boolean_Ptr result = SASS_MEMORY_NEW(Boolean, u->pstate(), (bool)*operand);-      result->value(!result->value());-      return result;-    }-    else if (operand->concrete_type() == Expression::NUMBER) {-      Number_Ptr result = SASS_MEMORY_NEW(Number, static_cast<Number_Ptr>(&operand));-      result->value(u->type() == Unary_Expression::MINUS-                    ? -result->value()-                    :  result->value());-      return result;-    }-    else {-      // Special cases: +/- variables which evaluate to null ouput just +/-,-      // but +/- null itself outputs the string-      if (operand->concrete_type() == Expression::NULL_VAL && SASS_MEMORY_CAST(Variable, u->operand())) {-        u->operand(SASS_MEMORY_NEW(String_Quoted, u->pstate(), ""));-      }-      // Never apply unary opertions on colors @see #2140-      else if (operand->concrete_type() == Expression::COLOR) {-        Color_Ptr c = dynamic_cast<Color_Ptr>(&operand);--        // Use the color name if this was eval with one-        if (c->disp().length() > 0) {-          operand = SASS_MEMORY_NEW(String_Constant, operand->pstate(), c->disp());-          u->operand(operand);-        }-      }-      else {-        u->operand(operand);-      }--      String_Constant_Ptr result = SASS_MEMORY_NEW(String_Quoted,-                                                  u->pstate(),-                                                  u->inspect());-      return result;-    }-    // unreachable-    return u;-  }--  Expression_Ptr Eval::operator()(Function_Call_Ptr c)-  {-    if (backtrace()->parent != NULL && backtrace()->depth() > Constants::MaxCallStack) {-        // XXX: this is never hit via spec tests-        std::ostringstream stm;-        stm << "Stack depth exceeded max of " << Constants::MaxCallStack;-        error(stm.str(), c->pstate(), backtrace());-    }-    std::string name(Util::normalize_underscores(c->name()));-    std::string full_name(name + "[f]");-    // we make a clone here, need to implement that further-    Arguments_Obj args = c->arguments();--    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 = SASS_MEMORY_CAST(List, arg->value())) {-            if (ls->size() == 0) error("() isn't a valid CSS value.", c->pstate());-          }-        }-        args = SASS_MEMORY_CAST_PTR(Arguments, args->perform(this));-        Function_Call_Obj lit = SASS_MEMORY_NEW(Function_Call,-                                             c->pstate(),-                                             c->name(),-                                             &args);-        if (args->has_named_arguments()) {-          error("Function " + c->name() + " doesn't support keyword arguments", c->pstate());-        }-        String_Quoted_Ptr str = SASS_MEMORY_NEW(String_Quoted,-                                             c->pstate(),-                                             lit->to_string(ctx.c_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 = SASS_MEMORY_CAST_PTR(Arguments, args->perform(this));-    }-    Definition_Ptr def = SASS_MEMORY_CAST(Definition, (*env)[full_name]);--    if (def->is_overload_stub()) {-      std::stringstream ss;-      size_t L = args->length();-      // account for rest arguments-      if (args->has_rest_argument() && args->length() > 0) {-        // get the rest arguments list-        List_Ptr rest = SASS_MEMORY_CAST(List, args->last()->value());-        // arguments before rest argument plus rest-        if (rest) L += rest->length() - 1;-      }-      ss << full_name << L;-      full_name = ss.str();-      std::string resolved_name(full_name);-      if (!env->has(resolved_name)) error("overloaded function `" + std::string(c->name()) + "` given wrong number of arguments", c->pstate());-      def = SASS_MEMORY_CAST(Definition, (*env)[resolved_name]);-    }--    Expression_Obj     result = c;-    Block_Obj          body   = def->block();-    Native_Function func   = def->native_function();-    Sass_Function_Entry c_function = def->c_function();--    Parameters_Obj params = def->parameters();-    Env fn_env(def->environment());-    exp.env_stack.push_back(&fn_env);--    if (func || body) {-      bind(std::string("Function"), c->name(), params, args, &ctx, &fn_env, this);-      Backtrace here(backtrace(), c->pstate(), ", in function `" + c->name() + "`");-      exp.backtrace_stack.push_back(&here);-      // 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(), backtrace(), exp.selector_stack);-      }-      if (!result) {-        error(std::string("Function ") + c->name() + " did not return a value", c->pstate());-      }-      exp.backtrace_stack.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-      std::string ff(c->name());-      bind(std::string("Function"), c->name(), params, args, &ctx, &fn_env, this);--      Backtrace here(backtrace(), c->pstate(), ", in function `" + c->name() + "`");-      exp.backtrace_stack.push_back(&here);--      To_C to_c;-      union Sass_Value* c_args = sass_make_list(params->length(), SASS_COMMA);-      for(size_t i = 0; i < params->length(); i++) {-        Parameter_Obj param = params->at(i);-        std::string key = param->name();-        AST_Node_Obj node = fn_env.get_local(key);-        Expression_Obj arg = SASS_MEMORY_CAST(Expression, node);-        sass_list_set_value(c_args, i, arg->perform(&to_c));-      }-      union Sass_Value* c_val = c_func(c_args, c_function, ctx.c_compiler);-      if (sass_value_get_tag(c_val) == SASS_ERROR) {-        error("error in C function " + c->name() + ": " + sass_error_get_message(c_val), c->pstate(), backtrace());-      } else if (sass_value_get_tag(c_val) == SASS_WARNING) {-        error("warning in C function " + c->name() + ": " + sass_warning_get_message(c_val), c->pstate(), backtrace());-      }-      result = cval_to_astnode(c_val, backtrace(), c->pstate());--      exp.backtrace_stack.pop_back();-      sass_delete_value(c_args);-      if (c_val != c_args)-        sass_delete_value(c_val);-    }--    // link back to function definition-    // only do this for custom functions-    if (result->pstate().file == std::string::npos)-      result->pstate(c->pstate());--    result = result->perform(this);-    result->is_interpolant(c->is_interpolant());-    exp.env_stack.pop_back();-    return result.detach();-  }--  Expression_Ptr Eval::operator()(Function_Call_Schema_Ptr s)-  {-    Expression_Ptr evaluated_name = s->name()->perform(this);-    Expression_Ptr evaluated_args = s->arguments()->perform(this);-    String_Schema_Obj ss = SASS_MEMORY_NEW(String_Schema, s->pstate(), 2);-    ss->append(evaluated_name);-    ss->append(evaluated_args);-    return ss->perform(this);-  }--  Expression_Ptr Eval::operator()(Variable_Ptr v)-  {-    std::string name(v->name());-    Expression_Obj value = 0;-    Env* env = environment();-    if (env->has(name)) {-      value = SASS_MEMORY_CAST(Expression, (*env)[name]);-    }-    else error("Undefined variable: \"" + v->name() + "\".", v->pstate());-    if (typeid(*value) == typeid(Argument)) {-      value = SASS_MEMORY_CAST(Argument, value)->value();-    }--    // behave according to as ruby sass (add leading zero)-    if (Number_Ptr nr = SASS_MEMORY_CAST(Number, value)) {-      nr->zero(true);-    }--    value->is_interpolant(v->is_interpolant());-    if (force) value->is_expanded(false);-    value->set_delayed(false); // verified-    value = value->perform(this);-    if(!force) (*env)[name] = &value;-    return value.detach();-  }--  Expression_Ptr Eval::operator()(Textual_Ptr t)-  {-    using Prelexer::number;-    Expression_Obj result = 0;-    size_t L = t->value().length();-    bool zero = !( (L > 0 && t->value().substr(0, 1) == ".") ||-                   (L > 1 && t->value().substr(0, 2) == "0.") ||-                   (L > 1 && t->value().substr(0, 2) == "-.")  ||-                   (L > 2 && t->value().substr(0, 3) == "-0.")-                 );--    const std::string& text = t->value();-    size_t num_pos = text.find_first_not_of(" \n\r\t");-    if (num_pos == std::string::npos) num_pos = text.length();-    size_t unit_pos = text.find_first_not_of("-+0123456789.", num_pos);-    if (unit_pos == std::string::npos) unit_pos = text.length();-    const std::string& num = text.substr(num_pos, unit_pos - num_pos);--    switch (t->type())-    {-      case Textual::NUMBER:-        result = SASS_MEMORY_NEW(Number,-                                 t->pstate(),-                                 sass_atof(num.c_str()),-                                 "",-                                 zero);-        break;-      case Textual::PERCENTAGE:-        result = SASS_MEMORY_NEW(Number,-                                 t->pstate(),-                                 sass_atof(num.c_str()),-                                 "%",-                                 true);-        break;-      case Textual::DIMENSION:-        result = SASS_MEMORY_NEW(Number,-                                 t->pstate(),-                                 sass_atof(num.c_str()),-                                 Token(number(text.c_str())),-                                 zero);-        break;-      case Textual::HEX: {-        if (t->value().substr(0, 1) != "#") {-          result = SASS_MEMORY_NEW(String_Quoted, t->pstate(), t->value());-          break;-        }-        std::string hext(t->value().substr(1)); // chop off the '#'-        if (hext.length() == 6) {-          std::string r(hext.substr(0,2));-          std::string g(hext.substr(2,2));-          std::string b(hext.substr(4,2));-          result = SASS_MEMORY_NEW(Color,-                                   t->pstate(),-                                   static_cast<double>(strtol(r.c_str(), NULL, 16)),-                                   static_cast<double>(strtol(g.c_str(), NULL, 16)),-                                   static_cast<double>(strtol(b.c_str(), NULL, 16)),-                                   1, // alpha channel-                                   t->value());-        }-        else {-          result = SASS_MEMORY_NEW(Color,-                                   t->pstate(),-                                   static_cast<double>(strtol(std::string(2,hext[0]).c_str(), NULL, 16)),-                                   static_cast<double>(strtol(std::string(2,hext[1]).c_str(), NULL, 16)),-                                   static_cast<double>(strtol(std::string(2,hext[2]).c_str(), NULL, 16)),-                                   1, // alpha channel-                                   t->value());-        }-      } break;-    }-    result->is_interpolant(t->is_interpolant());-    return result.detach();-  }--  Expression_Ptr Eval::operator()(Color_Ptr c)-  {-    return c;-  }--  Expression_Ptr Eval::operator()(Number_Ptr n)-  {-    return n;-  }--  Expression_Ptr Eval::operator()(Boolean_Ptr b)-  {-    return b;-  }--  void Eval::interpolation(Context& ctx, std::string& res, Expression_Obj ex, bool into_quotes, bool was_itpl) {--    bool needs_closing_brace = false;--    if (Arguments_Ptr args = SASS_MEMORY_CAST(Arguments, ex)) {-      List_Ptr ll = SASS_MEMORY_NEW(List, args->pstate(), 0, SASS_COMMA);-      for(auto arg : args->elements()) {-        ll->append(arg->value());-      }-      ll->is_interpolant(args->is_interpolant());-      needs_closing_brace = true;-      res += "(";-      ex = ll;-    }-    if (Number_Ptr nr = SASS_MEMORY_CAST(Number, ex)) {-      if (!nr->is_valid_css_unit()) {-        throw Exception::InvalidValue(*nr);-      }-    }-    if (Argument_Ptr arg = SASS_MEMORY_CAST(Argument, ex)) {-      ex = &arg->value();-    }-    if (String_Quoted_Ptr sq = SASS_MEMORY_CAST(String_Quoted, 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);-      }-    }--    if (SASS_MEMORY_CAST(Null, ex)) { return; }--    // parent selector needs another go-    if (SASS_MEMORY_CAST(Parent_Selector, ex)) {-      // XXX: this is never hit via spec tests-      ex = ex->perform(this);-    }--    if (List_Ptr l = SASS_MEMORY_CAST(List, 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() && dynamic_cast<Null_Ptr>((*l)[0])) { res += ""; }-      for(Expression_Obj item : *l) {-        item->is_interpolant(l->is_interpolant());-        std::string rl(""); interpolation(ctx, rl, &item, into_quotes, l->is_interpolant());-        bool is_null = dynamic_cast<Null_Ptr>(&item) != 0; // rl != ""-        if (!is_null) ll->append(SASS_MEMORY_NEW(String_Quoted, item->pstate(), rl));-      }-      // Check indicates that we probably should not get a list-      // here. Normally single list items are already unwrapped.-      if (l->size() > 1) {-        // string_to_output would fail "#{'_\a' '_\a'}";-        std::string str(ll->to_string(ctx.c_options));-        newline_to_space(str); // replace directly-        res += str; // append to result string-      } else {-        res += (ll->to_string(ctx.c_options));-      }-      ll->is_interpolant(l->is_interpolant());-    }--    // Value-    // Textual-    // Function_Call-    // Selector_List-    // String_Quoted-    // String_Constant-    // Parent_Selector-    // Binary_Expression-    else {-      // ex = ex->perform(this);-      if (into_quotes && ex->is_interpolant()) {-        res += evacuate_escapes(ex ? ex->to_string(ctx.c_options) : "");-      } else {-        res += ex ? ex->to_string(ctx.c_options) : "";-      }-    }--    if (needs_closing_brace) res += ")";--  }--  Expression_Ptr Eval::operator()(String_Schema_Ptr s)-  {-    size_t L = s->length();-    bool into_quotes = false;-    if (L > 1) {-      if (!SASS_MEMORY_CAST(String_Quoted, (*s)[0]) && !SASS_MEMORY_CAST(String_Quoted, (*s)[L - 1])) {-      if (String_Constant_Ptr l = SASS_MEMORY_CAST(String_Constant, (*s)[0])) {-        if (String_Constant_Ptr r = SASS_MEMORY_CAST(String_Constant, (*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;-          }-        }-      }-      }-    }-    bool was_quoted = false;-    bool was_interpolant = false;-    std::string res("");-    for (size_t i = 0; i < L; ++i) {-      bool is_quoted = SASS_MEMORY_CAST(String_Quoted, (*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 += " "; }-      Expression_Obj ex = (*s)[i]->perform(this);-      interpolation(ctx, res, ex, into_quotes, ex->is_interpolant());-      was_quoted = SASS_MEMORY_CAST(String_Quoted, (*s)[i]) != NULL;-      was_interpolant = (*s)[i]->is_interpolant();--    }-    if (!s->is_interpolant()) {-      if (s->length() > 1 && res == "") return SASS_MEMORY_NEW(Null, s->pstate());-      return SASS_MEMORY_NEW(String_Constant, s->pstate(), res);-    }-    // 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);-    // if (s->is_interpolant()) str->quote_mark(0);-    // String_Constant_Ptr str = SASS_MEMORY_NEW(String_Constant, s->pstate(), res);-    if (str->quote_mark()) str->quote_mark('*');-    else if (!is_in_comment) str->value(string_to_output(str->value()));-    str->is_interpolant(s->is_interpolant());-    return str.detach();-  }---  Expression_Ptr Eval::operator()(String_Constant_Ptr s)-  {-    if (!s->is_delayed() && name_to_color(s->value())) {-      Color_Ptr c = SASS_MEMORY_COPY(name_to_color(s->value())); // copy-      c->pstate(s->pstate());-      c->disp(s->value());-      c->is_delayed(true);-      return c;-    }-    return s;-  }--  Expression_Ptr Eval::operator()(String_Quoted_Ptr s)-  {-    String_Quoted_Ptr str = SASS_MEMORY_NEW(String_Quoted, s->pstate(), "");-    str->value(s->value());-    str->quote_mark(s->quote_mark());-    str->is_interpolant(s->is_interpolant());-    return str;-  }--  Expression_Ptr Eval::operator()(Supports_Operator_Ptr c)-  {-    Expression_Ptr left = c->left()->perform(this);-    Expression_Ptr right = c->right()->perform(this);-    Supports_Operator_Ptr cc = SASS_MEMORY_NEW(Supports_Operator,-                                 c->pstate(),-                                 dynamic_cast<Supports_Condition_Ptr>(left),-                                 dynamic_cast<Supports_Condition_Ptr>(right),-                                 c->operand());-    return cc;-  }--  Expression_Ptr Eval::operator()(Supports_Negation_Ptr c)-  {-    Expression_Ptr condition = c->condition()->perform(this);-    Supports_Negation_Ptr cc = SASS_MEMORY_NEW(Supports_Negation,-                                 c->pstate(),-                                 dynamic_cast<Supports_Condition_Ptr>(condition));-    return cc;-  }--  Expression_Ptr Eval::operator()(Supports_Declaration_Ptr c)-  {-    Expression_Ptr feature = c->feature()->perform(this);-    Expression_Ptr value = c->value()->perform(this);-    Supports_Declaration_Ptr cc = SASS_MEMORY_NEW(Supports_Declaration,-                              c->pstate(),-                              feature,-                              value);-    return cc;-  }--  Expression_Ptr Eval::operator()(Supports_Interpolation_Ptr c)-  {-    Expression_Ptr value = c->value()->perform(this);-    Supports_Interpolation_Ptr cc = SASS_MEMORY_NEW(Supports_Interpolation,-                            c->pstate(),-                            value);-    return cc;-  }--  Expression_Ptr Eval::operator()(At_Root_Query_Ptr e)-  {-    Expression_Obj feature = e->feature();-    feature = (feature ? feature->perform(this) : 0);-    Expression_Obj value = e->value();-    value = (value ? value->perform(this) : 0);-    Expression_Ptr ee = SASS_MEMORY_NEW(At_Root_Query,-                                     e->pstate(),-                                     dynamic_cast<String_Ptr>(&feature),-                                     value);-    return ee;-  }--  Expression_Ptr Eval::operator()(Media_Query_Ptr q)-  {-    String_Obj t = q->media_type();-    t = static_cast<String_Ptr>(&t ? t->perform(this) : 0);-    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<Media_Query_Expression_Ptr>((*q)[i]->perform(this)));-    }-    return qq.detach();-  }--  Expression_Ptr Eval::operator()(Media_Query_Expression_Ptr e)-  {-    Expression_Obj feature = e->feature();-    feature = (feature ? feature->perform(this) : 0);-    if (feature && SASS_MEMORY_CAST(String_Quoted, feature)) {-      feature = SASS_MEMORY_NEW(String_Quoted,-                                  feature->pstate(),-                                  SASS_MEMORY_CAST(String_Quoted, feature)->value());-    }-    Expression_Obj value = e->value();-    value = (value ? value->perform(this) : 0);-    if (value && SASS_MEMORY_CAST(String_Quoted, value)) {-      // XXX: this is never hit via spec tests-      value = SASS_MEMORY_NEW(String_Quoted,-                                value->pstate(),-                                SASS_MEMORY_CAST(String_Quoted, value)->value());-    }-    return SASS_MEMORY_NEW(Media_Query_Expression,-                           e->pstate(),-                           feature,-                           value,-                           e->is_interpolated());-  }--  Expression_Ptr Eval::operator()(Null_Ptr n)-  {-    return n;-  }--  Expression_Ptr Eval::operator()(Argument_Ptr a)-  {-    Expression_Obj val = a->value()->perform(this);-    bool is_rest_argument = a->is_rest_argument();-    bool is_keyword_argument = a->is_keyword_argument();--    if (a->is_rest_argument()) {-      if (val->concrete_type() == Expression::MAP) {-        is_rest_argument = false;-        is_keyword_argument = true;-      }-      else if(val->concrete_type() != Expression::LIST) {-        List_Obj wrapper = SASS_MEMORY_NEW(List,-                                        val->pstate(),-                                        0,-                                        SASS_COMMA,-                                        true);-        wrapper->append(val);-        val = &wrapper;-      }-    }-    return SASS_MEMORY_NEW(Argument,-                           a->pstate(),-                           val,-                           a->name(),-                           is_rest_argument,-                           is_keyword_argument);-  }--  Expression_Ptr Eval::operator()(Arguments_Ptr a)-  {-    Arguments_Obj aa = SASS_MEMORY_NEW(Arguments, a->pstate());-    if (a->length() == 0) return aa.detach();-    for (size_t i = 0, L = a->length(); i < L; ++i) {-      Expression_Obj rv = (*a)[i]->perform(this);-      Argument_Ptr arg = SASS_MEMORY_CAST(Argument, rv);-      if (!(arg->is_rest_argument() || arg->is_keyword_argument())) {-        aa->append(arg);-      }-    }--    if (a->has_rest_argument()) {-      Expression_Obj rest = a->get_rest_argument()->perform(this);-      Expression_Obj splat = SASS_MEMORY_CAST(Argument, rest)->value()->perform(this);--      Sass_Separator separator = SASS_COMMA;-      List_Ptr ls = SASS_MEMORY_CAST(List, splat);-      Map_Ptr ms = SASS_MEMORY_CAST(Map, splat);--      List_Obj arglist = SASS_MEMORY_NEW(List,-                                      splat->pstate(),-                                      0,-                                      ls ? ls->separator() : separator,-                                      true);--      if (ls && ls->is_arglist()) {-        arglist->concat(ls);-      } else if (ms) {-        aa->append(SASS_MEMORY_NEW(Argument, splat->pstate(), ms, "", false, true));-      } else if (ls) {-        arglist->concat(ls);-      } else {-        arglist->append(splat);-      }-      if (arglist->length()) {-        aa->append(SASS_MEMORY_NEW(Argument, splat->pstate(), &arglist, "", true));-      }-    }--    if (a->has_keyword_argument()) {-      Expression_Obj rv = a->get_keyword_argument()->perform(this);-      Argument_Ptr rvarg = SASS_MEMORY_CAST(Argument, rv);-      Expression_Obj kwarg = rvarg->value()->perform(this);--      aa->append(SASS_MEMORY_NEW(Argument, kwarg->pstate(), kwarg, "", false, true));-    }-    return aa.detach();-  }--  Expression_Ptr Eval::operator()(Comment_Ptr c)-  {-    return 0;-  }--  inline Expression_Ptr Eval::fallback_impl(AST_Node_Ptr n)-  {-    return static_cast<Expression_Ptr>(n);-  }--  // All the binary helpers.--  bool Eval::eq(Expression_Obj lhs, Expression_Obj rhs)-  {-    // use compare operator from ast node-    return lhs && rhs && *lhs == *rhs;-  }--  bool Eval::lt(Expression_Obj lhs, Expression_Obj rhs, std::string op)-  {-    Number_Obj l = SASS_MEMORY_CAST(Number, lhs);-    Number_Obj r = SASS_MEMORY_CAST(Number, rhs);-    // use compare operator from ast node-    if (!l || !r) throw Exception::UndefinedOperation(&lhs, &rhs, op);-    // use compare operator from ast node-    return *l < *r;-  }--  Value_Ptr Eval::op_numbers(enum Sass_OP op, const Number& l, const Number& r, struct Sass_Inspect_Options opt, ParserState* pstate)-  {-    double lv = l.value();-    double rv = r.value();-    if (op == Sass_OP::DIV && rv == 0) {-      // XXX: this is never hit via spec tests-      return SASS_MEMORY_NEW(String_Quoted, pstate ? *pstate : l.pstate(), lv ? "Infinity" : "NaN");-    }-    if (op == Sass_OP::MOD && !rv) {-      // XXX: this is never hit via spec tests-      throw Exception::ZeroDivisionError(l, r);-    }--    Number tmp(&r); // copy-    bool strict = op != Sass_OP::MUL && op != Sass_OP::DIV;-    tmp.normalize(l.find_convertible_unit(), strict);-    std::string l_unit(l.unit());-    std::string r_unit(tmp.unit());-    Number_Obj v = SASS_MEMORY_COPY(&l); // copy-    v->pstate(pstate ? *pstate : l.pstate());-    if (l_unit.empty() && (op == Sass_OP::ADD || op == Sass_OP::SUB || op == Sass_OP::MOD)) {-      v->numerator_units() = r.numerator_units();-      v->denominator_units() = r.denominator_units();-    }--    if (op == Sass_OP::MUL) {-      v->value(ops[op](lv, rv));-      for (size_t i = 0, S = r.numerator_units().size(); i < S; ++i) {-        v->numerator_units().push_back(r.numerator_units()[i]);-      }-      for (size_t i = 0, S = r.denominator_units().size(); i < S; ++i) {-        v->denominator_units().push_back(r.denominator_units()[i]);-      }-    }-    else if (op == Sass_OP::DIV) {-      v->value(ops[op](lv, rv));-      for (size_t i = 0, S = r.numerator_units().size(); i < S; ++i) {-        v->denominator_units().push_back(r.numerator_units()[i]);-      }-      for (size_t i = 0, S = r.denominator_units().size(); i < S; ++i) {-        v->numerator_units().push_back(r.denominator_units()[i]);-      }-    } else {-      v->value(ops[op](lv, r.value() * r.convert_factor(l)));-      // v->normalize();-      return v.detach();--      v->value(ops[op](lv, tmp.value()));-    }-    v->normalize();-    return v.detach();-  }--  Value_Ptr Eval::op_number_color(enum Sass_OP op, const Number& l, const Color& r, struct Sass_Inspect_Options opt, ParserState* pstate)-  {-    double lv = l.value();-    switch (op) {-      case Sass_OP::ADD:-      case Sass_OP::MUL: {-        return SASS_MEMORY_NEW(Color,-                               pstate ? *pstate : l.pstate(),-                               ops[op](lv, r.r()),-                               ops[op](lv, r.g()),-                               ops[op](lv, r.b()),-                               r.a());-      } break;-      case Sass_OP::SUB:-      case Sass_OP::DIV: {-        std::string sep(op == Sass_OP::SUB ? "-" : "/");-        std::string color(r.to_string(opt));-        return SASS_MEMORY_NEW(String_Quoted,-                               pstate ? *pstate : l.pstate(),-                               l.to_string(opt)-                               + sep-                               + color);-      } break;-      case Sass_OP::MOD: {-        throw Exception::UndefinedOperation(&l, &r, sass_op_to_name(op));-      } break;-      default: break; // caller should ensure that we don't get here-    }-    // unreachable-    return NULL;-  }--  Value_Ptr Eval::op_color_number(enum Sass_OP op, const Color& l, const Number& r, struct Sass_Inspect_Options opt, ParserState* pstate)-  {-    double rv = r.value();-    if (op == Sass_OP::DIV && !rv) {-      // comparison of Fixnum with Float failed?-      throw Exception::ZeroDivisionError(l, r);-    }-    return SASS_MEMORY_NEW(Color,-                           pstate ? *pstate : l.pstate(),-                           ops[op](l.r(), rv),-                           ops[op](l.g(), rv),-                           ops[op](l.b(), rv),-                           l.a());-  }--  Value_Ptr Eval::op_colors(enum Sass_OP op, const Color& l, const Color& r, struct Sass_Inspect_Options opt, ParserState* pstate)-  {-    if (l.a() != r.a()) {-      throw Exception::AlphaChannelsNotEqual(&l, &r, "+");-    }-    if (op == Sass_OP::DIV && (!r.r() || !r.g() ||!r.b())) {-      // comparison of Fixnum with Float failed?-      throw Exception::ZeroDivisionError(l, r);-    }-    return SASS_MEMORY_NEW(Color,-                           pstate ? *pstate : l.pstate(),-                           ops[op](l.r(), r.r()),-                           ops[op](l.g(), r.g()),-                           ops[op](l.b(), r.b()),-                           l.a());-  }--  Value_Ptr Eval::op_strings(Sass::Operand operand, Value& lhs, Value& rhs, struct Sass_Inspect_Options opt, ParserState* pstate, bool delayed)-  {-    Expression::Concrete_Type ltype = lhs.concrete_type();-    Expression::Concrete_Type rtype = rhs.concrete_type();-    enum Sass_OP op = operand.operand;--    String_Quoted_Ptr lqstr = dynamic_cast<String_Quoted_Ptr>(&lhs);-    String_Quoted_Ptr rqstr = dynamic_cast<String_Quoted_Ptr>(&rhs);--    std::string lstr(lqstr ? lqstr->value() : lhs.to_string(opt));-    std::string rstr(rqstr ? rqstr->value() : rhs.to_string(opt));--    if (ltype == Expression::NULL_VAL) throw Exception::InvalidNullOperation(&lhs, &rhs, sass_op_to_name(op));-    if (rtype == Expression::NULL_VAL) throw Exception::InvalidNullOperation(&lhs, &rhs, sass_op_to_name(op));-    if (op == Sass_OP::MOD) throw Exception::UndefinedOperation(&lhs, &rhs, sass_op_to_name(op));-    if (op == Sass_OP::MUL) throw Exception::UndefinedOperation(&lhs, &rhs, sass_op_to_name(op));-    std::string sep;-    switch (op) {-      case Sass_OP::SUB: sep = "-"; break;-      case Sass_OP::DIV: sep = "/"; break;-      case Sass_OP::MUL: sep = "*"; break;-      case Sass_OP::MOD: 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:                      break;-    }--    if ( (sep == "") /* &&-         (sep != "/" || !rqstr || !rqstr->quote_mark()) */-    ) {-      // create a new string that might be quoted on output (but do not unquote what we pass)-      return SASS_MEMORY_NEW(String_Quoted, pstate ? *pstate : lhs.pstate(), lstr + rstr, 0, false, true);-    }--    if (sep != "" && !delayed) {-      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 ? *pstate : lhs.pstate(), lstr + sep + rstr);-  }--  Expression_Ptr cval_to_astnode(union Sass_Value* v, Backtrace* backtrace, ParserState pstate)-  {-    using std::strlen;-    using std::strcpy;-    Expression_Ptr e = NULL;-    switch (sass_value_get_tag(v)) {-      case SASS_BOOLEAN: {-        e = SASS_MEMORY_NEW(Boolean, pstate, !!sass_boolean_get_value(v));-      } break;-      case SASS_NUMBER: {-        e = SASS_MEMORY_NEW(Number, pstate, sass_number_get_value(v), sass_number_get_unit(v));-      } break;-      case SASS_COLOR: {-        e = SASS_MEMORY_NEW(Color, pstate, sass_color_get_r(v), sass_color_get_g(v), sass_color_get_b(v), sass_color_get_a(v));-      } break;-      case SASS_STRING: {-        if (sass_string_is_quoted(v))-          e = SASS_MEMORY_NEW(String_Quoted, pstate, sass_string_get_value(v));-        else {-          e = SASS_MEMORY_NEW(String_Constant, pstate, sass_string_get_value(v));-        }-      } break;-      case SASS_LIST: {-        List_Ptr l = SASS_MEMORY_NEW(List, pstate, sass_list_get_length(v), sass_list_get_separator(v));-        for (size_t i = 0, L = sass_list_get_length(v); i < L; ++i) {-          l->append(cval_to_astnode(sass_list_get_value(v, i), backtrace, pstate));-        }-        e = l;-      } break;-      case SASS_MAP: {-        Map_Ptr m = SASS_MEMORY_NEW(Map, pstate);-        for (size_t i = 0, L = sass_map_get_length(v); i < L; ++i) {-          *m << std::make_pair(-            cval_to_astnode(sass_map_get_key(v, i), backtrace, pstate),-            cval_to_astnode(sass_map_get_value(v, i), backtrace, pstate));-        }-        e = m;-      } break;-      case SASS_NULL: {-        e = SASS_MEMORY_NEW(Null, pstate);-      } break;-      case SASS_ERROR: {-        error("Error in C function: " + std::string(sass_error_get_message(v)), pstate, backtrace);-      } break;-      case SASS_WARNING: {-        error("Warning in C function: " + std::string(sass_warning_get_message(v)), pstate, backtrace);-      } break;-    }-    return e;-  }--  Selector_List_Ptr Eval::operator()(Selector_List_Ptr s)-  {-    std::vector<Selector_List_Obj> rv;-    Selector_List_Obj sl = SASS_MEMORY_NEW(Selector_List, s->pstate());-    sl->is_optional(s->is_optional());-    sl->media_block(s->media_block());-    sl->is_optional(s->is_optional());-    for (size_t i = 0, iL = s->length(); i < iL; ++i) {-      rv.push_back(operator()(&(*s)[i]));-    }--    // we should actually permutate parent first-    // but here we have permutated the selector first-    size_t round = 0;-    while (round != std::string::npos) {-      bool abort = true;-      for (size_t i = 0, iL = rv.size(); i < iL; ++i) {-        if (rv[i]->length() > round) {-          sl->append((*rv[i])[round]);-          abort = false;-        }-      }-      if (abort) {-        round = std::string::npos;-      } else {-        ++ round;-      }--    }-    return sl.detach();-  }---  Selector_List_Ptr Eval::operator()(Complex_Selector_Ptr s)-  {-    bool implicit_parent = !exp.old_at_root_without_rule;-    return s->resolve_parent_refs(ctx, exp.selector_stack, implicit_parent);-  }--  // XXX: this is never hit via spec tests-  Attribute_Selector_Ptr Eval::operator()(Attribute_Selector_Ptr s)-  {-    String_Obj attr = s->value();-    if (attr) { attr = static_cast<String_Ptr>(attr->perform(this)); }-    Attribute_Selector_Ptr ss = SASS_MEMORY_COPY(s);-    ss->value(attr);-    return ss;-  }--  Selector_List_Ptr Eval::operator()(Selector_Schema_Ptr s)-  {-    // the parser will look for a brace to end the selector-    Expression_Obj sel = s->contents()->perform(this);-    std::string result_str(sel->to_string(ctx.c_options));-    result_str = unquote(Util::rtrim(result_str)) + "\n{";-    Parser p = Parser::from_c_str(result_str.c_str(), ctx, s->pstate());-    p.last_media_block = s->media_block();-    Selector_List_Obj sl = p.parse_selector_list(exp.block_stack.back()->is_root());-    if (s->has_parent_ref()) sl->remove_parent_selectors();-    return operator()(&sl);-  }--  Expression_Ptr Eval::operator()(Parent_Selector_Ptr p)-  {-    if (Selector_List_Obj pr = selector()) {-      exp.selector_stack.pop_back();-      Selector_List_Obj rv = operator()(&pr);-      exp.selector_stack.push_back(rv);-      return rv.detach();-    } else {-      return SASS_MEMORY_NEW(Null, p->pstate());-    }-  }+#include "operators.hpp"+#include "environment.hpp"+#include "position.hpp"+#include "sass/values.h"+#include "to_value.hpp"+#include "to_c.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"++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)+  {+    bool_true = SASS_MEMORY_NEW(Boolean, "[NA]", true);+    bool_false = SASS_MEMORY_NEW(Boolean, "[NA]", false);+  }+  Eval::~Eval() { }++  Env* Eval::environment()+  {+    return exp.environment();+  }++  Selector_List_Obj Eval::selector()+  {+    return exp.selector();+  }++  Expression_Ptr Eval::operator()(Block_Ptr b)+  {+    Expression_Ptr 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;+  }++  Expression_Ptr Eval::operator()(Assignment_Ptr a)+  {+    Env* env = exp.environment();+    std::string var(a->variable());+    if (a->is_global()) {+      if (a->is_default()) {+        if (env->has_global(var)) {+          Expression_Ptr e = Cast<Expression>(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));+        }+      }+      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_Ptr e = Cast<Expression>(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");+      }+      else if (env->has_global(var)) {+        if (AST_Node_Obj node = env->get_global(var)) {+          Expression_Ptr e = Cast<Expression>(node);+          if (!e || e->concrete_type() == Expression::NULL_VAL) {+            env->set_global(var, a->value()->perform(this));+          }+        }+      }+      else if (env->is_lexical()) {+        env->set_local(var, a->value()->perform(this));+      }+      else {+        env->set_local(var, a->value()->perform(this));+      }+    }+    else {+      env->set_lexical(var, a->value()->perform(this));+    }+    return 0;+  }++  Expression_Ptr Eval::operator()(If_Ptr i)+  {+    Expression_Obj rv = 0;+    Env env(exp.environment());+    exp.env_stack.push_back(&env);+    Expression_Obj cond = i->predicate()->perform(this);+    if (!cond->is_false()) {+      rv = i->block()->perform(this);+    }+    else {+      Block_Obj alt = i->alternative();+      if (alt) rv = alt->perform(this);+    }+    exp.env_stack.pop_back();+    return rv.detach();+  }++  // For does not create a new env scope+  // But iteration vars are reset afterwards+  Expression_Ptr Eval::operator()(For_Ptr f)+  {+    std::string variable(f->variable());+    Expression_Obj low = f->lower_bound()->perform(this);+    if (low->concrete_type() != Expression::NUMBER) {+      traces.push_back(Backtrace(low->pstate()));+      throw Exception::TypeMismatch(traces, *low, "integer");+    }+    Expression_Obj high = f->upper_bound()->perform(this);+    if (high->concrete_type() != Expression::NUMBER) {+      traces.push_back(Backtrace(high->pstate()));+      throw Exception::TypeMismatch(traces, *high, "integer");+    }+    Number_Obj sass_start = Cast<Number>(low);+    Number_Obj sass_end = Cast<Number>(high);+    // check if units are valid for sequence+    if (sass_start->unit() != sass_end->unit()) {+      std::stringstream msg; msg << "Incompatible units: '"+        << sass_end->unit() << "' and '"+        << sass_start->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);+    exp.env_stack.push_back(&env);+    Block_Obj body = f->block();+    Expression_Ptr 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;+      }+    }+    exp.env_stack.pop_back();+    return val;+  }++  // Eval does not create a new env scope+  // But iteration vars are reset afterwards+  Expression_Ptr Eval::operator()(Each_Ptr e)+  {+    std::vector<std::string> variables(e->variables());+    Expression_Obj expr = e->list()->perform(this);+    Env env(environment(), true);+    exp.env_stack.push_back(&env);+    List_Obj list = 0;+    Map_Ptr map = 0;+    if (expr->concrete_type() == Expression::MAP) {+      map = Cast<Map>(expr);+    }+    else if (Selector_List_Ptr ls = Cast<Selector_List>(expr)) {+      Listize listize;+      Expression_Obj rv = ls->perform(&listize);+      list = Cast<List>(rv);+    }+    else if (expr->concrete_type() != Expression::LIST) {+      list = SASS_MEMORY_NEW(List, expr->pstate(), 1, SASS_COMMA);+      list->append(expr);+    }+    else {+      list = Cast<List>(expr);+    }++    Block_Obj body = e->block();+    Expression_Obj val = 0;++    if (map) {+      for (Expression_Obj key : map->keys()) {+        Expression_Obj value = map->at(key);++        if (variables.size() == 1) {+          List_Ptr variable = SASS_MEMORY_NEW(List, map->pstate(), 2, SASS_SPACE);+          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;+      }+    }+    else {+      if (list->length() == 1 && Cast<Selector_List>(list)) {+        list = Cast<List>(list);+      }+      for (size_t i = 0, L = list->length(); i < L; ++i) {+        Expression_Ptr item = list->at(i);+        // unwrap value if the expression is an argument+        if (Argument_Ptr arg = Cast<Argument>(item)) item = arg->value();+        // check if we got passed a list of args (investigate)+        if (List_Ptr scalars = Cast<List>(item)) {+          if (variables.size() == 1) {+            Expression_Ptr var = scalars;+            env.set_local(variables[0], var);+          } else {+            // XXX: this is never hit via spec tests+            for (size_t j = 0, K = variables.size(); j < K; ++j) {+              Expression_Ptr res = j >= scalars->length()+                ? SASS_MEMORY_NEW(Null, expr->pstate())+                : scalars->at(j);+              env.set_local(variables[j], res);+            }+          }+        } 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_Ptr res = SASS_MEMORY_NEW(Null, expr->pstate());+              env.set_local(variables[j], res);+            }+          }+        }+        val = body->perform(this);+        if (val) break;+      }+    }+    exp.env_stack.pop_back();+    return val.detach();+  }++  Expression_Ptr Eval::operator()(While_Ptr w)+  {+    Expression_Obj pred = w->predicate();+    Block_Obj body = w->block();+    Env env(environment(), true);+    exp.env_stack.push_back(&env);+    Expression_Obj cond = pred->perform(this);+    while (!cond->is_false()) {+      Expression_Obj val = body->perform(this);+      if (val) {+        exp.env_stack.pop_back();+        return val.detach();+      }+      cond = pred->perform(this);+    }+    exp.env_stack.pop_back();+    return 0;+  }++  Expression_Ptr Eval::operator()(Return_Ptr r)+  {+    return r->value()->perform(this);+  }++  Expression_Ptr Eval::operator()(Warning_Ptr w)+  {+    Sass_Output_Style outstyle = ctx.c_options.output_style;+    ctx.c_options.output_style = NESTED;+    Expression_Obj message = w->message()->perform(this);+    Env* env = exp.environment();++    // try to use generic function+    if (env->has("@warn[f]")) {++      // add call stack entry+      ctx.callee_stack.push_back({+        "@warn",+        w->pstate().path,+        w->pstate().line + 1,+        w->pstate().column + 1,+        SASS_CALLEE_FUNCTION,+        { env }+      });++      Definition_Ptr def = Cast<Definition>((*env)["@warn[f]"]);+      // Block_Obj          body   = def->block();+      // Native_Function func   = def->native_function();+      Sass_Function_Entry c_function = def->c_function();+      Sass_Function_Fn c_func = sass_function_get_function(c_function);++      To_C to_c;+      union Sass_Value* c_args = sass_make_list(1, SASS_COMMA, false);+      sass_list_set_value(c_args, 0, message->perform(&to_c));+      union Sass_Value* c_val = c_func(c_args, c_function, ctx.c_compiler);+      ctx.c_options.output_style = outstyle;+      ctx.callee_stack.pop_back();+      sass_delete_value(c_args);+      sass_delete_value(c_val);+      return 0;++    }++    std::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;+    ctx.c_options.output_style = outstyle;+    traces.pop_back();+    return 0;+  }++  Expression_Ptr Eval::operator()(Error_Ptr e)+  {+    Sass_Output_Style outstyle = ctx.c_options.output_style;+    ctx.c_options.output_style = NESTED;+    Expression_Obj message = e->message()->perform(this);+    Env* env = exp.environment();++    // try to use generic function+    if (env->has("@error[f]")) {++      // add call stack entry+      ctx.callee_stack.push_back({+        "@error",+        e->pstate().path,+        e->pstate().line + 1,+        e->pstate().column + 1,+        SASS_CALLEE_FUNCTION,+        { env }+      });++      Definition_Ptr def = Cast<Definition>((*env)["@error[f]"]);+      // Block_Obj          body   = def->block();+      // Native_Function func   = def->native_function();+      Sass_Function_Entry c_function = def->c_function();+      Sass_Function_Fn c_func = sass_function_get_function(c_function);++      To_C to_c;+      union Sass_Value* c_args = sass_make_list(1, SASS_COMMA, false);+      sass_list_set_value(c_args, 0, message->perform(&to_c));+      union Sass_Value* c_val = c_func(c_args, c_function, ctx.c_compiler);+      ctx.c_options.output_style = outstyle;+      ctx.callee_stack.pop_back();+      sass_delete_value(c_args);+      sass_delete_value(c_val);+      return 0;++    }++    std::string result(unquote(message->to_sass()));+    ctx.c_options.output_style = outstyle;+    error(result, e->pstate(), traces);+    return 0;+  }++  Expression_Ptr Eval::operator()(Debug_Ptr d)+  {+    Sass_Output_Style outstyle = ctx.c_options.output_style;+    ctx.c_options.output_style = NESTED;+    Expression_Obj message = d->value()->perform(this);+    Env* env = exp.environment();++    // try to use generic function+    if (env->has("@debug[f]")) {++      // add call stack entry+      ctx.callee_stack.push_back({+        "@debug",+        d->pstate().path,+        d->pstate().line + 1,+        d->pstate().column + 1,+        SASS_CALLEE_FUNCTION,+        { env }+      });++      Definition_Ptr def = Cast<Definition>((*env)["@debug[f]"]);+      // Block_Obj          body   = def->block();+      // Native_Function func   = def->native_function();+      Sass_Function_Entry c_function = def->c_function();+      Sass_Function_Fn c_func = sass_function_get_function(c_function);++      To_C to_c;+      union Sass_Value* c_args = sass_make_list(1, SASS_COMMA, false);+      sass_list_set_value(c_args, 0, message->perform(&to_c));+      union Sass_Value* c_val = c_func(c_args, c_function, ctx.c_compiler);+      ctx.c_options.output_style = outstyle;+      ctx.callee_stack.pop_back();+      sass_delete_value(c_args);+      sass_delete_value(c_val);+      return 0;++    }++    std::string cwd(ctx.cwd());+    std::string result(unquote(message->to_sass()));+    std::string abs_path(Sass::File::rel2abs(d->pstate().path, cwd, cwd));+    std::string rel_path(Sass::File::abs2rel(d->pstate().path, cwd, cwd));+    std::string output_path(Sass::File::path_for_console(rel_path, abs_path, d->pstate().path));+    ctx.c_options.output_style = outstyle;++    std::cerr << output_path << ":" << d->pstate().line+1 << " DEBUG: " << result;+    std::cerr << std::endl;+    return 0;+  }++  Expression_Ptr Eval::operator()(List_Ptr l)+  {+    // 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)+      {+        Expression_Obj key = (*l)[i+0]->perform(this);+        Expression_Obj 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);+    }+    // 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_Ptr Eval::operator()(Map_Ptr m)+  {+    if (m->is_expanded()) return m;++    // make sure we're not starting with duplicate keys.+    // the duplicate key state will have been set in the parser phase.+    if (m->has_duplicate_key()) {+      traces.push_back(Backtrace(m->pstate()));+      throw Exception::DuplicateKeyError(traces, *m, *m);+    }++    Map_Obj mm = SASS_MEMORY_NEW(Map,+                                m->pstate(),+                                m->length());+    for (auto key : m->keys()) {+      Expression_Ptr ex_key = key->perform(this);+      Expression_Ptr ex_val = m->at(key);+      if (ex_val == NULL) continue;+      ex_val = ex_val->perform(this);+      *mm << std::make_pair(ex_key, ex_val);+    }++    // check the evaluated keys aren't duplicates.+    if (mm->has_duplicate_key()) {+      traces.push_back(Backtrace(m->pstate()));+      throw Exception::DuplicateKeyError(traces, *mm, *m);+    }++    mm->is_expanded(true);+    return mm.detach();+  }++  Expression_Ptr Eval::operator()(Binary_Expression_Ptr b_in)+  {++    Expression_Obj lhs = b_in->left();+    Expression_Obj 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);+    }+    else if (op_type == Sass_OP::OR) {+      // LOCAL_FLAG(force, true);+      lhs = lhs->perform(this);+      if (*lhs) return lhs.detach();+      return rhs->perform(this);+    }++    // Evaluate variables as early o+    while (Variable_Ptr l_v = Cast<Variable>(lhs)) {+      lhs = operator()(l_v);+    }+    while (Variable_Ptr r_v = Cast<Variable>(rhs)) {+      rhs = operator()(r_v);+    }++    Binary_Expression_Obj b = b_in;++    // Evaluate sub-expressions early on+    while (Binary_Expression_Ptr l_b = Cast<Binary_Expression>(lhs)) {+      if (!force && l_b->is_delayed()) break;+      lhs = operator()(l_b);+    }+    while (Binary_Expression_Ptr r_b = Cast<Binary_Expression>(rhs)) {+      if (!force && r_b->is_delayed()) break;+      rhs = operator()(r_b);+    }++    // don't eval delayed expressions (the '/' when used as a separator)+    if (!force && op_type == Sass_OP::DIV && b->is_delayed()) {+      b->right(b->right()->perform(this));+      b->left(b->left()->perform(this));+      return b.detach();+    }++    // specific types we know are final+    // handle them early to avoid overhead+    if (Number_Ptr l_n = Cast<Number>(lhs)) {+      // lhs is number and rhs is number+      if (Number_Ptr r_n = Cast<Number>(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, ctx.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);+        }+      }+      // lhs is number and rhs is color+      else if (Color_Ptr r_c = Cast<Color>(rhs)) {+        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::LT: return *l_n < *r_c ? bool_true : bool_false;+            case Sass_OP::GTE: return *l_n < *r_c ? bool_false : bool_true;+            case Sass_OP::LTE: return *l_n < *r_c || *l_n == *r_c ? bool_true : bool_false;+            case Sass_OP::GT: return *l_n < *r_c || *l_n == *r_c ? bool_false : bool_true;+            case Sass_OP::ADD: case Sass_OP::SUB: case Sass_OP::MUL: case Sass_OP::DIV: case Sass_OP::MOD:+              return Operators::op_number_color(op_type, *l_n, *r_c, ctx.c_options, b_in->pstate());+            default: break;+          }+        }+        catch (Exception::OperationError& err)+        {+          traces.push_back(Backtrace(b_in->pstate()));+          throw Exception::SassValueError(traces, b_in->pstate(), err);+        }+      }+    }+    else if (Color_Ptr l_c = Cast<Color>(lhs)) {+      // lhs is color and rhs is color+      if (Color_Ptr r_c = Cast<Color>(rhs)) {+        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, ctx.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);+        }+      }+      // lhs is color and rhs is number+      else if (Number_Ptr r_n = Cast<Number>(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::LT: return *l_c < *r_n ? bool_true : bool_false;+            case Sass_OP::GTE: return *l_c < *r_n ? bool_false : bool_true;+            case Sass_OP::LTE: return *l_c < *r_n || *l_c == *r_n ? bool_true : bool_false;+            case Sass_OP::GT: return *l_c < *r_n || *l_c == *r_n ? bool_false : bool_true;+            case Sass_OP::ADD: case Sass_OP::SUB: case Sass_OP::MUL: case Sass_OP::DIV: case Sass_OP::MOD:+              return Operators::op_color_number(op_type, *l_c, *r_n, ctx.c_options, b_in->pstate());+            default: break;+          }+        }+        catch (Exception::OperationError& err)+        {+          traces.push_back(Backtrace(b_in->pstate()));+          throw Exception::SassValueError(traces, b_in->pstate(), err);+        }+      }+    }++    String_Schema_Obj ret_schema;++    // only the last item will be used to eval the binary expression+    if (String_Schema_Ptr s_l = Cast<String_Schema>(b->left())) {+      if (!s_l->has_interpolant() && (!s_l->is_right_interpolant())) {+        ret_schema = SASS_MEMORY_NEW(String_Schema, b->pstate());+        Binary_Expression_Obj bin_ex = SASS_MEMORY_NEW(Binary_Expression, b->pstate(),+                                                    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_Ptr s_r = Cast<String_Schema>(b->right())) {++      if (!s_r->has_interpolant() && (!s_r->is_left_interpolant() || op_type == Sass_OP::DIV)) {+        ret_schema = SASS_MEMORY_NEW(String_Schema, b->pstate());+        Binary_Expression_Obj 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);+    }++    // 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;++    Expression::Concrete_Type l_type;+    Expression::Concrete_Type r_type;++    // Is one of the operands an interpolant?+    String_Schema_Obj s1 = Cast<String_Schema>(b->left());+    String_Schema_Obj s2 = Cast<String_Schema>(b->right());+    Binary_Expression_Obj b1 = Cast<Binary_Expression>(b->left());+    Binary_Expression_Obj b2 = Cast<Binary_Expression>(b->right());++    bool schema_op = false;++    bool force_delay = (s2 && s2->is_left_interpolant()) ||+                       (s1 && s1->is_right_interpolant()) ||+                       (b1 && b1->is_right_interpolant()) ||+                       (b2 && b2->is_left_interpolant());++    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_Ptr str = Cast<String_Constant>(lhs)) {+          std::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_Ptr str = Cast<String_Constant>(rhs)) {+          std::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());+          }+        }+      }++      To_Value to_value(ctx);+      Value_Obj v_l = Cast<Value>(lhs->perform(&to_value));+      Value_Obj v_r = Cast<Value>(rhs->perform(&to_value));++      if (force_delay) {+        std::string str("");+        str += v_l->to_string(ctx.c_options);+        if (b->op().ws_before) str += " ";+        str += b->separator();+        if (b->op().ws_after) str += " ";+        str += v_r->to_string(ctx.c_options);+        String_Constant_Ptr val = SASS_MEMORY_NEW(String_Constant, b->pstate(), str);+        val->is_interpolant(b->left()->has_interpolant());+        return val;+      }+    }++    // 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;+      }+    }+    catch (Exception::OperationError& err)+    {+      // throw Exception::Base(b->pstate(), err.what());+      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+    Expression_Obj rv;+    try {+      ParserState pstate(b->pstate());+      if (l_type == Expression::NUMBER && r_type == Expression::NUMBER) {+        Number_Ptr l_n = Cast<Number>(lhs);+        Number_Ptr r_n = Cast<Number>(rhs);+        l_n->reduce(); r_n->reduce();+        rv = Operators::op_numbers(op_type, *l_n, *r_n, ctx.c_options, pstate);+      }+      else if (l_type == Expression::NUMBER && r_type == Expression::COLOR) {+        Number_Ptr l_n = Cast<Number>(lhs);+        Color_Ptr r_c = Cast<Color>(rhs);+        rv = Operators::op_number_color(op_type, *l_n, *r_c, ctx.c_options, pstate);+      }+      else if (l_type == Expression::COLOR && r_type == Expression::NUMBER) {+        Color_Ptr l_c = Cast<Color>(lhs);+        Number_Ptr r_n = Cast<Number>(rhs);+        rv = Operators::op_color_number(op_type, *l_c, *r_n, ctx.c_options, pstate);+      }+      else if (l_type == Expression::COLOR && r_type == Expression::COLOR) {+        Color_Ptr l_c = Cast<Color>(lhs);+        Color_Ptr r_c = Cast<Color>(rhs);+        rv = Operators::op_colors(op_type, *l_c, *r_c, ctx.c_options, pstate);+      }+      else {+        To_Value to_value(ctx);+        // this will leak if perform does not return a value!+        Value_Obj v_l = Cast<Value>(lhs->perform(&to_value));+        Value_Obj v_r = Cast<Value>(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_Ptr ex = Operators::op_strings(b->op(), *v_l, *v_r, ctx.c_options, pstate, !interpolant); // pass true to compress+        if (String_Constant_Ptr str = Cast<String_Constant>(ex))+        {+          if (str->concrete_type() == Expression::STRING)+          {+            String_Constant_Ptr lstr = Cast<String_Constant>(lhs);+            String_Constant_Ptr rstr = Cast<String_Constant>(rhs);+            if (op_type != Sass_OP::SUB) {+              if (String_Constant_Ptr 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);+    }++    if (rv) {+      if (schema_op) {+        // XXX: this is never hit via spec tests+        (*s2)[0] = rv;+        rv = s2->perform(this);+      }+    }++    return rv.detach();++  }++  Expression_Ptr Eval::operator()(Unary_Expression_Ptr u)+  {+    Expression_Obj operand = u->operand()->perform(this);+    if (u->optype() == Unary_Expression::NOT) {+      Boolean_Ptr result = SASS_MEMORY_NEW(Boolean, u->pstate(), (bool)*operand);+      result->value(!result->value());+      return result;+    }+    else if (Number_Obj nr = Cast<Number>(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) {+        std::string str = '/' + nr->to_string(ctx.c_options);+        return SASS_MEMORY_NEW(String_Constant, u->pstate(), str);+      }+      // nothing for positive+      return nr.detach();+    }+    else {+      // Special cases: +/- variables which evaluate to null ouput just +/-,+      // but +/- null itself outputs the string+      if (operand->concrete_type() == Expression::NULL_VAL && Cast<Variable>(u->operand())) {+        u->operand(SASS_MEMORY_NEW(String_Quoted, u->pstate(), ""));+      }+      // Never apply unary opertions on colors @see #2140+      else if (Color_Ptr color = Cast<Color>(operand)) {+        // Use the color name if this was eval with one+        if (color->disp().length() > 0) {+          operand = SASS_MEMORY_NEW(String_Constant, operand->pstate(), color->disp());+          u->operand(operand);+        }+      }+      else {+        u->operand(operand);+      }++      return SASS_MEMORY_NEW(String_Quoted,+                             u->pstate(),+                             u->inspect());+    }+    // unreachable+    return u;+  }++  Expression_Ptr Eval::operator()(Function_Call_Ptr c)+  {+    if (traces.size() > Constants::MaxCallStack) {+        // XXX: this is never hit via spec tests+        std::ostringstream stm;+        stm << "Stack depth exceeded max of " << Constants::MaxCallStack;+        error(stm.str(), c->pstate(), traces);+    }+    std::string name(Util::normalize_underscores(c->name()));+    std::string full_name(name + "[f]");+    // we make a clone here, need to implement that further+    Arguments_Obj args = c->arguments();++    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<List>(arg->value())) {+            if (ls->size() == 0) error("() isn't a valid CSS value.", c->pstate(), traces);+          }+        }+        args = Cast<Arguments>(args->perform(this));+        Function_Call_Obj lit = SASS_MEMORY_NEW(Function_Call,+                                             c->pstate(),+                                             c->name(),+                                             args);+        if (args->has_named_arguments()) {+          error("Function " + c->name() + " doesn't support keyword arguments", c->pstate(), traces);+        }+        String_Quoted_Ptr str = SASS_MEMORY_NEW(String_Quoted,+                                             c->pstate(),+                                             lit->to_string(ctx.c_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<Arguments>(args->perform(this));+    }+    Definition_Ptr def = Cast<Definition>((*env)[full_name]);++    if (c->func()) def = c->func()->definition();++    if (def->is_overload_stub()) {+      std::stringstream ss;+      size_t L = args->length();+      // account for rest arguments+      if (args->has_rest_argument() && args->length() > 0) {+        // get the rest arguments list+        List_Ptr rest = Cast<List>(args->last()->value());+        // arguments before rest argument plus rest+        if (rest) L += rest->length() - 1;+      }+      ss << full_name << L;+      full_name = ss.str();+      std::string resolved_name(full_name);+      if (!env->has(resolved_name)) error("overloaded function `" + std::string(c->name()) + "` given wrong number of arguments", c->pstate(), traces);+      def = Cast<Definition>((*env)[resolved_name]);+    }++    Expression_Obj     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());+    exp.env_stack.push_back(&fn_env);++    if (func || body) {+      bind(std::string("Function"), c->name(), params, args, &ctx, &fn_env, this);+      std::string msg(", in function `" + c->name() + "`");+      traces.push_back(Backtrace(c->pstate(), msg));+      ctx.callee_stack.push_back({+        c->name().c_str(),+        c->pstate().path,+        c->pstate().line + 1,+        c->pstate().column + 1,+        SASS_CALLEE_FUNCTION,+        { env }+      });++      // eval the body if user-defined or special, invoke underlying CPP function if native+      if (body /* && !Prelexer::re_special_fun(name.c_str()) */) {+        result = body->perform(this);+      }+      else if (func) {+        result = func(fn_env, *env, ctx, def->signature(), c->pstate(), traces, exp.selector_stack);+      }+      if (!result) {+        error(std::string("Function ") + c->name() + " finished without @return", c->pstate(), traces);+      }+      ctx.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+      std::string ff(c->name());+      bind(std::string("Function"), c->name(), params, args, &ctx, &fn_env, this);+      std::string msg(", in function `" + c->name() + "`");+      traces.push_back(Backtrace(c->pstate(), msg));+      ctx.callee_stack.push_back({+        c->name().c_str(),+        c->pstate().path,+        c->pstate().line + 1,+        c->pstate().column + 1,+        SASS_CALLEE_C_FUNCTION,+        { env }+      });++      To_C to_c;+      union Sass_Value* c_args = sass_make_list(params->length(), SASS_COMMA, false);+      for(size_t i = 0; i < params->length(); i++) {+        Parameter_Obj param = params->at(i);+        std::string key = param->name();+        AST_Node_Obj node = fn_env.get_local(key);+        Expression_Obj arg = Cast<Expression>(node);+        sass_list_set_value(c_args, i, arg->perform(&to_c));+      }+      union Sass_Value* c_val = c_func(c_args, c_function, ctx.c_compiler);+      if (sass_value_get_tag(c_val) == SASS_ERROR) {+        error("error in C function " + c->name() + ": " + sass_error_get_message(c_val), c->pstate(), traces);+      } else if (sass_value_get_tag(c_val) == SASS_WARNING) {+        error("warning in C function " + c->name() + ": " + sass_warning_get_message(c_val), c->pstate(), traces);+      }+      result = cval_to_astnode(c_val, traces, c->pstate());++      ctx.callee_stack.pop_back();+      traces.pop_back();+      sass_delete_value(c_args);+      if (c_val != c_args)+        sass_delete_value(c_val);+    }++    // link back to function definition+    // only do this for custom functions+    if (result->pstate().file == std::string::npos)+      result->pstate(c->pstate());++    result = result->perform(this);+    result->is_interpolant(c->is_interpolant());+    exp.env_stack.pop_back();+    return result.detach();+  }++  Expression_Ptr Eval::operator()(Function_Call_Schema_Ptr s)+  {+    Expression_Ptr evaluated_name = s->name()->perform(this);+    Expression_Ptr evaluated_args = s->arguments()->perform(this);+    String_Schema_Obj ss = SASS_MEMORY_NEW(String_Schema, s->pstate(), 2);+    ss->append(evaluated_name);+    ss->append(evaluated_args);+    return ss->perform(this);+  }++  Expression_Ptr Eval::operator()(Variable_Ptr v)+  {+    Expression_Obj value = 0;+    Env* env = environment();+    const std::string& name(v->name());+    EnvResult rv(env->find(name));+    if (rv.found) value = static_cast<Expression*>(rv.it->second.ptr());+    else error("Undefined variable: \"" + v->name() + "\".", v->pstate(), traces);+    if (Argument_Ptr arg = Cast<Argument>(value)) value = arg->value();+    if (Number_Ptr nr = Cast<Number>(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();+  }++  Expression_Ptr Eval::operator()(Color_Ptr c)+  {+    return c;+  }++  Expression_Ptr Eval::operator()(Number_Ptr n)+  {+    return n;+  }++  Expression_Ptr Eval::operator()(Boolean_Ptr b)+  {+    return b;+  }++  void Eval::interpolation(Context& ctx, std::string& res, Expression_Obj ex, bool into_quotes, bool was_itpl) {++    bool needs_closing_brace = false;++    if (Arguments_Ptr args = Cast<Arguments>(ex)) {+      List_Ptr ll = SASS_MEMORY_NEW(List, args->pstate(), 0, SASS_COMMA);+      for(auto arg : args->elements()) {+        ll->append(arg->value());+      }+      ll->is_interpolant(args->is_interpolant());+      needs_closing_brace = true;+      res += "(";+      ex = ll;+    }+    if (Number_Ptr nr = Cast<Number>(ex)) {+      Number reduced(nr);+      reduced.reduce();+      if (!reduced.is_valid_css_unit()) {+        traces.push_back(Backtrace(nr->pstate()));+        throw Exception::InvalidValue(traces, *nr);+      }+    }+    if (Argument_Ptr arg = Cast<Argument>(ex)) {+      ex = arg->value();+    }+    if (String_Quoted_Ptr sq = Cast<String_Quoted>(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);+      }+    }++    if (Cast<Null>(ex)) { return; }++    // parent selector needs another go+    if (Cast<Parent_Selector>(ex)) {+      // XXX: this is never hit via spec tests+      ex = ex->perform(this);+    }++    if (List_Ptr l = Cast<List>(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<Null>((*l)[0])) { res += ""; }+      for(Expression_Obj item : *l) {+        item->is_interpolant(l->is_interpolant());+        std::string rl(""); interpolation(ctx, rl, item, into_quotes, l->is_interpolant());+        bool is_null = Cast<Null>(item) != 0; // rl != ""+        if (!is_null) ll->append(SASS_MEMORY_NEW(String_Quoted, item->pstate(), rl));+      }+      // Check indicates that we probably should not get a list+      // here. Normally single list items are already unwrapped.+      if (l->size() > 1) {+        // string_to_output would fail "#{'_\a' '_\a'}";+        std::string str(ll->to_string(ctx.c_options));+        str = read_hex_escapes(str); // read escapes+        newline_to_space(str); // replace directly+        res += str; // append to result string+      } else {+        res += (ll->to_string(ctx.c_options));+      }+      ll->is_interpolant(l->is_interpolant());+    }++    // Value+    // Function_Call+    // Selector_List+    // String_Quoted+    // String_Constant+    // Parent_Selector+    // Binary_Expression+    else {+      // ex = ex->perform(this);+      if (into_quotes && ex->is_interpolant()) {+        res += evacuate_escapes(ex ? ex->to_string(ctx.c_options) : "");+      } else {+        std::string str(ex ? ex->to_string(ctx.c_options) : "");+        if (into_quotes) str = read_hex_escapes(str);+        res += str; // append to result string+      }+    }++    if (needs_closing_brace) res += ")";++  }++  Expression_Ptr Eval::operator()(String_Schema_Ptr s)+  {+    size_t L = s->length();+    bool into_quotes = false;+    if (L > 1) {+      if (!Cast<String_Quoted>((*s)[0]) && !Cast<String_Quoted>((*s)[L - 1])) {+      if (String_Constant_Ptr l = Cast<String_Constant>((*s)[0])) {+        if (String_Constant_Ptr r = Cast<String_Constant>((*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;+          }+        }+      }+      }+    }+    bool was_quoted = false;+    bool was_interpolant = false;+    std::string res("");+    for (size_t i = 0; i < L; ++i) {+      bool is_quoted = Cast<String_Quoted>((*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 += " "; }+      Expression_Obj ex = (*s)[i]->perform(this);+      interpolation(ctx, res, ex, into_quotes, ex->is_interpolant());+      was_quoted = Cast<String_Quoted>((*s)[i]) != NULL;+      was_interpolant = (*s)[i]->is_interpolant();++    }+    if (!s->is_interpolant()) {+      if (s->length() > 1 && res == "") return SASS_MEMORY_NEW(Null, s->pstate());+      return SASS_MEMORY_NEW(String_Constant, s->pstate(), res, s->css());+    }+    // string schema seems to have a special unquoting behavior (also handles "nested" quotes)+    String_Quoted_Obj str = SASS_MEMORY_NEW(String_Quoted, s->pstate(), res, 0, false, false, false, s->css());+    // if (s->is_interpolant()) str->quote_mark(0);+    // String_Constant_Ptr str = SASS_MEMORY_NEW(String_Constant, s->pstate(), res);+    if (str->quote_mark()) str->quote_mark('*');+    else if (!is_in_comment) str->value(string_to_output(str->value()));+    str->is_interpolant(s->is_interpolant());+    return str.detach();+  }+++  Expression_Ptr Eval::operator()(String_Constant_Ptr s)+  {+    if (!s->is_delayed() && name_to_color(s->value())) {+      Color_Ptr c = SASS_MEMORY_COPY(name_to_color(s->value())); // copy+      c->pstate(s->pstate());+      c->disp(s->value());+      c->is_delayed(true);+      return c;+    }+    return s;+  }++  Expression_Ptr Eval::operator()(String_Quoted_Ptr s)+  {+    String_Quoted_Ptr str = SASS_MEMORY_NEW(String_Quoted, s->pstate(), "");+    str->value(s->value());+    str->quote_mark(s->quote_mark());+    str->is_interpolant(s->is_interpolant());+    return str;+  }++  Expression_Ptr Eval::operator()(Supports_Operator_Ptr c)+  {+    Expression_Ptr left = c->left()->perform(this);+    Expression_Ptr right = c->right()->perform(this);+    Supports_Operator_Ptr cc = SASS_MEMORY_NEW(Supports_Operator,+                                 c->pstate(),+                                 Cast<Supports_Condition>(left),+                                 Cast<Supports_Condition>(right),+                                 c->operand());+    return cc;+  }++  Expression_Ptr Eval::operator()(Supports_Negation_Ptr c)+  {+    Expression_Ptr condition = c->condition()->perform(this);+    Supports_Negation_Ptr cc = SASS_MEMORY_NEW(Supports_Negation,+                                 c->pstate(),+                                 Cast<Supports_Condition>(condition));+    return cc;+  }++  Expression_Ptr Eval::operator()(Supports_Declaration_Ptr c)+  {+    Expression_Ptr feature = c->feature()->perform(this);+    Expression_Ptr value = c->value()->perform(this);+    Supports_Declaration_Ptr cc = SASS_MEMORY_NEW(Supports_Declaration,+                              c->pstate(),+                              feature,+                              value);+    return cc;+  }++  Expression_Ptr Eval::operator()(Supports_Interpolation_Ptr c)+  {+    Expression_Ptr value = c->value()->perform(this);+    Supports_Interpolation_Ptr cc = SASS_MEMORY_NEW(Supports_Interpolation,+                            c->pstate(),+                            value);+    return cc;+  }++  Expression_Ptr Eval::operator()(At_Root_Query_Ptr e)+  {+    Expression_Obj feature = e->feature();+    feature = (feature ? feature->perform(this) : 0);+    Expression_Obj value = e->value();+    value = (value ? value->perform(this) : 0);+    Expression_Ptr ee = SASS_MEMORY_NEW(At_Root_Query,+                                     e->pstate(),+                                     Cast<String>(feature),+                                     value);+    return ee;+  }++  Media_Query_Ptr Eval::operator()(Media_Query_Ptr q)+  {+    String_Obj t = q->media_type();+    t = static_cast<String_Ptr>(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<Media_Query_Expression_Ptr>((*q)[i]->perform(this)));+    }+    return qq.detach();+  }++  Expression_Ptr Eval::operator()(Media_Query_Expression_Ptr e)+  {+    Expression_Obj feature = e->feature();+    feature = (feature ? feature->perform(this) : 0);+    if (feature && Cast<String_Quoted>(feature)) {+      feature = SASS_MEMORY_NEW(String_Quoted,+                                  feature->pstate(),+                                  Cast<String_Quoted>(feature)->value());+    }+    Expression_Obj value = e->value();+    value = (value ? value->perform(this) : 0);+    if (value && Cast<String_Quoted>(value)) {+      // XXX: this is never hit via spec tests+      value = SASS_MEMORY_NEW(String_Quoted,+                                value->pstate(),+                                Cast<String_Quoted>(value)->value());+    }+    return SASS_MEMORY_NEW(Media_Query_Expression,+                           e->pstate(),+                           feature,+                           value,+                           e->is_interpolated());+  }++  Expression_Ptr Eval::operator()(Null_Ptr n)+  {+    return n;+  }++  Expression_Ptr Eval::operator()(Argument_Ptr a)+  {+    Expression_Obj val = a->value()->perform(this);+    bool is_rest_argument = a->is_rest_argument();+    bool is_keyword_argument = a->is_keyword_argument();++    if (a->is_rest_argument()) {+      if (val->concrete_type() == Expression::MAP) {+        is_rest_argument = false;+        is_keyword_argument = true;+      }+      else if(val->concrete_type() != Expression::LIST) {+        List_Obj wrapper = SASS_MEMORY_NEW(List,+                                        val->pstate(),+                                        0,+                                        SASS_COMMA,+                                        true);+        wrapper->append(val);+        val = wrapper;+      }+    }+    return SASS_MEMORY_NEW(Argument,+                           a->pstate(),+                           val,+                           a->name(),+                           is_rest_argument,+                           is_keyword_argument);+  }++  Expression_Ptr Eval::operator()(Arguments_Ptr a)+  {+    Arguments_Obj aa = SASS_MEMORY_NEW(Arguments, a->pstate());+    if (a->length() == 0) return aa.detach();+    for (size_t i = 0, L = a->length(); i < L; ++i) {+      Expression_Obj rv = (*a)[i]->perform(this);+      Argument_Ptr arg = Cast<Argument>(rv);+      if (!(arg->is_rest_argument() || arg->is_keyword_argument())) {+        aa->append(arg);+      }+    }++    if (a->has_rest_argument()) {+      Expression_Obj rest = a->get_rest_argument()->perform(this);+      Expression_Obj splat = Cast<Argument>(rest)->value()->perform(this);++      Sass_Separator separator = SASS_COMMA;+      List_Ptr ls = Cast<List>(splat);+      Map_Ptr ms = Cast<Map>(splat);++      List_Obj arglist = SASS_MEMORY_NEW(List,+                                      splat->pstate(),+                                      0,+                                      ls ? ls->separator() : separator,+                                      true);++      if (ls && ls->is_arglist()) {+        arglist->concat(ls);+      } else if (ms) {+        aa->append(SASS_MEMORY_NEW(Argument, splat->pstate(), ms, "", false, true));+      } else if (ls) {+        arglist->concat(ls);+      } else {+        arglist->append(splat);+      }+      if (arglist->length()) {+        aa->append(SASS_MEMORY_NEW(Argument, splat->pstate(), arglist, "", true));+      }+    }++    if (a->has_keyword_argument()) {+      Expression_Obj rv = a->get_keyword_argument()->perform(this);+      Argument_Ptr rvarg = Cast<Argument>(rv);+      Expression_Obj kwarg = rvarg->value()->perform(this);++      aa->append(SASS_MEMORY_NEW(Argument, kwarg->pstate(), kwarg, "", false, true));+    }+    return aa.detach();+  }++  Expression_Ptr Eval::operator()(Comment_Ptr c)+  {+    return 0;+  }++  inline Expression_Ptr Eval::fallback_impl(AST_Node_Ptr n)+  {+    return static_cast<Expression_Ptr>(n);+  }++  // All the binary helpers.++  Expression_Ptr cval_to_astnode(union Sass_Value* v, Backtraces traces, ParserState pstate)+  {+    using std::strlen;+    using std::strcpy;+    Expression_Ptr e = NULL;+    switch (sass_value_get_tag(v)) {+      case SASS_BOOLEAN: {+        e = SASS_MEMORY_NEW(Boolean, pstate, !!sass_boolean_get_value(v));+      } break;+      case SASS_NUMBER: {+        e = SASS_MEMORY_NEW(Number, pstate, sass_number_get_value(v), sass_number_get_unit(v));+      } break;+      case SASS_COLOR: {+        e = SASS_MEMORY_NEW(Color, pstate, sass_color_get_r(v), sass_color_get_g(v), sass_color_get_b(v), sass_color_get_a(v));+      } break;+      case SASS_STRING: {+        if (sass_string_is_quoted(v))+          e = SASS_MEMORY_NEW(String_Quoted, pstate, sass_string_get_value(v));+        else {+          e = SASS_MEMORY_NEW(String_Constant, pstate, sass_string_get_value(v));+        }+      } break;+      case SASS_LIST: {+        List_Ptr l = SASS_MEMORY_NEW(List, pstate, sass_list_get_length(v), sass_list_get_separator(v));+        for (size_t i = 0, L = sass_list_get_length(v); i < L; ++i) {+          l->append(cval_to_astnode(sass_list_get_value(v, i), traces, pstate));+        }+        l->is_bracketed(sass_list_get_is_bracketed(v));+        e = l;+      } break;+      case SASS_MAP: {+        Map_Ptr m = SASS_MEMORY_NEW(Map, pstate);+        for (size_t i = 0, L = sass_map_get_length(v); i < L; ++i) {+          *m << std::make_pair(+            cval_to_astnode(sass_map_get_key(v, i), traces, pstate),+            cval_to_astnode(sass_map_get_value(v, i), traces, pstate));+        }+        e = m;+      } break;+      case SASS_NULL: {+        e = SASS_MEMORY_NEW(Null, pstate);+      } break;+      case SASS_ERROR: {+        error("Error in C function: " + std::string(sass_error_get_message(v)), pstate, traces);+      } break;+      case SASS_WARNING: {+        error("Warning in C function: " + std::string(sass_warning_get_message(v)), pstate, traces);+      } break;+      default: break;+    }+    return e;+  }++  Selector_List_Ptr Eval::operator()(Selector_List_Ptr s)+  {+    std::vector<Selector_List_Obj> rv;+    Selector_List_Obj sl = SASS_MEMORY_NEW(Selector_List, s->pstate());+    sl->is_optional(s->is_optional());+    sl->media_block(s->media_block());+    sl->is_optional(s->is_optional());+    for (size_t i = 0, iL = s->length(); i < iL; ++i) {+      rv.push_back(operator()((*s)[i]));+    }++    // we should actually permutate parent first+    // but here we have permutated the selector first+    size_t round = 0;+    while (round != std::string::npos) {+      bool abort = true;+      for (size_t i = 0, iL = rv.size(); i < iL; ++i) {+        if (rv[i]->length() > round) {+          sl->append((*rv[i])[round]);+          abort = false;+        }+      }+      if (abort) {+        round = std::string::npos;+      } else {+        ++ round;+      }++    }+    return sl.detach();+  }+++  Selector_List_Ptr Eval::operator()(Complex_Selector_Ptr s)+  {+    bool implicit_parent = !exp.old_at_root_without_rule;+    if (is_in_selector_schema) exp.selector_stack.push_back(0);+    Selector_List_Obj resolved = s->resolve_parent_refs(exp.selector_stack, traces, implicit_parent);+    if (is_in_selector_schema) exp.selector_stack.pop_back();+    for (size_t i = 0; i < resolved->length(); i++) {+      Complex_Selector_Ptr is = resolved->at(i)->first();+      while (is) {+        if (is->head()) {+          is->head(operator()(is->head()));+        }+        is = is->tail();+      }+    }+    return resolved.detach();+  }++  Compound_Selector_Ptr Eval::operator()(Compound_Selector_Ptr s)+  {+    for (size_t i = 0; i < s->length(); i++) {+      Simple_Selector_Ptr ss = s->at(i);+      // skip parents here (called via resolve_parent_refs)+      if (ss == NULL || Cast<Parent_Selector>(ss)) continue;+      s->at(i) = Cast<Simple_Selector>(ss->perform(this));+    }+    return s;+  }++  Selector_List_Ptr Eval::operator()(Selector_Schema_Ptr s)+  {+    LOCAL_FLAG(is_in_selector_schema, true);+    // the parser will look for a brace to end the selector+    ctx.c_options.in_selector = true; // do not compress colors+    Expression_Obj sel = s->contents()->perform(this);+    std::string result_str(sel->to_string(ctx.c_options));+    ctx.c_options.in_selector = false; // flag temporary only+    result_str = unquote(Util::rtrim(result_str));+    char* temp_cstr = sass_copy_c_string(result_str.c_str());+    ctx.strings.push_back(temp_cstr); // attach to context+    Parser p = Parser::from_c_str(temp_cstr, ctx, traces, s->pstate());+    p.last_media_block = s->media_block();+    // a selector schema may or may not connect to parent?+    bool chroot = s->connect_parent() == false;+    Selector_List_Obj sl = p.parse_selector_list(chroot);+    auto vec_str_rend = ctx.strings.rend();+    auto vec_str_rbegin = ctx.strings.rbegin();+    // remove the first item searching from the back+    // we cannot assume our item is still the last one+    // order is not important, so we can optimize this+    auto it = std::find(vec_str_rbegin, vec_str_rend, temp_cstr);+    // undefined behavior if not found!+    if (it != vec_str_rend) {+      // overwrite with last item+      *it = ctx.strings.back();+      // remove last one from vector+      ctx.strings.pop_back();+      // free temporary copy+      free(temp_cstr);+    }+    flag_is_in_selector_schema.reset();+    return operator()(sl);+  }++  Expression_Ptr Eval::operator()(Parent_Selector_Ptr p)+  {+    if (Selector_List_Obj pr = selector()) {+      exp.selector_stack.pop_back();+      Selector_List_Obj rv = operator()(pr);+      exp.selector_stack.push_back(rv);+      return rv.detach();+    } else {+      return SASS_MEMORY_NEW(Null, p->pstate());+    }+  }++  Simple_Selector_Ptr Eval::operator()(Simple_Selector_Ptr s)+  {+    return s;+  }++  // hotfix to avoid invalid nested `:not` selectors+  // probably the wrong place, but this should ultimately+  // be fixed by implement superselector correctly for `:not`+  // first use of "find" (ATM only implemented for selectors)+  bool hasNotSelector(AST_Node_Obj obj) {+    if (Wrapped_Selector_Ptr w = Cast<Wrapped_Selector>(obj)) {+      return w->name() == ":not";+    }+    return false;+  }++  Wrapped_Selector_Ptr Eval::operator()(Wrapped_Selector_Ptr s)+  {++    if (s->name() == ":not") {+      if (exp.selector_stack.back()) {+        if (s->selector()->find(hasNotSelector)) {+          s->selector()->clear();+          s->name(" ");+        } else if (s->selector()->length() == 1) {+          Complex_Selector_Ptr cs = s->selector()->at(0);+          if (cs->tail()) {+            s->selector()->clear();+            s->name(" ");+          }+        } else if (s->selector()->length() > 1) {+          s->selector()->clear();+          s->name(" ");+        }+      }+    }+    return s;+  };  }
libsass/src/eval.hpp view
@@ -18,16 +18,20 @@     Expression_Ptr fallback_impl(AST_Node_Ptr n);     public:-    Expand&  exp;+    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();-    Backtrace* backtrace();     Selector_List_Obj selector();      // for evaluating function bodies@@ -49,7 +53,6 @@     Expression_Ptr operator()(Function_Call_Ptr);     Expression_Ptr operator()(Function_Call_Schema_Ptr);     Expression_Ptr operator()(Variable_Ptr);-    Expression_Ptr operator()(Textual_Ptr);     Expression_Ptr operator()(Number_Ptr);     Expression_Ptr operator()(Color_Ptr);     Expression_Ptr operator()(Boolean_Ptr);@@ -57,7 +60,7 @@     Expression_Ptr operator()(String_Quoted_Ptr);     Expression_Ptr operator()(String_Constant_Ptr);     // Expression_Ptr operator()(Selector_List_Ptr);-    Expression_Ptr operator()(Media_Query_Ptr);+    Media_Query_Ptr operator()(Media_Query_Ptr);     Expression_Ptr operator()(Media_Query_Expression_Ptr);     Expression_Ptr operator()(At_Root_Query_Ptr);     Expression_Ptr operator()(Supports_Operator_Ptr);@@ -72,14 +75,15 @@     // these will return selectors     Selector_List_Ptr operator()(Selector_List_Ptr);     Selector_List_Ptr operator()(Complex_Selector_Ptr);-    Attribute_Selector_Ptr operator()(Attribute_Selector_Ptr);-    // they don't have any specific implementatio (yet)-    Element_Selector_Ptr operator()(Element_Selector_Ptr s) { return s; };-    Pseudo_Selector_Ptr operator()(Pseudo_Selector_Ptr s) { return s; };-    Wrapped_Selector_Ptr operator()(Wrapped_Selector_Ptr s) { return s; };-    Class_Selector_Ptr operator()(Class_Selector_Ptr s) { return s; };-    Id_Selector_Ptr operator()(Id_Selector_Ptr s) { return s; };-    Placeholder_Selector_Ptr operator()(Placeholder_Selector_Ptr s) { return s; };+    Compound_Selector_Ptr operator()(Compound_Selector_Ptr);+    Simple_Selector_Ptr operator()(Simple_Selector_Ptr s);+    Wrapped_Selector_Ptr operator()(Wrapped_Selector_Ptr s);+    // they don't have any specific implementation (yet)+    // Element_Selector_Ptr operator()(Element_Selector_Ptr s) { return s; };+    // Pseudo_Selector_Ptr operator()(Pseudo_Selector_Ptr s) { return s; };+    // Class_Selector_Ptr operator()(Class_Selector_Ptr s) { return s; };+    // Id_Selector_Ptr operator()(Id_Selector_Ptr s) { return s; };+    // Placeholder_Selector_Ptr operator()(Placeholder_Selector_Ptr s) { return s; };     // actual evaluated selectors     Selector_List_Ptr operator()(Selector_Schema_Ptr);     Expression_Ptr operator()(Parent_Selector_Ptr);@@ -87,22 +91,12 @@     template <typename U>     Expression_Ptr fallback(U x) { return fallback_impl(x); } -    // -- only need to define two comparisons, and the rest can be implemented in terms of them-    static bool eq(Expression_Obj, Expression_Obj);-    static bool lt(Expression_Obj, Expression_Obj, std::string op);-    // -- arithmetic on the combinations that matter-    static Value_Ptr op_numbers(enum Sass_OP, const Number&, const Number&, struct Sass_Inspect_Options opt, ParserState* pstate = 0);-    static Value_Ptr op_number_color(enum Sass_OP, const Number&, const Color&, struct Sass_Inspect_Options opt, ParserState* pstate = 0);-    static Value_Ptr op_color_number(enum Sass_OP, const Color&, const Number&, struct Sass_Inspect_Options opt, ParserState* pstate = 0);-    static Value_Ptr op_colors(enum Sass_OP, const Color&, const Color&, struct Sass_Inspect_Options opt, ParserState* pstate = 0);-    static Value_Ptr op_strings(Sass::Operand, Value&, Value&, struct Sass_Inspect_Options opt, ParserState* pstate = 0, bool interpolant = false);-   private:     void interpolation(Context& ctx, std::string& res, Expression_Obj ex, bool into_quotes, bool was_itpl = false);    }; -  Expression_Ptr cval_to_astnode(union Sass_Value* v, Backtrace* backtrace, ParserState pstate = ParserState("[AST]"));+  Expression_Ptr cval_to_astnode(union Sass_Value* v, Backtraces traces, ParserState pstate = ParserState("[AST]"));  } 
libsass/src/expand.cpp view
@@ -9,14 +9,16 @@ #include "backtrace.hpp" #include "context.hpp" #include "parser.hpp"+#include "sass_functions.hpp"  namespace Sass {    // simple endless recursion protection   const size_t maxRecursion = 500; -  Expand::Expand(Context& ctx, Env* env, Backtrace* bt, std::vector<Selector_List_Obj>* stack)+  Expand::Expand(Context& ctx, Env* env, std::vector<Selector_List_Obj>* stack)   : ctx(ctx),+    traces(ctx.traces),     eval(Eval(*this)),     recursions(0),     in_keyframes(false),@@ -26,8 +28,7 @@     block_stack(std::vector<Block_Ptr>()),     call_stack(std::vector<AST_Node_Obj>()),     selector_stack(std::vector<Selector_List_Obj>()),-    media_block_stack(std::vector<Media_Block_Ptr>()),-    backtrace_stack(std::vector<Backtrace*>())+    media_block_stack(std::vector<Media_Block_Ptr>())   {     env_stack.push_back(0);     env_stack.push_back(env);@@ -36,15 +37,8 @@     if (stack == NULL) { selector_stack.push_back(0); }     else { selector_stack.insert(selector_stack.end(), stack->begin(), stack->end()); }     media_block_stack.push_back(0);-    backtrace_stack.push_back(0);-    backtrace_stack.push_back(bt);   } -  Context& Expand::context()-  {-    return ctx;-  }-   Env* Expand::environment()   {     if (env_stack.size() > 0)@@ -59,13 +53,6 @@     return 0;   } -  Backtrace* Expand::backtrace()-  {-    if (backtrace_stack.size() > 0)-      return backtrace_stack.back();-    return 0;-  }-   // blocks create new variable scopes   Block_Ptr Expand::operator()(Block_Ptr b)   {@@ -78,7 +65,7 @@                                 b->length(),                                 b->is_root());     // setup block and env stack-    this->block_stack.push_back(&bb);+    this->block_stack.push_back(bb);     this->env_stack.push_back(&env);     // operate on block     // this may throw up!@@ -95,12 +82,14 @@     LOCAL_FLAG(old_at_root_without_rule, at_root_without_rule);      if (in_keyframes) {-      Block_Ptr bb = operator()(&r->block());+      Block_Ptr bb = operator()(r->block());       Keyframe_Rule_Obj k = SASS_MEMORY_NEW(Keyframe_Rule, r->pstate(), bb);       if (r->selector()) {-        selector_stack.push_back(0);-        k->name(SASS_MEMORY_CAST_PTR(Selector_List, r->selector()->perform(&eval)));-        selector_stack.pop_back();+        if (Selector_List_Ptr s = r->selector()) {+          selector_stack.push_back(0);+          k->name(s->eval(eval));+          selector_stack.pop_back();+        }       }       return k.detach();     }@@ -108,50 +97,54 @@     // reset when leaving scope     LOCAL_FLAG(at_root_without_rule, false); -    // do some special checks for the base level rules-    if (r->is_root()) {-      if (Selector_List_Ptr selector_list = SASS_MEMORY_CAST(Selector_List, r->selector())) {+    // `&` is allowed in `@at-root`!+    bool has_parent_selector = false;+    for (size_t i = 0, L = selector_stack.size(); i < L && !has_parent_selector; i++) {+      Selector_List_Obj ll = selector_stack.at(i);+      has_parent_selector = ll != 0 && ll->length() > 0;+    }++    Selector_List_Obj sel = r->selector();+    if (sel) sel = sel->eval(eval);++    // check for parent selectors in base level rules+    if (r->is_root() || (block_stack.back() && block_stack.back()->is_root())) {+      if (Selector_List_Ptr selector_list = Cast<Selector_List>(r->selector())) {         for (Complex_Selector_Obj complex_selector : selector_list->elements()) {-          Complex_Selector_Ptr tail = &complex_selector;+          Complex_Selector_Ptr tail = complex_selector;           while (tail) {             if (tail->head()) for (Simple_Selector_Obj header : tail->head()->elements()) {-              if (SASS_MEMORY_CAST(Parent_Selector, header) == NULL) continue; // skip all others+              Parent_Selector_Ptr ptr = Cast<Parent_Selector>(header);+              if (ptr == NULL || (!ptr->real() || has_parent_selector)) continue;               std::string sel_str(complex_selector->to_string(ctx.c_options));-              error("Base-level rules cannot contain the parent-selector-referencing character '&'.", header->pstate(), backtrace());+              error("Base-level rules cannot contain the parent-selector-referencing character '&'.", header->pstate(), traces);             }-            tail = &tail->tail();+            tail = tail->tail();           }         }       }     }--    Expression_Obj ex = 0;-    if (r->selector()) ex = r->selector()->perform(&eval);-    Selector_List_Obj sel = SASS_MEMORY_CAST(Selector_List, ex);-    if (sel == 0) throw std::runtime_error("Expanded null selector");--    if (sel->length() == 0 || sel->has_parent_ref()) {-      bool has_parent_selector = false;-      for (size_t i = 0, L = selector_stack.size(); i < L && !has_parent_selector; i++) {-        Selector_List_Obj ll = selector_stack.at(i);-        has_parent_selector = ll != 0 && ll->length() > 0;-      }-      if (sel->has_real_parent_ref() && !has_parent_selector) {-        error("Base-level rules cannot contain the parent-selector-referencing character '&'.", sel->pstate(), backtrace());+    else {+      if (sel->length() == 0 || sel->has_parent_ref()) {+        if (sel->has_real_parent_ref() && !has_parent_selector) {+          error("Base-level rules cannot contain the parent-selector-referencing character '&'.", sel->pstate(), traces);+        }       }     } -    selector_stack.push_back(&sel);+    // do not connect parent again+    sel->remove_parent_selectors();+    selector_stack.push_back(sel);     Env env(environment());     if (block_stack.back()->is_root()) {       env_stack.push_back(&env);     }     sel->set_media_block(media_block_stack.back());     Block_Obj blk = 0;-    if (r->block()) blk = operator()(&r->block());+    if (r->block()) blk = operator()(r->block());     Ruleset_Ptr rr = SASS_MEMORY_NEW(Ruleset,                                   r->pstate(),-                                  &sel,+                                  sel,                                   blk);     selector_stack.pop_back();     if (block_stack.back()->is_root()) {@@ -169,27 +162,33 @@     Expression_Obj condition = f->condition()->perform(&eval);     Supports_Block_Obj ff = SASS_MEMORY_NEW(Supports_Block,                                        f->pstate(),-                                       SASS_MEMORY_CAST(Supports_Condition, condition),-                                       operator()(&f->block()));+                                       Cast<Supports_Condition>(condition),+                                       operator()(f->block()));     return ff.detach();   }    Statement_Ptr Expand::operator()(Media_Block_Ptr m)   {-    media_block_stack.push_back(m);-    Expression_Obj mq = m->media_queries()->perform(&eval);+    Media_Block_Obj cpy = SASS_MEMORY_COPY(m);+    // Media_Blocks are prone to have circular references+    // Copy could leak memory if it does not get picked up+    // Looks like we are able to reset block reference for copy+    // Good as it will ensure a low memory overhead for this fix+    // So this is a cheap solution with a minimal price+    ctx.ast_gc.push_back(cpy); cpy->block(0);+    Expression_Obj mq = eval(m->media_queries());     std::string str_mq(mq->to_string(ctx.c_options));     char* str = sass_copy_c_string(str_mq.c_str());     ctx.strings.push_back(str);-    Parser p(Parser::from_c_str(str, ctx, mq->pstate()));-    mq = &p.parse_media_queries(); // re-assign now-    List_Obj ls = SASS_MEMORY_CAST_PTR(List, mq->perform(&eval));-    Block_Obj blk = operator()(&m->block());+    Parser p(Parser::from_c_str(str, ctx, traces, mq->pstate()));+    mq = p.parse_media_queries(); // re-assign now+    cpy->media_queries(mq);+    media_block_stack.push_back(cpy);+    Block_Obj blk = operator()(m->block());     Media_Block_Ptr mm = SASS_MEMORY_NEW(Media_Block,                                       m->pstate(),-                                      ls,-                                      blk,-                                      0);+                                      mq,+                                      blk);     media_block_stack.pop_back();     mm->tabs(m->tabs());     return mm;@@ -198,7 +197,7 @@   Statement_Ptr Expand::operator()(At_Root_Block_Ptr a)   {     Block_Obj ab = a->block();-    Expression_Obj ae = &a->expression();+    Expression_Obj ae = a->expression();      if (ae) ae = ae->perform(&eval);     else ae = SASS_MEMORY_NEW(At_Root_Query, a->pstate());@@ -208,23 +207,23 @@                                         ; -    Block_Obj bb = ab ? operator()(&ab) : NULL;+    Block_Obj bb = ab ? operator()(ab) : NULL;     At_Root_Block_Obj aa = SASS_MEMORY_NEW(At_Root_Block,                                         a->pstate(),                                         bb,-                                        SASS_MEMORY_CAST(At_Root_Query, ae));+                                        Cast<At_Root_Query>(ae));     return aa.detach();   }    Statement_Ptr Expand::operator()(Directive_Ptr a)   {     LOCAL_FLAG(in_keyframes, a->is_keyframes());-    Block_Ptr ab = &a->block();-    Selector_Ptr as = &a->selector();-    Expression_Ptr av = &a->value();+    Block_Ptr ab = a->block();+    Selector_List_Ptr as = a->selector();+    Expression_Ptr av = a->value();     selector_stack.push_back(0);     if (av) av = av->perform(&eval);-    if (as) as = SASS_MEMORY_CAST_PTR(Selector, as->perform(&eval));+    if (as) as = eval(as);     selector_stack.pop_back();     Block_Ptr bb = ab ? operator()(ab) : NULL;     Directive_Ptr aa = SASS_MEMORY_NEW(Directive,@@ -241,14 +240,14 @@     Block_Obj ab = d->block();     String_Obj old_p = d->property();     Expression_Obj prop = old_p->perform(&eval);-    String_Obj new_p = SASS_MEMORY_CAST(String, prop);+    String_Obj new_p = Cast<String>(prop);     // we might get a color back     if (!new_p) {       std::string str(prop->to_string(ctx.c_options));       new_p = SASS_MEMORY_NEW(String_Constant, old_p->pstate(), str);     }     Expression_Obj value = d->value()->perform(&eval);-    Block_Obj bb = ab ? operator()(&ab) : NULL;+    Block_Obj bb = ab ? operator()(ab) : NULL;     if (!bb) {       if (!value || (value->is_invisible() && !d->is_important())) return 0;     }@@ -257,6 +256,7 @@                                         new_p,                                         value,                                         d->is_important(),+                                        d->is_custom_property(),                                         bb);     decl->tabs(d->tabs());     return decl;@@ -265,11 +265,11 @@   Statement_Ptr Expand::operator()(Assignment_Ptr a)   {     Env* env = environment();-    std::string var(a->variable());+    const std::string& var(a->variable());     if (a->is_global()) {       if (a->is_default()) {         if (env->has_global(var)) {-          Expression_Obj e = SASS_MEMORY_CAST(Expression, env->get_global(var));+          Expression_Obj e = Cast<Expression>(env->get_global(var));           if (!e || e->concrete_type() == Expression::NULL_VAL) {             env->set_global(var, a->value()->perform(&eval));           }@@ -288,7 +288,7 @@         while (cur && cur->is_lexical()) {           if (cur->has_local(var)) {             if (AST_Node_Obj node = cur->get_local(var)) {-              Expression_Obj e = SASS_MEMORY_CAST(Expression, node);+              Expression_Obj e = Cast<Expression>(node);               if (!e || e->concrete_type() == Expression::NULL_VAL) {                 cur->set_local(var, a->value()->perform(&eval));               }@@ -304,7 +304,7 @@       }       else if (env->has_global(var)) {         if (AST_Node_Obj node = env->get_global(var)) {-          Expression_Obj e = SASS_MEMORY_CAST(Expression, node);+          Expression_Obj e = Cast<Expression>(node);           if (!e || e->concrete_type() == Expression::NULL_VAL) {             env->set_global(var, a->value()->perform(&eval));           }@@ -328,7 +328,7 @@     Import_Obj result = SASS_MEMORY_NEW(Import, imp->pstate());     if (imp->import_queries() && imp->import_queries()->size()) {       Expression_Obj ex = imp->import_queries()->perform(&eval);-      result->import_queries(SASS_MEMORY_CAST(List, ex));+      result->import_queries(Cast<List>(ex));     }     for ( size_t i = 0, S = imp->urls().size(); i < S; ++i) {       result->urls().push_back(imp->urls()[i]->perform(&eval));@@ -340,10 +340,11 @@    Statement_Ptr Expand::operator()(Import_Stub_Ptr i)   {+    traces.push_back(Backtrace(i->pstate()));     // get parent node from call stack     AST_Node_Obj parent = call_stack.back();-    if (SASS_MEMORY_CAST(Block, parent) == NULL) {-      error("Import directives may not be used within control directives or mixins.", i->pstate());+    if (Cast<Block>(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(@@ -352,10 +353,18 @@       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 std::string& abs_path(i->resource().abs_path);-    append_block(&ctx.sheets.at(abs_path).root);+    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;   } @@ -382,8 +391,13 @@    Statement_Ptr Expand::operator()(Comment_Ptr 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_Ptr rv = SASS_MEMORY_NEW(Comment, c->pstate(), SASS_MEMORY_CAST_PTR(String, c->text()->perform(&eval)), c->is_important());+    Comment_Ptr rv = SASS_MEMORY_NEW(Comment, c->pstate(), Cast<String>(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;@@ -396,10 +410,10 @@     call_stack.push_back(i);     Expression_Obj rv = i->predicate()->perform(&eval);     if (*rv) {-      append_block(&i->block());+      append_block(i->block());     }     else {-      Block_Ptr alt = &i->alternative();+      Block_Ptr alt = i->alternative();       if (alt) append_block(alt);     }     call_stack.pop_back();@@ -414,20 +428,22 @@     std::string variable(f->variable());     Expression_Obj low = f->lower_bound()->perform(&eval);     if (low->concrete_type() != Expression::NUMBER) {-      throw Exception::TypeMismatch(*low, "integer");+      traces.push_back(Backtrace(low->pstate()));+      throw Exception::TypeMismatch(traces, *low, "integer");     }     Expression_Obj high = f->upper_bound()->perform(&eval);     if (high->concrete_type() != Expression::NUMBER) {-      throw Exception::TypeMismatch(*high, "integer");+      traces.push_back(Backtrace(high->pstate()));+      throw Exception::TypeMismatch(traces, *high, "integer");     }-    Number_Obj sass_start = SASS_MEMORY_CAST(Number, low);-    Number_Obj sass_end = SASS_MEMORY_CAST(Number, high);+    Number_Obj sass_start = Cast<Number>(low);+    Number_Obj sass_end = Cast<Number>(high);     // check if units are valid for sequence     if (sass_start->unit() != sass_end->unit()) {       std::stringstream msg; msg << "Incompatible units: '"         << sass_start->unit() << "' and '"         << sass_end->unit() << "'.";-      error(msg.str(), low->pstate(), backtrace());+      error(msg.str(), low->pstate(), traces);     }     double start = sass_start->value();     double end = sass_end->value();@@ -435,17 +451,14 @@     Env env(environment(), true);     env_stack.push_back(&env);     call_stack.push_back(f);-    Number_Obj it = SASS_MEMORY_NEW(Number, low->pstate(), start, sass_end->unit());-    env.set_local(variable, &it);-    Block_Ptr body = &f->block();+    Block_Ptr body = f->block();     if (start < end) {       if (f->is_inclusive()) ++end;       for (double i = start;            i < end;            ++i) {-        it = SASS_MEMORY_COPY(it);-        it->value(i);-        env.set_local(variable, &it);+        Number_Obj it = SASS_MEMORY_NEW(Number, low->pstate(), i, sass_end->unit());+        env.set_local(variable, it);         append_block(body);       }     } else {@@ -453,9 +466,8 @@       for (double i = start;            i > end;            --i) {-        it = SASS_MEMORY_COPY(it);-        it->value(i);-        env.set_local(variable, &it);+        Number_Obj it = SASS_MEMORY_NEW(Number, low->pstate(), i, sass_end->unit());+        env.set_local(variable, it);         append_block(body);       }     }@@ -473,25 +485,25 @@     List_Obj list = 0;     Map_Obj map;     if (expr->concrete_type() == Expression::MAP) {-      map = SASS_MEMORY_CAST(Map, expr);+      map = Cast<Map>(expr);     }-    else if (Selector_List_Ptr ls = SASS_MEMORY_CAST(Selector_List, expr)) {+    else if (Selector_List_Ptr ls = Cast<Selector_List>(expr)) {       Listize listize;       Expression_Obj rv = ls->perform(&listize);-      list = SASS_MEMORY_CAST(List, rv);+      list = Cast<List>(rv);     }     else if (expr->concrete_type() != Expression::LIST) {       list = SASS_MEMORY_NEW(List, expr->pstate(), 1, SASS_COMMA);       list->append(expr);     }     else {-      list = SASS_MEMORY_CAST(List, expr);+      list = Cast<List>(expr);     }     // remember variables and then reset them     Env env(environment(), true);     env_stack.push_back(&env);     call_stack.push_back(e);-    Block_Ptr body = &e->block();+    Block_Ptr body = e->block();      if (map) {       for (auto key : map->keys()) {@@ -502,43 +514,43 @@           List_Obj variable = SASS_MEMORY_NEW(List, map->pstate(), 2, SASS_SPACE);           variable->append(k);           variable->append(v);-          env.set_local(variables[0], &variable);+          env.set_local(variables[0], variable);         } else {-          env.set_local(variables[0], &k);-          env.set_local(variables[1], &v);+          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 && SASS_MEMORY_CAST(Selector_List, list)) {-        list = SASS_MEMORY_CAST(List, list);+      if (list->length() == 1 && Cast<Selector_List>(list)) {+        list = Cast<List>(list);       }       for (size_t i = 0, L = list->length(); i < L; ++i) {-        Expression_Obj e = list->at(i);+        Expression_Obj item = list->at(i);         // unwrap value if the expression is an argument-        if (Argument_Obj arg = SASS_MEMORY_CAST(Argument, e)) e = arg->value();+        if (Argument_Obj arg = Cast<Argument>(item)) item = arg->value();         // check if we got passed a list of args (investigate)-        if (List_Obj scalars = SASS_MEMORY_CAST(List, e)) {+        if (List_Obj scalars = Cast<List>(item)) {           if (variables.size() == 1) {             List_Obj var = scalars;             // if (arglist) var = (*scalars)[0];-            env.set_local(variables[0], &var);+            env.set_local(variables[0], var);           } else {             for (size_t j = 0, K = variables.size(); j < K; ++j) {               Expression_Obj res = j >= scalars->length()                 ? SASS_MEMORY_NEW(Null, expr->pstate())                 : (*scalars)[j]->perform(&eval);-              env.set_local(variables[j], &res);+              env.set_local(variables[j], res);             }           }         } else {           if (variables.size() > 0) {-            env.set_local(variables.at(0), &e);+            env.set_local(variables.at(0), item);             for (size_t j = 1, K = variables.size(); j < K; ++j) {               Expression_Obj res = SASS_MEMORY_NEW(Null, expr->pstate());-              env.set_local(variables[j], &res);+              env.set_local(variables[j], res);             }           }         }@@ -553,12 +565,12 @@   Statement_Ptr Expand::operator()(While_Ptr w)   {     Expression_Obj pred = w->predicate();-    Block_Ptr body = &w->block();+    Block_Ptr body = w->block();     Env env(environment(), true);     env_stack.push_back(&env);     call_stack.push_back(w);     Expression_Obj cond = pred->perform(&eval);-    while (*&cond) {+    while (!cond->is_false()) {       append_block(body);       cond = pred->perform(&eval);     }@@ -569,21 +581,21 @@    Statement_Ptr Expand::operator()(Return_Ptr r)   {-    error("@return may only be used within a function", r->pstate(), backtrace());+    error("@return may only be used within a function", r->pstate(), traces);     return 0;   }     void Expand::expand_selector_list(Selector_Obj s, Selector_List_Obj extender) { -    if (Selector_List_Obj sl = SASS_MEMORY_CAST(Selector_List, s)) {+    if (Selector_List_Obj sl = Cast<Selector_List>(s)) {       for (Complex_Selector_Obj complex_selector : sl->elements()) {         Complex_Selector_Obj tail = complex_selector;         while (tail) {           if (tail->head()) for (Simple_Selector_Obj header : tail->head()->elements()) {-            if (SASS_MEMORY_CAST(Parent_Selector, header) == NULL) continue; // skip all others+            if (Cast<Parent_Selector>(header) == NULL) continue; // skip all others             std::string sel_str(complex_selector->to_string(ctx.c_options));-            error("Can't extend " + sel_str + ": can't extend parent selectors", header->pstate(), backtrace());+            error("Can't extend " + sel_str + ": can't extend parent selectors", header->pstate(), traces);           }           tail = tail->tail();         }@@ -591,20 +603,20 @@     }  -    Selector_List_Obj contextualized = SASS_MEMORY_CAST_PTR(Selector_List, s->perform(&eval));+    Selector_List_Obj contextualized = Cast<Selector_List>(s->perform(&eval));     if (contextualized == false) return;     for (auto complex_sel : contextualized->elements()) {       Complex_Selector_Obj c = complex_sel;       if (!c->head() || c->tail()) {         std::string sel_str(contextualized->to_string(ctx.c_options));-        error("Can't extend " + sel_str + ": can't extend nested selectors", c->pstate(), backtrace());+        error("Can't extend " + sel_str + ": can't extend nested selectors", c->pstate(), traces);       }-      Compound_Selector_Obj placeholder = c->head();-      if (contextualized->is_optional()) placeholder->is_optional(true);+      Compound_Selector_Obj target = c->head();+      if (contextualized->is_optional()) target->is_optional(true);       for (size_t i = 0, L = extender->length(); i < L; ++i) {         Complex_Selector_Obj sel = (*extender)[i];         if (!(sel->head() && sel->head()->length() > 0 &&-            SASS_MEMORY_CAST(Parent_Selector, (*sel->head())[0])))+            Cast<Parent_Selector>((*sel->head())[0])))         {           Compound_Selector_Obj hh = SASS_MEMORY_NEW(Compound_Selector, (*extender)[i]->pstate());           hh->media_block((*extender)[i]->media_block());@@ -613,13 +625,13 @@           if (sel->has_line_feed()) ssel->has_line_feed(true);           Parent_Selector_Obj ps = SASS_MEMORY_NEW(Parent_Selector, (*extender)[i]->pstate());           ps->media_block((*extender)[i]->media_block());-          hh->append(&ps);+          hh->append(ps);           ssel->tail(sel);           ssel->head(hh);           sel = ssel;         }         // if (c->has_line_feed()) sel->has_line_feed(true);-        ctx.subset_map.put(placeholder, std::make_pair(sel, placeholder));+        ctx.subset_map.put(target, std::make_pair(sel, target));       }     } @@ -627,31 +639,30 @@    Statement* Expand::operator()(Extension_Ptr e)   {-    if (Selector_List_Obj extender = SASS_MEMORY_CAST(Selector_List, selector())) {-      Selector_Obj s = e->selector();-      Selector_List_Obj sl = NULL;-      // check if we already have a valid selector list-      if ((sl = SASS_MEMORY_CAST(Selector_List, s))) {}-      // convert selector schema to a selector list-      else if (Selector_Schema_Obj schema = SASS_MEMORY_CAST(Selector_Schema, s)) {+    if (Selector_List_Ptr extender = selector()) {+      Selector_List_Ptr sl = e->selector();+      // abort on invalid selector+      if (sl == NULL) return NULL;+      if (Selector_Schema_Ptr schema = sl->schema()) {         if (schema->has_real_parent_ref()) {-          sl = eval(&schema);+          // put root block on stack again (ignore parents)+          // selector schema must not connect in eval!+          block_stack.push_back(block_stack.at(1));+          sl = eval(sl->schema());+          block_stack.pop_back();         } else {           selector_stack.push_back(0);-          sl = eval(&schema);-          sl->remove_parent_selectors();+          sl = eval(sl->schema());           selector_stack.pop_back();         }       }-      // abort on invalid selector-      if (sl.isNull()) return NULL;       for (Complex_Selector_Obj cs : sl->elements()) {         if (!cs.isNull() && !cs->head().isNull()) {           cs->head()->media_block(media_block_stack.back());         }       }       selector_stack.push_back(0);-      expand_selector_list(&sl, extender);+      expand_selector_list(sl, extender);       selector_stack.pop_back();     }     return 0;@@ -662,7 +673,7 @@     Env* env = environment();     Definition_Obj dd = SASS_MEMORY_COPY(d);     env->local_frame()[d->name() +-                        (d->type() == Definition::MIXIN ? "[m]" : "[f]")] = &dd;+                        (d->type() == Definition::MIXIN ? "[m]" : "[f]")] = dd;      if (d->type() == Definition::FUNCTION && (       Prelexer::calc_fn_call(d->name().c_str()) ||@@ -671,9 +682,9 @@       d->name() == "url"     )) {       deprecated(-        "Naming a function \"" + d->name() + "\" is disallowed",+        "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.",-         d->pstate()+        false, d->pstate()       );     } @@ -684,9 +695,8 @@    Statement_Ptr Expand::operator()(Mixin_Call_Ptr c)   {-     if (recursions > maxRecursion) {-      throw Exception::StackError(*c);+      throw Exception::StackError(traces, *c);     }      recursions ++;@@ -694,19 +704,28 @@     Env* env = environment();     std::string full_name(c->name() + "[m]");     if (!env->has(full_name)) {-      error("no mixin named " + c->name(), c->pstate(), backtrace());+      error("no mixin named " + c->name(), c->pstate(), traces);     }-    Definition_Obj def = SASS_MEMORY_CAST(Definition, (*env)[full_name]);+    Definition_Obj def = Cast<Definition>((*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(), backtrace());+      error("Mixin \"" + c->name() + "\" does not accept a content block.", c->pstate(), traces);     }     Expression_Obj rv = c->arguments()->perform(&eval);-    Arguments_Obj args = SASS_MEMORY_CAST(Arguments, rv);-    Backtrace new_bt(backtrace(), c->pstate(), ", in mixin `" + c->name() + "`");-    backtrace_stack.push_back(&new_bt);+    Arguments_Obj args = Cast<Arguments>(rv);+    std::string msg(", in mixin `" + c->name() + "`");+    traces.push_back(Backtrace(c->pstate(), msg));+    ctx.callee_stack.push_back({+      c->name().c_str(),+      c->pstate().path,+      c->pstate().line + 1,+      c->pstate().column + 1,+      SASS_CALLEE_MIXIN,+      { env }+    });+     Env new_env(def->environment());     env_stack.push_back(&new_env);     if (c->block()) {@@ -718,7 +737,7 @@                                           c->block(),                                           Definition::MIXIN);       thunk->environment(env);-      new_env.local_frame()["@content[m]"] = &thunk;+      new_env.local_frame()["@content[m]"] = thunk;     }      bind(std::string("Mixin"), c->name(), params, args, &ctx, &new_env, &eval);@@ -726,16 +745,24 @@     Block_Obj trace_block = SASS_MEMORY_NEW(Block, c->pstate());     Trace_Obj trace = SASS_MEMORY_NEW(Trace, c->pstate(), c->name(), trace_block); --    block_stack.push_back(&trace_block);+    env->set_global("is_in_mixin", bool_true);+    if (Block_Ptr pr = block_stack.back()) {+      trace_block->is_root(pr->is_root());+    }+    block_stack.push_back(trace_block);     for (auto bb : body->elements()) {+      if (Ruleset_Ptr r = Cast<Ruleset>(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();-    backtrace_stack.pop_back();+    traces.pop_back();      recursions --;     return trace.detach();@@ -756,7 +783,7 @@                                        "@content",                                        SASS_MEMORY_NEW(Arguments, c->pstate())); -    Trace_Obj trace = SASS_MEMORY_CAST_PTR(Trace, call->perform(this));+    Trace_Obj trace = Cast<Trace>(call->perform(this));      if (block_stack.back()->is_root()) {       selector_stack.pop_back();@@ -770,8 +797,8 @@   {     std::string err =std:: string("`Expand` doesn't handle ") + typeid(*n).name();     String_Quoted_Obj msg = SASS_MEMORY_NEW(String_Quoted, ParserState("[WARN]"), err);-    error("unknown internal error; please contact the LibSass maintainers", n->pstate(), backtrace());-    return SASS_MEMORY_NEW(Warning, ParserState("[WARN]"), &msg);+    error("unknown internal error; please contact the LibSass maintainers", n->pstate(), traces);+    return SASS_MEMORY_NEW(Warning, ParserState("[WARN]"), msg);   }    // process and add to last block on stack@@ -779,7 +806,7 @@   {     if (b->is_root()) call_stack.push_back(b);     for (size_t i = 0, L = b->length(); i < L; ++i) {-      Statement_Ptr stm = &b->at(i);+      Statement_Ptr stm = b->at(i);       Statement_Obj ith = stm->perform(this);       if (ith) block_stack.back()->append(ith);     }
libsass/src/expand.hpp view
@@ -13,18 +13,16 @@   class Listize;   class Context;   class Eval;-  typedef Environment<AST_Node_Obj> Env;   struct Backtrace;    class Expand : public Operation_CRTP<Statement_Ptr, Expand> {   public:      Env* environment();-    Context& context();     Selector_List_Obj selector();-    Backtrace* backtrace();      Context&          ctx;+    Backtraces&       traces;     Eval              eval;     size_t            recursions;     bool              in_keyframes;@@ -37,15 +35,16 @@     std::vector<AST_Node_Obj>      call_stack;     std::vector<Selector_List_Obj> selector_stack;     std::vector<Media_Block_Ptr>   media_block_stack;-    std::vector<Backtrace*>        backtrace_stack; +    Boolean_Obj bool_true;+     Statement_Ptr fallback_impl(AST_Node_Ptr n);    private:     void expand_selector_list(Selector_Obj, Selector_List_Obj extender);    public:-    Expand(Context&, Env*, Backtrace*, std::vector<Selector_List_Obj>* stack = NULL);+    Expand(Context&, Env*, std::vector<Selector_List_Obj>* stack = NULL);     ~Expand() { }      Block_Ptr operator()(Block_Ptr);
libsass/src/extend.cpp view
@@ -4,6 +4,7 @@ #include "backtrace.hpp" #include "paths.hpp" #include "parser.hpp"+#include "expand.hpp" #include "node.hpp" #include "sass_util.hpp" #include "remove_placeholders.hpp"@@ -61,8 +62,6 @@ namespace Sass {  -  typedef std::pair<Complex_Selector_Obj, Compound_Selector_Obj> ExtensionPair;-  typedef std::vector<ExtensionPair> SubsetMapEntries;  #ifdef DEBUG @@ -112,11 +111,6 @@   }    // Print a string representation of a Compound_Selector-    typedef std::pair<Compound_Selector_Obj, Complex_Selector_Obj> SelsNewSeqPair;-    typedef std::vector<SelsNewSeqPair> SelsNewSeqPairCollection;---  // Print a string representation of a Compound_Selector   static void printCompoundSelector(Compound_Selector_Ptr pCompoundSelector, const char* message=NULL, bool newline=true) {      if (message) {@@ -186,14 +180,14 @@     }   } -  static void printSelsNewSeqPairCollection(SelsNewSeqPairCollection& collection, const char* message=NULL, bool newline=true) {+  static void printSelsNewSeqPairCollection(SubSetMapLookups& collection, const char* message=NULL, bool newline=true) {      if (message) {       std::cerr << message;     }     bool first = true;     std::cerr << "[";-    for(SelsNewSeqPair& pair : collection) {+    for(SubSetMapLookup& pair : collection) {       if (first) {         first = false;       } else {@@ -212,8 +206,8 @@     }   } -  // Print a string representation of a SourcesSet-  static void printSourcesSet(SourcesSet& sources, Context& ctx, const char* message=NULL, bool newline=true) {+  // Print a string representation of a ComplexSelectorSet+  static void printSourcesSet(ComplexSelectorSet& sources, const char* message=NULL, bool newline=true) {      if (message) {       std::cerr << message;@@ -223,17 +217,17 @@     // the differences we see when debug printing.     typedef std::deque<std::string> SourceStrings;     SourceStrings sourceStrings;-    for (SourcesSet::iterator iterator = sources.begin(), iteratorEnd = sources.end(); iterator != iteratorEnd; ++iterator) {+    for (ComplexSelectorSet::iterator iterator = sources.begin(), iteratorEnd = sources.end(); iterator != iteratorEnd; ++iterator) {       Complex_Selector_Ptr pSource = *iterator;       std::stringstream sstream;-      sstream << complexSelectorToNode(pSource, ctx);+      sstream << complexSelectorToNode(pSource);       sourceStrings.push_back(sstream.str());     }      // Sort to get consistent output     std::sort(sourceStrings.begin(), sourceStrings.end()); -    std::cerr << "SourcesSet[";+    std::cerr << "ComplexSelectorSet[";     for (SourceStrings::iterator iterator = sourceStrings.begin(), iteratorEnd = sourceStrings.end(); iterator != iteratorEnd; ++iterator) {       std::string source = *iterator;       if (iterator != sourceStrings.begin()) {@@ -249,10 +243,10 @@   }  -  std::ostream& operator<<(std::ostream& os, SubsetMapEntries& entries) {+  std::ostream& operator<<(std::ostream& os, SubSetMapPairs& entries) {     os << "SUBSET_MAP_ENTRIES["; -    for (SubsetMapEntries::iterator iterator = entries.begin(), endIterator = entries.end(); iterator != endIterator; ++iterator) {+    for (SubSetMapPairs::iterator iterator = entries.begin(), endIterator = entries.end(); iterator != endIterator; ++iterator) {       Complex_Selector_Obj pExtComplexSelector = iterator->first;    // The selector up to where the @extend is (ie, the thing to merge)       Compound_Selector_Obj pExtCompoundSelector = iterator->second; // The stuff after the @extend @@ -286,17 +280,16 @@   } #endif -  static bool parentSuperselector(Complex_Selector_Ptr pOne, Complex_Selector_Ptr pTwo, Context& ctx) {+  static bool parentSuperselector(Complex_Selector_Ptr pOne, Complex_Selector_Ptr pTwo) {     // TODO: figure out a better way to create a Complex_Selector from scratch     // TODO: There's got to be a better way. This got ugly quick...-    Position noPosition(-1, -1, -1);     Element_Selector_Obj fakeParent = SASS_MEMORY_NEW(Element_Selector, ParserState("[FAKE]"), "temp");     Compound_Selector_Obj fakeHead = SASS_MEMORY_NEW(Compound_Selector, ParserState("[FAKE]"), 1 /*size*/);-    fakeHead->elements().push_back(&fakeParent);-    Complex_Selector_Obj fakeParentContainer = SASS_MEMORY_NEW(Complex_Selector, ParserState("[FAKE]"), Complex_Selector::ANCESTOR_OF, &fakeHead /*head*/, NULL /*tail*/);+    fakeHead->elements().push_back(fakeParent);+    Complex_Selector_Obj fakeParentContainer = SASS_MEMORY_NEW(Complex_Selector, ParserState("[FAKE]"), Complex_Selector::ANCESTOR_OF, fakeHead /*head*/, NULL /*tail*/); -    pOne->set_innermost(&fakeParentContainer, Complex_Selector::ANCESTOR_OF);-    pTwo->set_innermost(&fakeParentContainer, Complex_Selector::ANCESTOR_OF);+    pOne->set_innermost(fakeParentContainer, Complex_Selector::ANCESTOR_OF);+    pTwo->set_innermost(fakeParentContainer, Complex_Selector::ANCESTOR_OF);      bool isSuperselector = pOne->is_superselector_of(pTwo); @@ -306,19 +299,19 @@     return isSuperselector;   } -  void nodeToComplexSelectorDeque(const Node& node, ComplexSelectorDeque& out, Context& ctx) {+  void nodeToComplexSelectorDeque(const Node& node, ComplexSelectorDeque& out) {     for (NodeDeque::iterator iter = node.collection()->begin(), iterEnd = node.collection()->end(); iter != iterEnd; iter++) {       Node& child = *iter;-      out.push_back(nodeToComplexSelector(child, ctx));+      out.push_back(nodeToComplexSelector(child));     }   } -  Node complexSelectorDequeToNode(const ComplexSelectorDeque& deque, Context& ctx) {+  Node complexSelectorDequeToNode(const ComplexSelectorDeque& deque) {     Node result = Node::createCollection();      for (ComplexSelectorDeque::const_iterator iter = deque.begin(), iterEnd = deque.end(); iter != iterEnd; iter++) {       Complex_Selector_Obj pChild = *iter;-      result.collection()->push_back(complexSelectorToNode(&pChild, ctx));+      result.collection()->push_back(complexSelectorToNode(pChild));     }      return result;@@ -326,9 +319,7 @@    class LcsCollectionComparator {   public:-    LcsCollectionComparator(Context& ctx) : mCtx(ctx) {}--    Context& mCtx;+    LcsCollectionComparator() {}      bool operator()(Complex_Selector_Obj pOne, Complex_Selector_Obj pTwo, Complex_Selector_Obj& pOut) const {       /*@@ -341,7 +332,7 @@         end       */ -      if (selectors_equal(*pOne, *pTwo, true /*simpleSelectorOrderDependent*/)) {+      if (*pOne == *pTwo) {         pOut = pOne;         return true;       }@@ -350,12 +341,12 @@         return false;       } -      if (parentSuperselector(&pOne, &pTwo, mCtx)) {+      if (parentSuperselector(pOne, pTwo)) {         pOut = pTwo;         return true;       } -      if (parentSuperselector(&pTwo, &pOne, mCtx)) {+      if (parentSuperselector(pTwo, pOne)) {         pOut = pOne;         return true;       }@@ -420,7 +411,7 @@       for (size_t j = 1; j < y.size(); j++) {         Complex_Selector_Obj pCompareOut; -        if (comparator(&x[i], &y[j], pCompareOut)) {+        if (comparator(x[i], y[j], pCompareOut)) {           c[i][j] = c[i - 1][j - 1] + 1;         } else {           c[i][j] = std::max(c[i][j - 1], c[i - 1][j]);@@ -445,7 +436,7 @@    http://en.wikipedia.org/wiki/Longest_common_subsequence_problem   */-  void lcs(ComplexSelectorDeque& x, ComplexSelectorDeque& y, const LcsCollectionComparator& comparator, Context& ctx, ComplexSelectorDeque& out) {+  void lcs(ComplexSelectorDeque& x, ComplexSelectorDeque& y, const LcsCollectionComparator& comparator, ComplexSelectorDeque& out) {     //DEBUG_PRINTLN(LCS, "LCS: X=" << x << " Y=" << y)     // TODO: make printComplexSelectorDeque and use DEBUG_EXEC AND DEBUG_PRINTLN HERE to get equivalent output @@ -525,7 +516,7 @@   /*    - IMPROVEMENT: We could probably work directly in the output trimmed deque.    */-  static Node trim(Node& seqses, Context& ctx, bool isReplace) {+  Node Extend::trim(Node& seqses, bool isReplace) {     // See the comments in the above ruby code before embarking on understanding this function.      // Avoid poor performance in extreme cases.@@ -558,7 +549,7 @@       for (NodeDeque::iterator seqs1Iter = seqs1.collection()->begin(), seqs1EndIter = seqs1.collection()->end(); seqs1Iter != seqs1EndIter; ++seqs1Iter) {         Node& seq1 = *seqs1Iter; -        Complex_Selector_Obj pSeq1 = nodeToComplexSelector(seq1, ctx);+        Complex_Selector_Obj pSeq1 = nodeToComplexSelector(seq1);          // Compute the maximum specificity. This requires looking at the "sources" of the sequence. See SimpleSequence.sources in the ruby code         // for a good description of sources.@@ -569,12 +560,12 @@         // best guess at this point is that we're cloning an object somewhere and maintaining the sources when we shouldn't be. This is purely         // a guess though.         unsigned long maxSpecificity = isReplace ? pSeq1->specificity() : 0;-        SourcesSet sources = pSeq1->sources();+        ComplexSelectorSet sources = pSeq1->sources();          DEBUG_PRINTLN(TRIM, "TRIM SEQ1: " << seq1)-        DEBUG_EXEC(TRIM, printSourcesSet(sources, ctx, "TRIM SOURCES: "))+        DEBUG_EXEC(TRIM, printSourcesSet(sources, "TRIM SOURCES: ")) -        for (SourcesSet::iterator sourcesSetIterator = sources.begin(), sourcesSetIteratorEnd = sources.end(); sourcesSetIterator != sourcesSetIteratorEnd; ++sourcesSetIterator) {+        for (ComplexSelectorSet::iterator sourcesSetIterator = sources.begin(), sourcesSetIteratorEnd = sources.end(); sourcesSetIterator != sourcesSetIteratorEnd; ++sourcesSetIterator) {           const Complex_Selector_Obj& pCurrentSelector = *sourcesSetIterator;           maxSpecificity = std::max(maxSpecificity, pCurrentSelector->specificity());         }@@ -605,7 +596,7 @@           for (NodeDeque::iterator seqs2Iter = seqs2.collection()->begin(), seqs2IterEnd = seqs2.collection()->end(); seqs2Iter != seqs2IterEnd; ++seqs2Iter) {             Node& seq2 = *seqs2Iter; -            Complex_Selector_Obj pSeq2 = nodeToComplexSelector(seq2, ctx);+            Complex_Selector_Obj pSeq2 = nodeToComplexSelector(seq2);              DEBUG_PRINTLN(TRIM, "SEQ2 SPEC: " << pSeq2->specificity())             DEBUG_PRINTLN(TRIM, "IS SPEC: " << pSeq2->specificity() << " >= " << maxSpecificity << " " << (pSeq2->specificity() >= maxSpecificity ? "true" : "false"))@@ -649,19 +640,18 @@   -  static bool parentSuperselector(const Node& one, const Node& two, Context& ctx) {+  static bool parentSuperselector(const Node& one, const Node& two) {     // TODO: figure out a better way to create a Complex_Selector from scratch     // TODO: There's got to be a better way. This got ugly quick...-    Position noPosition(-1, -1, -1);     Element_Selector_Obj fakeParent = SASS_MEMORY_NEW(Element_Selector, ParserState("[FAKE]"), "temp");     Compound_Selector_Obj fakeHead = SASS_MEMORY_NEW(Compound_Selector, ParserState("[FAKE]"), 1 /*size*/);-    fakeHead->elements().push_back(&fakeParent);-    Complex_Selector_Obj fakeParentContainer = SASS_MEMORY_NEW(Complex_Selector, ParserState("[FAKE]"), Complex_Selector::ANCESTOR_OF, &fakeHead /*head*/, NULL /*tail*/);+    fakeHead->elements().push_back(fakeParent);+    Complex_Selector_Obj fakeParentContainer = SASS_MEMORY_NEW(Complex_Selector, ParserState("[FAKE]"), Complex_Selector::ANCESTOR_OF, fakeHead /*head*/, NULL /*tail*/); -    Complex_Selector_Obj pOneWithFakeParent = nodeToComplexSelector(one, ctx);-    pOneWithFakeParent->set_innermost(&fakeParentContainer, Complex_Selector::ANCESTOR_OF);-    Complex_Selector_Obj pTwoWithFakeParent = nodeToComplexSelector(two, ctx);-    pTwoWithFakeParent->set_innermost(&fakeParentContainer, Complex_Selector::ANCESTOR_OF);+    Complex_Selector_Obj pOneWithFakeParent = nodeToComplexSelector(one);+    pOneWithFakeParent->set_innermost(fakeParentContainer, Complex_Selector::ANCESTOR_OF);+    Complex_Selector_Obj pTwoWithFakeParent = nodeToComplexSelector(two);+    pTwoWithFakeParent->set_innermost(fakeParentContainer, Complex_Selector::ANCESTOR_OF);      return pOneWithFakeParent->is_superselector_of(pTwoWithFakeParent);   }@@ -669,14 +659,13 @@    class ParentSuperselectorChunker {   public:-    ParentSuperselectorChunker(Node& lcs, Context& ctx) : mLcs(lcs), mCtx(ctx) {}+    ParentSuperselectorChunker(Node& lcs) : mLcs(lcs) {}     Node& mLcs;-    Context& mCtx;      bool operator()(const Node& seq) const {       // {|s| parent_superselector?(s.first, lcs.first)}       if (seq.collection()->size() == 0) return false;-      return parentSuperselector(seq.collection()->front(), mLcs.collection()->front(), mCtx);+      return parentSuperselector(seq.collection()->front(), mLcs.collection()->front());     }   }; @@ -730,7 +719,7 @@     }      Node chunk2 = Node::createCollection();-    while (!chunker(seq2)) {+    while (!seq2.collection()->empty() && !chunker(seq2)) {       chunk2.collection()->push_back(seq2.collection()->front());       seq2.collection()->pop_front();     }@@ -772,7 +761,7 @@   }  -  static Node groupSelectors(Node& seq, Context& ctx) {+  static Node groupSelectors(Node& seq) {     Node newSeq = Node::createCollection();      Node tail = Node::createCollection();@@ -832,7 +821,7 @@         return (newline ? ["\n"] : []) + (ops1.size > ops2.size ? ops1 : ops2)       end   */-  static Node mergeInitialOps(Node& seq1, Node& seq2, Context& ctx) {+  static Node mergeInitialOps(Node& seq1, Node& seq2) {     Node ops1 = Node::createCollection();     Node ops2 = Node::createCollection(); @@ -846,9 +835,9 @@      // If neither sequence is a subsequence of the other, they cannot be merged successfully     DefaultLcsComparator lcsDefaultComparator;-    Node opsLcs = lcs(ops1, ops2, lcsDefaultComparator, ctx);+    Node opsLcs = lcs(ops1, ops2, lcsDefaultComparator); -    if (!(nodesEqual(opsLcs, ops1, true) || nodesEqual(opsLcs, ops2, true))) {+    if (!(opsLcs == ops1 || opsLcs == ops2)) {       return Node::createNil();     } @@ -923,7 +912,7 @@         end       end   */-  static Node mergeFinalOps(Node& seq1, Node& seq2, Context& ctx, Node& res) {+  static Node mergeFinalOps(Node& seq1, Node& seq2, Node& res) {      Node ops1 = Node::createCollection();     Node ops2 = Node::createCollection();@@ -941,11 +930,11 @@      if (ops1.collection()->size() > 1 || ops2.collection()->size() > 1) {       DefaultLcsComparator lcsDefaultComparator;-      Node opsLcs = lcs(ops1, ops2, lcsDefaultComparator, ctx);+      Node opsLcs = lcs(ops1, ops2, lcsDefaultComparator);        // If there are multiple operators, something hacky's going on. If one is a supersequence of the other, use that, otherwise give up. -      if (!(nodesEqual(opsLcs, ops1, true) || nodesEqual(opsLcs, ops2, true))) {+      if (!(opsLcs == ops1 || opsLcs == ops2)) {         return Node::createNil();       } @@ -988,7 +977,7 @@            Complex_Selector_Obj pMergedWrapper = SASS_MEMORY_CLONE(sel1.selector()); // Clone the Complex_Selector to get back to something we can transform to a node once we replace the head with the unification result           // TODO: does subject matter? Ruby: return unless merged = sel1.unify(sel2.members, sel2.subject?)-          Compound_Selector_Ptr pMerged = sel1.selector()->head()->unify_with(&sel2.selector()->head(), ctx);+          Compound_Selector_Ptr pMerged = sel1.selector()->head()->unify_with(sel2.selector()->head());           pMergedWrapper->head(pMerged);            DEBUG_EXEC(ALL, printCompoundSelector(pMerged, "MERGED: "))@@ -1011,7 +1000,7 @@            if (pMerged) {             Node mergedPerm = Node::createCollection();-            mergedPerm.collection()->push_back(Node::createSelector(&pMergedWrapper, ctx));+            mergedPerm.collection()->push_back(Node::createSelector(pMergedWrapper));             mergedPerm.collection()->push_back(Node::createCombinator(Complex_Selector::PRECEDES));             newRes.collection()->push_back(mergedPerm);           }@@ -1025,12 +1014,10 @@       } else if (((op1.combinator() == Complex_Selector::PRECEDES && op2.combinator() == Complex_Selector::ADJACENT_TO)) || ((op1.combinator() == Complex_Selector::ADJACENT_TO && op2.combinator() == Complex_Selector::PRECEDES))) {            Node tildeSel = sel1;-          Node tildeOp = op1;           Node plusSel = sel2;           Node plusOp = op2;           if (op1.combinator() != Complex_Selector::PRECEDES) {             tildeSel = sel2;-            tildeOp = op2;             plusSel = sel1;             plusOp = op1;           }@@ -1047,7 +1034,7 @@              Complex_Selector_Obj pMergedWrapper = SASS_MEMORY_CLONE(plusSel.selector()); // Clone the Complex_Selector to get back to something we can transform to a node once we replace the head with the unification result             // TODO: does subject matter? Ruby: merged = plus_sel.unify(tilde_sel.members, tilde_sel.subject?)-            Compound_Selector_Ptr pMerged = plusSel.selector()->head()->unify_with(&tildeSel.selector()->head(), ctx);+            Compound_Selector_Ptr pMerged = plusSel.selector()->head()->unify_with(tildeSel.selector()->head());             pMergedWrapper->head(pMerged);              DEBUG_EXEC(ALL, printCompoundSelector(pMerged, "MERGED: "))@@ -1063,7 +1050,7 @@              if (pMerged) {               Node mergedPerm = Node::createCollection();-              mergedPerm.collection()->push_back(Node::createSelector(&pMergedWrapper, ctx));+              mergedPerm.collection()->push_back(Node::createSelector(pMergedWrapper));               mergedPerm.collection()->push_back(Node::createCombinator(Complex_Selector::ADJACENT_TO));               newRes.collection()->push_back(mergedPerm);             }@@ -1096,7 +1083,7 @@          Complex_Selector_Obj pMergedWrapper = SASS_MEMORY_CLONE(sel1.selector()); // Clone the Complex_Selector to get back to something we can transform to a node once we replace the head with the unification result         // TODO: does subject matter? Ruby: return unless merged = sel1.unify(sel2.members, sel2.subject?)-        Compound_Selector_Ptr pMerged = sel1.selector()->head()->unify_with(&sel2.selector()->head(), ctx);+        Compound_Selector_Ptr pMerged = sel1.selector()->head()->unify_with(sel2.selector()->head());         pMergedWrapper->head(pMerged);          DEBUG_EXEC(ALL, printCompoundSelector(pMerged, "MERGED: "))@@ -1106,7 +1093,7 @@         }          res.collection()->push_front(op1);-        res.collection()->push_front(Node::createSelector(&pMergedWrapper, ctx));+        res.collection()->push_front(Node::createSelector(pMergedWrapper));          DEBUG_PRINTLN(ALL, "RESULT: " << res) @@ -1114,7 +1101,7 @@         return Node::createNil();       } -      return mergeFinalOps(seq1, seq2, ctx, res);+      return mergeFinalOps(seq1, seq2, res);      } else if (!ops1.collection()->empty()) { @@ -1129,7 +1116,7 @@       res.collection()->push_front(seq1.collection()->back());       seq1.collection()->pop_back(); -      return mergeFinalOps(seq1, seq2, ctx, res);+      return mergeFinalOps(seq1, seq2, res);      } else { // !ops2.collection()->empty() @@ -1143,7 +1130,7 @@       res.collection()->push_front(seq2.collection()->back());       seq2.collection()->pop_back(); -      return mergeFinalOps(seq1, seq2, ctx, res);+      return mergeFinalOps(seq1, seq2, res);      } @@ -1186,7 +1173,7 @@         result       end   */-  Node Extend::subweave(Node& one, Node& two, Context& ctx) {+  Node subweave(Node& one, Node& two) {     // Check for the simple cases     if (one.collection()->size() == 0) {       Node out = Node::createCollection();@@ -1199,8 +1186,6 @@       return out;     } --     Node seq1 = Node::createCollection();     seq1.plus(one);     Node seq2 = Node::createCollection();@@ -1209,7 +1194,7 @@     DEBUG_PRINTLN(SUBWEAVE, "SUBWEAVE ONE: " << seq1)     DEBUG_PRINTLN(SUBWEAVE, "SUBWEAVE TWO: " << seq2) -    Node init = mergeInitialOps(seq1, seq2, ctx);+    Node init = mergeInitialOps(seq1, seq2);     if (init.isNil()) {       return Node::createNil();     }@@ -1217,7 +1202,7 @@     DEBUG_PRINTLN(SUBWEAVE, "INIT: " << init)      Node res = Node::createCollection();-    Node fin = mergeFinalOps(seq1, seq2, ctx, res);+    Node fin = mergeFinalOps(seq1, seq2, res);     if (fin.isNil()) {       return Node::createNil();     }@@ -1245,23 +1230,23 @@   -    Node groupSeq1 = groupSelectors(seq1, ctx);+    Node groupSeq1 = groupSelectors(seq1);     DEBUG_PRINTLN(SUBWEAVE, "SEQ1: " << groupSeq1) -    Node groupSeq2 = groupSelectors(seq2, ctx);+    Node groupSeq2 = groupSelectors(seq2);     DEBUG_PRINTLN(SUBWEAVE, "SEQ2: " << groupSeq2)       ComplexSelectorDeque groupSeq1Converted;-    nodeToComplexSelectorDeque(groupSeq1, groupSeq1Converted, ctx);+    nodeToComplexSelectorDeque(groupSeq1, groupSeq1Converted);      ComplexSelectorDeque groupSeq2Converted;-    nodeToComplexSelectorDeque(groupSeq2, groupSeq2Converted, ctx);+    nodeToComplexSelectorDeque(groupSeq2, groupSeq2Converted);      ComplexSelectorDeque out;-    LcsCollectionComparator collectionComparator(ctx);-    lcs(groupSeq2Converted, groupSeq1Converted, collectionComparator, ctx, out);-    Node seqLcs = complexSelectorDequeToNode(out, ctx);+    LcsCollectionComparator collectionComparator;+    lcs(groupSeq2Converted, groupSeq1Converted, collectionComparator, out);+    Node seqLcs = complexSelectorDequeToNode(out);      DEBUG_PRINTLN(SUBWEAVE, "SEQLCS: " << seqLcs) @@ -1275,7 +1260,7 @@       while (!seqLcs.collection()->empty()) {-      ParentSuperselectorChunker superselectorChunker(seqLcs, ctx);+      ParentSuperselectorChunker superselectorChunker(seqLcs);       Node chunksResult = chunks(groupSeq1, groupSeq2, superselectorChunker);       diff.collection()->push_back(chunksResult); @@ -1320,7 +1305,7 @@     DEBUG_PRINTLN(SUBWEAVE, "DIFF POST REJECT: " << diff)  -    Node pathsResult = paths(diff, ctx);+    Node pathsResult = paths(diff);      DEBUG_PRINTLN(SUBWEAVE, "PATHS: " << pathsResult) @@ -1330,7 +1315,7 @@       pathsIter != pathsEndIter; ++pathsIter) {        Node& child = *pathsIter;-      child = flatten(child, ctx);+      child = flatten(child);     }      DEBUG_PRINTLN(SUBWEAVE, "FLATTENED: " << pathsResult)@@ -1348,25 +1333,25 @@   }   /*   // disabled to avoid clang warning [-Wunused-function]-  static Node subweaveNaive(const Node& one, const Node& two, Context& ctx) {+  static Node subweaveNaive(const Node& one, const Node& two) {     Node out = Node::createCollection();      // Check for the simple cases     if (one.isNil()) {-      out.collection()->push_back(two.klone(ctx));+      out.collection()->push_back(two.klone());     } else if (two.isNil()) {-      out.collection()->push_back(one.klone(ctx));+      out.collection()->push_back(one.klone());     } else {       // Do the naive implementation. pOne = A B and pTwo = C D ...yields...  A B C D and C D A B       // See https://gist.github.com/nex3/7609394 for details. -      Node firstPerm = one.klone(ctx);-      Node twoCloned = two.klone(ctx);+      Node firstPerm = one.klone();+      Node twoCloned = two.klone();       firstPerm.plus(twoCloned);       out.collection()->push_back(firstPerm); -      Node secondPerm = two.klone(ctx);-      Node oneCloned = one.klone(ctx);+      Node secondPerm = two.klone();+      Node oneCloned = one.klone();       secondPerm.plus(oneCloned );       out.collection()->push_back(secondPerm);     }@@ -1449,7 +1434,7 @@         return befores       end   */-  static Node weave(Node& path, Context& ctx) {+  Node Extend::weave(Node& path) {      DEBUG_PRINTLN(WEAVE, "WEAVE: " << path) @@ -1460,7 +1445,7 @@     afters.plus(path);      while (!afters.collection()->empty()) {-      Node current = afters.collection()->front().klone(ctx);+      Node current = afters.collection()->front().klone();       afters.collection()->pop_front();       DEBUG_PRINTLN(WEAVE, "CURRENT: " << current)       if (current.collection()->size() == 0) continue;@@ -1476,7 +1461,7 @@       for (NodeDeque::iterator beforesIter = befores.collection()->begin(), beforesEndIter = befores.collection()->end(); beforesIter != beforesEndIter; beforesIter++) {         Node& before = *beforesIter; -        Node sub = Extend::subweave(before, current, ctx);+        Node sub = subweave(before, current);          DEBUG_PRINTLN(WEAVE, "SUB: " << sub) @@ -1491,6 +1476,13 @@           toPush.plus(seqs);           toPush.plus(last_current); +          // move line feed from inner to outer selector (very hacky indeed)+          if (last_current.collection() && last_current.collection()->front().selector()) {+            toPush.got_line_feed = last_current.collection()->front().got_line_feed;+            last_current.collection()->front().selector()->has_line_feed(false);+            last_current.collection()->front().got_line_feed = false;+          }+           tempResult.collection()->push_back(toPush);          }@@ -1505,16 +1497,6 @@   -  // This forward declaration is needed since extendComplexSelector calls extendCompoundSelector, which may recursively-  // call extendComplexSelector again.-  static Node extendComplexSelector(-    Complex_Selector_Ptr pComplexSelector,-    Context& ctx,-    Subset_Map& subset_map,-    std::set<Compound_Selector> seen, bool isReplace, bool isOriginal);---   /*    This is the equivalent of ruby's SimpleSequence.do_extend. @@ -1530,65 +1512,60 @@   template<typename KeyType>   class GroupByToAFunctor {   public:-    KeyType operator()(ExtensionPair& extPair) const {+    KeyType operator()(SubSetMapPair& extPair) const {       Complex_Selector_Obj pSelector = extPair.first;-      return &pSelector;+      return pSelector;     }   };-  static Node extendCompoundSelector(-    Compound_Selector_Ptr pSelector,-    Context& ctx,-    Subset_Map& subset_map,-    std::set<Compound_Selector> seen, bool isReplace) {+  Node Extend::extendCompoundSelector(Compound_Selector_Ptr pSelector, CompoundSelectorSet& seen, bool isReplace) { +    /* this turned out to be too much overhead+       probably due to holding a "Node" object+    // check if we already extended this selector+    // we can do this since subset_map is "static"+    auto memoized = memoizeCompound.find(pSelector);+    if (memoized != memoizeCompound.end()) {+      return memoized->second.klone();+    }+    */+     DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pSelector, "EXTEND COMPOUND: "))     // TODO: Ruby has another loop here to skip certain members? -    Node extendedSelectors = Node::createCollection();+    // let RESULTS be an empty list of complex selectors+    Node results = Node::createCollection();     // extendedSelectors.got_line_feed = true; -    SubsetMapEntries entries = subset_map.get_v(pSelector);--    typedef std::vector<std::pair<Complex_Selector_Obj, std::vector<ExtensionPair> > > GroupedByToAResult;+    SubSetMapPairs entries = subset_map.get_v(pSelector);      GroupByToAFunctor<Complex_Selector_Obj> extPairKeyFunctor;-    GroupedByToAResult arr;+    SubSetMapResults arr;     group_by_to_a(entries, extPairKeyFunctor, arr); -    typedef std::pair<Compound_Selector_Obj, Complex_Selector_Obj> SelsNewSeqPair;-    typedef std::vector<SelsNewSeqPair> SelsNewSeqPairCollection;---    SelsNewSeqPairCollection holder;-+    SubSetMapLookups holder; -    for (GroupedByToAResult::iterator groupedIter = arr.begin(), groupedIterEnd = arr.end(); groupedIter != groupedIterEnd; groupedIter++) {-      std::pair<Complex_Selector_Obj, std::vector<ExtensionPair> >& groupedPair = *groupedIter;+    // for each (EXTENDER, TARGET) in MAP.get(COMPOUND):+    for (SubSetMapResult& groupedPair : arr) {        Complex_Selector_Obj seq = groupedPair.first;-      std::vector<ExtensionPair>& group = groupedPair.second;+      SubSetMapPairs& group = groupedPair.second; -      DEBUG_EXEC(EXTEND_COMPOUND, printComplexSelector(&seq, "SEQ: "))+      DEBUG_EXEC(EXTEND_COMPOUND, printComplexSelector(seq, "SEQ: ")) -// changing this makes aua       Compound_Selector_Obj pSels = SASS_MEMORY_NEW(Compound_Selector, pSelector->pstate());-      for (std::vector<ExtensionPair>::iterator groupIter = group.begin(), groupIterEnd = group.end(); groupIter != groupIterEnd; groupIter++) {-        ExtensionPair& pair = *groupIter;-        Compound_Selector_Obj pCompound = pair.second;-        for (size_t index = 0; index < pCompound->length(); index++) {-          Simple_Selector_Obj pSimpleSelector = (*pCompound)[index];-          pSels->append(&pSimpleSelector);-          pCompound->extended(true);-        }+      for (SubSetMapPair& pair : group) {+        pair.second->extended(true);+        pSels->concat(pair.second);       } -      DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(&pSels, "SELS: "))+      DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pSels, "SELS: ")) -      Complex_Selector_Ptr pExtComplexSelector = &seq;    // The selector up to where the @extend is (ie, the thing to merge)+      // The selector up to where the @extend is (ie, the thing to merge)+      Complex_Selector_Ptr pExtComplexSelector = seq;        // TODO: This can return a Compound_Selector with no elements. Should that just be returning NULL?       // RUBY: self_without_sel = Sass::Util.array_minus(members, sels)-      Compound_Selector_Obj pSelectorWithoutExtendSelectors = pSelector->minus(&pSels, ctx);+      Compound_Selector_Obj pSelectorWithoutExtendSelectors = pSelector->minus(pSels);        DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pSelector, "MEMBERS: "))       DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pSelectorWithoutExtendSelectors, "SELF_WO_SEL: "))@@ -1598,7 +1575,7 @@       if (!pInnermostCompoundSelector) {         pInnermostCompoundSelector = SASS_MEMORY_NEW(Compound_Selector, pSelector->pstate());       }-      Compound_Selector_Obj pUnifiedSelector = pInnermostCompoundSelector->unify_with(&pSelectorWithoutExtendSelectors, ctx);+      Compound_Selector_Obj pUnifiedSelector = pInnermostCompoundSelector->unify_with(pSelectorWithoutExtendSelectors);        DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pInnermostCompoundSelector, "LHS: "))       DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pSelectorWithoutExtendSelectors, "RHS: "))@@ -1621,10 +1598,10 @@       Complex_Selector_Obj pNewInnerMost = SASS_MEMORY_NEW(Complex_Selector, pSelector->pstate(), Complex_Selector::ANCESTOR_OF, pUnifiedSelector, NULL);        Complex_Selector::Combinator combinator = pNewSelector->clear_innermost();-      pNewSelector->set_innermost(&pNewInnerMost, combinator);+      pNewSelector->set_innermost(pNewInnerMost, combinator);  #ifdef DEBUG-      SourcesSet debugSet;+      ComplexSelectorSet debugSet;       debugSet = pNewSelector->sources();       if (debugSet.size() > 0) {         throw std::runtime_error("The new selector should start with no sources. Something needs to be cloned to fix this.");@@ -1638,21 +1615,22 @@        // if (pSelector && pSelector->has_line_feed()) pNewInnerMost->has_line_feed(true);       // Set the sources on our new Complex_Selector to the sources of this simple sequence plus the thing we're extending.-      DEBUG_PRINTLN(EXTEND_COMPOUND, "SOURCES SETTING ON NEW SEQ: " << complexSelectorToNode(pNewSelector, ctx))+      DEBUG_PRINTLN(EXTEND_COMPOUND, "SOURCES SETTING ON NEW SEQ: " << complexSelectorToNode(pNewSelector)) -      DEBUG_EXEC(EXTEND_COMPOUND, SourcesSet oldSet = pNewSelector->sources(); printSourcesSet(oldSet, ctx, "SOURCES NEW SEQ BEGIN: "))+      DEBUG_EXEC(EXTEND_COMPOUND, ComplexSelectorSet oldSet = pNewSelector->sources(); printSourcesSet(oldSet, "SOURCES NEW SEQ BEGIN: ")) -      SourcesSet newSourcesSet = pSelector->sources();-      DEBUG_EXEC(EXTEND_COMPOUND, printSourcesSet(newSourcesSet, ctx, "SOURCES THIS EXTEND: "))+      // I actually want to create a copy here (performance!)+      ComplexSelectorSet newSourcesSet = pSelector->sources(); // XXX+      DEBUG_EXEC(EXTEND_COMPOUND, printSourcesSet(newSourcesSet, "SOURCES THIS EXTEND: "))        newSourcesSet.insert(pExtComplexSelector);-      DEBUG_EXEC(EXTEND_COMPOUND, printSourcesSet(newSourcesSet, ctx, "SOURCES WITH NEW SOURCE: "))+      DEBUG_EXEC(EXTEND_COMPOUND, printSourcesSet(newSourcesSet, "SOURCES WITH NEW SOURCE: "))        // RUBY: new_seq.add_sources!(sources + [seq])-      pNewSelector->addSources(newSourcesSet, ctx);+      pNewSelector->addSources(newSourcesSet); -      DEBUG_EXEC(EXTEND_COMPOUND, SourcesSet newSet = pNewSelector->sources(); printSourcesSet(newSet, ctx, "SOURCES ON NEW SELECTOR AFTER ADD: "))-      DEBUG_EXEC(EXTEND_COMPOUND, printSourcesSet(pSelector->sources(), ctx, "SOURCES THIS EXTEND WHICH SHOULD BE SAME STILL: "))+      DEBUG_EXEC(EXTEND_COMPOUND, ComplexSelectorSet newSet = pNewSelector->sources(); printSourcesSet(newSet, "SOURCES ON NEW SELECTOR AFTER ADD: "))+      DEBUG_EXEC(EXTEND_COMPOUND, printSourcesSet(pSelector->sources(), "SOURCES THIS EXTEND WHICH SHOULD BE SAME STILL: "))         if (pSels->has_line_feed()) pNewSelector->has_line_feed(true);@@ -1661,25 +1639,24 @@     }  -    for (SelsNewSeqPairCollection::iterator holderIter = holder.begin(), holderIterEnd = holder.end(); holderIter != holderIterEnd; holderIter++) {-      SelsNewSeqPair& pair = *holderIter;+    for (SubSetMapLookup& pair : holder) {        Compound_Selector_Obj pSels = pair.first;       Complex_Selector_Obj pNewSelector = pair.second;         // RUBY??: next [] if seen.include?(sels)-      if (seen.find(*pSels) != seen.end()) {+      if (seen.find(pSels) != seen.end()) {         continue;       }  -      std::set<Compound_Selector> recurseSeen(seen);-      recurseSeen.insert(*pSels);+      CompoundSelectorSet recurseSeen(seen);+      recurseSeen.insert(pSels);  -      DEBUG_PRINTLN(EXTEND_COMPOUND, "RECURSING DO EXTEND: " << complexSelectorToNode(pNewSelector, ctx))-      Node recurseExtendedSelectors = extendComplexSelector(&pNewSelector, ctx, subset_map, recurseSeen, isReplace, false); // !:isOriginal+      DEBUG_PRINTLN(EXTEND_COMPOUND, "RECURSING DO EXTEND: " << complexSelectorToNode(pNewSelector))+      Node recurseExtendedSelectors = extendComplexSelector(pNewSelector, recurseSeen, isReplace, false); // !:isOriginal        DEBUG_PRINTLN(EXTEND_COMPOUND, "RECURSING DO EXTEND RETURN: " << recurseExtendedSelectors) @@ -1687,38 +1664,39 @@            iterator != endIterator; ++iterator) {         Node newSelector = *iterator; -//        DEBUG_PRINTLN(EXTEND_COMPOUND, "EXTENDED AT THIS POINT: " << extendedSelectors)-//        DEBUG_PRINTLN(EXTEND_COMPOUND, "SELECTOR EXISTS ALREADY: " << newSelector << " " << extendedSelectors.contains(newSelector, false /*simpleSelectorOrderDependent*/));+//        DEBUG_PRINTLN(EXTEND_COMPOUND, "EXTENDED AT THIS POINT: " << results)+//        DEBUG_PRINTLN(EXTEND_COMPOUND, "SELECTOR EXISTS ALREADY: " << newSelector << " " << results.contains(newSelector, false /*simpleSelectorOrderDependent*/)); -        if (!extendedSelectors.contains(newSelector, false /*simpleSelectorOrderDependent*/)) {+        if (!results.contains(newSelector)) { //          DEBUG_PRINTLN(EXTEND_COMPOUND, "ADDING NEW SELECTOR")-          extendedSelectors.collection()->push_back(newSelector);+          results.collection()->push_back(newSelector);         }       }     }      DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pSelector, "EXTEND COMPOUND END: ")) -    return extendedSelectors;+    // this turned out to be too much overhead+    // memory results in a map table - since extending is very expensive+    // memoizeCompound.insert(std::pair<Compound_Selector_Obj, Node>(pSelector, results));++    return results;   }  -  static bool complexSelectorHasExtension(-    Complex_Selector_Ptr pComplexSelector,-    Context& ctx,-    Subset_Map& subset_map,-    std::set<Compound_Selector>& seen) {+  // check if selector has something to be extended by subset_map+  bool Extend::complexSelectorHasExtension(Complex_Selector_Ptr selector, CompoundSelectorSet& seen) {      bool hasExtension = false; -    Complex_Selector_Obj pIter = pComplexSelector;+    Complex_Selector_Obj pIter = selector;      while (!hasExtension && pIter) {       Compound_Selector_Obj pHead = pIter->head();        if (pHead) {-        SubsetMapEntries entries = subset_map.get_v(pHead);-        for (ExtensionPair ext : entries) {+        SubSetMapPairs entries = subset_map.get_v(pHead);+        for (SubSetMapPair ext : entries) {           // check if both selectors have the same media block parent           // if (ext.first->media_block() == pComplexSelector->media_block()) continue;           if (ext.second->media_block() == 0) continue;@@ -1726,8 +1704,8 @@               ext.second->media_block()->media_queries() &&               pHead->media_block()->media_queries()           ) {-            std::string query_left(ext.second->media_block()->media_queries()->to_string(ctx.c_options));-            std::string query_right(pHead->media_block()->media_queries()->to_string(ctx.c_options));+            std::string query_left(ext.second->media_block()->media_queries()->to_string());+            std::string query_right(pHead->media_block()->media_queries()->to_string());             if (query_left == query_right) continue;           } @@ -1738,9 +1716,9 @@           std::string rel_path(Sass::File::abs2rel(pstate.path, cwd, cwd));           err << "You may not @extend an outer selector from within @media.\n";           err << "You may only @extend selectors within the same directive.\n";-          err << "From \"@extend " << ext.second->to_string(ctx.c_options) << "\"";+          err << "From \"@extend " << ext.second->to_string() << "\"";           err << " on line " << pstate.line+1 << " of " << rel_path << "\n";-          error(err.str(), pComplexSelector->pstate());+          error(err.str(), selector->pstate(), eval->exp.traces);         }         if (entries.size() > 0) hasExtension = true;       }@@ -1763,23 +1741,25 @@      the combinator and compound selector are one unit      next [[sseq_or_op]] unless sseq_or_op.is_a?(SimpleSequence)    */-  static Node extendComplexSelector(-    Complex_Selector_Ptr pComplexSelector,-    Context& ctx,-    Subset_Map& subset_map,-    std::set<Compound_Selector> seen, bool isReplace, bool isOriginal) {+  Node Extend::extendComplexSelector(Complex_Selector_Ptr selector, CompoundSelectorSet& seen, bool isReplace, bool isOriginal) { -    Node complexSelector = complexSelectorToNode(pComplexSelector, ctx);-    DEBUG_PRINTLN(EXTEND_COMPLEX, "EXTEND COMPLEX: " << complexSelector)+    // check if we already extended this selector+    // we can do this since subset_map is "static"+    auto memoized = memoizeComplex.find(selector);+    if (memoized != memoizeComplex.end()) {+      return memoized->second;+    } -    Node extendedNotExpanded = Node::createCollection();+    // convert the input selector to extend node format+    Node complexSelector = complexSelectorToNode(selector);+    DEBUG_PRINTLN(EXTEND_COMPLEX, "EXTEND COMPLEX: " << complexSelector) -    for (NodeDeque::iterator complexSelIter = complexSelector.collection()->begin(),-                             complexSelIterEnd = complexSelector.collection()->end();-         complexSelIter != complexSelIterEnd; ++complexSelIter)-    {+    // let CHOICES be an empty list of selector-lists+    // create new collection to hold the results+    Node choices = Node::createCollection(); -      Node& sseqOrOp = *complexSelIter;+    // for each compound selector COMPOUND in COMPLEX:+    for (Node& sseqOrOp : *complexSelector.collection()) {        DEBUG_PRINTLN(EXTEND_COMPLEX, "LOOP: " << sseqOrOp) @@ -1792,94 +1772,93 @@         Node inner = Node::createCollection();         outer.collection()->push_back(inner);         inner.collection()->push_back(sseqOrOp);-        extendedNotExpanded.collection()->push_back(outer);+        choices.collection()->push_back(outer);         continue;       } -      Compound_Selector_Obj pCompoundSelector = sseqOrOp.selector()->head();+      // verified now that node is a valid selector+      Complex_Selector_Obj sseqSel = sseqOrOp.selector();+      Compound_Selector_Obj sseqHead = sseqSel->head(); +      // let EXTENDED be extend_compound(COMPOUND, SEEN)+      // extend the compound selector against the given subset_map       // RUBY: extended = sseq_or_op.do_extend(extends, parent_directives, replace, seen)-      Node extended = extendCompoundSelector(&pCompoundSelector, ctx, subset_map, seen, isReplace);+      Node extended = extendCompoundSelector(sseqHead, seen, isReplace); // slow(17%)!       if (sseqOrOp.got_line_feed) extended.got_line_feed = true;       DEBUG_PRINTLN(EXTEND_COMPLEX, "EXTENDED: " << extended) --      // Prepend the Compound_Selector based on the choices logic; choices seems to be extend but with an ruby Array instead of a Sequence-      // due to the member mapping: choices = extended.map {|seq| seq.members}-      Complex_Selector_Obj pJustCurrentCompoundSelector = sseqOrOp.selector();-+      // Prepend the Compound_Selector based on the choices logic; choices seems to be extend but with a ruby+      // Array instead of a Sequence due to the member mapping: choices = extended.map {|seq| seq.members}       // RUBY: extended.first.add_sources!([self]) if original && !has_placeholder?-      if (isOriginal && !pComplexSelector->has_placeholder()) {-        SourcesSet srcset;-        srcset.insert(pComplexSelector);-        pJustCurrentCompoundSelector->addSources(srcset, ctx);-        DEBUG_PRINTLN(EXTEND_COMPLEX, "ADD SOURCES: " << *pComplexSelector)+      if (isOriginal && !selector->has_placeholder()) {+        ComplexSelectorSet srcset;+        srcset.insert(selector);+        sseqSel->addSources(srcset);+        // DEBUG_PRINTLN(EXTEND_COMPLEX, "ADD SOURCES: " << *pComplexSelector)       }        bool isSuperselector = false;-      for (NodeDeque::iterator iterator = extended.collection()->begin(), endIterator = extended.collection()->end();-           iterator != endIterator; ++iterator) {-        Node& childNode = *iterator;-        Complex_Selector_Obj pExtensionSelector = nodeToComplexSelector(childNode, ctx);-        if (pExtensionSelector->is_superselector_of(pJustCurrentCompoundSelector)) {+      // if no complex selector in EXTENDED is a superselector of COMPOUND:+      for (Node& childNode : *extended.collection()) {+        Complex_Selector_Obj pExtensionSelector = nodeToComplexSelector(childNode);+        if (pExtensionSelector->is_superselector_of(sseqSel)) {           isSuperselector = true;           break;         }       }        if (!isSuperselector) {-        if (sseqOrOp.got_line_feed) pJustCurrentCompoundSelector->has_line_feed(sseqOrOp.got_line_feed);-        extended.collection()->push_front(complexSelectorToNode(&pJustCurrentCompoundSelector, ctx));+        // add a complex selector composed only of COMPOUND to EXTENDED+        if (sseqOrOp.got_line_feed) sseqSel->has_line_feed(sseqOrOp.got_line_feed);+        extended.collection()->push_front(complexSelectorToNode(sseqSel));       }        DEBUG_PRINTLN(EXTEND_COMPLEX, "CHOICES UNSHIFTED: " << extended) +      // add EXTENDED to CHOICES       // Aggregate our current extensions-      extendedNotExpanded.collection()->push_back(extended);+      choices.collection()->push_back(extended);     }  -    DEBUG_PRINTLN(EXTEND_COMPLEX, "EXTENDED NOT EXPANDED: " << extendedNotExpanded)+    DEBUG_PRINTLN(EXTEND_COMPLEX, "EXTENDED NOT EXPANDED: " << choices)        // Ruby Equivalent: paths-    Node paths = Sass::paths(extendedNotExpanded, ctx);+    Node paths = Sass::paths(choices);      DEBUG_PRINTLN(EXTEND_COMPLEX, "PATHS: " << paths) ---    // Ruby Equivalent: weave+    // let WEAVES be an empty list of selector lists     Node weaves = Node::createCollection(); -    for (NodeDeque::iterator pathsIter = paths.collection()->begin(), pathsEndIter = paths.collection()->end(); pathsIter != pathsEndIter; ++pathsIter) {-      Node& path = *pathsIter;-      Node weaved = weave(path, ctx);+    // for each list of complex selectors PATH in paths(CHOICES):+    for (Node& path : *paths.collection()) {+      // add weave(PATH) to WEAVES+      Node weaved = weave(path); // slow(12%)!       weaved.got_line_feed = path.got_line_feed;       weaves.collection()->push_back(weaved);     }      DEBUG_PRINTLN(EXTEND_COMPLEX, "WEAVES: " << weaves) --     // Ruby Equivalent: trim-    Node trimmed = trim(weaves, ctx, isReplace);+    Node trimmed(trim(weaves, isReplace)); // slow(19%)!      DEBUG_PRINTLN(EXTEND_COMPLEX, "TRIMMED: " << trimmed) -     // Ruby Equivalent: flatten-    Node extendedSelectors = flatten(trimmed, ctx, 1);+    Node flattened(flatten(trimmed, 1));      DEBUG_PRINTLN(EXTEND_COMPLEX, ">>>>> EXTENDED: " << extendedSelectors)--     DEBUG_PRINTLN(EXTEND_COMPLEX, "EXTEND COMPLEX END: " << complexSelector) +    // memory results in a map table - since extending is very expensive+    memoizeComplex.insert(std::pair<Complex_Selector_Obj, Node>(selector, flattened)); -    return extendedSelectors;+    // return trim(WEAVES)+    return flattened;   }  @@ -1887,20 +1866,23 @@   /*    This is the equivalent of ruby's CommaSequence.do_extend.   */-  Selector_List_Ptr Extend::extendSelectorList(Selector_List_Obj pSelectorList, Context& ctx, Subset_Map& subset_map, bool isReplace, bool& extendedSomething) {-    std::set<Compound_Selector> seen;-    return extendSelectorList(pSelectorList, ctx, subset_map, isReplace, extendedSomething, seen);-  }--  /*-   This is the equivalent of ruby's CommaSequence.do_extend.-  */-  Selector_List_Ptr Extend::extendSelectorList(Selector_List_Obj pSelectorList, Context& ctx, Subset_Map& subset_map, bool isReplace, bool& extendedSomething, std::set<Compound_Selector>& seen) {+  // We get a selector list with has something to extend and a subset_map with+  // all extenders. Pick the ones that match our selectors in the list.+  Selector_List_Ptr Extend::extendSelectorList(Selector_List_Obj pSelectorList, bool isReplace, bool& extendedSomething, CompoundSelectorSet& seen) {      Selector_List_Obj pNewSelectors = SASS_MEMORY_NEW(Selector_List, pSelectorList->pstate(), pSelectorList->length()); -    extendedSomething = false;+    // check if we already extended this selector+    // we can do this since subset_map is "static"+    auto memoized = memoizeList.find(pSelectorList);+    if (memoized != memoizeList.end()) {+      extendedSomething = true;+      return memoized->second;+    } +    extendedSomething = false;+    // process each comlplex selector in the selector list.+    // Find the ones that can be extended by given subset_map.     for (size_t index = 0, length = pSelectorList->length(); index < length; index++) {       Complex_Selector_Obj pSelector = (*pSelectorList)[index]; @@ -1909,89 +1891,100 @@       // run through the extend code (which does a data model transformation), check if there is anything to extend before doing       // the extend. We might be able to optimize extendComplexSelector, but this approach keeps us closer to ruby sass (which helps       // when debugging).-      if (!complexSelectorHasExtension(&pSelector, ctx, subset_map, seen)) {-        pNewSelectors->append(&pSelector);+      if (!complexSelectorHasExtension(pSelector, seen)) {+        pNewSelectors->append(pSelector);         continue;       } +      // complexSelectorHasExtension was true!       extendedSomething = true; -      Node extendedSelectors = extendComplexSelector(&pSelector, ctx, subset_map, seen, isReplace, true);+      // now do the actual extension of the complex selector+      Node extendedSelectors = extendComplexSelector(pSelector, seen, isReplace, true);+       if (!pSelector->has_placeholder()) {-        if (!extendedSelectors.contains(complexSelectorToNode(&pSelector, ctx), true /*simpleSelectorOrderDependent*/)) {+        Node nSelector(complexSelectorToNode(pSelector));+        if (!extendedSelectors.contains(nSelector)) {           pNewSelectors->append(pSelector);           continue;         }       } -      for (NodeDeque::iterator iterator = extendedSelectors.collection()->begin(), iteratorBegin = extendedSelectors.collection()->begin(), iteratorEnd = extendedSelectors.collection()->end(); iterator != iteratorEnd; ++iterator) {+      bool doReplace = isReplace;+      for (Node& childNode : *extendedSelectors.collection()) {         // When it is a replace, skip the first one, unless there is only one-        if(isReplace && iterator == iteratorBegin && extendedSelectors.collection()->size() > 1 ) continue;--        Node& childNode = *iterator;-        pNewSelectors->append(nodeToComplexSelector(childNode, ctx));+        if(doReplace && extendedSelectors.collection()->size() > 1 ) {+          doReplace = false;+          continue;+        }+        pNewSelectors->append(nodeToComplexSelector(childNode));       }     } -    Remove_Placeholders remove_placeholders(ctx);+    Remove_Placeholders remove_placeholders;     // it seems that we have to remove the place holders early here     // normally we do this as the very last step (compare to ruby sass)-    pNewSelectors = remove_placeholders.remove_placeholders(&pNewSelectors);+    pNewSelectors = remove_placeholders.remove_placeholders(pNewSelectors);      // unwrap all wrapped selectors with inner lists     for (Complex_Selector_Obj cur : pNewSelectors->elements()) {       // process tails       while (cur) {         // process header-        if (cur->head() && seen.find(*cur->head()) == seen.end()) {-          std::set<Compound_Selector> recseen(seen);-          recseen.insert(*cur->head());+        if (cur->head() && seen.find(cur->head()) == seen.end()) {+          CompoundSelectorSet recseen(seen);+          recseen.insert(cur->head());           // create a copy since we add multiple items if stuff get unwrapped           Compound_Selector_Obj cpy_head = SASS_MEMORY_NEW(Compound_Selector, cur->pstate());           for (Simple_Selector_Obj hs : *cur->head()) {-            if (Wrapped_Selector_Obj ws = SASS_MEMORY_CAST(Wrapped_Selector, hs)) {+            if (Wrapped_Selector_Obj ws = Cast<Wrapped_Selector>(hs)) {               ws->selector(SASS_MEMORY_CLONE(ws->selector()));-              if (Selector_List_Obj sl = SASS_MEMORY_CAST(Selector_List, ws->selector())) {+              if (Selector_List_Obj sl = Cast<Selector_List>(ws->selector())) {                 // special case for ruby ass                 if (sl->empty()) {                   // this seems inconsistent but it is how ruby sass seems to remove parentheses                   cpy_head->append(SASS_MEMORY_NEW(Element_Selector, hs->pstate(), ws->name()));                 }-                // has wrapped selectors-                else {+                // has wrapped not selectors+                else if (ws->name() == ":not") {                   // extend the inner list of wrapped selector-                  Selector_List_Obj ext_sl = extendSelectorList(sl, ctx, subset_map, recseen);+                  bool extended = false;+                  Selector_List_Obj ext_sl = extendSelectorList(sl, false, extended, recseen);                   for (size_t i = 0; i < ext_sl->length(); i += 1) {                     if (Complex_Selector_Obj ext_cs = ext_sl->at(i)) {                       // create clones for wrapped selector and the inner list-                      Wrapped_Selector_Obj cpy_ws = SASS_MEMORY_COPY(&ws);+                      Wrapped_Selector_Obj cpy_ws = SASS_MEMORY_COPY(ws);                       Selector_List_Obj cpy_ws_sl = SASS_MEMORY_NEW(Selector_List, sl->pstate());                       // remove parent selectors from inner selector-                      if (ext_cs->first() && ext_cs->first()->head()->length() > 0) {-                        Wrapped_Selector_Ptr ext_ws = SASS_MEMORY_CAST(Wrapped_Selector, ext_cs->first()->head()->first());-                        if (ext_ws/* && ext_cs->length() == 1*/) {-                          Selector_List_Obj ws_cs = SASS_MEMORY_CAST(Selector_List, ext_ws->selector());-                          Compound_Selector_Obj ws_ss = ws_cs->first()->head();-                          if (!(-                            SASS_MEMORY_CAST(Pseudo_Selector, ws_ss->first()) ||-                            SASS_MEMORY_CAST(Element_Selector, ws_ss->first()) ||-                            SASS_MEMORY_CAST(Placeholder_Selector, ws_ss->first())-                          )) continue;-                        }+                      Compound_Selector_Obj ext_head = NULL;+                      if (ext_cs->first()) ext_head = ext_cs->first()->head();+                      if (ext_head && ext_head && ext_head->length() > 0) {                         cpy_ws_sl->append(ext_cs->first());                       }                       // assign list to clone-                      cpy_ws->selector(&cpy_ws_sl);+                      cpy_ws->selector(cpy_ws_sl);                       // append the clone-                      cpy_head->append(&cpy_ws);+                      cpy_head->append(cpy_ws);                     }                   }+                  if (eval && extended) {+                    eval->exp.selector_stack.push_back(pNewSelectors);+                    cpy_head->perform(eval);+                    eval->exp.selector_stack.pop_back();+                  }                 }+                // has wrapped selectors+                else {+                  Wrapped_Selector_Obj cpy_ws = SASS_MEMORY_COPY(ws);+                  Selector_List_Obj ext_sl = extendSelectorList(sl, recseen);+                  cpy_ws->selector(ext_sl);+                  cpy_head->append(cpy_ws);+                }               } else {-                cpy_head->append(&hs);+                cpy_head->append(hs);               }             } else {-              cpy_head->append(&hs);+              cpy_head->append(hs);             }           }           // replace header@@ -2001,6 +1994,10 @@         cur = cur->tail();       }     }++    // memory results in a map table - since extending is very expensive+    memoizeList.insert(std::pair<Selector_List_Obj, Selector_List_Obj>(pSelectorList, pNewSelectors));+     return pNewSelectors.detach();    }@@ -2024,8 +2021,9 @@     for (size_t i = 0, L = b->length(); i < L; ++i) {       Statement_Obj stm = b->at(i); -      if (dynamic_cast<Ruleset_Ptr>(&stm)) {-        // Do nothing. This doesn't count as a statement that causes extension since we'll iterate over this rule set in a future visit and try to extend it.+      if (Cast<Ruleset>(stm)) {+        // Do nothing. This doesn't count as a statement that causes extension since we'll+        // iterate over this rule set in a future visit and try to extend it.       }       else {         return true;@@ -2038,37 +2036,45 @@     // Extend a ruleset by extending the selectors and updating them on the ruleset. The block's rules don't need to change.-  template <typename ObjectType>-  static void extendObjectWithSelectorAndBlock(ObjectType* pObject, Context& ctx, Subset_Map& subset_map) {+  // Every Ruleset in the whole tree is calling this function. We decide if there+  // was is @extend that matches our selector. If we find one, we will go further+  // and call the extend magic for our selector. The subset_map contains all blocks+  // where @extend was found. Pick the ones that match our selector!+  void Extend::extendObjectWithSelectorAndBlock(Ruleset_Ptr pObject) { -    DEBUG_PRINTLN(EXTEND_OBJECT, "FOUND SELECTOR: " << static_cast<Selector_List_Ptr>(pObject->selector())->to_string(ctx.c_options))+    DEBUG_PRINTLN(EXTEND_OBJECT, "FOUND SELECTOR: " << Cast<Selector_List>(pObject->selector())->to_string()) -    // Ruby sass seems to filter nodes that don't have any content well before we get here. I'm not sure the repercussions-    // of doing so, so for now, let's just not extend things that won't be output later.+    // Ruby sass seems to filter nodes that don't have any content well before we get here.+    // I'm not sure the repercussions of doing so, so for now, let's just not extend things+    // that won't be output later. Profiling shows this may us 0.2% or so.     if (!shouldExtendBlock(pObject->block())) {       DEBUG_PRINTLN(EXTEND_OBJECT, "RETURNING WITHOUT EXTEND ATTEMPT")       return;     }      bool extendedSomething = false;-    Selector_List_Obj pNewSelectorList = Extend::extendSelectorList(SASS_MEMORY_CAST(Selector_List, pObject->selector()), ctx, subset_map, false, extendedSomething); +    CompoundSelectorSet seen;+    Selector_List_Obj pNewSelectorList = extendSelectorList(pObject->selector(), false, extendedSomething, seen);+     if (extendedSomething && pNewSelectorList) {-      DEBUG_PRINTLN(EXTEND_OBJECT, "EXTEND ORIGINAL SELECTORS: " << static_cast<Selector_List_Ptr>(pObject->selector())->to_string(ctx.c_options))-      DEBUG_PRINTLN(EXTEND_OBJECT, "EXTEND SETTING NEW SELECTORS: " << pNewSelectorList->to_string(ctx.c_options))+      DEBUG_PRINTLN(EXTEND_OBJECT, "EXTEND ORIGINAL SELECTORS: " << pObject->selector()->to_string())+      DEBUG_PRINTLN(EXTEND_OBJECT, "EXTEND SETTING NEW SELECTORS: " << pNewSelectorList->to_string())       pNewSelectorList->remove_parent_selectors();-      pObject->selector(&pNewSelectorList);+      pObject->selector(pNewSelectorList);     } else {       DEBUG_PRINTLN(EXTEND_OBJECT, "EXTEND DID NOT TRY TO EXTEND ANYTHING")     }   } ---  Extend::Extend(Context& ctx, Subset_Map& ssm)-  : ctx(ctx), subset_map(ssm)+  Extend::Extend(Subset_Map& ssm)+  : subset_map(ssm), eval(NULL)   { } +  void Extend::setEval(Eval& e) {+    eval = &e;+  }+   void Extend::operator()(Block_Ptr b)   {     for (size_t i = 0, L = b->length(); i < L; ++i) {@@ -2080,17 +2086,19 @@     if (b->is_root()) {       // debug_subset_map(subset_map);       for(auto const &it : subset_map.values()) {-        Complex_Selector_Ptr sel = it.first ? &it.first->first() : NULL;-        Compound_Selector_Ptr ext = it.second ? &it.second : NULL;+        Complex_Selector_Ptr sel = NULL;+        Compound_Selector_Ptr ext = NULL;+        if (it.first) sel = it.first->first();+        if (it.second) ext = it.second;         if (ext && (ext->extended() || ext->is_optional())) continue;-        std::string str_sel(sel->to_string({ NESTED, 5 }));-        std::string str_ext(ext->to_string({ NESTED, 5 }));+        std::string str_sel(sel ? sel->to_string({ NESTED, 5 }) : "NULL");+        std::string str_ext(ext ? ext->to_string({ NESTED, 5 }) : "NULL");         // debug_ast(sel, "sel: ");         // debug_ast(ext, "ext: ");         error("\"" + str_sel + "\" failed to @extend \"" + str_ext + "\".\n"               "The selector \"" + str_ext + "\" was not found.\n"               "Use \"@extend " + str_ext + " !optional\" if the"-                " extend should be able to fail.", ext->pstate());+              " extend should be able to fail.", (ext ? ext->pstate() : NULL), eval->exp.traces);       }     } @@ -2098,7 +2106,7 @@    void Extend::operator()(Ruleset_Ptr pRuleset)   {-    extendObjectWithSelectorAndBlock( pRuleset, ctx, subset_map);+    extendObjectWithSelectorAndBlock( pRuleset );     pRuleset->block()->perform(this);   } @@ -2114,7 +2122,7 @@    void Extend::operator()(Directive_Ptr a)   {-    // Selector_List_Ptr ls = dynamic_cast<Selector_List_Ptr>(a->selector());+    // Selector_List_Ptr ls = Cast<Selector_List>(a->selector());     // selector_stack.push_back(ls);     if (a->block()) a->block()->perform(this);     // exp.selector_stack.pop_back();
libsass/src/extend.hpp view
@@ -5,35 +5,70 @@ #include <set>  #include "ast.hpp"+#include "node.hpp"+#include "eval.hpp" #include "operation.hpp" #include "subset_map.hpp"+#include "ast_fwd_decl.hpp"  namespace Sass { -  class Context;-  class Node;+  Node subweave(Node& one, Node& two);    class Extend : public Operation_CRTP<void, Extend> { -    Context&            ctx;     Subset_Map& subset_map;+    Eval* eval;      void fallback_impl(AST_Node_Ptr n) { } +  private:++    std::unordered_map<+      Selector_List_Obj, // key+      Selector_List_Obj, // value+      HashNodes, // hasher+      CompareNodes // compare+    > memoizeList;++    std::unordered_map<+      Complex_Selector_Obj, // key+      Node, // value+      HashNodes, // hasher+      CompareNodes // compare+    > memoizeComplex;++    /* this turned out to be too much overhead+       re-evaluate once we store an ast selector+    std::unordered_map<+      Compound_Selector_Obj, // key+      Node, // value+      HashNodes, // hasher+      CompareNodes // compare+    > memoizeCompound;+    */++    void extendObjectWithSelectorAndBlock(Ruleset_Ptr pObject);+    Node extendComplexSelector(Complex_Selector_Ptr sel, CompoundSelectorSet& seen, bool isReplace, bool isOriginal);+    Node extendCompoundSelector(Compound_Selector_Ptr sel, CompoundSelectorSet& seen, bool isReplace);+    bool complexSelectorHasExtension(Complex_Selector_Ptr selector, CompoundSelectorSet& seen);+    Node trim(Node& seqses, bool isReplace);+    Node weave(Node& path);+   public:-    static Node subweave(Node& one, Node& two, Context& ctx);-    static Selector_List_Ptr extendSelectorList(Selector_List_Obj pSelectorList, Context& ctx, Subset_Map& subset_map, bool isReplace, bool& extendedSomething, std::set<Compound_Selector>& seen);-    static Selector_List_Ptr extendSelectorList(Selector_List_Obj pSelectorList, Context& ctx, Subset_Map& subset_map, bool isReplace, bool& extendedSomething);-    static Selector_List_Ptr extendSelectorList(Selector_List_Obj pSelectorList, Context& ctx, Subset_Map& subset_map, bool isReplace = false) {+    void setEval(Eval& eval);+    Selector_List_Ptr extendSelectorList(Selector_List_Obj pSelectorList, bool isReplace, bool& extendedSomething, CompoundSelectorSet& seen);+    Selector_List_Ptr extendSelectorList(Selector_List_Obj pSelectorList, bool isReplace = false) {       bool extendedSomething = false;-      return extendSelectorList(pSelectorList, ctx, subset_map, isReplace, extendedSomething);+      CompoundSelectorSet seen;+      return extendSelectorList(pSelectorList, isReplace, extendedSomething, seen);     }-    static Selector_List_Ptr extendSelectorList(Selector_List_Obj pSelectorList, Context& ctx, Subset_Map& subset_map, std::set<Compound_Selector>& seen) {+    Selector_List_Ptr extendSelectorList(Selector_List_Obj pSelectorList, CompoundSelectorSet& seen) {       bool isReplace = false;       bool extendedSomething = false;-      return extendSelectorList(pSelectorList, ctx, subset_map, isReplace, extendedSomething, seen);+      return extendSelectorList(pSelectorList, isReplace, extendedSomething, seen);     }-    Extend(Context&, Subset_Map&);+    Extend(Subset_Map&);     ~Extend() { }      void operator()(Block_Ptr);
libsass/src/file.cpp view
@@ -1,3 +1,4 @@+#include "sass.hpp" #ifdef _WIN32 # ifdef __MINGW32__ #  ifndef off64_t@@ -9,7 +10,6 @@ #else # include <unistd.h> #endif-#include "sass.hpp" #include <iostream> #include <fstream> #include <cctype>@@ -20,6 +20,7 @@ #include "context.hpp" #include "prelexer.hpp" #include "utf8_string.hpp"+#include "sass_functions.hpp" #include "sass2scss.h"  #ifdef _WIN32@@ -48,15 +49,22 @@      // return the current directory     // always with forward slashes+    // always with trailing slash     std::string get_cwd()     {-      const size_t wd_len = 1024;+      const size_t wd_len = 4096;       #ifndef _WIN32         char wd[wd_len];-        std::string cwd = getcwd(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");+        std::string cwd = pwd;       #else         wchar_t wd[wd_len];-        std::string cwd = wstring_to_string(_wgetcwd(wd, wd_len));+        wchar_t* pwd = _wgetcwd(wd, wd_len);+        if (pwd == NULL) throw Exception::OperationError("cwd gone missing");+        std::string cwd = wstring_to_string(pwd);         //convert backslashes to forward slashes         replace(cwd.begin(), cwd.end(), '\\', '/');       #endif@@ -68,8 +76,15 @@     bool file_exists(const std::string& path)     {       #ifdef _WIN32-        std::wstring wpath = UTF_8::convert_to_utf16(path);-        DWORD dwAttrib = GetFileAttributesW(wpath.c_str());+        wchar_t resolved[32768];+        // windows unicode filepaths are encoded in utf16+        std::string abspath(join_paths(get_cwd(), path));+        std::wstring wpath(UTF_8::convert_to_utf16("\\\\?\\" + abspath));+        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)));       #else@@ -99,7 +114,7 @@     // helper function to find the last directory seperator     inline size_t find_last_folder_separator(const std::string& path, size_t limit = std::string::npos)     {-      size_t pos = std::string::npos;+      size_t pos;       size_t pos_p = path.find_last_of('/', limit);       #ifdef _WIN32         size_t pos_w = path.find_last_of('\\', limit);@@ -150,7 +165,7 @@       pos = 0; // remove all self references inside the path string       while((pos = path.find("/./", pos)) != std::string::npos) path.erase(pos, 2); -      pos = 0; // remove all leading and trailing self references+      // remove all leading and trailing self references       while(path.length() > 1 && path.substr(0, 2) == "./") path.erase(0, 2);       while((pos = path.length()) > 1 && path.substr(pos - 2) == "/.") path.erase(pos - 2); @@ -191,6 +206,13 @@       if (is_absolute_path(r)) return r;       if (l[l.length()-1] != '/') l += '/'; +      // this does a logical cleanup of the right hand path+      // Note that this does collapse x/../y sections into y.+      // This is by design. If /foo on your system is a symlink+      // to /bar/baz, then /foo/../cd is actually /bar/cd,+      // not /cd as a naive ../ removal would give you.+      // will only work on leading double dot dirs on rhs+      // therefore it is safe if lhs is already resolved cwd       while ((r.length() > 3) && ((r.substr(0, 3) == "../") || (r.substr(0, 3)) == "..\\")) {         size_t L = l.length(), pos = find_last_folder_separator(l, L - 2);         bool is_slash = pos + 2 == L && (l[pos+1] == '/' || l[pos+1] == '\\');@@ -301,13 +323,9 @@     // (2) underscore + given     // (3) underscore + given + extension     // (4) given + extension-    std::vector<Include> resolve_includes(const std::string& root, const std::string& file)+    std::vector<Include> resolve_includes(const std::string& root, const std::string& file, const std::vector<std::string>& exts)     {       std::string filename = join_paths(root, file);-      // supported extensions-      const std::vector<std::string> exts = {-        ".scss", ".sass", ".css"-      };       // split the filename       std::string base(dir_name(file));       std::string name(base_name(file));@@ -336,9 +354,42 @@       return includes;     } -    // helper function to resolve a filename+    std::vector<std::string> find_files(const std::string& file, const std::vector<std::string> paths)+    {+      std::vector<std::string> includes;+      for (std::string path : paths) {+        std::string abs_path(join_paths(path, file));+        if (file_exists(abs_path)) includes.push_back(abs_path);+      }+      return includes;+    }++    std::vector<std::string> find_files(const std::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 std::vector<std::string>& incs = compiler->cpp_ctx->include_paths;+      // create the vector with paths to lookup+      std::vector<std::string> 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);+    }++    // helper function to search one file in all include paths+    // this is normally not used internally by libsass (C-API sugar)     std::string find_file(const std::string& file, const std::vector<std::string> paths)     {+      if (file.empty()) return file;+      auto res = find_files(file, paths);+      return res.empty() ? "" : res.front();+    }++    // helper function to resolve a filename+    std::string find_include(const std::string& file, const std::vector<std::string> paths)+    {       // search in every include path for a match       for (size_t i = 0, S = paths.size(); i < S; ++i)       {@@ -349,20 +400,6 @@       return std::string("");     } -    // inc paths can be directly passed from C code-    std::string find_file(const std::string& file, const char* paths[])-    {-      if (paths == 0) return std::string("");-      std::vector<std::string> includes(0);-      // includes.push_back(".");-      const char** it = paths;-      while (it && *it) {-        includes.push_back(*it);-        ++it;-      }-      return find_file(file, includes);-    }-     // try to load the given filename     // returned memory must be freed     // will auto convert .sass files@@ -371,16 +408,24 @@       #ifdef _WIN32         BYTE* pBuffer;         DWORD dwBytes;+        wchar_t resolved[32768];         // windows unicode filepaths are encoded in utf16-        std::wstring wpath = UTF_8::convert_to_utf16(path);-        HANDLE hFile = CreateFileW(wpath.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);+        std::string abspath(join_paths(get_cwd(), path));+        std::wstring wpath(UTF_8::convert_to_utf16("\\\\?\\" + abspath));+        std::replace(wpath.begin(), wpath.end(), '/', '\\');+        DWORD rv = GetFullPathNameW(wpath.c_str(), 32767, resolved, NULL);+        if (rv > 32767) throw Exception::OperationError("Path is too long");+        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;         DWORD dwFileLength = GetFileSize(hFile, NULL);         if (dwFileLength == INVALID_FILE_SIZE) return 0;         // allocate an extra byte for the null char-        pBuffer = (BYTE*)malloc((dwFileLength+1)*sizeof(BYTE));+        // and another one for edge-cases in lexer+        pBuffer = (BYTE*)malloc((dwFileLength+2)*sizeof(BYTE));         ReadFile(hFile, pBuffer, dwFileLength, &dwBytes, NULL);-        pBuffer[dwFileLength] = '\0';+        pBuffer[dwFileLength+0] = '\0';+        pBuffer[dwFileLength+1] = '\0';         CloseHandle(hFile);         // just convert from unsigned char*         char* contents = (char*) pBuffer;@@ -392,10 +437,12 @@         if (file.is_open()) {           size_t size = file.tellg();           // allocate an extra byte for the null char-          contents = (char*) malloc((size+1)*sizeof(char));+          // and another one for edge-cases in lexer+          contents = (char*) malloc((size+2)*sizeof(char));           file.seekg(0, std::ios::beg);           file.read(contents, size);-          contents[size] = '\0';+          contents[size+0] = '\0';+          contents[size+1] = '\0';           file.close();         }       #endif@@ -412,6 +459,26 @@       } else {         return contents;       }+    }++    // split a path string delimited by semicolons or colons (OS dependent)+    std::vector<std::string> split_path_list(const char* str)+    {+      std::vector<std::string> paths;+      if (str == NULL) return paths;+      // find delimiter via prelexer (return zero at end)+      const char* end = Prelexer::find_first<PATH_SEP>(str);+      // search until null delimiter+      while (end) {+        // add path from current position to delimiter+        paths.push_back(std::string(str, end - str));+        str = end + 1; // skip delimiter+        end = Prelexer::find_first<PATH_SEP>(str);+      }+      // add path from current position to end+      paths.push_back(std::string(str));+      // return back+      return paths;     }    }
libsass/src/file.hpp view
@@ -4,6 +4,7 @@ #include <string> #include <vector> +#include "sass/context.h" #include "ast_fwd_decl.hpp"  namespace Sass {@@ -47,10 +48,17 @@     std::string abs2rel(const std::string& path, const std::string& base = ".", const std::string& cwd = get_cwd());      // helper function to resolve a filename+    // searching without variations in all paths+    std::string find_file(const std::string& file, struct Sass_Compiler* options);     std::string find_file(const std::string& file, const std::vector<std::string> paths);-    // inc paths can be directly passed from C code-    std::string find_file(const std::string& file, const char** paths); +    // helper function to resolve a include filename+    // this has the original resolve logic for sass include+    std::string find_include(const std::string& file, const std::vector<std::string> paths);++    // split a path string delimited by semicolons or colons (OS dependent)+    std::vector<std::string> split_path_list(const char* paths);+     // try to load the given filename     // returned memory must be freed     // will auto convert .sass files@@ -113,7 +121,10 @@    namespace File { -    std::vector<Include> resolve_includes(const std::string& root, const std::string& file);+    static std::vector<std::string> defaultExtensions = { ".scss", ".sass", ".css" };++    std::vector<Include> resolve_includes(const std::string& root, const std::string& file,+      const std::vector<std::string>& exts = defaultExtensions);    } 
libsass/src/functions.cpp view
@@ -10,6 +10,7 @@ #include "eval.hpp" #include "util.hpp" #include "expand.hpp"+#include "operators.hpp" #include "utf8_string.hpp" #include "sass/base.h" #include "utf8.h"@@ -30,17 +31,34 @@ #include "wincrypt.h" #endif -#define ARG(argname, argtype) get_arg<argtype>(argname, env, sig, pstate, backtrace)-#define ARGR(argname, argtype, lo, hi) get_arg_r(argname, env, sig, pstate, lo, hi, backtrace)-#define ARGM(argname, argtype, ctx) get_arg_m(argname, env, sig, pstate, backtrace, ctx)+#define ARG(argname, argtype) get_arg<argtype>(argname, env, sig, pstate, traces)+#define ARGM(argname, argtype, ctx) get_arg_m(argname, env, sig, pstate, traces, ctx) +// return a number object (copied since we want to have reduced units)+#define ARGN(argname) get_arg_n(argname, env, sig, pstate, traces) // Number copy++// special function for weird hsla percent (10px == 10% == 10 != 0.1)+#define ARGVAL(argname) get_arg_val(argname, env, sig, pstate, traces) // double++// macros for common ranges (u mean unsigned or upper, r for full range)+#define DARG_U_FACT(argname) get_arg_r(argname, env, sig, pstate, traces, - 0.0, 1.0) // double+#define DARG_R_FACT(argname) get_arg_r(argname, env, sig, pstate, traces, - 1.0, 1.0) // double+#define DARG_U_BYTE(argname) get_arg_r(argname, env, sig, pstate, traces, - 0.0, 255.0) // double+#define DARG_R_BYTE(argname) get_arg_r(argname, env, sig, pstate, traces, - 255.0, 255.0) // double+#define DARG_U_PRCT(argname) get_arg_r(argname, env, sig, pstate, traces, - 0.0, 100.0) // double+#define DARG_R_PRCT(argname) get_arg_r(argname, env, sig, pstate, traces, - 100.0, 100.0) // double++// macros for color related inputs (rbg and alpha/opacity values)+#define COLOR_NUM(argname) color_num(argname, env, sig, pstate, traces) // double+#define ALPHA_NUM(argname) alpha_num(argname, env, sig, pstate, traces) // double+ namespace Sass {   using std::stringstream;   using std::endl;    Definition_Ptr make_native_function(Signature sig, Native_Function func, Context& ctx)   {-    Parser sig_parser = Parser::from_c_str(sig, ctx, ParserState("[built-in function]"));+    Parser sig_parser = Parser::from_c_str(sig, ctx, ctx.traces, ParserState("[built-in function]"));     sig_parser.lex<Prelexer::identifier>();     std::string name(Util::normalize_underscores(sig_parser.lexed));     Parameters_Obj params = sig_parser.parse_parameters();@@ -58,7 +76,7 @@     using namespace Prelexer;      const char* sig = sass_function_get_signature(c_func);-    Parser sig_parser = Parser::from_c_str(sig, ctx, ParserState("[c function]"));+    Parser sig_parser = Parser::from_c_str(sig, ctx, ctx.traces, ParserState("[c function]"));     // allow to overload generic callback plus @warn, @error and @debug with custom functions     sig_parser.lex < alternatives < identifier, exactly <'*'>,                                     exactly < Constants::warn_kwd >,@@ -84,31 +102,31 @@    namespace Functions { -    inline void handle_utf8_error (const ParserState& pstate, Backtrace* backtrace)+    inline void handle_utf8_error (const ParserState& pstate, Backtraces traces)     {       try {        throw;       }       catch (utf8::invalid_code_point) {         std::string msg("utf8::invalid_code_point");-        error(msg, pstate, backtrace);+        error(msg, pstate, traces);       }       catch (utf8::not_enough_room) {         std::string msg("utf8::not_enough_room");-        error(msg, pstate, backtrace);+        error(msg, pstate, traces);       }       catch (utf8::invalid_utf8) {         std::string msg("utf8::invalid_utf8");-        error(msg, pstate, backtrace);+        error(msg, pstate, traces);       }       catch (...) { throw; }     }      template <typename T>-    T* get_arg(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtrace* backtrace)+    T* get_arg(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces)     {       // Minimal error handling -- the expectation is that built-ins will be written correctly!-      T* val = dynamic_cast<T*>(&env[argname]);+      T* val = Cast<T>(env[argname]);       if (!val) {         std::string msg("argument `");         msg += argname;@@ -116,74 +134,138 @@         msg += sig;         msg += "` must be a ";         msg += T::type_name();-        error(msg, pstate, backtrace);+        error(msg, pstate, traces);       }       return val;     } -    Map_Ptr get_arg_m(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtrace* backtrace, Context& ctx)+    Map_Ptr get_arg_m(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces, Context& ctx)     {       // Minimal error handling -- the expectation is that built-ins will be written correctly!-      Map_Ptr val = SASS_MEMORY_CAST(Map, env[argname]);+      Map_Ptr val = Cast<Map>(env[argname]);       if (val) return val; -      List_Ptr lval = SASS_MEMORY_CAST(List, env[argname]);+      List_Ptr lval = Cast<List>(env[argname]);       if (lval && lval->length() == 0) return SASS_MEMORY_NEW(Map, pstate, 0);        // fallback on get_arg for error handling-      val = get_arg<Map>(argname, env, sig, pstate, backtrace);+      val = get_arg<Map>(argname, env, sig, pstate, traces);       return val;     } -    Number_Ptr get_arg_r(const std::string& argname, Env& env, Signature sig, ParserState pstate, double lo, double hi, Backtrace* backtrace)+    double get_arg_r(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces, double lo, double hi)     {       // Minimal error handling -- the expectation is that built-ins will be written correctly!-      Number_Ptr val = get_arg<Number>(argname, env, sig, pstate, backtrace);-      double v = val->value();+      Number_Ptr val = get_arg<Number>(argname, env, sig, pstate, traces);+      Number tmpnr(val);+      tmpnr.reduce();+      double v = tmpnr.value();       if (!(lo <= v && v <= hi)) {         std::stringstream msg;         msg << "argument `" << argname << "` of `" << sig << "` must be between ";         msg << lo << " and " << hi;-        error(msg.str(), pstate, backtrace);+        error(msg.str(), pstate, traces);       }+      return v;+    }++    Number_Ptr get_arg_n(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces)+    {+      // Minimal error handling -- the expectation is that built-ins will be written correctly!+      Number_Ptr val = get_arg<Number>(argname, env, sig, pstate, traces);+      val = SASS_MEMORY_COPY(val);+      val->reduce();       return val;     } -    #define ARGSEL(argname, seltype, contextualize) get_arg_sel<seltype>(argname, env, sig, pstate, backtrace, ctx)+    double get_arg_v(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces)+    {+      // Minimal error handling -- the expectation is that built-ins will be written correctly!+      Number_Ptr val = get_arg<Number>(argname, env, sig, pstate, traces);+      Number tmpnr(val);+      tmpnr.reduce();+      /*+      if (tmpnr.unit() == "%") {+        tmpnr.value(tmpnr.value() / 100);+        tmpnr.numerators.clear();+      } else {+        if (!tmpnr.is_unitless()) error("argument " + argname + " of `" + std::string(sig) + "` must be unitless", pstate);+      }+      */+      return tmpnr.value();+    } +    double get_arg_val(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces)+    {+      // Minimal error handling -- the expectation is that built-ins will be written correctly!+      Number_Ptr val = get_arg<Number>(argname, env, sig, pstate, traces);+      Number tmpnr(val);+      tmpnr.reduce();+      return tmpnr.value();+    }++    double color_num(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces)+    {+      Number_Ptr val = get_arg<Number>(argname, env, sig, pstate, traces);+      Number tmpnr(val);+      tmpnr.reduce();+      if (tmpnr.unit() == "%") {+        return std::min(std::max(tmpnr.value() * 255 / 100.0, 0.0), 255.0);+      } else {+        return std::min(std::max(tmpnr.value(), 0.0), 255.0);+      }+    }+++    inline double alpha_num(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces) {+      Number_Ptr val = get_arg<Number>(argname, env, sig, pstate, traces);+      Number tmpnr(val);+      tmpnr.reduce();+      if (tmpnr.unit() == "%") {+        return std::min(std::max(tmpnr.value(), 0.0), 100.0);+      } else {+        return std::min(std::max(tmpnr.value(), 0.0), 1.0);+      }+    }++    #define ARGSEL(argname, seltype, contextualize) get_arg_sel<seltype>(argname, env, sig, pstate, traces, ctx)+     template <typename T>-    T get_arg_sel(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtrace* backtrace, Context& ctx);+    T get_arg_sel(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces, Context& ctx);      template <>-    Selector_List_Obj get_arg_sel(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtrace* backtrace, Context& ctx) {+    Selector_List_Obj get_arg_sel(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces, Context& ctx) {       Expression_Obj exp = ARG(argname, Expression);       if (exp->concrete_type() == Expression::NULL_VAL) {         std::stringstream msg;         msg << argname << ": null is not a valid selector: it must be a string,\n";         msg << "a list of strings, or a list of lists of strings for `" << function_name(sig) << "'";-        error(msg.str(), pstate);+        error(msg.str(), pstate, traces);       }-      if (String_Constant_Ptr str = SASS_MEMORY_CAST(String_Constant, exp)) {+      if (String_Constant_Ptr str = Cast<String_Constant>(exp)) {         str->quote_mark(0);       }-      std::string exp_src = exp->to_string(ctx.c_options) + "{";-      return Parser::parse_selector(exp_src.c_str(), ctx);+      std::string exp_src = exp->to_string(ctx.c_options);+      return Parser::parse_selector(exp_src.c_str(), ctx, traces);     }      template <>-    Compound_Selector_Obj get_arg_sel(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtrace* backtrace, Context& ctx) {+    Compound_Selector_Obj get_arg_sel(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces, Context& ctx) {       Expression_Obj exp = ARG(argname, Expression);       if (exp->concrete_type() == Expression::NULL_VAL) {         std::stringstream msg;         msg << argname << ": null is not a string for `" << function_name(sig) << "'";-        error(msg.str(), pstate);+        error(msg.str(), pstate, traces);       }-      if (String_Constant_Ptr str = SASS_MEMORY_CAST(String_Constant, exp)) {+      if (String_Constant_Ptr str = Cast<String_Constant>(exp)) {         str->quote_mark(0);       }-      std::string exp_src = exp->to_string(ctx.c_options) + "{";-      Selector_List_Obj sel_list = Parser::parse_selector(exp_src.c_str(), ctx);-      return (sel_list->length() > 0) ? &sel_list->first()->tail()->head() : 0;+      std::string exp_src = exp->to_string(ctx.c_options);+      Selector_List_Obj sel_list = Parser::parse_selector(exp_src.c_str(), ctx, traces);+      if (sel_list->length() == 0) return NULL;+      Complex_Selector_Obj first = sel_list->first();+      if (!first->tail()) return first->head();+      return first->tail()->head();     }      #ifdef __MINGW32__@@ -219,56 +301,110 @@       "global-variable-shadowing",       "extend-selector-pseudoclass",       "at-error",-      "units-level-3"+      "units-level-3",+      "custom-property"     };      ////////////////     // RGB FUNCTIONS     //////////////// -    inline double color_num(Number_Ptr n) {-      if (n->unit() == "%") {-        return std::min(std::max(n->value() * 255 / 100.0, 0.0), 255.0);-      } else {-        return std::min(std::max(n->value(), 0.0), 255.0);-      }-    }--    inline double alpha_num(Number_Ptr n) {-      if (n->unit() == "%") {-        return std::min(std::max(n->value(), 0.0), 100.0);-      } else {-        return std::min(std::max(n->value(), 0.0), 1.0);+    inline bool special_number(String_Constant_Ptr s) {+      if (s) {+        std::string calc("calc(");+        std::string var("var(");+        std::string ss(s->value());+        return std::equal(calc.begin(), calc.end(), ss.begin()) ||+               std::equal(var.begin(), var.end(), ss.begin());       }+      return false;     }      Signature rgb_sig = "rgb($red, $green, $blue)";     BUILT_IN(rgb)     {+      if (+        special_number(Cast<String_Constant>(env["$red"])) ||+        special_number(Cast<String_Constant>(env["$green"])) ||+        special_number(Cast<String_Constant>(env["$blue"]))+      ) {+        return SASS_MEMORY_NEW(String_Constant, pstate, "rgb("+                                                        + env["$red"]->to_string()+                                                        + ", "+                                                        + env["$green"]->to_string()+                                                        + ", "+                                                        + env["$blue"]->to_string()+                                                        + ")"+        );+      }+       return SASS_MEMORY_NEW(Color,                              pstate,-                             color_num(ARG("$red",   Number)),-                             color_num(ARG("$green", Number)),-                             color_num(ARG("$blue",  Number)));+                             COLOR_NUM("$red"),+                             COLOR_NUM("$green"),+                             COLOR_NUM("$blue"));     }      Signature rgba_4_sig = "rgba($red, $green, $blue, $alpha)";     BUILT_IN(rgba_4)     {+      if (+        special_number(Cast<String_Constant>(env["$red"])) ||+        special_number(Cast<String_Constant>(env["$green"])) ||+        special_number(Cast<String_Constant>(env["$blue"])) ||+        special_number(Cast<String_Constant>(env["$alpha"]))+      ) {+        return SASS_MEMORY_NEW(String_Constant, pstate, "rgba("+                                                        + env["$red"]->to_string()+                                                        + ", "+                                                        + env["$green"]->to_string()+                                                        + ", "+                                                        + env["$blue"]->to_string()+                                                        + ", "+                                                        + env["$alpha"]->to_string()+                                                        + ")"+        );+      }+       return SASS_MEMORY_NEW(Color,                              pstate,-                             color_num(ARG("$red",   Number)),-                             color_num(ARG("$green", Number)),-                             color_num(ARG("$blue",  Number)),-                             alpha_num(ARG("$alpha", Number)));+                             COLOR_NUM("$red"),+                             COLOR_NUM("$green"),+                             COLOR_NUM("$blue"),+                             ALPHA_NUM("$alpha"));     }      Signature rgba_2_sig = "rgba($color, $alpha)";     BUILT_IN(rgba_2)     {+      if (+        special_number(Cast<String_Constant>(env["$color"]))+      ) {+        return SASS_MEMORY_NEW(String_Constant, pstate, "rgba("+                                                        + env["$color"]->to_string()+                                                        + ", "+                                                        + env["$alpha"]->to_string()+                                                        + ")"+        );+      }+       Color_Ptr c_arg = ARG("$color", Color);++      if (+        special_number(Cast<String_Constant>(env["$alpha"]))+      ) {+        std::stringstream strm;+        strm << "rgba("+                 << (int)c_arg->r() << ", "+                 << (int)c_arg->g() << ", "+                 << (int)c_arg->b() << ", "+                 << env["$alpha"]->to_string()+             << ")";+        return SASS_MEMORY_NEW(String_Constant, pstate, strm.str());+      }+       Color_Ptr new_c = SASS_MEMORY_COPY(c_arg);-      new_c->a(alpha_num(ARG("$alpha", Number)));+      new_c->a(ALPHA_NUM("$alpha"));       new_c->disp("");       return new_c;     }@@ -285,14 +421,8 @@     BUILT_IN(blue)     { return SASS_MEMORY_NEW(Number, pstate, ARG("$color", Color)->b()); } -    Signature mix_sig = "mix($color-1, $color-2, $weight: 50%)";-    BUILT_IN(mix)-    {-      Color_Ptr  color1 = ARG("$color-1", Color);-      Color_Ptr  color2 = ARG("$color-2", Color);-      Number_Ptr weight = ARGR("$weight", Number, 0, 100);--      double p = weight->value()/100;+    Color* colormix(Context& ctx, ParserState& pstate, Color* color1, Color* color2, double weight) {+      double p = weight/100;       double w = 2*p - 1;       double a = color1->a() - color2->a(); @@ -307,6 +437,16 @@                              color1->a()*p + color2->a()*(1-p));     } +    Signature mix_sig = "mix($color-1, $color-2, $weight: 50%)";+    BUILT_IN(mix)+    {+      Color_Obj  color1 = ARG("$color-1", Color);+      Color_Obj  color2 = ARG("$color-2", Color);+      double weight = DARG_U_PRCT("$weight");+      return colormix(ctx, pstate, color1, color2, weight);++    }+     ////////////////     // HSL FUNCTIONS     ////////////////@@ -323,7 +463,9 @@       double min = std::min(r, std::min(g, b));       double delta = max - min; -      double h = 0, s = 0, l = (max + min) / 2.0;+      double h = 0;+      double s;+      double l = (max + min) / 2.0;        if (max == min) {         h = s = 0; // achromatic@@ -390,9 +532,24 @@     Signature hsl_sig = "hsl($hue, $saturation, $lightness)";     BUILT_IN(hsl)     {-      return hsla_impl(ARG("$hue", Number)->value(),-                       ARG("$saturation", Number)->value(),-                       ARG("$lightness",  Number)->value(),+      if (+        special_number(Cast<String_Constant>(env["$hue"])) ||+        special_number(Cast<String_Constant>(env["$saturation"])) ||+        special_number(Cast<String_Constant>(env["$lightness"]))+      ) {+        return SASS_MEMORY_NEW(String_Constant, pstate, "hsl("+                                                        + env["$hue"]->to_string()+                                                        + ", "+                                                        + env["$saturation"]->to_string()+                                                        + ", "+                                                        + env["$lightness"]->to_string()+                                                        + ")"+        );+      }++      return hsla_impl(ARGVAL("$hue"),+                       ARGVAL("$saturation"),+                       ARGVAL("$lightness"),                        1.0,                        ctx,                        pstate);@@ -401,10 +558,28 @@     Signature hsla_sig = "hsla($hue, $saturation, $lightness, $alpha)";     BUILT_IN(hsla)     {-      return hsla_impl(ARG("$hue", Number)->value(),-                       ARG("$saturation", Number)->value(),-                       ARG("$lightness",  Number)->value(),-                       ARG("$alpha",  Number)->value(),+      if (+        special_number(Cast<String_Constant>(env["$hue"])) ||+        special_number(Cast<String_Constant>(env["$saturation"])) ||+        special_number(Cast<String_Constant>(env["$lightness"])) ||+        special_number(Cast<String_Constant>(env["$alpha"]))+      ) {+        return SASS_MEMORY_NEW(String_Constant, pstate, "hsla("+                                                        + env["$hue"]->to_string()+                                                        + ", "+                                                        + env["$saturation"]->to_string()+                                                        + ", "+                                                        + env["$lightness"]->to_string()+                                                        + ", "+                                                        + env["$alpha"]->to_string()+                                                        + ")"+        );+      }++      return hsla_impl(ARGVAL("$hue"),+                       ARGVAL("$saturation"),+                       ARGVAL("$lightness"),+                       ARGVAL("$alpha"),                        ctx,                        pstate);     }@@ -443,11 +618,11 @@     BUILT_IN(adjust_hue)     {       Color_Ptr rgb_color = ARG("$color", Color);-      Number_Ptr degrees = ARG("$degrees", Number);+      double degrees = ARGVAL("$degrees");       HSL hsl_color = rgb_to_hsl(rgb_color->r(),                                  rgb_color->g(),                                  rgb_color->b());-      return hsla_impl(hsl_color.h + degrees->value(),+      return hsla_impl(hsl_color.h + degrees,                        hsl_color.s,                        hsl_color.l,                        rgb_color->a(),@@ -459,7 +634,7 @@     BUILT_IN(lighten)     {       Color_Ptr rgb_color = ARG("$color", Color);-      Number_Ptr amount = ARGR("$amount", Number, 0, 100);+      double amount = DARG_U_PRCT("$amount");       HSL hsl_color = rgb_to_hsl(rgb_color->r(),                                  rgb_color->g(),                                  rgb_color->b());@@ -471,7 +646,7 @@        return hsla_impl(hsl_color.h,                        hsl_color.s,-                       hslcolorL + amount->value(),+                       hslcolorL + amount,                        rgb_color->a(),                        ctx,                        pstate);@@ -481,7 +656,7 @@     BUILT_IN(darken)     {       Color_Ptr rgb_color = ARG("$color", Color);-      Number_Ptr amount = ARGR("$amount", Number, 0, 100);+      double amount = DARG_U_PRCT("$amount");       HSL hsl_color = rgb_to_hsl(rgb_color->r(),                                  rgb_color->g(),                                  rgb_color->b());@@ -494,7 +669,7 @@        return hsla_impl(hsl_color.h,                        hsl_color.s,-                       hslcolorL - amount->value(),+                       hslcolorL - amount,                        rgb_color->a(),                        ctx,                        pstate);@@ -504,18 +679,17 @@     BUILT_IN(saturate)     {       // CSS3 filter function overload: pass literal through directly-      Number_Ptr amount = SASS_MEMORY_CAST(Number, env["$amount"]);-      if (!amount) {+      if (!Cast<Number>(env["$amount"])) {         return SASS_MEMORY_NEW(String_Quoted, pstate, "saturate(" + env["$color"]->to_string(ctx.c_options) + ")");       } -      ARGR("$amount", Number, 0, 100);+      double amount = DARG_U_PRCT("$amount");       Color_Ptr rgb_color = ARG("$color", Color);       HSL hsl_color = rgb_to_hsl(rgb_color->r(),                                  rgb_color->g(),                                  rgb_color->b()); -      double hslcolorS = hsl_color.s + amount->value();+      double hslcolorS = hsl_color.s + amount;        // Saturation cannot be below 0 or above 100       if (hslcolorS < 0) {@@ -537,12 +711,12 @@     BUILT_IN(desaturate)     {       Color_Ptr rgb_color = ARG("$color", Color);-      Number_Ptr amount = ARGR("$amount", Number, 0, 100);+      double amount = DARG_U_PRCT("$amount");       HSL hsl_color = rgb_to_hsl(rgb_color->r(),                                  rgb_color->g(),                                  rgb_color->b()); -      double hslcolorS = hsl_color.s - amount->value();+      double hslcolorS = hsl_color.s - amount;        // Saturation cannot be below 0 or above 100       if (hslcolorS <= 0) {@@ -564,7 +738,7 @@     BUILT_IN(grayscale)     {       // CSS3 filter function overload: pass literal through directly-      Number_Ptr amount = SASS_MEMORY_CAST(Number, env["$color"]);+      Number_Ptr amount = Cast<Number>(env["$color"]);       if (amount) {         return SASS_MEMORY_NEW(String_Quoted, pstate, "grayscale(" + amount->to_string(ctx.c_options) + ")");       }@@ -596,22 +770,24 @@                        pstate);     } -    Signature invert_sig = "invert($color)";+    Signature invert_sig = "invert($color, $weight: 100%)";     BUILT_IN(invert)     {       // CSS3 filter function overload: pass literal through directly-      Number_Ptr amount = SASS_MEMORY_CAST(Number, env["$color"]);+      Number_Ptr amount = Cast<Number>(env["$color"]);       if (amount) {         return SASS_MEMORY_NEW(String_Quoted, pstate, "invert(" + amount->to_string(ctx.c_options) + ")");       } +      double weight = DARG_U_PRCT("$weight");       Color_Ptr rgb_color = ARG("$color", Color);-      return SASS_MEMORY_NEW(Color,+      Color_Obj inv = SASS_MEMORY_NEW(Color,                              pstate,                              255 - rgb_color->r(),                              255 - rgb_color->g(),                              255 - rgb_color->b(),                              rgb_color->a());+      return colormix(ctx, pstate, inv, rgb_color, weight);     }      ////////////////////@@ -621,13 +797,13 @@     Signature opacity_sig = "opacity($color)";     BUILT_IN(alpha)     {-      String_Constant_Ptr ie_kwd = SASS_MEMORY_CAST(String_Constant, env["$color"]);+      String_Constant_Ptr ie_kwd = Cast<String_Constant>(env["$color"]);       if (ie_kwd) {         return SASS_MEMORY_NEW(String_Quoted, pstate, "alpha(" + ie_kwd->value() + ")");       }        // CSS3 filter function overload: pass literal through directly-      Number_Ptr amount = SASS_MEMORY_CAST(Number, env["$color"]);+      Number_Ptr amount = Cast<Number>(env["$color"]);       if (amount) {         return SASS_MEMORY_NEW(String_Quoted, pstate, "opacity(" + amount->to_string(ctx.c_options) + ")");       }@@ -640,7 +816,7 @@     BUILT_IN(opacify)     {       Color_Ptr color = ARG("$color", Color);-      double amount = ARGR("$amount", Number, 0, 1)->value();+      double amount = DARG_U_FACT("$amount");       double alpha = std::min(color->a() + amount, 1.0);       return SASS_MEMORY_NEW(Color,                              pstate,@@ -655,7 +831,7 @@     BUILT_IN(transparentize)     {       Color_Ptr color = ARG("$color", Color);-      double amount = ARGR("$amount", Number, 0, 1)->value();+      double amount = DARG_U_FACT("$amount");       double alpha = std::max(color->a() - amount, 0.0);       return SASS_MEMORY_NEW(Color,                              pstate,@@ -673,25 +849,25 @@     BUILT_IN(adjust_color)     {       Color_Ptr color = ARG("$color", Color);-      Number_Ptr r = SASS_MEMORY_CAST(Number, env["$red"]);-      Number_Ptr g = SASS_MEMORY_CAST(Number, env["$green"]);-      Number_Ptr b = SASS_MEMORY_CAST(Number, env["$blue"]);-      Number_Ptr h = SASS_MEMORY_CAST(Number, env["$hue"]);-      Number_Ptr s = SASS_MEMORY_CAST(Number, env["$saturation"]);-      Number_Ptr l = SASS_MEMORY_CAST(Number, env["$lightness"]);-      Number_Ptr a = SASS_MEMORY_CAST(Number, env["$alpha"]);+      Number_Ptr r = Cast<Number>(env["$red"]);+      Number_Ptr g = Cast<Number>(env["$green"]);+      Number_Ptr b = Cast<Number>(env["$blue"]);+      Number_Ptr h = Cast<Number>(env["$hue"]);+      Number_Ptr s = Cast<Number>(env["$saturation"]);+      Number_Ptr l = Cast<Number>(env["$lightness"]);+      Number_Ptr a = Cast<Number>(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);+        error("Cannot specify HSL and RGB values for a color at the same time for `adjust-color'", pstate, traces);       }       if (rgb) {-        double rr = r ? ARGR("$red",   Number, -255, 255)->value() : 0;-        double gg = g ? ARGR("$green", Number, -255, 255)->value() : 0;-        double bb = b ? ARGR("$blue",  Number, -255, 255)->value() : 0;-        double aa = a ? ARGR("$alpha", Number, -1, 1)->value() : 0;+        double rr = r ? DARG_R_BYTE("$red") : 0;+        double gg = g ? DARG_R_BYTE("$green") : 0;+        double bb = b ? DARG_R_BYTE("$blue") : 0;+        double aa = a ? DARG_R_FACT("$alpha") : 0;         return SASS_MEMORY_NEW(Color,                                pstate,                                color->r() + rr,@@ -701,9 +877,9 @@       }       if (hsl) {         HSL hsl_struct = rgb_to_hsl(color->r(), color->g(), color->b());-        double ss = s ? ARGR("$saturation", Number, -100, 100)->value() : 0;-        double ll = l ? ARGR("$lightness",  Number, -100, 100)->value() : 0;-        double aa = a ? ARGR("$alpha",      Number, -1, 1)->value() : 0;+        double ss = s ? DARG_R_PRCT("$saturation") : 0;+        double ll = l ? DARG_R_PRCT("$lightness") : 0;+        double aa = a ? DARG_R_FACT("$alpha") : 0;         return hsla_impl(hsl_struct.h + (h ? h->value() : 0),                          hsl_struct.s + ss,                          hsl_struct.l + ll,@@ -719,7 +895,7 @@                                color->b(),                                color->a() + (a ? a->value() : 0));       }-      error("not enough arguments for `adjust-color'", pstate);+      error("not enough arguments for `adjust-color'", pstate, traces);       // unreachable       return color;     }@@ -728,25 +904,25 @@     BUILT_IN(scale_color)     {       Color_Ptr color = ARG("$color", Color);-      Number_Ptr r = SASS_MEMORY_CAST(Number, env["$red"]);-      Number_Ptr g = SASS_MEMORY_CAST(Number, env["$green"]);-      Number_Ptr b = SASS_MEMORY_CAST(Number, env["$blue"]);-      Number_Ptr h = SASS_MEMORY_CAST(Number, env["$hue"]);-      Number_Ptr s = SASS_MEMORY_CAST(Number, env["$saturation"]);-      Number_Ptr l = SASS_MEMORY_CAST(Number, env["$lightness"]);-      Number_Ptr a = SASS_MEMORY_CAST(Number, env["$alpha"]);+      Number_Ptr r = Cast<Number>(env["$red"]);+      Number_Ptr g = Cast<Number>(env["$green"]);+      Number_Ptr b = Cast<Number>(env["$blue"]);+      Number_Ptr h = Cast<Number>(env["$hue"]);+      Number_Ptr s = Cast<Number>(env["$saturation"]);+      Number_Ptr l = Cast<Number>(env["$lightness"]);+      Number_Ptr a = Cast<Number>(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);+        error("Cannot specify HSL and RGB values for a color at the same time for `scale-color'", pstate, traces);       }       if (rgb) {-        double rscale = (r ? ARGR("$red",   Number, -100.0, 100.0)->value() : 0.0) / 100.0;-        double gscale = (g ? ARGR("$green", Number, -100.0, 100.0)->value() : 0.0) / 100.0;-        double bscale = (b ? ARGR("$blue",  Number, -100.0, 100.0)->value() : 0.0) / 100.0;-        double ascale = (a ? ARGR("$alpha", Number, -100.0, 100.0)->value() : 0.0) / 100.0;+        double rscale = (r ? DARG_R_PRCT("$red") : 0.0) / 100.0;+        double gscale = (g ? DARG_R_PRCT("$green") : 0.0) / 100.0;+        double bscale = (b ? DARG_R_PRCT("$blue") : 0.0) / 100.0;+        double ascale = (a ? DARG_R_PRCT("$alpha") : 0.0) / 100.0;         return SASS_MEMORY_NEW(Color,                                pstate,                                color->r() + rscale * (rscale > 0.0 ? 255 - color->r() : color->r()),@@ -755,10 +931,10 @@                                color->a() + ascale * (ascale > 0.0 ? 1.0 - color->a() : color->a()));       }       if (hsl) {-        double hscale = (h ? ARGR("$hue",        Number, -100.0, 100.0)->value() : 0.0) / 100.0;-        double sscale = (s ? ARGR("$saturation", Number, -100.0, 100.0)->value() : 0.0) / 100.0;-        double lscale = (l ? ARGR("$lightness",  Number, -100.0, 100.0)->value() : 0.0) / 100.0;-        double ascale = (a ? ARGR("$alpha",      Number, -100.0, 100.0)->value() : 0.0) / 100.0;+        double hscale = (h ? DARG_R_PRCT("$hue") : 0.0) / 100.0;+        double sscale = (s ? DARG_R_PRCT("$saturation") : 0.0) / 100.0;+        double lscale = (l ? DARG_R_PRCT("$lightness") : 0.0) / 100.0;+        double ascale = (a ? DARG_R_PRCT("$alpha") : 0.0) / 100.0;         HSL hsl_struct = rgb_to_hsl(color->r(), color->g(), color->b());         hsl_struct.h += hscale * (hscale > 0.0 ? 360.0 - hsl_struct.h : hsl_struct.h);         hsl_struct.s += sscale * (sscale > 0.0 ? 100.0 - hsl_struct.s : hsl_struct.s);@@ -767,7 +943,7 @@         return hsla_impl(hsl_struct.h, hsl_struct.s, hsl_struct.l, alpha, ctx, pstate);       }       if (a) {-        double ascale = (a ? ARGR("$alpha", Number, -100.0, 100.0)->value() : 0.0) / 100.0;+        double ascale = (DARG_R_PRCT("$alpha")) / 100.0;         return SASS_MEMORY_NEW(Color,                                pstate,                                color->r(),@@ -775,7 +951,7 @@                                color->b(),                                color->a() + ascale * (ascale > 0.0 ? 1.0 - color->a() : color->a()));       }-      error("not enough arguments for `scale-color'", pstate);+      error("not enough arguments for `scale-color'", pstate, traces);       // unreachable       return color;     }@@ -784,38 +960,38 @@     BUILT_IN(change_color)     {       Color_Ptr color = ARG("$color", Color);-      Number_Ptr r = SASS_MEMORY_CAST(Number, env["$red"]);-      Number_Ptr g = SASS_MEMORY_CAST(Number, env["$green"]);-      Number_Ptr b = SASS_MEMORY_CAST(Number, env["$blue"]);-      Number_Ptr h = SASS_MEMORY_CAST(Number, env["$hue"]);-      Number_Ptr s = SASS_MEMORY_CAST(Number, env["$saturation"]);-      Number_Ptr l = SASS_MEMORY_CAST(Number, env["$lightness"]);-      Number_Ptr a = SASS_MEMORY_CAST(Number, env["$alpha"]);+      Number_Ptr r = Cast<Number>(env["$red"]);+      Number_Ptr g = Cast<Number>(env["$green"]);+      Number_Ptr b = Cast<Number>(env["$blue"]);+      Number_Ptr h = Cast<Number>(env["$hue"]);+      Number_Ptr s = Cast<Number>(env["$saturation"]);+      Number_Ptr l = Cast<Number>(env["$lightness"]);+      Number_Ptr a = Cast<Number>(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);+        error("Cannot specify HSL and RGB values for a color at the same time for `change-color'", pstate, traces);       }       if (rgb) {         return SASS_MEMORY_NEW(Color,                                pstate,-                               r ? ARGR("$red",   Number, 0, 255)->value() : color->r(),-                               g ? ARGR("$green", Number, 0, 255)->value() : color->g(),-                               b ? ARGR("$blue",  Number, 0, 255)->value() : color->b(),-                               a ? ARGR("$alpha", Number, 0, 255)->value() : color->a());+                               r ? DARG_U_BYTE("$red") : color->r(),+                               g ? DARG_U_BYTE("$green") : color->g(),+                               b ? DARG_U_BYTE("$blue") : color->b(),+                               a ? DARG_U_BYTE("$alpha") : color->a());       }       if (hsl) {         HSL hsl_struct = rgb_to_hsl(color->r(), color->g(), color->b());         if (h) hsl_struct.h = std::fmod(h->value(), 360.0);-        if (s) hsl_struct.s = ARGR("$saturation", Number, 0, 100)->value();-        if (l) hsl_struct.l = ARGR("$lightness",  Number, 0, 100)->value();-        double alpha = a ? ARGR("$alpha", Number, 0, 1.0)->value() : color->a();+        if (s) hsl_struct.s = DARG_U_PRCT("$saturation");+        if (l) hsl_struct.l = DARG_U_PRCT("$lightness");+        double alpha = a ? DARG_U_FACT("$alpha") : color->a();         return hsla_impl(hsl_struct.h, hsl_struct.s, hsl_struct.l, alpha, ctx, pstate);       }       if (a) {-        double alpha = a ? ARGR("$alpha", Number, 0, 1.0)->value() : color->a();+        double alpha = DARG_U_FACT("$alpha");         return SASS_MEMORY_NEW(Color,                                pstate,                                color->r(),@@ -823,7 +999,7 @@                                color->b(),                                alpha);       }-      error("not enough arguments for `change-color'", pstate);+      error("not enough arguments for `change-color'", pstate, traces);       // unreachable       return color;     }@@ -866,25 +1042,26 @@     BUILT_IN(sass_unquote)     {       AST_Node_Obj arg = env["$string"];-      if (String_Quoted_Ptr string_quoted = SASS_MEMORY_CAST(String_Quoted, arg)) {+      if (String_Quoted_Ptr string_quoted = Cast<String_Quoted>(arg)) {         String_Constant_Ptr result = SASS_MEMORY_NEW(String_Constant, pstate, string_quoted->value());         // remember if the string was quoted (color tokens)         result->is_delayed(true); // delay colors         return result;       }-      else if (SASS_MEMORY_CAST(String_Constant, arg)) {-        return (Expression_Ptr) &arg;+      else if (String_Constant_Ptr str = Cast<String_Constant>(arg)) {+        return str;       }-      else {+      else if (Expression_Ptr ex = Cast<Expression>(arg)) {         Sass_Output_Style oldstyle = ctx.c_options.output_style;         ctx.c_options.output_style = SASS_STYLE_NESTED;         std::string val(arg->to_string(ctx.c_options));-        val = SASS_MEMORY_CAST(Null, arg) ? "null" : val;+        val = Cast<Null>(arg) ? "null" : val;         ctx.c_options.output_style = oldstyle;          deprecated_function("Passing " + val + ", a non-string value, to unquote()", pstate);-        return (Expression_Ptr) &arg;+        return ex;       }+      throw std::runtime_error("Invalid Data Type for unquote");     }      Signature quote_sig = "quote($string)";@@ -892,7 +1069,7 @@     {       AST_Node_Obj arg = env["$string"];       // only set quote mark to true if already a string-      if (String_Quoted_Ptr qstr = SASS_MEMORY_CAST(String_Quoted, arg)) {+      if (String_Quoted_Ptr qstr = Cast<String_Quoted>(arg)) {         qstr->quote_mark('*');         return qstr;       }@@ -915,7 +1092,7 @@       }       // handle any invalid utf8 errors       // other errors will be re-thrown-      catch (...) { handle_utf8_error(pstate, backtrace); }+      catch (...) { handle_utf8_error(pstate, traces); }       // return something even if we had an error (-1)       return SASS_MEMORY_NEW(Number, pstate, (double)len);     }@@ -931,8 +1108,7 @@         String_Constant_Ptr i = ARG("$insert", String_Constant);         std::string ins = i->value();         ins = unquote(ins);-        Number_Ptr ind = ARG("$index", Number);-        double index = ind->value();+        double index = ARGVAL("$index");         size_t len = UTF_8::code_point_count(str, 0, str.size());          if (index > 0 && index <= len) {@@ -956,13 +1132,13 @@           str = ins + str;         } -        if (String_Quoted_Ptr ss = SASS_MEMORY_CAST_PTR(String_Quoted, s)) {+        if (String_Quoted_Ptr ss = Cast<String_Quoted>(s)) {           if (ss->quote_mark()) str = quote(str);         }       }       // handle any invalid utf8 errors       // other errors will be re-thrown-      catch (...) { handle_utf8_error(pstate, backtrace); }+      catch (...) { handle_utf8_error(pstate, traces); }       return SASS_MEMORY_NEW(String_Quoted, pstate, str);     } @@ -986,7 +1162,7 @@       }       // handle any invalid utf8 errors       // other errors will be re-thrown-      catch (...) { handle_utf8_error(pstate, backtrace); }+      catch (...) { handle_utf8_error(pstate, traces); }       // return something even if we had an error (-1)       return SASS_MEMORY_NEW(Number, pstate, (double)index);     }@@ -997,15 +1173,15 @@       std::string newstr;       try {         String_Constant_Ptr s = ARG("$string", String_Constant);-        double start_at = ARG("$start-at", Number)->value();-        double end_at = ARG("$end-at", Number)->value();-        String_Quoted_Ptr ss = SASS_MEMORY_CAST_PTR(String_Quoted, s);+        double start_at = ARGVAL("$start-at");+        double end_at = ARGVAL("$end-at");+        String_Quoted_Ptr ss = Cast<String_Quoted>(s);          std::string str = unquote(s->value());          size_t size = utf8::distance(str.begin(), str.end()); -        if (!SASS_MEMORY_CAST(Number, env["$end-at"])) {+        if (!Cast<Number>(env["$end-at"])) {           end_at = -1;         } @@ -1039,7 +1215,7 @@       }       // handle any invalid utf8 errors       // other errors will be re-thrown-      catch (...) { handle_utf8_error(pstate, backtrace); }+      catch (...) { handle_utf8_error(pstate, traces); }       return SASS_MEMORY_NEW(String_Quoted, pstate, newstr);     } @@ -1055,7 +1231,7 @@         }       } -      if (String_Quoted_Ptr ss = SASS_MEMORY_CAST_PTR(String_Quoted, s)) {+      if (String_Quoted_Ptr ss = Cast<String_Quoted>(s)) {         String_Quoted_Ptr cpy = SASS_MEMORY_COPY(ss);         cpy->value(str);         return cpy;@@ -1076,7 +1252,7 @@         }       } -      if (String_Quoted_Ptr ss = SASS_MEMORY_CAST_PTR(String_Quoted, s)) {+      if (String_Quoted_Ptr ss = Cast<String_Quoted>(s)) {         String_Quoted_Ptr cpy = SASS_MEMORY_COPY(ss);         cpy->value(str);         return cpy;@@ -1092,49 +1268,45 @@     Signature percentage_sig = "percentage($number)";     BUILT_IN(percentage)     {-      Number_Ptr n = ARG("$number", Number);-      if (!n->is_unitless()) error("argument $number of `" + std::string(sig) + "` must be unitless", pstate);+      Number_Obj n = ARGN("$number");+      if (!n->is_unitless()) error("argument $number of `" + std::string(sig) + "` must be unitless", pstate, traces);       return SASS_MEMORY_NEW(Number, pstate, n->value() * 100, "%");     }      Signature round_sig = "round($number)";     BUILT_IN(round)     {-      Number_Ptr n = ARG("$number", Number);-      Number_Ptr r = SASS_MEMORY_COPY(n);-      r->pstate(pstate);+      Number_Obj r = ARGN("$number");       r->value(Sass::round(r->value(), ctx.c_options.precision));-      return r;+      r->pstate(pstate);+      return r.detach();     }      Signature ceil_sig = "ceil($number)";     BUILT_IN(ceil)     {-      Number_Ptr n = ARG("$number", Number);-      Number_Ptr r = SASS_MEMORY_COPY(n);-      r->pstate(pstate);+      Number_Obj r = ARGN("$number");       r->value(std::ceil(r->value()));-      return r;+      r->pstate(pstate);+      return r.detach();     }      Signature floor_sig = "floor($number)";     BUILT_IN(floor)     {-      Number_Ptr n = ARG("$number", Number);-      Number_Ptr r = SASS_MEMORY_COPY(n);-      r->pstate(pstate);+      Number_Obj r = ARGN("$number");       r->value(std::floor(r->value()));-      return r;+      r->pstate(pstate);+      return r.detach();     }      Signature abs_sig = "abs($number)";     BUILT_IN(abs)     {-      Number_Ptr n = ARG("$number", Number);-      Number_Ptr r = SASS_MEMORY_COPY(n);-      r->pstate(pstate);+      Number_Obj r = ARGN("$number");       r->value(std::abs(r->value()));-      return r;+      r->pstate(pstate);+      return r.detach();     }      Signature min_sig = "min($numbers...)";@@ -1144,9 +1316,9 @@       Number_Obj least = NULL;       for (size_t i = 0, L = arglist->length(); i < L; ++i) {         Expression_Obj val = arglist->value_at_index(i);-        Number_Obj xi = SASS_MEMORY_CAST(Number, val);+        Number_Obj xi = Cast<Number>(val);         if (!xi) {-          error("\"" + val->to_string(ctx.c_options) + "\" is not a number for `min'", pstate);+          error("\"" + val->to_string(ctx.c_options) + "\" is not a number for `min'", pstate, traces);         }         if (least) {           if (*xi < *least) least = xi;@@ -1162,9 +1334,9 @@       Number_Obj greatest = NULL;       for (size_t i = 0, L = arglist->length(); i < L; ++i) {         Expression_Obj val = arglist->value_at_index(i);-        Number_Obj xi = SASS_MEMORY_CAST(Number, val);+        Number_Obj xi = Cast<Number>(val);         if (!xi) {-          error("\"" + val->to_string(ctx.c_options) + "\" is not a number for `max'", pstate);+          error("\"" + val->to_string(ctx.c_options) + "\" is not a number for `max'", pstate, traces);         }         if (greatest) {           if (*greatest < *xi) greatest = xi;@@ -1177,23 +1349,23 @@     BUILT_IN(random)     {       AST_Node_Obj arg = env["$limit"];-      Value_Ptr v = SASS_MEMORY_CAST(Value, arg);-      Number_Ptr l = SASS_MEMORY_CAST(Number, arg);-      Boolean_Ptr b = SASS_MEMORY_CAST(Boolean, arg);+      Value_Ptr v = Cast<Value>(arg);+      Number_Ptr l = Cast<Number>(arg);+      Boolean_Ptr b = Cast<Boolean>(arg);       if (l) {-        double v = l->value();-        if (v < 1) {+        double lv = l->value();+        if (lv < 1) {           stringstream err;-          err << "$limit " << v << " must be greater than or equal to 1 for `random'";-          error(err.str(), pstate);+          err << "$limit " << lv << " must be greater than or equal to 1 for `random'";+          error(err.str(), pstate, traces);         }-        bool eq_int = std::fabs(trunc(v) - v) < NUMBER_EPSILON;+        bool eq_int = std::fabs(trunc(lv) - lv) < NUMBER_EPSILON;         if (!eq_int) {           stringstream err;-          err << "Expected $limit to be an integer but got " << v << " for `random'";-          error(err.str(), pstate);+          err << "Expected $limit to be an integer but got " << lv << " for `random'";+          error(err.str(), pstate, traces);         }-        std::uniform_real_distribution<> distributor(1, v + 1);+        std::uniform_real_distribution<> distributor(1, lv + 1);         uint_fast32_t distributed = static_cast<uint_fast32_t>(distributor(rand));         return SASS_MEMORY_NEW(Number, pstate, (double)distributed);       }@@ -1202,11 +1374,12 @@         double distributed = static_cast<double>(distributor(rand));         return SASS_MEMORY_NEW(Number, pstate, distributed);       } else if (v) {-        throw Exception::InvalidArgumentType(pstate, "random", "$limit", "number", v);+        traces.push_back(Backtrace(pstate));+        throw Exception::InvalidArgumentType(pstate, traces, "random", "$limit", "number", v);       } else {-        throw Exception::InvalidArgumentType(pstate, "random", "$limit", "number");+        traces.push_back(Backtrace(pstate));+        throw Exception::InvalidArgumentType(pstate, traces, "random", "$limit", "number");       }-      return 0;     }      /////////////////@@ -1216,25 +1389,25 @@     Signature length_sig = "length($list)";     BUILT_IN(length)     {-      if (Selector_List_Ptr sl = SASS_MEMORY_CAST(Selector_List, env["$list"])) {+      if (Selector_List_Ptr sl = Cast<Selector_List>(env["$list"])) {         return SASS_MEMORY_NEW(Number, pstate, (double)sl->length());       }       Expression_Ptr v = ARG("$list", Expression);       if (v->concrete_type() == Expression::MAP) {-        Map_Ptr map = SASS_MEMORY_CAST(Map, env["$list"]);+        Map_Ptr map = Cast<Map>(env["$list"]);         return SASS_MEMORY_NEW(Number, pstate, (double)(map ? map->length() : 1));       }       if (v->concrete_type() == Expression::SELECTOR) {-        if (Compound_Selector_Ptr h = dynamic_cast<Compound_Selector_Ptr>(v)) {+        if (Compound_Selector_Ptr h = Cast<Compound_Selector>(v)) {           return SASS_MEMORY_NEW(Number, pstate, (double)h->length());-        } else if (Selector_List_Ptr ls = SASS_MEMORY_CAST_PTR(Selector_List, v)) {+        } else if (Selector_List_Ptr ls = Cast<Selector_List>(v)) {           return SASS_MEMORY_NEW(Number, pstate, (double)ls->length());         } else {           return SASS_MEMORY_NEW(Number, pstate, 1);         }       } -      List_Ptr list = SASS_MEMORY_CAST(List, env["$list"]);+      List_Ptr list = Cast<List>(env["$list"]);       return SASS_MEMORY_NEW(Number,                              pstate,                              (double)(list ? list->size() : 1));@@ -1243,20 +1416,20 @@     Signature nth_sig = "nth($list, $n)";     BUILT_IN(nth)     {-      Number_Ptr n = ARG("$n", Number);-      Map_Ptr m = SASS_MEMORY_CAST(Map, env["$list"]);-      if (Selector_List_Ptr sl = SASS_MEMORY_CAST(Selector_List, env["$list"])) {+      double nr = ARGVAL("$n");+      Map_Ptr m = Cast<Map>(env["$list"]);+      if (Selector_List_Ptr sl = Cast<Selector_List>(env["$list"])) {         size_t len = m ? m->length() : sl->length();         bool empty = m ? m->empty() : sl->empty();-        if (empty) error("argument `$list` of `" + std::string(sig) + "` must not be empty", pstate);-        double index = std::floor(n->value() < 0 ? len + n->value() : n->value() - 1);-        if (index < 0 || index > len - 1) error("index out of bounds for `" + std::string(sig) + "`", pstate);+        if (empty) error("argument `$list` of `" + std::string(sig) + "` must not be empty", pstate, traces);+        double index = std::floor(nr < 0 ? len + nr : nr - 1);+        if (index < 0 || index > len - 1) error("index out of bounds for `" + std::string(sig) + "`", pstate, traces);         // return (*sl)[static_cast<int>(index)];         Listize listize;         return (*sl)[static_cast<int>(index)]->perform(&listize);       }-      List_Obj l = SASS_MEMORY_CAST(List, env["$list"]);-      if (n->value() == 0) error("argument `$n` of `" + std::string(sig) + "` must be non-zero", pstate);+      List_Obj l = Cast<List>(env["$list"]);+      if (nr == 0) error("argument `$n` of `" + std::string(sig) + "` must be non-zero", pstate, traces);       // if the argument isn't a list, then wrap it in a singleton list       if (!m && !l) {         l = SASS_MEMORY_NEW(List, pstate, 1);@@ -1264,9 +1437,9 @@       }       size_t len = m ? m->length() : l->length();       bool empty = m ? m->empty() : l->empty();-      if (empty) error("argument `$list` of `" + std::string(sig) + "` must not be empty", pstate);-      double index = std::floor(n->value() < 0 ? len + n->value() : n->value() - 1);-      if (index < 0 || index > len - 1) error("index out of bounds for `" + std::string(sig) + "`", pstate);+      if (empty) error("argument `$list` of `" + std::string(sig) + "` must not be empty", pstate, traces);+      double index = std::floor(nr < 0 ? len + nr : nr - 1);+      if (index < 0 || index > len - 1) error("index out of bounds for `" + std::string(sig) + "`", pstate, traces);        if (m) {         l = SASS_MEMORY_NEW(List, pstate, 1);@@ -1284,8 +1457,8 @@     Signature set_nth_sig = "set-nth($list, $n, $value)";     BUILT_IN(set_nth)     {-      Map_Obj m = SASS_MEMORY_CAST(Map, env["$list"]);-      List_Obj l = SASS_MEMORY_CAST(List, env["$list"]);+      Map_Obj m = Cast<Map>(env["$list"]);+      List_Obj l = Cast<List>(env["$list"]);       Number_Obj n = ARG("$n", Number);       Expression_Obj v = ARG("$value", Expression);       if (!l) {@@ -1293,12 +1466,12 @@         l->append(ARG("$list", Expression));       }       if (m) {-        l = m->to_list(ctx, pstate);+        l = m->to_list(pstate);       }-      if (l->empty()) error("argument `$list` of `" + std::string(sig) + "` must not be empty", pstate);+      if (l->empty()) error("argument `$list` of `" + std::string(sig) + "` must not be empty", pstate, traces);       double index = std::floor(n->value() < 0 ? l->length() + n->value() : n->value() - 1);-      if (index < 0 || index > l->length() - 1) error("index out of bounds for `" + std::string(sig) + "`", pstate);-      List_Ptr result = SASS_MEMORY_NEW(List, pstate, l->length(), l->separator());+      if (index < 0 || index > l->length() - 1) error("index out of bounds for `" + std::string(sig) + "`", pstate, traces);+      List_Ptr result = SASS_MEMORY_NEW(List, pstate, l->length(), l->separator(), false, l->is_bracketed());       for (size_t i = 0, L = l->length(); i < L; ++i) {         result->append(((i == index) ? v : (*l)[i]));       }@@ -1308,67 +1481,75 @@     Signature index_sig = "index($list, $value)";     BUILT_IN(index)     {-      Map_Obj m = SASS_MEMORY_CAST(Map, env["$list"]);-      List_Obj l = SASS_MEMORY_CAST(List, env["$list"]);+      Map_Obj m = Cast<Map>(env["$list"]);+      List_Obj l = Cast<List>(env["$list"]);       Expression_Obj v = ARG("$value", Expression);       if (!l) {         l = SASS_MEMORY_NEW(List, pstate, 1);         l->append(ARG("$list", Expression));       }       if (m) {-        l = m->to_list(ctx, pstate);+        l = m->to_list(pstate);       }       for (size_t i = 0, L = l->length(); i < L; ++i) {-        if (Eval::eq(l->value_at_index(i), v)) return SASS_MEMORY_NEW(Number, pstate, (double)(i+1));+        if (Operators::eq(l->value_at_index(i), v)) return SASS_MEMORY_NEW(Number, pstate, (double)(i+1));       }       return SASS_MEMORY_NEW(Null, pstate);     } -    Signature join_sig = "join($list1, $list2, $separator: auto)";+    Signature join_sig = "join($list1, $list2, $separator: auto, $bracketed: auto)";     BUILT_IN(join)     {-      Map_Obj m1 = SASS_MEMORY_CAST(Map, env["$list1"]);-      Map_Obj m2 = SASS_MEMORY_CAST(Map, env["$list2"]);-      List_Obj l1 = SASS_MEMORY_CAST(List, env["$list1"]);-      List_Obj l2 = SASS_MEMORY_CAST(List, env["$list2"]);+      Map_Obj m1 = Cast<Map>(env["$list1"]);+      Map_Obj m2 = Cast<Map>(env["$list2"]);+      List_Obj l1 = Cast<List>(env["$list1"]);+      List_Obj l2 = Cast<List>(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(ctx, pstate);+        l1 = m1->to_list(pstate);         sep_val = SASS_COMMA;       }       if (m2) {-        l2 = m2->to_list(ctx, pstate);+        l2 = m2->to_list(pstate);       }       size_t len = l1->length() + l2->length();       std::string sep_str = unquote(sep->value());       if (sep_str == "space") sep_val = SASS_SPACE;       else if (sep_str == "comma") sep_val = SASS_COMMA;-      else if (sep_str != "auto") error("argument `$separator` of `" + std::string(sig) + "` must be `space`, `comma`, or `auto`", pstate);-      List_Obj result = SASS_MEMORY_NEW(List, pstate, len, sep_val);-      result->concat(&l1);-      result->concat(&l2);+      else if (sep_str != "auto") error("argument `$separator` of `" + std::string(sig) + "` must be `space`, `comma`, or `auto`", pstate, traces);+      String_Constant_Obj bracketed_as_str = Cast<String_Constant>(bracketed);+      bool bracketed_is_auto = bracketed_as_str && unquote(bracketed_as_str->value()) == "auto";+      if (!bracketed_is_auto) {+        is_bracketed = !bracketed->is_false();+      }+      List_Obj result = SASS_MEMORY_NEW(List, pstate, len, sep_val, false, is_bracketed);+      result->concat(l1);+      result->concat(l2);       return result.detach();     }      Signature append_sig = "append($list, $val, $separator: auto)";     BUILT_IN(append)     {-      Map_Obj m = SASS_MEMORY_CAST(Map, env["$list"]);-      List_Obj l = SASS_MEMORY_CAST(List, env["$list"]);+      Map_Obj m = Cast<Map>(env["$list"]);+      List_Obj l = Cast<List>(env["$list"]);       Expression_Obj v = ARG("$val", Expression);-      if (Selector_List_Ptr sl = SASS_MEMORY_CAST(Selector_List, env["$list"])) {+      if (Selector_List_Ptr sl = Cast<Selector_List>(env["$list"])) {         Listize listize;-        l = SASS_MEMORY_CAST_PTR(List, sl->perform(&listize));+        l = Cast<List>(sl->perform(&listize));       }       String_Constant_Obj sep = ARG("$separator", String_Constant);       if (!l) {@@ -1376,14 +1557,14 @@         l->append(ARG("$list", Expression));       }       if (m) {-        l = m->to_list(ctx, pstate);+        l = m->to_list(pstate);       }       List_Ptr result = SASS_MEMORY_COPY(l);       std::string sep_str(unquote(sep->value()));       if (sep_str != "auto") { // check default first         if (sep_str == "space") result->separator(SASS_SPACE);         else if (sep_str == "comma") result->separator(SASS_COMMA);-        else error("argument `$separator` of `" + std::string(sig) + "` must be `space`, `comma`, or `auto`", pstate);+        else error("argument `$separator` of `" + std::string(sig) + "` must be `space`, `comma`, or `auto`", pstate, traces);       }       if (l->is_arglist()) {         result->append(SASS_MEMORY_NEW(Argument,@@ -1405,20 +1586,20 @@       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 = SASS_MEMORY_CAST(List, arglist->value_at_index(i));-        Map_Obj mith = SASS_MEMORY_CAST(Map, arglist->value_at_index(i));+        List_Obj ith = Cast<List>(arglist->value_at_index(i));+        Map_Obj mith = Cast<Map>(arglist->value_at_index(i));         if (!ith) {           if (mith) {-            ith = mith->to_list(ctx, pstate);+            ith = mith->to_list(pstate);           } else {             ith = SASS_MEMORY_NEW(List, pstate, 1);             ith->append(arglist->value_at_index(i));           }           if (arglist->is_arglist()) {-            Argument_Obj arg = (Argument_Ptr)(&arglist->at(i));-            arg->value(&ith);+            Argument_Obj arg = (Argument_Ptr)(arglist->at(i).ptr()); // XXX+            arg->value(ith);           } else {-            (*arglist)[i] = &ith;+            (*arglist)[i] = ith;           }         }         shortest = (i ? std::min(shortest, ith->length()) : ith->length());@@ -1428,7 +1609,7 @@       for (size_t i = 0; i < shortest; ++i) {         List_Ptr zipper = SASS_MEMORY_NEW(List, pstate, L);         for (size_t j = 0; j < L; ++j) {-          zipper->append(SASS_MEMORY_CAST(List, arglist->value_at_index(j))->at(i));+          zipper->append(Cast<List>(arglist->value_at_index(j))->at(i));         }         zippers->append(zipper);       }@@ -1438,7 +1619,7 @@     Signature list_separator_sig = "list_separator($list)";     BUILT_IN(list_separator)     {-      List_Obj l = SASS_MEMORY_CAST(List, env["$list"]);+      List_Obj l = Cast<List>(env["$list"]);       if (!l) {         l = SASS_MEMORY_NEW(List, pstate, 1);         l->append(ARG("$list", Expression));@@ -1460,8 +1641,10 @@       Map_Obj m = ARGM("$map", Map, ctx);       Expression_Obj v = ARG("$key", Expression);       try {-        Expression_Obj val = m->at(v); // XXX-        return val ? val.detach() : SASS_MEMORY_NEW(Null, pstate);+        Expression_Obj val = m->at(v);+        if (!val) return SASS_MEMORY_NEW(Null, pstate);+        val->set_delayed(false);+        return val.detach();       } catch (const std::out_of_range&) {         return SASS_MEMORY_NEW(Null, pstate);       }@@ -1507,8 +1690,8 @@       size_t len = m1->length() + m2->length();       Map_Ptr result = SASS_MEMORY_NEW(Map, pstate, len);       // concat not implemented for maps-      *result += &m1;-      *result += &m2;+      *result += m1;+      *result += m2;       return result;     } @@ -1522,7 +1705,7 @@       for (auto key : m->keys()) {         remove = false;         for (size_t j = 0, K = arglist->length(); j < K && !remove; ++j) {-          remove = Eval::eq(key, arglist->value_at_index(j));+          remove = Operators::eq(key, arglist->value_at_index(j));         }         if (!remove) *result << std::make_pair(key, m->at(key));       }@@ -1536,7 +1719,7 @@       Map_Obj result = SASS_MEMORY_NEW(Map, pstate, 1);       for (size_t i = arglist->size(), L = arglist->length(); i < L; ++i) {         Expression_Obj obj = arglist->at(i);-        Argument_Obj arg = (Argument_Ptr)&obj;+        Argument_Obj arg = (Argument_Ptr) obj.ptr(); // XXX         std::string name = std::string(arg->name());         name = name.erase(0, 1); // sanitize name (remove dollar sign)         *result << std::make_pair(SASS_MEMORY_NEW(String_Quoted,@@ -1559,23 +1742,33 @@      Signature unit_sig = "unit($number)";     BUILT_IN(unit)-    { return SASS_MEMORY_NEW(String_Quoted, pstate, quote(ARG("$number", Number)->unit(), '"')); }+    {+      Number_Obj arg = ARGN("$number");+      std::string str(quote(arg->unit(), '"'));+      return SASS_MEMORY_NEW(String_Quoted, pstate, str);+    }      Signature unitless_sig = "unitless($number)";     BUILT_IN(unitless)-    { return SASS_MEMORY_NEW(Boolean, pstate, ARG("$number", Number)->is_unitless()); }+    {+      Number_Obj arg = ARGN("$number");+      bool unitless = arg->is_unitless();+      return SASS_MEMORY_NEW(Boolean, pstate, unitless);+    }      Signature comparable_sig = "comparable($number-1, $number-2)";     BUILT_IN(comparable)     {-      Number_Ptr n1 = ARG("$number-1", Number);-      Number_Ptr n2 = ARG("$number-2", Number);+      Number_Obj n1 = ARGN("$number-1");+      Number_Obj n2 = ARGN("$number-2");       if (n1->is_unitless() || n2->is_unitless()) {         return SASS_MEMORY_NEW(Boolean, pstate, true);       }-      Number tmp_n2(n2); // copy-      tmp_n2.normalize(n1->find_convertible_unit());-      return SASS_MEMORY_NEW(Boolean, pstate, n1->unit() == tmp_n2.unit());+      // normalize into main units+      n1->normalize(); n2->normalize();+      Units &lhs_unit = *n1, &rhs_unit = *n2;+      bool is_comparable = (lhs_unit == rhs_unit);+      return SASS_MEMORY_NEW(Boolean, pstate, is_comparable);     }      Signature variable_exists_sig = "variable-exists($name)";@@ -1607,9 +1800,14 @@     Signature function_exists_sig = "function-exists($name)";     BUILT_IN(function_exists)     {-      std::string s = Util::normalize_underscores(unquote(ARG("$name", String_Constant)->value()));+      String_Constant_Ptr ss = Cast<String_Constant>(env["$name"]);+      if (!ss) {+        error("$name: " + (env["$name"]->to_string()) + " is not a string for `function-exists'", pstate, traces);+      } -      if(d_env.has_global(s+"[f]")) {+      std::string name = Util::normalize_underscores(unquote(ss->value()));++      if(d_env.has_global(name+"[f]")) {         return SASS_MEMORY_NEW(Boolean, pstate, true);       }       else {@@ -1646,24 +1844,37 @@     Signature call_sig = "call($name, $args...)";     BUILT_IN(call)     {-      std::string name = Util::normalize_underscores(unquote(ARG("$name", String_Constant)->value()));+      std::string name;+      Function_Ptr ff = Cast<Function>(env["$name"]);+      String_Constant_Ptr ss = Cast<String_Constant>(env["$name"]);++      if (ss) {+        name = Util::normalize_underscores(unquote(ss->value()));+        std::cerr << "DEPRECATION WARNING: ";+        std::cerr << "Passing a string to call() is deprecated and will be illegal" << std::endl;+        std::cerr << "in Sass 4.0. Use call(get-function(" + quote(name) + ")) instead." << std::endl;+        std::cerr << std::endl;+      } else if (ff) {+        name = ff->name();+      }+       List_Obj arglist = SASS_MEMORY_COPY(ARG("$args", List));        Arguments_Obj args = SASS_MEMORY_NEW(Arguments, pstate);       // std::string full_name(name + "[f]");-      // Definition_Ptr def = d_env.has(full_name) ? static_cast<Definition_Ptr>((d_env)[full_name]) : 0;+      // Definition_Ptr def = d_env.has(full_name) ? Cast<Definition>((d_env)[full_name]) : 0;       // Parameters_Ptr params = def ? def->parameters() : 0;       // size_t param_size = params ? params->length() : 0;       for (size_t i = 0, L = arglist->length(); i < L; ++i) {         Expression_Obj expr = arglist->value_at_index(i);         // if (params && params->has_rest_parameter()) {         //   Parameter_Obj p = param_size > i ? (*params)[i] : 0;-        //   List_Ptr list = SASS_MEMORY_CAST(List, expr);+        //   List_Ptr list = Cast<List>(expr);         //   if (list && p && !p->is_rest_parameter()) expr = (*list)[0];         // }         if (arglist->is_arglist()) {           Expression_Obj obj = arglist->at(i);-          Argument_Obj arg = (Argument_Ptr)&obj;+          Argument_Obj arg = (Argument_Ptr) obj.ptr(); // XXX           args->append(SASS_MEMORY_NEW(Argument,                                        pstate,                                        expr,@@ -1674,11 +1885,11 @@           args->append(SASS_MEMORY_NEW(Argument, pstate, expr));         }       }-      Function_Call_Obj func = SASS_MEMORY_NEW(Function_Call, pstate, name, &args);-      Expand expand(ctx, &d_env, backtrace, &selector_stack);+      Function_Call_Obj func = SASS_MEMORY_NEW(Function_Call, pstate, name, args);+      Expand expand(ctx, &d_env, &selector_stack);       func->via_call(true); // calc invoke is allowed+      if (ff) func->func(ff);       return func->perform(&expand.eval);-     }      ////////////////////@@ -1696,24 +1907,13 @@     // { return ARG("$condition", Expression)->is_false() ? ARG("$if-false", Expression) : ARG("$if-true", Expression); }     BUILT_IN(sass_if)     {-      Expand expand(ctx, &d_env, backtrace, &selector_stack);+      Expand expand(ctx, &d_env, &selector_stack);       Expression_Obj cond = ARG("$condition", Expression)->perform(&expand.eval);       bool is_true = !cond->is_false();-      Expression_Ptr res = ARG(is_true ? "$if-true" : "$if-false", Expression);+      Expression_Obj res = ARG(is_true ? "$if-true" : "$if-false", Expression);       res = res->perform(&expand.eval);       res->set_delayed(false); // clone?-      return res;-    }--    ////////////////-    // URL FUNCTIONS-    ////////////////--    Signature image_url_sig = "image-url($path, $only-path: false, $cache-buster: false)";-    BUILT_IN(image_url)-    {-      error("`image_url` has been removed from libsass because it's not part of the Sass spec", pstate);-      return 0; // suppress warning, error will exit anyway+      return res.detach();     }      //////////////////////////@@ -1754,24 +1954,24 @@        // Not enough parameters       if( arglist->length() == 0 )-        error("$selectors: At least one selector must be passed for `selector-nest'", pstate);+        error("$selectors: At least one selector must be passed for `selector-nest'", pstate, traces);        // Parse args into vector of selectors       std::vector<Selector_List_Obj> parsedSelectors;       for (size_t i = 0, L = arglist->length(); i < L; ++i) {-        Expression_Obj exp = SASS_MEMORY_CAST(Expression, arglist->value_at_index(i));+        Expression_Obj exp = Cast<Expression>(arglist->value_at_index(i));         if (exp->concrete_type() == Expression::NULL_VAL) {           std::stringstream msg;           msg << "$selectors: null is not a valid selector: it must be a string,\n";           msg << "a list of strings, or a list of lists of strings for 'selector-nest'";-          error(msg.str(), pstate);+          error(msg.str(), pstate, traces);         }-        if (String_Constant_Obj str = SASS_MEMORY_CAST(String_Constant, exp)) {+        if (String_Constant_Obj str = Cast<String_Constant>(exp)) {           str->quote_mark(0);         }-        std::string exp_src = exp->to_string(ctx.c_options) + "{";-        Selector_List_Obj sel = Parser::parse_selector(exp_src.c_str(), ctx);-        parsedSelectors.push_back(&sel);+        std::string exp_src = exp->to_string(ctx.c_options);+        Selector_List_Obj sel = Parser::parse_selector(exp_src.c_str(), ctx, traces);+        parsedSelectors.push_back(sel);       }        // Nothing to do@@ -1787,8 +1987,8 @@       for(;itr != parsedSelectors.end(); ++itr) {         Selector_List_Obj child = *itr;         std::vector<Complex_Selector_Obj> exploded;-        selector_stack.push_back(&result);-        Selector_List_Obj rv = child->resolve_parent_refs(ctx, selector_stack);+        selector_stack.push_back(result);+        Selector_List_Obj rv = child->resolve_parent_refs(selector_stack, traces);         selector_stack.pop_back();         for (size_t m = 0, mLen = rv->length(); m < mLen; ++m) {           exploded.push_back((*rv)[m]);@@ -1807,24 +2007,24 @@        // Not enough parameters       if( arglist->length() == 0 )-        error("$selectors: At least one selector must be passed for `selector-append'", pstate);+        error("$selectors: At least one selector must be passed for `selector-append'", pstate, traces);        // Parse args into vector of selectors       std::vector<Selector_List_Obj> parsedSelectors;       for (size_t i = 0, L = arglist->length(); i < L; ++i) {-        Expression_Obj exp = SASS_MEMORY_CAST(Expression, arglist->value_at_index(i));+        Expression_Obj exp = Cast<Expression>(arglist->value_at_index(i));         if (exp->concrete_type() == Expression::NULL_VAL) {           std::stringstream msg;           msg << "$selectors: null is not a valid selector: it must be a string,\n";           msg << "a list of strings, or a list of lists of strings for 'selector-append'";-          error(msg.str(), pstate);+          error(msg.str(), pstate, traces);         }-        if (String_Constant_Ptr str = SASS_MEMORY_CAST(String_Constant, exp)) {+        if (String_Constant_Ptr str = Cast<String_Constant>(exp)) {           str->quote_mark(0);         }-        std::string exp_src = exp->to_string() + "{";-        Selector_List_Obj sel = Parser::parse_selector(exp_src.c_str(), ctx);-        parsedSelectors.push_back(&sel);+        std::string exp_src = exp->to_string();+        Selector_List_Obj sel = Parser::parse_selector(exp_src.c_str(), ctx, traces);+        parsedSelectors.push_back(sel);       }        // Nothing to do@@ -1861,29 +2061,29 @@               msg += "\" to \"";               msg += parentSeqClone->to_string();               msg += "\" for `selector-append'";-              error(msg, pstate, backtrace);+              error(msg, pstate, traces);             }              // Cannot be a Universal selector-            Element_Selector_Obj pType = SASS_MEMORY_CAST(Element_Selector, childSeq->head()->first());+            Element_Selector_Obj pType = Cast<Element_Selector>(childSeq->head()->first());             if(pType && pType->name() == "*") {               std::string msg("Can't append \"");               msg += childSeq->to_string();               msg += "\" to \"";               msg += parentSeqClone->to_string();               msg += "\" for `selector-append'";-              error(msg, pstate, backtrace);+              error(msg, pstate, traces);             }              // TODO: Add check for namespace stuff              // append any selectors in childSeq's head-            parentSeqClone->innermost()->head()->concat(&base->head());+            parentSeqClone->innermost()->head()->concat(base->head());              // Set parentSeqClone new tail             parentSeqClone->innermost()->tail( base->tail() ); -            newElements.push_back(&parentSeqClone);+            newElements.push_back(parentSeqClone);           }         } @@ -1900,7 +2100,7 @@       Selector_List_Obj selector1 = ARGSEL("$selector1", Selector_List_Obj, p_contextualize);       Selector_List_Obj selector2 = ARGSEL("$selector2", Selector_List_Obj, p_contextualize); -      Selector_List_Obj result = selector1->unify_with(&selector2, ctx);+      Selector_List_Obj result = selector1->unify_with(selector2);       Listize listize;       return result->perform(&listize);     }@@ -1930,9 +2130,10 @@       Selector_List_Obj  extender = ARGSEL("$extender", Selector_List_Obj, p_contextualize);        Subset_Map subset_map;-      extender->populate_extends(extendee, ctx, subset_map);+      extender->populate_extends(extendee, subset_map);+      Extend extend(subset_map); -      Selector_List_Obj result = Extend::extendSelectorList(selector, ctx, subset_map, false);+      Selector_List_Obj result = extend.extendSelectorList(selector, false);        Listize listize;       return result->perform(&listize);@@ -1945,9 +2146,10 @@       Selector_List_Obj original = ARGSEL("$original", Selector_List_Obj, p_contextualize);       Selector_List_Obj replacement = ARGSEL("$replacement", Selector_List_Obj, p_contextualize);       Subset_Map subset_map;-      replacement->populate_extends(original, ctx, subset_map);+      replacement->populate_extends(original, subset_map);+      Extend extend(subset_map); -      Selector_List_Obj result = Extend::extendSelectorList(selector, ctx, subset_map, true);+      Selector_List_Obj result = extend.extendSelectorList(selector, true);        Listize listize;       return result->perform(&listize);@@ -1981,5 +2183,52 @@       return SASS_MEMORY_NEW(String_Quoted, pstate, ss.str());     } +    Signature is_bracketed_sig = "is-bracketed($list)";+    BUILT_IN(is_bracketed)+    {+      Value_Obj value = ARG("$list", Value);+      List_Obj list = Cast<List>(value);+      return SASS_MEMORY_NEW(Boolean, pstate, list && list->is_bracketed());+    }++    Signature content_exists_sig = "content-exists()";+    BUILT_IN(content_exists)+    {+      if (!d_env.has_global("is_in_mixin")) {+        error("Cannot call content-exists() except within a mixin.", pstate, traces);+      }+      return SASS_MEMORY_NEW(Boolean, pstate, d_env.has_lexical("@content[m]"));+    }++    Signature get_function_sig = "get-function($name, $css: false)";+    BUILT_IN(get_function)+    {+      String_Constant_Ptr ss = Cast<String_Constant>(env["$name"]);+      if (!ss) {+        error("$name: " + (env["$name"]->to_string()) + " is not a string for `get-function'", pstate, traces);+      }++      std::string name = Util::normalize_underscores(unquote(ss->value()));+      std::string full_name = name + "[f]";++      Boolean_Obj css = ARG("$css", Boolean);+      if (!css->is_false()) {+        Definition_Ptr def = SASS_MEMORY_NEW(Definition,+                                         pstate,+                                         name,+                                         SASS_MEMORY_NEW(Parameters, pstate),+                                         SASS_MEMORY_NEW(Block, pstate, 0, false),+                                         Definition::FUNCTION);+        return SASS_MEMORY_NEW(Function, pstate, def, true);+      }+++      if (!d_env.has_global(full_name)) {+        error("Function not found: " + name, pstate, traces);+      }++      Definition_Ptr def = Cast<Definition>(d_env[full_name]);+      return SASS_MEMORY_NEW(Function, pstate, def, false);+    }   } }
libsass/src/functions.hpp view
@@ -8,13 +8,12 @@ #include "sass/functions.h"  #define BUILT_IN(name) Expression_Ptr \-name(Env& env, Env& d_env, Context& ctx, Signature sig, ParserState pstate, Backtrace* backtrace, std::vector<Selector_List_Obj> selector_stack)+name(Env& env, Env& d_env, Context& ctx, Signature sig, ParserState pstate, Backtraces traces, std::vector<Selector_List_Obj> selector_stack)  namespace Sass {   struct Backtrace;-  typedef Environment<AST_Node_Obj> Env;   typedef const char* Signature;-  typedef Expression_Ptr (*Native_Function)(Env&, Env&, Context&, Signature, ParserState, Backtrace*, std::vector<Selector_List_Obj>);+  typedef Expression_Ptr (*Native_Function)(Env&, Env&, Context&, Signature, ParserState, Backtraces, std::vector<Selector_List_Obj>);    Definition_Ptr make_native_function(Signature, Native_Function, Context& ctx);   Definition_Ptr make_c_function(Sass_Function_Entry c_func, Context& ctx);@@ -89,7 +88,6 @@     extern Signature call_sig;     extern Signature not_sig;     extern Signature if_sig;-    extern Signature image_url_sig;     extern Signature map_get_sig;     extern Signature map_merge_sig;     extern Signature map_remove_sig;@@ -107,6 +105,9 @@     extern Signature is_superselector_sig;     extern Signature simple_selectors_sig;     extern Signature selector_parse_sig;+    extern Signature is_bracketed_sig;+    extern Signature content_exists_sig;+    extern Signature get_function_sig;      BUILT_IN(rgb);     BUILT_IN(rgba_4);@@ -171,7 +172,6 @@     BUILT_IN(call);     BUILT_IN(sass_not);     BUILT_IN(sass_if);-    BUILT_IN(image_url);     BUILT_IN(map_get);     BUILT_IN(map_merge);     BUILT_IN(map_remove);@@ -189,6 +189,9 @@     BUILT_IN(is_superselector);     BUILT_IN(simple_selectors);     BUILT_IN(selector_parse);+    BUILT_IN(is_bracketed);+    BUILT_IN(content_exists);+    BUILT_IN(get_function);   } } 
libsass/src/inspect.cpp view
@@ -15,7 +15,7 @@  namespace Sass { -  Inspect::Inspect(Emitter emi)+  Inspect::Inspect(const Emitter& emi)   : Emitter(emi)   { }   Inspect::~Inspect() { }@@ -42,7 +42,9 @@   void Inspect::operator()(Ruleset_Ptr ruleset)   {     if (ruleset->selector()) {+      opt.in_selector = true;       ruleset->selector()->perform(this);+      opt.in_selector = false;     }     if (ruleset->block()) {       ruleset->block()->perform(this);@@ -90,7 +92,7 @@     append_token("@at-root ", at_root_block);     append_mandatory_space();     if(at_root_block->expression()) at_root_block->expression()->perform(this);-    at_root_block->block()->perform(this);+    if(at_root_block->block()) at_root_block->block()->perform(this);   }    void Inspect::operator()(Directive_Ptr at_rule)@@ -121,6 +123,8 @@     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();@@ -166,7 +170,7 @@        import->urls().front()->perform(this);       if (import->urls().size() == 1) {-        if (&import->import_queries()) {+        if (import->import_queries()) {           append_mandatory_space();           import->import_queries()->perform(this);         }@@ -179,7 +183,7 @@          import->urls()[i]->perform(this);         if (import->urls().size() - 1 == i) {-          if (&import->import_queries()) {+          if (import->import_queries()) {             append_mandatory_space();             import->import_queries()->perform(this);           }@@ -354,16 +358,27 @@       if (items_output) append_comma_separator();       key->perform(this);       append_colon_separator();+      LOCAL_FLAG(in_space_array, true);+      LOCAL_FLAG(in_comma_array, true);       map->at(key)->perform(this);       items_output = true;     }     append_string(")");   } +  std::string Inspect::lbracket(List_Ptr list) {+    return list->is_bracketed() ? "[" : "(";+  }++  std::string Inspect::rbracket(List_Ptr list) {+    return list->is_bracketed() ? "]" : ")";+  }+   void Inspect::operator()(List_Ptr list)   {-    if (output_style() == TO_SASS && list->empty()) {-      append_string("()");+    if (list->empty() && (output_style() == TO_SASS || list->is_bracketed())) {+      append_string(lbracket(list));+      append_string(rbracket(list));       return;     }     std::string sep(list->separator() == SASS_SPACE ? " " : ",");@@ -374,19 +389,24 @@      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 eqivalent of element_needs_parens-    if (output_style() == TO_SASS &&+    else if (output_style() == TO_SASS &&         list->length() == 1 &&         !list->from_selector() &&-        !SASS_MEMORY_CAST(List, list->at(0)) &&-        !SASS_MEMORY_CAST(Selector_List, list->at(0))) {-      append_string("(");+        !Cast<List>(list->at(0)) &&+        !Cast<Selector_List>(list->at(0))+    ) {+      append_string(lbracket(list));     }     else if (!in_declaration && (list->separator() == SASS_HASH ||         (list->separator() == SASS_SPACE && in_space_array) ||         (list->separator() == SASS_COMMA && in_comma_array)     )) {-      append_string("(");+      append_string(lbracket(list));     }      if (list->separator() == SASS_SPACE) in_space_array = true;@@ -399,7 +419,7 @@       if (output_style() != TO_SASS) {         if (list_item->is_invisible()) {           // this fixes an issue with "" in a list-          if (!SASS_MEMORY_CAST(String_Constant, list_item)) {+          if (!Cast<String_Constant>(list_item)) {             continue;           }         }@@ -415,19 +435,29 @@      in_comma_array = was_comma_array;     in_space_array = was_space_array;++    // if the list is bracketed, always include the right bracket+    if (list->is_bracketed()) {+      if (list->separator() == SASS_COMMA && list->size() == 1) {+        append_string(",");+      }+      append_string(rbracket(list));+    }     // probably ruby sass eqivalent of element_needs_parens-    if (output_style() == TO_SASS &&+    else if (output_style() == TO_SASS &&         list->length() == 1 &&         !list->from_selector() &&-        !SASS_MEMORY_CAST(List, list->at(0)) &&-        !SASS_MEMORY_CAST(Selector_List, list->at(0))) {-      append_string(",)");+        !Cast<List>(list->at(0)) &&+        !Cast<Selector_List>(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(")");+      append_string(rbracket(list));     }    }@@ -443,7 +473,7 @@               expr->is_right_interpolant())      )) append_string(" ");-    switch (expr->type()) {+    switch (expr->optype()) {       case Sass_OP::AND: append_string("&&"); break;       case Sass_OP::OR:  append_string("||");  break;       case Sass_OP::EQ:  append_string("==");  break;@@ -471,8 +501,9 @@    void Inspect::operator()(Unary_Expression_Ptr expr)   {-    if (expr->type() == Unary_Expression::PLUS) append_string("+");-    else                                        append_string("-");+    if (expr->optype() == Unary_Expression::PLUS)       append_string("+");+    else if (expr->optype() == Unary_Expression::SLASH) append_string("/");+    else                                                append_string("-");     expr->operand()->perform(this);   } @@ -493,16 +524,14 @@     append_token(var->name(), var);   } -  void Inspect::operator()(Textual_Ptr txt)-  {-    append_token(txt->value(), txt);-  }-   void Inspect::operator()(Number_Ptr n)   {      std::string res; +    // reduce units+    n->reduce();+     // check if the fractional part of the value equals to zero     // neat trick from http://stackoverflow.com/a/1521682/1550314     // double int_part; bool is_int = modf(value, &int_part) == 0.0;@@ -600,6 +629,11 @@     // maybe an unknown token     std::string name = c->disp(); +    if (opt.in_selector && name != "") {+      append_token(name, c);+      return;+    }+     // resolved color     std::string res_name = name; @@ -648,7 +682,7 @@       ss << name;     }     else if (r == 0 && g == 0 && b == 0 && a == 0) {-        ss << "transparent";+      ss << "transparent";     }     else if (a >= 1) {       if (res_name != "") {@@ -744,9 +778,9 @@   {     append_token("not", sn);     append_mandatory_space();-    if (sn->needs_parens(&sn->condition())) append_string("(");+    if (sn->needs_parens(sn->condition())) append_string("(");     sn->condition()->perform(this);-    if (sn->needs_parens(&sn->condition())) append_string(")");+    if (sn->needs_parens(sn->condition())) append_string(")");   }    void Inspect::operator()(Supports_Declaration_Ptr sd)@@ -766,7 +800,7 @@   void Inspect::operator()(Media_Query_Ptr mq)   {     size_t i = 0;-    if (&mq->media_type()) {+    if (mq->media_type()) {       if      (mq->is_negated())    append_string("not ");       else if (mq->is_restricted()) append_string("only ");       mq->media_type()->perform(this);@@ -798,12 +832,22 @@    void Inspect::operator()(At_Root_Query_Ptr ae)   {-    append_string("(");-    ae->feature()->perform(this);-    if (ae->value()) {-      append_colon_separator();-      ae->value()->perform(this);+    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_Ptr f)+  {+    append_token("get-function", f);+    append_string("(");+    append_string(quote(f->name()));     append_string(")");   } @@ -851,7 +895,7 @@       return;     }     if (a->value()->concrete_type() == Expression::STRING) {-      String_Constant_Ptr s = SASS_MEMORY_CAST(String_Constant, a->value());+      String_Constant_Ptr s = Cast<String_Constant>(a->value());       if (s) s->perform(this);     } else {       a->value()->perform(this);@@ -877,7 +921,9 @@    void Inspect::operator()(Selector_Schema_Ptr s)   {+    opt.in_selector = true;     s->contents()->perform(this);+    opt.in_selector = false;   }    void Inspect::operator()(Parent_Selector_Ptr p)@@ -919,11 +965,15 @@     append_token(s->ns_name(), s);     if (!s->matcher().empty()) {       append_string(s->matcher());-      if (&s->value() && *s->value()) {+      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("]");   } @@ -939,16 +989,20 @@    void Inspect::operator()(Wrapped_Selector_Ptr s)   {-    bool was = in_wrapped;-    in_wrapped = true;-    append_token(s->name(), s);-    append_string("(");-    bool was_comma_array = in_comma_array;-    in_comma_array = false;-    s->selector()->perform(this);-    in_comma_array = was_comma_array;-    append_string(")");-    in_wrapped = was;+    if (s->name() == " ") {+      append_string("");+    } else {+      bool was = in_wrapped;+      in_wrapped = true;+      append_token(s->name(), s);+      append_string("(");+      bool was_comma_array = in_comma_array;+      in_comma_array = false;+      s->selector()->perform(this);+      in_comma_array = was_comma_array;+      append_string(")");+      in_wrapped = was;+    }   }    void Inspect::operator()(Compound_Selector_Ptr s)@@ -1014,6 +1068,7 @@         if (tail) append_mandatory_space();         else append_optional_space();       break;+      default: break;     }     if (tail && comb != Complex_Selector::ANCESTOR_OF) {       if (c->has_line_break()) append_optional_linefeed();@@ -1040,8 +1095,8 @@     bool was_comma_array = in_comma_array;     // probably ruby sass eqivalent of element_needs_parens     if (output_style() == TO_SASS && g->length() == 1 &&-      (!SASS_MEMORY_CAST(List, (*g)[0]) &&-       !SASS_MEMORY_CAST(Selector_List, (*g)[0]))) {+      (!Cast<List>((*g)[0]) &&+       !Cast<Selector_List>((*g)[0]))) {       append_string("(");     }     else if (!in_declaration && in_comma_array) {@@ -1053,7 +1108,7 @@     for (size_t i = 0, L = g->length(); i < L; ++i) {       if (!in_wrapped && i == 0) append_indentation();       if ((*g)[i] == 0) continue;-      schedule_mapping(&g->at(i)->last());+      schedule_mapping(g->at(i)->last());       // add_open_mapping((*g)[i]->last());       (*g)[i]->perform(this);       // add_close_mapping((*g)[i]->last());@@ -1066,8 +1121,8 @@     in_comma_array = was_comma_array;     // probably ruby sass eqivalent of element_needs_parens     if (output_style() == TO_SASS && g->length() == 1 &&-      (!SASS_MEMORY_CAST(List, (*g)[0]) &&-       !SASS_MEMORY_CAST(Selector_List, (*g)[0]))) {+      (!Cast<List>((*g)[0]) &&+       !Cast<Selector_List>((*g)[0]))) {       append_string(",)");     }     else if (!in_declaration && in_comma_array) {
libsass/src/inspect.hpp view
@@ -17,7 +17,7 @@    public: -    Inspect(Emitter emi);+    Inspect(const Emitter& emi);     virtual ~Inspect();      // statements@@ -48,6 +48,7 @@     virtual void operator()(Content_Ptr);     // expressions     virtual void operator()(Map_Ptr);+    virtual void operator()(Function_Ptr);     virtual void operator()(List_Ptr);     virtual void operator()(Binary_Expression_Ptr);     virtual void operator()(Unary_Expression_Ptr);@@ -56,7 +57,6 @@     // virtual void operator()(Custom_Warning_Ptr);     // virtual void operator()(Custom_Error_Ptr);     virtual void operator()(Variable_Ptr);-    virtual void operator()(Textual_Ptr);     virtual void operator()(Number_Ptr);     virtual void operator()(Color_Ptr);     virtual void operator()(Boolean_Ptr);@@ -91,6 +91,9 @@     virtual void operator()(Compound_Selector_Ptr);     virtual void operator()(Complex_Selector_Ptr);     virtual void operator()(Selector_List_Ptr);++    virtual std::string lbracket(List_Ptr);+    virtual std::string rbracket(List_Ptr);      // template <typename U>     // void fallback(U x) { fallback_impl(reinterpret_cast<AST_Node_Ptr>(x)); }
libsass/src/lexer.cpp view
@@ -1,6 +1,5 @@ #include "sass.hpp" #include <cctype>-#include <cstddef> #include <iostream> #include <iomanip> #include "lexer.hpp"@@ -50,6 +49,12 @@       return unsigned(chr - '0') <= '9' - '0';     } +    bool is_number(const char& chr)+    {+      // adapted the technique from is_alpha+      return is_digit(chr) || chr == '-' || chr == '+';+    }+     bool is_xdigit(const char& chr)     {       // adapted the technique from is_alpha@@ -80,10 +85,11 @@     // but with specific ranges (copied from Ruby Sass)     bool is_nonascii(const char& chr)     {+      unsigned int cmp = unsigned(chr);       return (-        (unsigned(chr) >= 128 && unsigned(chr) <= 15572911) ||-        (unsigned(chr) >= 15630464 && unsigned(chr) <= 15712189) ||-        (unsigned(chr) >= 4036001920)+        (cmp >= 128 && cmp <= 15572911) ||+        (cmp >= 15630464 && cmp <= 15712189) ||+        (cmp >= 4036001920)       );     } @@ -91,15 +97,17 @@     // valid in a uri (copied from Ruby Sass)     bool is_uri_character(const char& chr)     {-      return (unsigned(chr) > 41 && unsigned(chr) < 127) ||-             unsigned(chr) == ':' || unsigned(chr) == '/';+      unsigned int cmp = unsigned(chr);+      return (cmp > 41 && cmp < 127) ||+             cmp == ':' || cmp == '/';     }      // check if char is within a reduced ascii range     // valid for escaping (copied from Ruby Sass)     bool is_escapable_character(const char& chr)     {-      return unsigned(chr) > 31 && unsigned(chr) < 127;+      unsigned int cmp = unsigned(chr);+      return cmp > 31 && cmp < 127;     }      // Match word character (look ahead)
libsass/src/lexer.hpp view
@@ -29,6 +29,7 @@     bool is_alpha(const char& src);     bool is_punct(const char& src);     bool is_digit(const char& src);+    bool is_number(const char& src);     bool is_alnum(const char& src);     bool is_xdigit(const char& src);     bool is_unicode(const char& src);@@ -96,9 +97,9 @@     // Regex equivalent: /(?:literal)/     template <const char* str>     const char* exactly(const char* src) {-      if (str == 0) return 0;+      if (str == NULL) return 0;       const char* pre = str;-      if (src == 0) return 0;+      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) {@@ -109,14 +110,22 @@     }  +    // Match a single character literal.+    // Regex equivalent: /(?:x)/i+    // only define lower case alpha chars+    template <char chr>+    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* str>     const char* insensitive(const char* src) {-      if (str == 0) return 0;+      if (str == NULL) return 0;       const char* pre = str;-      if (src == 0) return 0;+      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)) {
libsass/src/listize.cpp view
@@ -64,13 +64,14 @@       break;       case Complex_Selector::ANCESTOR_OF:       break;+      default: break;     }      Complex_Selector_Obj tail = sel->tail();     if (tail)     {       Expression_Obj tt = tail->perform(this);-      if (List_Ptr ls = SASS_MEMORY_CAST(List, tt))+      if (List_Ptr ls = Cast<List>(tt))       { l->concat(ls); }     }     if (l->length() == 0) return 0;@@ -79,7 +80,7 @@    Expression_Ptr Listize::fallback_impl(AST_Node_Ptr n)   {-    return dynamic_cast<Expression_Ptr>(n);+    return Cast<Expression>(n);   }  }
libsass/src/listize.hpp view
@@ -11,7 +11,6 @@  namespace Sass { -  typedef Environment<AST_Node_Obj> Env;   struct Backtrace;    class Listize : public Operation_CRTP<Expression_Ptr, Listize> {
libsass/src/memory/SharedPtr.cpp view
@@ -17,8 +17,8 @@       std::cerr << "###################################\n";       std::cerr << "# REPORTING MISSING DEALLOCATIONS #\n";       std::cerr << "###################################\n";-      for (auto var : all) {-        if (AST_Node_Ptr ast = SASS_MEMORY_CAST_PTR(AST_Node, var)) {+      for (SharedObj* var : all) {+        if (AST_Node_Ptr ast = dynamic_cast<AST_Node*>(var)) {           debug_ast(ast);         } else {           std::cerr << "LEAKED " << var << "\n";@@ -37,22 +37,20 @@     , dbg(false)     #endif   {-      refcounter = 0;-      #ifdef DEBUG_SHARED_PTR-        if (taint) all.push_back(this);-      #endif-    };--    SharedObj::~SharedObj() {-      #ifdef DEBUG_SHARED_PTR-          if (dbg) std::cerr << "Destruct " << this << "\n";-          if(!all.empty()) { // check needed for MSVC (no clue why?)-            all.erase(std::remove(all.begin(), all.end(), this), all.end());-          }-      #endif-    };-+    refcounter = 0;+    #ifdef DEBUG_SHARED_PTR+      if (taint) all.push_back(this);+    #endif+  }; +  SharedObj::~SharedObj() {+    #ifdef DEBUG_SHARED_PTR+      if (dbg) std::cerr << "Destruct " << this << "\n";+      if(!all.empty()) { // check needed for MSVC (no clue why?)+        all.erase(std::remove(all.begin(), all.end(), this), all.end());+      }+    #endif+  };    void SharedPtr::decRefCount() {     if (node) {@@ -62,7 +60,7 @@       #endif       if (node->refcounter == 0) {         #ifdef DEBUG_SHARED_PTR-          AST_Node_Ptr ptr = SASS_MEMORY_CAST_PTR(AST_Node, node);+          // AST_Node_Ptr ast = dynamic_cast<AST_Node*>(node);           if (node->dbg) std::cerr << "DELETE NODE " << node << "\n";         #endif         if (!node->detached) {
libsass/src/memory/SharedPtr.hpp view
@@ -17,7 +17,7 @@   #ifdef DEBUG_SHARED_PTR      #define SASS_MEMORY_NEW(Class, ...) \-      static_cast<Class##_Ptr>((new Class(__VA_ARGS__))->trace(__FILE__, __LINE__)) \+      ((Class*)(new Class(__VA_ARGS__))->trace(__FILE__, __LINE__)) \      #define SASS_MEMORY_COPY(obj) \       ((obj)->copy(__FILE__, __LINE__)) \@@ -38,12 +38,6 @@    #endif -  #define SASS_MEMORY_CAST(Class, obj) \-    (dynamic_cast<Class##_Ptr>(&obj)) \--  #define SASS_MEMORY_CAST_PTR(Class, ptr) \-    (dynamic_cast<Class##_Ptr>(ptr)) \-   class SharedObj {   protected:   friend class SharedPtr;@@ -92,9 +86,9 @@     class SharedPtr {-  private:+  protected:     SharedObj* node;-  private:+  protected:     void decRefCount();     void incRefCount();   public:@@ -103,24 +97,21 @@     : node(NULL) {};     // the create constructor     SharedPtr(SharedObj* ptr);-    // copy assignment operator-    SharedPtr& operator=(const SharedPtr& rhs);-    // move assignment operator-    /* SharedPtr& operator=(SharedPtr&& rhs); */     // the copy constructor     SharedPtr(const SharedPtr& obj);     // the move constructor-    /* SharedPtr(SharedPtr&& obj); */-    // destructor-    ~SharedPtr();+    SharedPtr(SharedPtr&& obj);+    // copy assignment operator+    SharedPtr& operator=(const SharedPtr& obj);+    // move assignment operator+    SharedPtr& operator=(SharedPtr&& obj);+    // pure virtual destructor+    virtual ~SharedPtr() = 0;   public:-    SharedObj* obj () {-      return node;-    };     SharedObj* obj () const {       return node;     };-    SharedObj* operator-> () {+    SharedObj* operator-> () const {       return node;     };     bool isNull () {@@ -129,68 +120,81 @@     bool isNull () const {       return node == NULL;     };-    SharedObj* detach() {-      node->detached = true;-      return node;-    };     SharedObj* detach() const {       if (node) {         node->detached = true;       }       return node;     };-    operator bool() {-      return node != NULL;-    };     operator bool() const {       return node != NULL;     };    }; -  template < typename T >+  template < class T >   class SharedImpl : private SharedPtr {   public:     SharedImpl()     : SharedPtr(NULL) {};     SharedImpl(T* node)     : SharedPtr(node) {};+    template < class U >+    SharedImpl(SharedImpl<U> obj)+    : SharedPtr(static_cast<T*>(obj.ptr())) {}     SharedImpl(T&& node)     : SharedPtr(node) {};     SharedImpl(const T& node)     : SharedPtr(node) {};+    // the copy constructor+    SharedImpl(const SharedImpl<T>& impl)+    : SharedPtr(impl.node) {};+    // the move constructor+    SharedImpl(SharedImpl<T>&& impl)+    : SharedPtr(impl.node) {};+    // copy assignment operator+    SharedImpl<T>& operator=(const SharedImpl<T>& rhs) {+      if (node) decRefCount();+      node = rhs.node;+      incRefCount();+      return *this;+    }+    // move assignment operator+    SharedImpl<T>& operator=(SharedImpl<T>&& rhs) {+      // don't move our self+      if (this != &rhs) {+        if (node) decRefCount();+        node = std::move(rhs.node);+        rhs.node = NULL;+      }+      return *this;+    }     ~SharedImpl() {};   public:-    T* operator& () {-      return static_cast<T*>(this->obj());-    };-    T* operator& () const {+    operator T*() const {       return static_cast<T*>(this->obj());-    };-    T& operator* () {+    }+    operator T&() const {       return *static_cast<T*>(this->obj());-    };+    }     T& operator* () const {       return *static_cast<T*>(this->obj());     };-    T* operator-> () {-      return static_cast<T*>(this->obj());-    };     T* operator-> () const {       return static_cast<T*>(this->obj());     };-    T* ptr () {+    T* ptr () const {       return static_cast<T*>(this->obj());     };-    T* detach() {+    T* detach() const {       if (this->obj() == NULL) return NULL;       return static_cast<T*>(SharedPtr::detach());     }-    bool isNull() {+    bool isNull() const {       return this->obj() == NULL;     }-    operator bool() {-      return this->obj() != NULL;+    bool operator<(const T& rhs) const {+      return *this->ptr() < rhs;     };     operator bool() const {       return this->obj() != NULL;
libsass/src/node.cpp view
@@ -14,15 +14,15 @@   }  -  Node Node::createSelector(Complex_Selector_Ptr pSelector, Context& ctx) {+  Node Node::createSelector(const Complex_Selector& pSelector) {     NodeDequePtr null; -    Complex_Selector_Ptr pStripped = SASS_MEMORY_COPY(pSelector);+    Complex_Selector_Ptr pStripped = SASS_MEMORY_COPY(&pSelector);     pStripped->tail(NULL);     pStripped->combinator(Complex_Selector::ANCESTOR_OF);      Node n(SELECTOR, Complex_Selector::ANCESTOR_OF, pStripped, null /*pCollection*/);-    if (pSelector) n.got_line_feed = pSelector->has_line_feed();+    n.got_line_feed = pSelector.has_line_feed();     return n;   } @@ -50,12 +50,12 @@   { if (pSelector) got_line_feed = pSelector->has_line_feed(); }  -  Node Node::klone(Context& ctx) const {+  Node Node::klone() const {     NodeDequePtr pNewCollection = std::make_shared<NodeDeque>();     if (mpCollection) {       for (NodeDeque::iterator iter = mpCollection->begin(), iterEnd = mpCollection->end(); iter != iterEnd; iter++) {         Node& toClone = *iter;-        pNewCollection->push_back(toClone.klone(ctx));+        pNewCollection->push_back(toClone.klone());       }     } @@ -65,13 +65,13 @@   }  -  bool Node::contains(const Node& potentialChild, bool simpleSelectorOrderDependent) const {-  	bool found = false;+  bool Node::contains(const Node& potentialChild) const {+    bool found = false;      for (NodeDeque::iterator iter = mpCollection->begin(), iterEnd = mpCollection->end(); iter != iterEnd; iter++) {       Node& toTest = *iter; -      if (nodesEqual(toTest, potentialChild, simpleSelectorOrderDependent)) {+      if (toTest == potentialChild) {         found = true;         break;       }@@ -82,37 +82,32 @@     bool Node::operator==(const Node& rhs) const {-  	return nodesEqual(*this, rhs, true /*simpleSelectorOrderDependent*/);-  }---  bool nodesEqual(const Node& lhs, const Node& rhs, bool simpleSelectorOrderDependent) {-    if (lhs.type() != rhs.type()) {+    if (this->type() != rhs.type()) {       return false;     } -    if (lhs.isCombinator()) {+    if (this->isCombinator()) { -    	return lhs.combinator() == rhs.combinator();+      return this->combinator() == rhs.combinator(); -    } else if (lhs.isNil()) {+    } else if (this->isNil()) {        return true; // no state to check -    } else if (lhs.isSelector()){+    } else if (this->isSelector()){ -      return selectors_equal(*&lhs.selector(), *&rhs.selector(), simpleSelectorOrderDependent);+      return *this->selector() == *rhs.selector(); -    } else if (lhs.isCollection()) {+    } else if (this->isCollection()) { -      if (lhs.collection()->size() != rhs.collection()->size()) {+      if (this->collection()->size() != rhs.collection()->size()) {         return false;       } -      for (NodeDeque::iterator lhsIter = lhs.collection()->begin(), lhsIterEnd = lhs.collection()->end(),+      for (NodeDeque::iterator lhsIter = this->collection()->begin(), lhsIterEnd = this->collection()->end(),            rhsIter = rhs.collection()->begin(); lhsIter != lhsIterEnd; lhsIter++, rhsIter++) { -        if (!nodesEqual(*lhsIter, *rhsIter, simpleSelectorOrderDependent)) {+        if (*lhsIter != *rhsIter) {           return false;         } @@ -128,10 +123,10 @@     void Node::plus(Node& rhs) {-  	if (!this->isCollection() || !rhs.isCollection()) {-    	throw "Both the current node and rhs must be collections.";+    if (!this->isCollection() || !rhs.isCollection()) {+      throw "Both the current node and rhs must be collections.";     }-  	this->collection()->insert(this->collection()->end(), rhs.collection()->begin(), rhs.collection()->end());+    this->collection()->insert(this->collection()->end(), rhs.collection()->begin(), rhs.collection()->end());   }  #ifdef DEBUG@@ -177,7 +172,7 @@ #endif  -  Node complexSelectorToNode(Complex_Selector_Ptr pToConvert, Context& ctx) {+  Node complexSelectorToNode(Complex_Selector_Ptr pToConvert) {     if (pToConvert == NULL) {       return Node::createNil();     }@@ -189,20 +184,22 @@     if (pToConvert->head() && pToConvert->head()->has_parent_ref()) {       Complex_Selector_Obj tail = pToConvert->tail();       if (tail) tail->has_line_feed(pToConvert->has_line_feed());-      pToConvert = &tail;+      pToConvert = tail;     }      while (pToConvert) {        bool empty_parent_ref = pToConvert->head() && pToConvert->head()->is_empty_reference(); -      if (pToConvert->head() || empty_parent_ref) {-      }-       // the first Complex_Selector may contain a dummy head pointer, skip it.       if (pToConvert->head() && !empty_parent_ref) {-        node.collection()->push_back(Node::createSelector(pToConvert, ctx));+        node.collection()->push_back(Node::createSelector(*pToConvert));         if (has_lf) node.collection()->back().got_line_feed = has_lf;+        if (pToConvert->head() || empty_parent_ref) {+          if (pToConvert->tail()) {+            pToConvert->tail()->has_line_feed(pToConvert->has_line_feed());+          }+        }         has_lf = false;       } @@ -216,14 +213,14 @@         // pToConvert->tail()->has_line_feed(pToConvert->has_line_feed());       } -      pToConvert = &pToConvert->tail();+      pToConvert = pToConvert->tail();     }      return node;   }  -  Complex_Selector_Ptr nodeToComplexSelector(const Node& toConvert, Context& ctx) {+  Complex_Selector_Ptr nodeToComplexSelector(const Node& toConvert) {     if (toConvert.isNil()) {       return NULL;     }@@ -237,7 +234,6 @@     NodeDeque& childNodes = *toConvert.collection();      std::string noPath("");-    Position noPosition(-1, -1, -1);     Complex_Selector_Obj pFirst = SASS_MEMORY_NEW(Complex_Selector, ParserState("[NODE]"), Complex_Selector::ANCESTOR_OF, NULL, NULL);      Complex_Selector_Obj pCurrent = pFirst;@@ -254,7 +250,7 @@         // collections, and can result in an infinite loop during the call to parentSuperselector()         pCurrent->tail(SASS_MEMORY_COPY(child.selector()));         // if (child.got_line_feed) pCurrent->has_line_feed(child.got_line_feed);-        pCurrent = &pCurrent->tail();+        pCurrent = pCurrent->tail();       } else if (child.isCombinator()) {         pCurrent->combinator(child.combinator());         if (child.got_line_feed) pCurrent->has_line_feed(child.got_line_feed);@@ -265,7 +261,7 @@           if (nextNode.isCombinator()) {             pCurrent->tail(SASS_MEMORY_NEW(Complex_Selector, ParserState("[NODE]"), Complex_Selector::ANCESTOR_OF, NULL, NULL));             if (nextNode.got_line_feed) pCurrent->tail()->has_line_feed(nextNode.got_line_feed);-            pCurrent = &pCurrent->tail();+            pCurrent = pCurrent->tail();           }         }       } else {@@ -285,7 +281,7 @@    // A very naive trim function, which removes duplicates in a node   // This is only used in Complex_Selector::unify_with for now, may need modifications to fit other needs-  Node Node::naiveTrim(Node& seqses, Context& ctx) {+  Node Node::naiveTrim(Node& seqses) {      std::vector<Node*> res;     std::vector<Complex_Selector_Obj> known;
libsass/src/node.hpp view
@@ -61,15 +61,15 @@     static Node createCombinator(const Complex_Selector::Combinator& combinator);      // This method will klone the selector, stripping off the tail and combinator-    static Node createSelector(Complex_Selector_Ptr pSelector, Context& ctx);+    static Node createSelector(const Complex_Selector& pSelector);      static Node createCollection();     static Node createCollection(const NodeDeque& values);      static Node createNil();-    static Node naiveTrim(Node& seqses, Context& ctx);+    static Node naiveTrim(Node& seqses); -    Node klone(Context& ctx) const;+    Node klone() const;      bool operator==(const Node& rhs) const;     inline bool operator!=(const Node& rhs) const { return !(*this == rhs); }@@ -91,7 +91,7 @@     // potentialChild must be a node collection of selectors/combinators. this must be a collection     // of collections of nodes/combinators. This method checks if potentialChild is a child of this     // Node.-    bool contains(const Node& potentialChild, bool simpleSelectorOrderDependent) const;+    bool contains(const Node& potentialChild) const;    private:     // Private constructor; Use the static methods (like createCombinator and createSelector)@@ -110,10 +110,8 @@ #ifdef DEBUG   std::ostream& operator<<(std::ostream& os, const Node& node); #endif-  Node complexSelectorToNode(Complex_Selector_Ptr pToConvert, Context& ctx);-  Complex_Selector_Ptr nodeToComplexSelector(const Node& toConvert, Context& ctx);--  bool nodesEqual(const Node& one, const Node& two, bool simpleSelectorOrderDependent);+  Node complexSelectorToNode(Complex_Selector_Ptr pToConvert);+  Complex_Selector_Ptr nodeToComplexSelector(const Node& toConvert);  } 
libsass/src/operation.hpp view
@@ -40,6 +40,7 @@     // expressions     virtual T operator()(List_Ptr x)                   = 0;     virtual T operator()(Map_Ptr x)                    = 0;+    virtual T operator()(Function_Ptr x)               = 0;     virtual T operator()(Binary_Expression_Ptr x)      = 0;     virtual T operator()(Unary_Expression_Ptr x)       = 0;     virtual T operator()(Function_Call_Ptr x)          = 0;@@ -47,7 +48,6 @@     virtual T operator()(Custom_Warning_Ptr x)         = 0;     virtual T operator()(Custom_Error_Ptr x)           = 0;     virtual T operator()(Variable_Ptr x)               = 0;-    virtual T operator()(Textual_Ptr x)                = 0;     virtual T operator()(Number_Ptr x)                 = 0;     virtual T operator()(Color_Ptr x)                  = 0;     virtual T operator()(Boolean_Ptr x)                = 0;@@ -122,6 +122,7 @@     // expressions     T operator()(List_Ptr x)                   { return static_cast<D*>(this)->fallback(x); }     T operator()(Map_Ptr x)                    { return static_cast<D*>(this)->fallback(x); }+    T operator()(Function_Ptr x)               { return static_cast<D*>(this)->fallback(x); }     T operator()(Binary_Expression_Ptr x)      { return static_cast<D*>(this)->fallback(x); }     T operator()(Unary_Expression_Ptr x)       { return static_cast<D*>(this)->fallback(x); }     T operator()(Function_Call_Ptr x)          { return static_cast<D*>(this)->fallback(x); }@@ -129,7 +130,6 @@     T operator()(Custom_Warning_Ptr x)         { return static_cast<D*>(this)->fallback(x); }     T operator()(Custom_Error_Ptr x)           { return static_cast<D*>(this)->fallback(x); }     T operator()(Variable_Ptr x)               { return static_cast<D*>(this)->fallback(x); }-    T operator()(Textual_Ptr x)                { return static_cast<D*>(this)->fallback(x); }     T operator()(Number_Ptr x)                 { return static_cast<D*>(this)->fallback(x); }     T operator()(Color_Ptr x)                  { return static_cast<D*>(this)->fallback(x); }     T operator()(Boolean_Ptr x)                { return static_cast<D*>(this)->fallback(x); }
+ libsass/src/operators.cpp view
@@ -0,0 +1,240 @@+#include "sass.hpp"+#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(Expression_Obj lhs, Expression_Obj 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(Expression_Obj lhs, Expression_Obj rhs, const Sass_OP op)+    {+      // can only compare numbers!?+      Number_Obj l = Cast<Number>(lhs);+      Number_Obj r = Cast<Number>(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(Expression_Obj lhs, Expression_Obj rhs) { return cmp(lhs, rhs, Sass_OP::LT); }+    bool neq(Expression_Obj lhs, Expression_Obj rhs) { return eq(lhs, rhs) == false; }+    bool gt(Expression_Obj lhs, Expression_Obj rhs) { return !cmp(lhs, rhs, Sass_OP::GT) && neq(lhs, rhs); }+    bool lte(Expression_Obj lhs, Expression_Obj rhs) { return cmp(lhs, rhs, Sass_OP::LTE) || eq(lhs, rhs); }+    bool gte(Expression_Obj lhs, Expression_Obj rhs) { return !cmp(lhs, rhs, Sass_OP::GTE) || eq(lhs, rhs); }++    /* static function, throws OperationError, has no traces but optional pstate for returned value */+    Value_Ptr op_strings(Sass::Operand operand, Value& lhs, Value& rhs, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed)+    {+      enum Sass_OP op = operand.operand;++      String_Quoted_Ptr lqstr = Cast<String_Quoted>(&lhs);+      String_Quoted_Ptr rqstr = Cast<String_Quoted>(&rhs);++      std::string lstr(lqstr ? lqstr->value() : lhs.to_string(opt));+      std::string rstr(rqstr ? rqstr->value() : rhs.to_string(opt));++      if (Cast<Null>(&lhs)) throw Exception::InvalidNullOperation(&lhs, &rhs, op);+      if (Cast<Null>(&rhs)) throw Exception::InvalidNullOperation(&lhs, &rhs, op);++      std::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);+    }++    /* static function, throws OperationError, has no traces but optional pstate for returned value */+    Value_Ptr op_colors(enum Sass_OP op, const Color& lhs, const Color& rhs, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed)+    {+      if (lhs.a() != rhs.a()) {+        throw Exception::AlphaChannelsNotEqual(&lhs, &rhs, op);+      }+      if (op == Sass_OP::DIV && (!rhs.r() || !rhs.g() || !rhs.b())) {+        throw Exception::ZeroDivisionError(lhs, rhs);+      }+      return SASS_MEMORY_NEW(Color,+                             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_Ptr op_numbers(enum Sass_OP op, const Number& lhs, const Number& rhs, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed)+    {+      double lval = lhs.value();+      double rval = rhs.value();++      if (op == Sass_OP::DIV && rval == 0) {+        std::string result(lval ? "Infinity" : "NaN");+        return SASS_MEMORY_NEW(String_Quoted, pstate, result);+      }++      if (op == Sass_OP::MOD && rval == 0) {+        throw Exception::ZeroDivisionError(lhs, rhs);+      }++      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_Ptr 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_Ptr op_number_color(enum Sass_OP op, const Number& lhs, const Color& rhs, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed)+    {+      double lval = lhs.value();+      switch (op) {+        case Sass_OP::ADD:+        case Sass_OP::MUL: {+          return SASS_MEMORY_NEW(Color,+                                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: {+          std::string color(rhs.to_string(opt));+          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_Ptr op_color_number(enum Sass_OP op, const Color& lhs, const Number& rhs, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed)+    {+      double rval = rhs.value();+      if (op == Sass_OP::DIV && rval == 0) {+        // comparison of Fixnum with Float failed?+        throw Exception::ZeroDivisionError(lhs, rhs);+      }+      return SASS_MEMORY_NEW(Color,+                            pstate,+                            ops[op](lhs.r(), rval),+                            ops[op](lhs.g(), rval),+                            ops[op](lhs.b(), rval),+                            lhs.a());+    }++  }++}
+ libsass/src/operators.hpp view
@@ -0,0 +1,30 @@+#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(Expression_Obj, Expression_Obj);+    bool neq(Expression_Obj, Expression_Obj);+    // specific operators based on cmp and eq+    bool lt(Expression_Obj, Expression_Obj);+    bool gt(Expression_Obj, Expression_Obj);+    bool lte(Expression_Obj, Expression_Obj);+    bool gte(Expression_Obj, Expression_Obj);+    // arithmetic for all the combinations that matter+    Value_Ptr op_strings(Sass::Operand, Value&, Value&, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed = false);+    Value_Ptr op_colors(enum Sass_OP, const Color&, const Color&, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed = false);+    Value_Ptr op_numbers(enum Sass_OP, const Number&, const Number&, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed = false);+    Value_Ptr op_number_color(enum Sass_OP, const Number&, const Color&, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed = false);+    Value_Ptr op_color_number(enum Sass_OP, const Color&, const Number&, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed = false);++  };++}++#endif
libsass/src/output.cpp view
@@ -19,13 +19,14 @@    void Output::operator()(Number_Ptr n)   {-    // use values to_string facility-    std::string res = n->to_string(opt);     // check for a valid unit here     // includes result for reporting     if (!n->is_valid_css_unit()) {-      throw Exception::InvalidValue(*n);+      // should be handle in check_expression+      throw Exception::InvalidValue({}, *n);     }+    // use values to_string facility+    std::string res = n->to_string(opt);     // output the final token     append_token(res, n);   }@@ -37,8 +38,8 @@    void Output::operator()(Map_Ptr m)   {-    std::string dbg(m->to_string(opt));-    error(dbg + " isn't a valid CSS value.", m->pstate());+    // should be handle in check_expression+    throw Exception::InvalidValue({}, *m);   }    OutputBuffer Output::get_buffer(void)@@ -116,8 +117,8 @@     if (!Util::isPrintable(r, output_style())) {       for (size_t i = 0, L = b->length(); i < L; ++i) {         const Statement_Obj& stm = b->at(i);-        if (dynamic_cast<Has_Block_Ptr>(&stm)) {-          if (!dynamic_cast<Declaration_Ptr>(&stm)) {+        if (Cast<Has_Block>(stm)) {+          if (!Cast<Declaration>(stm)) {             stm->perform(this);           }         }@@ -134,28 +135,29 @@       append_string(ss.str());       append_optional_linefeed();     }+    scheduled_crutch = s;     if (s) s->perform(this);-    append_scope_opener(&b);+    append_scope_opener(b);     for (size_t i = 0, L = b->length(); i < L; ++i) {       Statement_Obj stm = b->at(i);       bool bPrintExpression = true;       // Check print conditions-      if (Declaration_Ptr dec = SASS_MEMORY_CAST(Declaration, stm)) {-        if (String_Constant_Ptr valConst = SASS_MEMORY_CAST(String_Constant, dec->value())) {+      if (Declaration_Ptr dec = Cast<Declaration>(stm)) {+        if (String_Constant_Ptr valConst = Cast<String_Constant>(dec->value())) {           std::string val(valConst->value());-          if (String_Quoted_Ptr qstr = SASS_MEMORY_CAST_PTR(String_Quoted, valConst)) {+          if (String_Quoted_Ptr qstr = Cast<String_Quoted>(valConst)) {             if (!qstr->quote_mark() && val.empty()) {               bPrintExpression = false;             }           }         }-        else if (List_Ptr list = SASS_MEMORY_CAST(List, dec->value())) {+        else if (List_Ptr list = Cast<List>(dec->value())) {           bool all_invisible = true;           for (size_t list_i = 0, list_L = list->length(); list_i < list_L; ++list_i) {-            Expression_Ptr item = &list->at(list_i);+            Expression_Ptr item = list->at(list_i);             if (!item->is_invisible()) all_invisible = false;           }-          if (all_invisible) bPrintExpression = false;+          if (all_invisible && !list->is_bracketed()) bPrintExpression = false;         }       }       // Print if OK@@ -164,7 +166,7 @@       }     }     if (output_style() == NESTED) indentation -= r->tabs();-    append_scope_closer(&b);+    append_scope_closer(b);    }   void Output::operator()(Keyframe_Rule_Ptr r)@@ -172,7 +174,7 @@     Block_Obj b = r->block();     Selector_Obj v = r->name(); -    if (&v) {+    if (!v.isNull()) {       v->perform(this);     } @@ -201,7 +203,7 @@     if (!Util::isPrintable(f, output_style())) {       for (size_t i = 0, L = b->length(); i < L; ++i) {         Statement_Obj stm = b->at(i);-        if (dynamic_cast<Has_Block_Ptr>(&stm)) {+        if (Cast<Has_Block>(stm)) {           stm->perform(this);         }       }@@ -237,7 +239,7 @@     if (!Util::isPrintable(m, output_style())) {       for (size_t i = 0, L = b->length(); i < L; ++i) {         Statement_Obj stm = b->at(i);-        if (dynamic_cast<Has_Block_Ptr>(&stm)) {+        if (Cast<Has_Block>(stm)) {           stm->perform(this);         }       }@@ -282,7 +284,7 @@     if (v) {       append_mandatory_space();       // ruby sass bug? should use options?-      append_token(v->to_string(/* opt */), &v);+      append_token(v->to_string(/* opt */), v);     }     if (!b) {       append_delimiter();@@ -324,7 +326,7 @@     if (s->can_compress_whitespace() && output_style() == COMPRESSED) {       value.erase(std::remove_if(value.begin(), value.end(), ::isspace), value.end());     }-    if (!in_comment) {+    if (!in_comment && !in_custom_property) {       append_token(string_to_output(value), s);     } else {       append_token(value, s);
libsass/src/parser.cpp view
@@ -1,2776 +1,3121 @@ #include "sass.hpp"-#include <cstdlib>-#include <iostream>-#include <vector>-#include "parser.hpp"-#include "file.hpp"-#include "inspect.hpp"-#include "constants.hpp"-#include "util.hpp"-#include "prelexer.hpp"-#include "color_maps.hpp"-#include "sass/functions.h"-#include "error_handling.hpp"--// 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 <typeinfo>-#include <tuple>--namespace Sass {-  using namespace Constants;-  using namespace Prelexer;--  Parser Parser::from_c_str(const char* beg, Context& ctx, ParserState pstate, const char* source)-  {-    pstate.offset.column = 0;-    pstate.offset.line = 0;-    Parser p(ctx, pstate);-    p.source   = source ? source : beg;-    p.position = beg ? beg : p.source;-    p.end      = p.position + strlen(p.position);-    Block_Obj root = SASS_MEMORY_NEW(Block, pstate);-    p.block_stack.push_back(root);-    root->is_root(true);-    return p;-  }--  Parser Parser::from_c_str(const char* beg, const char* end, Context& ctx, ParserState pstate, const char* source)-  {-    pstate.offset.column = 0;-    pstate.offset.line = 0;-    Parser p(ctx, pstate);-    p.source   = source ? source : beg;-    p.position = beg ? beg : p.source;-    p.end      = end ? end : p.position + strlen(p.position);-    Block_Obj root = SASS_MEMORY_NEW(Block, pstate);-    p.block_stack.push_back(root);-    root->is_root(true);-    return p;-  }--   void Parser::advanceToNextToken() {-      lex < css_comments >(false);-      // advance to position-      pstate += pstate.offset;-      pstate.offset.column = 0;-      pstate.offset.line = 0;-    }--  Selector_List_Obj Parser::parse_selector(const char* beg, Context& ctx, ParserState pstate, const char* source)-  {-    Parser p = Parser::from_c_str(beg, ctx, pstate, source);-    // ToDo: ruby sass errors on parent references-    // ToDo: remap the source-map entries somehow-    return p.parse_selector_list(false);-  }--  bool Parser::peek_newline(const char* start)-  {-    return peek_linefeed(start ? start : position)-           && ! peek_css<exactly<'{'>>(start);-  }--  Parser Parser::from_token(Token t, Context& ctx, ParserState pstate, const char* source)-  {-    Parser p(ctx, pstate);-    p.source   = source ? source : t.begin;-    p.position = t.begin ? t.begin : p.source;-    p.end      = t.end ? t.end : p.position + strlen(p.position);-    Block_Obj root = SASS_MEMORY_NEW(Block, pstate);-    p.block_stack.push_back(root);-    root->is_root(true);-    return p;-  }--  /* main entry point to parse root block */-  Block_Obj Parser::parse()-  {-    bool is_root = false;-    Block_Obj root = SASS_MEMORY_NEW(Block, pstate, 0, true);-    read_bom();--    // custom headers-    if (ctx.resources.size() == 1) {-    is_root = true;-      ctx.apply_custom_headers(&root, path, pstate);-    }--    block_stack.push_back(root);-    /* bool rv = */ parse_block_nodes(is_root);-    block_stack.pop_back();--    // update for end position-    root->update_pstate(pstate);--    if (position != end) {-      css_error("Invalid CSS", " after ", ": expected selector or at-rule, was ");-    }--    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)-  {--    // parse comments before block-    // lex < optional_css_comments >();--    // lex mandatory opener or error out-    if (!lex_css < exactly<'{'> >()) {-      css_error("Invalid CSS", " after ", ": expected \"{\", was ");-    }-    // 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()) css_error("Invalid CSS", " after ", ": expected \"}\", was ");--    if (!lex_css < exactly<'}'> >()) {-      css_error("Invalid CSS", " after ", ": expected \"}\", was ");-    }--    // 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();--    return &block;-  }--  // 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)-  {-    LOCAL_FLAG(in_at_root, 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)-  {--    // 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;-    }-    // return success-    return true;-  }--  // 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.", pstate);-        }-      }-      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 (lex < kwd_extend >(true)) {-      Lookahead lookahead = lookahead_for_include(position);-      if (!lookahead.found) css_error("Invalid CSS", " after ", ": expected selector, was ");-      Selector_Obj target;-      if (lookahead.has_interpolants) target = &parse_selector_schema(lookahead.found);-      else                            target = &parse_selector_list(true);-      block->append(SASS_MEMORY_NEW(Extension, pstate, &target));-    }--    // selector may contain interpolations which need delayed evaluation-    else if (!(lookahead_result = lookahead_for_selector(position)).error)-    { block->append(&parse_ruleset(lookahead_result, is_root)); }--    // parse multiple specific keyword directives-    else if (lex < kwd_media >(true)) { block->append(&parse_media_block()); }-    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(); }--    // generic at keyword (keep last)-    else if (lex< re_special_directive >(true)) { block->append(&parse_special_directive()); }-    else if (lex< re_prefixed_directive >(true)) { block->append(&parse_prefixed_directive()); }-    else if (lex< at_keyword >(true)) { block->append(&parse_directive()); }--    else if (is_root /* && 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;-      }-    }-    // something matched-    return true;-  }-  // EO parse_block_nodes--  // parse imports inside the-  Import_Obj Parser::parse_import()-  {-    Import_Obj imp = SASS_MEMORY_NEW(Import, pstate);-    std::vector<std::pair<std::string,Function_Call_Obj>> to_import;-    bool first = true;-    do {-      while (lex< block_comment >());-      if (lex< quoted_string >()) {-        to_import.push_back(std::pair<std::string,Function_Call_Obj>(std::string(lexed), 0));-      }-      else if (lex< uri_prefix >()) {-        Arguments_Obj args = SASS_MEMORY_NEW(Arguments, pstate);-        Function_Call_Obj result = SASS_MEMORY_NEW(Function_Call, pstate, "url", &args);--        if (lex< quoted_string >()) {-          Expression_Obj the_url = &parse_string();-          args->append(SASS_MEMORY_NEW(Argument, the_url->pstate(), &the_url));-        }-        else if (String_Obj the_url = parse_url_function_argument()) {-          args->append(SASS_MEMORY_NEW(Argument, the_url->pstate(), &the_url));-        }-        else if (peek < skip_over_scopes < exactly < '(' >, exactly < ')' > > >(position)) {-          Expression_Obj the_url = parse_list(); // parse_interpolated_chunk(lexed);-          args->append(SASS_MEMORY_NEW(Argument, the_url->pstate(), &the_url));-        }-        else {-          error("malformed URL", pstate);-        }-        if (!lex< exactly<')'> >()) error("URI is missing ')'", pstate);-        to_import.push_back(std::pair<std::string, Function_Call_Obj>("", &result));-      }-      else {-        if (first) error("@import directive requires a url or quoted path", pstate);-        else error("expecting another url or quoted path in @import list", pstate);-      }-      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);-      } else if (!ctx.call_importers(unquote(location.first), path, pstate, &imp)) {-        ctx.import_url(&imp, location.first, path);-      }-    }--    return imp;-  }--  Definition_Obj Parser::parse_definition(Definition::Type which_type)-  {-    std::string which_str(lexed);-    if (!lex< identifier >()) error("invalid name in " + which_str + " definition", pstate);-    std::string name(Util::normalize_underscores(lexed));-    if (which_type == Definition::FUNCTION && (name == "and" || name == "or" || name == "not"))-    { error("Invalid function name \"" + name + "\".", pstate); }-    ParserState 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()-  {-    std::string name(lexed);-    Position position = after_token;-    Parameters_Obj params = SASS_MEMORY_NEW(Parameters, pstate);-    if (lex_css< exactly<'('> >()) {-      // if there's anything there at all-      if (!peek_css< exactly<')'> >()) {-        do params->append(&parse_parameter());-        while (lex_css< exactly<','> >());-      }-      if (!lex_css< exactly<')'> >()) error("expected a variable name (e.g. $x) or ')' for the parameter list for " + name, position);-    }-    return params;-  }--  Parameter_Obj Parser::parse_parameter()-  {-    while (lex< alternatives < spaces, block_comment > >());-    lex < variable >();-    std::string name(Util::normalize_underscores(lexed));-    ParserState pos = pstate;-    Expression_Obj val;-    bool is_rest = false;-    while (lex< alternatives < spaces, block_comment > >());-    if (lex< exactly<':'> >()) { // there's a default value-      while (lex< block_comment >());-      val = parse_space_list();-    }-    else if (lex< exactly< ellipsis > >()) {-      is_rest = true;-    }-    return SASS_MEMORY_NEW(Parameter, pos, name, &val, is_rest);-  }--  Arguments_Obj Parser::parse_arguments()-  {-    std::string name(lexed);-    Position position = after_token;-    Arguments_Obj args = SASS_MEMORY_NEW(Arguments, pstate);-    if (lex_css< exactly<'('> >()) {-      // if there's anything there at all-      if (!peek_css< exactly<')'> >()) {-        do args->append(&parse_argument());-        while (lex_css< exactly<','> >());-      }-      if (!lex_css< exactly<')'> >()) error("expected a variable name (e.g. $x) or ')' for the parameter list for " + name, position);-    }-    return args;-  }--  Argument_Obj Parser::parse_argument()-  {-    if (peek_css< sequence < exactly< hash_lbrace >, exactly< rbrace > > >()) {-      position += 2;-      css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was ");-    }--    Argument_Obj arg;-    if (peek_css< sequence < variable, optional_css_comments, exactly<':'> > >()) {-      lex_css< variable >();-      std::string name(Util::normalize_underscores(lexed));-      ParserState p = pstate;-      lex_css< exactly<':'> >();-      Expression_Obj val = parse_space_list();-      arg = SASS_MEMORY_NEW(Argument, p, val, name);-    }-    else {-      bool is_arglist = false;-      bool is_keyword = false;-      Expression_Obj val = parse_space_list();-      List_Ptr l = SASS_MEMORY_CAST(List, 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;-  }--  Assignment_Obj Parser::parse_assignment()-  {-    std::string name(Util::normalize_underscores(lexed));-    ParserState var_source_position = pstate;-    if (!lex< exactly<':'> >()) error("expected ':' after " + name + " in assignment statement", pstate);-    if (peek_css< alternatives < exactly<';'>, end_of_file > >()) {-      css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was ");-    }-    Expression_Obj 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);-  }--  // a ruleset connects a selector and a block-  Ruleset_Obj Parser::parse_ruleset(Lookahead lookahead, bool is_root)-  {-    // make sure to move up the the last position-    lex < optional_css_whitespace >(false, true);-    // create the connector object (add parts later)-    Ruleset_Obj ruleset = SASS_MEMORY_NEW(Ruleset, pstate);-    // parse selector static or as schema to be evaluated later-    if (lookahead.parsable) ruleset->selector(&parse_selector_list(is_root));-    else ruleset->selector(&parse_selector_schema(lookahead.found));-    // 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);-    // inherit is_root from parent block-    // need this info for sanity checks-    ruleset->is_root(is_root);-    // return AST Node-    return ruleset;-  }--  // 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)-  {-    // move up to the start-    lex< optional_spaces >();-    const char* i = position;-    // selector schema re-uses string schema implementation-    String_Schema_Ptr schema = SASS_MEMORY_NEW(String_Schema, pstate);-    // the selector schema is pretty much just a wrapper for the string schema-    Selector_Schema_Ptr selector_schema = SASS_MEMORY_NEW(Selector_Schema, pstate, schema);-    selector_schema->media_block(last_media_block);--    // process until end-    while (i < end_of_selector) {-      // try to parse mutliple interpolants-      if (const char* p = find_first_in_interval< exactly<hash_lbrace>, block_comment >(i, end_of_selector)) {-        // accumulate the preceding segment if the position has advanced-        if (i < p) {-          std::string parsed(i, p);-          String_Constant_Obj str = SASS_MEMORY_NEW(String_Constant, pstate, parsed);-          pstate += Offset(parsed);-          str->update_pstate(pstate);-          schema->append(&str);-        }--        // check if the interpolation only contains white-space (error out)-        if (peek < sequence < optional_spaces, exactly<rbrace> > >(p+2)) { position = p+2;-          css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was ");-        }-        // skip over all nested inner interpolations up to our own delimiter-        const char* j = skip_over_scopes< exactly<hash_lbrace>, exactly<rbrace> >(p + 2, end_of_selector);-        // pass inner expression to the parser to resolve nested interpolations-        pstate.add(p, p+2);-        Expression_Obj interpolant = Parser::from_c_str(p+2, j, ctx, pstate).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.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) {-          std::string parsed(i, end_of_selector);-          String_Constant_Obj str = SASS_MEMORY_NEW(String_Constant, pstate, parsed);-          pstate += Offset(parsed);-          str->update_pstate(pstate);-          i = end_of_selector;-          schema->append(&str);-        }-        // exit loop-      }-    }-    // EO until eos--    // update position-    position = i;--    // update for end position-    selector_schema->update_pstate(pstate);-    schema->update_pstate(pstate);--    after_token = before_token = pstate;--    // return parsed result-    return selector_schema;-  }-  // EO parse_selector_schema--  void Parser::parse_charset_directive()-  {-    lex <-      sequence <-        quoted_string,-        optional_spaces,-        exactly <';'>-      >-    >();-  }--  // 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-    std::string name(Util::normalize_underscores(lexed));-    // create the initial mixin call object-    Mixin_Call_Obj call = SASS_MEMORY_NEW(Mixin_Call, pstate, name, 0, 0);-    // parse mandatory arguments-    call->arguments(parse_arguments());-    // parse optional block-    if (peek < exactly <'{'> >()) {-      call->block(parse_block());-    }-    // return ast node-    return call.detach();-  }-  // EO parse_include_directive--  // parse a list of complex selectors-  // this is the main entry point for most-  Selector_List_Obj Parser::parse_selector_list(bool in_root)-  {-    bool reloop = true;-    bool had_linefeed = false;-    Complex_Selector_Obj sel;-    Selector_List_Obj group = SASS_MEMORY_NEW(Selector_List, pstate);-    group->media_block(last_media_block);-    do {-      reloop = false;--      had_linefeed = had_linefeed || peek_newline();--      if (peek_css< class_char < selector_list_delims > >())-        break; // in case there are superfluous commas at the end---      // now parse the complex selector-      sel = parse_complex_selector(in_root);--      if (!sel) return group.detach();--      sel->has_line_feed(had_linefeed);--      had_linefeed = false;--      while (peek_css< exactly<','> >())-      {-        lex< css_comments >(false);-        // consume everything up and including the comma separator-        reloop = lex< exactly<','> >() != 0;-        // remember line break (also between some commas)-        had_linefeed = had_linefeed || peek_newline();-        // remember line break (also between some commas)-      }-      group->append(sel);-    }-    while (reloop);-    while (lex_css< kwd_optional >()) {-      group->is_optional(true);-    }-    // update for end position-    group->update_pstate(pstate);-    if (sel) sel->last()->has_line_break(false);-    return group.detach();-  }-  // EO parse_selector_list--  // a complex selector combines a compound selector with another-  // complex selector, with one of four combinator operations.-  // the compound selector (head) is optional, since the combinator-  // can come first in the whole selector sequence (like `> DIV').-  Complex_Selector_Obj Parser::parse_complex_selector(bool in_root)-  {--    String_Ptr reference = 0;-    lex < block_comment >();-    advanceToNextToken();-    Complex_Selector_Obj sel = SASS_MEMORY_NEW(Complex_Selector, pstate);--    // parse the left hand side-    Compound_Selector_Obj lhs;-    // special case if it starts with combinator ([+~>])-    if (!peek_css< class_char < selector_combinator_ops > >()) {-      // parse the left hand side-      lhs = parse_compound_selector();-    }--    // check for end of file condition-    if (peek < end_of_file >()) return 0;--    // parse combinator between lhs and rhs-    Complex_Selector::Combinator combinator;-    if      (lex< exactly<'+'> >()) combinator = Complex_Selector::ADJACENT_TO;-    else if (lex< exactly<'~'> >()) combinator = Complex_Selector::PRECEDES;-    else if (lex< exactly<'>'> >()) combinator = Complex_Selector::PARENT_OF;-    else if (lex< sequence < exactly<'/'>, negate < exactly < '*' > > > >()) {-      // comments are allowed, but not spaces?-      combinator = Complex_Selector::REFERENCE;-      if (!lex < re_reference_combinator >()) return 0;-      reference = SASS_MEMORY_NEW(String_Constant, pstate, lexed);-      if (!lex < exactly < '/' > >()) return 0; // ToDo: error msg?-    }-    else /* if (lex< zero >()) */   combinator = Complex_Selector::ANCESTOR_OF;--    if (!lhs && combinator == Complex_Selector::ANCESTOR_OF) return 0;--    // lex < block_comment >();-    sel->head(lhs);-    sel->combinator(combinator);-    sel->media_block(last_media_block);--    if (combinator == Complex_Selector::REFERENCE) sel->reference(reference);-    // has linfeed after combinator?-    sel->has_line_break(peek_newline());-    // sel->has_line_feed(has_line_feed);--    // check if we got the abort condition (ToDo: optimize)-    if (!peek_css< class_char < complex_selector_delims > >()) {-      // parse next selector in sequence-      sel->tail(parse_complex_selector(true));-    }--    // add a parent selector if we are not in a root-    // also skip adding parent ref if we only have refs-    if (!sel->has_parent_ref() && !in_at_root && !in_root) {-      // create the objects to wrap parent selector reference-      Compound_Selector_Obj head = SASS_MEMORY_NEW(Compound_Selector, pstate);-      Parent_Selector_Ptr parent = SASS_MEMORY_NEW(Parent_Selector, pstate, false);-      parent->media_block(last_media_block);-      head->media_block(last_media_block);-      // add simple selector-      head->append(parent);-      // selector may not have any head yet-      if (!sel->head()) { sel->head(head); }-      // otherwise we need to create a new complex selector and set the old one as its tail-      else {-        sel = SASS_MEMORY_NEW(Complex_Selector, pstate, Complex_Selector::ANCESTOR_OF, head, sel);-        sel->media_block(last_media_block);-      }-      // peek for linefeed and remember result on head-      // if (peek_newline()) head->has_line_break(true);-    }--    sel->update_pstate(pstate);-    // complex selector-    return sel;-  }-  // EO parse_complex_selector--  // parse one compound selector, which is basically-  // a list of simple selectors (directly adjacent)-  // lex them exactly (without skipping white-space)-  Compound_Selector_Obj Parser::parse_compound_selector()-  {-    // init an empty compound selector wrapper-    Compound_Selector_Obj seq = SASS_MEMORY_NEW(Compound_Selector, pstate);-    seq->media_block(last_media_block);--    // skip initial white-space-    lex< css_whitespace >();--    // parse list-    while (true)-    {-      // remove all block comments (don't skip white-space)-      lex< delimited_by< slash_star, star_slash, false > >(false);-      // parse functional-      if (match < re_pseudo_selector >())-      {-        seq->append(&parse_simple_selector());-      }-      // parse parent selector-      else if (lex< exactly<'&'> >(false))-      {-        // this produces a linefeed!?-        seq->has_parent_reference(true);-        seq->append(SASS_MEMORY_NEW(Parent_Selector, pstate));-        // parent selector only allowed at start-        // upcoming Sass may allow also trailing-        if (seq->length() > 1) {-          ParserState state(pstate);-          Simple_Selector_Obj cur = (*seq)[seq->length()-1];-          Simple_Selector_Obj prev = (*seq)[seq->length()-2];-          std::string sel(prev->to_string({ NESTED, 5 }));-          std::string found(cur->to_string({ NESTED, 5 }));-          if (lex < identifier >()) { found += std::string(lexed); }-          error("Invalid CSS after \"" + sel + "\": expected \"{\", was \"" + found + "\"\n\n"-            "\"" + found + "\" may only be used at the beginning of a compound selector.", state);-        }-      }-      // parse type selector-      else if (lex< re_type_selector >(false))-      {-        seq->append(SASS_MEMORY_NEW(Element_Selector, pstate, lexed));-      }-      // peek for abort conditions-      else if (peek< spaces >()) break;-      else if (peek< end_of_file >()) { break; }-      else if (peek_css < class_char < selector_combinator_ops > >()) break;-      else if (peek_css < class_char < complex_selector_delims > >()) break;-      // otherwise parse another simple selector-      else {-        Simple_Selector_Obj sel = parse_simple_selector();-        if (!sel) return 0;-        seq->append(&sel);-      }-    }--    if (seq && !peek_css<exactly<'{'>>()) {-      seq->has_line_break(peek_newline());-    }--    // EO while true-    return seq;--  }-  // EO parse_compound_selector--  Simple_Selector_Obj Parser::parse_simple_selector()-  {-    lex < css_comments >(false);-    if (lex< class_name >()) {-      return SASS_MEMORY_NEW(Class_Selector, pstate, lexed);-    }-    else if (lex< id_name >()) {-      return SASS_MEMORY_NEW(Id_Selector, pstate, lexed);-    }-    else if (lex< quoted_string >()) {-      return SASS_MEMORY_NEW(Element_Selector, pstate, unquote(lexed));-    }-    else if (lex< alternatives < variable, number, static_reference_combinator > >()) {-      return SASS_MEMORY_NEW(Element_Selector, pstate, lexed);-    }-    else if (peek< pseudo_not >()) {-      return &parse_negated_selector();-    }-    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 >()) {-      Placeholder_Selector_Ptr sel = SASS_MEMORY_NEW(Placeholder_Selector, pstate, lexed);-      sel->media_block(last_media_block);-      return sel;-    }-    // failed-    return 0;-  }--  Wrapped_Selector_Obj Parser::parse_negated_selector()-  {-    lex< pseudo_not >();-    std::string name(lexed);-    ParserState nsource_position = pstate;-    Selector_List_Obj negated = parse_selector_list(true);-    if (!lex< exactly<')'> >()) {-      error("negated selector is missing ')'", pstate);-    }-    name.erase(name.size() - 1);-    return SASS_MEMORY_NEW(Wrapped_Selector, nsource_position, name, &negated);-  }--  // a pseudo selector often starts with one or two colons-  // it can contain more selectors inside parentheses-  Simple_Selector_Obj Parser::parse_pseudo_selector() {-    if (lex< sequence<-          optional < pseudo_prefix >,-          // we keep the space within the name, strange enough-          // ToDo: refactor output to schedule the space for it-          // or do we really want to keep the real white-space?-          sequence< identifier, optional < block_comment >, exactly<'('> >-        > >())-    {--      std::string name(lexed);-      name.erase(name.size() - 1);-      ParserState p = pstate;--      // specially parse static stuff-      // ToDo: really everything static?-      if (peek_css <-            sequence <-              alternatives <-                static_value,-                binomial-              >,-              optional_css_whitespace,-              exactly<')'>-            >-          >()-      ) {-        lex_css< alternatives < static_value, binomial > >();-        String_Constant_Ptr expr = SASS_MEMORY_NEW(String_Constant, pstate, lexed);-        if (expr && lex_css< exactly<')'> >()) {-          expr->can_compress_whitespace(true);-          return SASS_MEMORY_NEW(Pseudo_Selector, p, name, expr);-        }-      }-      else if (Selector_List_Obj wrapped = parse_selector_list(true)) {-        if (wrapped && lex_css< exactly<')'> >()) {-          return SASS_MEMORY_NEW(Wrapped_Selector, p, name, &wrapped);-        }-      }--    }-    // EO if pseudo selector--    else if (lex < sequence< optional < pseudo_prefix >, identifier > >()) {-      return SASS_MEMORY_NEW(Pseudo_Selector, pstate, lexed);-    }-    else if(lex < pseudo_prefix >()) {-      css_error("Invalid CSS", " after ", ": expected pseudoclass or pseudoelement, was ");-    }--    css_error("Invalid CSS", " after ", ": expected \")\", was ");--    // unreachable statement-    return 0;-  }--  Attribute_Selector_Obj Parser::parse_attribute_selector()-  {-    ParserState p = pstate;-    if (!lex_css< attribute_name >()) error("invalid attribute name in attribute selector", pstate);-    std::string name(lexed);-    if (lex_css< alternatives < exactly<']'>, exactly<'/'> > >()) return SASS_MEMORY_NEW(Attribute_Selector, p, name, "", 0);-    if (!lex_css< alternatives< exact_match, class_match, dash_match,-                                prefix_match, suffix_match, substring_match > >()) {-      error("invalid operator in attribute selector for " + name, pstate);-    }-    std::string matcher(lexed);--    String_Obj value = 0;-    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!-    }-    else {-      error("expected a string constant or identifier in attribute selector for " + name, pstate);-    }--    if (!lex_css< alternatives < exactly<']'>, exactly<'/'> > >()) error("unterminated attribute selector for " + name, pstate);-    return SASS_MEMORY_NEW(Attribute_Selector, p, name, matcher, value);-  }--  /* parse block comment and add to block */-  void Parser::parse_block_comments()-  {-    Block_Obj block = block_stack.back();--    while (lex< block_comment >()) {-      bool is_important = lexed.begin[2] == '!';-      // flag on second param is to skip loosely over comments-      String_Obj contents = parse_interpolated_chunk(lexed, true);-      block->append(SASS_MEMORY_NEW(Comment, pstate, contents, is_important));-    }-  }--  Declaration_Obj Parser::parse_declaration() {-    String_Obj prop;-    if (lex< sequence< optional< exactly<'*'> >, identifier_schema > >()) {-      prop = parse_identifier_schema();-    }-    else if (lex< sequence< optional< exactly<'*'> >, identifier, zero_plus< block_comment > > >()) {-      prop = SASS_MEMORY_NEW(String_Constant, pstate, lexed);-    }-    else {-      css_error("Invalid CSS", " after ", ": expected \"}\", was ");-    }-    bool is_indented = true;-    const std::string property(lexed);-    if (!lex_css< one_plus< exactly<':'> > >()) error("property \"" + property + "\" must be followed by a ':'", pstate);-    lex < css_comments >(false);-    if (peek_css< exactly<';'> >()) error("style declaration must contain a value", pstate);-    if (peek_css< exactly<'{'> >()) is_indented = false; // don't indent if value is empty-    if (peek_css< static_value >()) {-      return SASS_MEMORY_NEW(Declaration, prop->pstate(), prop, &parse_static_value()/*, lex<kwd_important>()*/);-    }-    else {-      Expression_Obj 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 {-        value = &parse_list(DELAYED);-        if (List_Ptr list = SASS_MEMORY_CAST(List, value)) {-          if (list->length() == 0 && !peek< exactly <'{'> >()) {-            css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was ");-          }-        }-      }-      lex < css_comments >(false);-      Declaration_Obj decl = SASS_MEMORY_NEW(Declaration, prop->pstate(), prop, value/*, lex<kwd_important>()*/);-      decl->is_indented(is_indented);-      decl->update_pstate(pstate);-      return decl;-    }-  }--  // parse +/- and return false if negative-  bool Parser::parse_number_prefix()-  {-    bool positive = true;-    while(true) {-      if (lex < block_comment >()) continue;-      if (lex < number_prefix >()) continue;-      if (lex < exactly < '-' > >()) {-        positive = !positive;-        continue;-      }-      break;-    }-    return positive;-  }--  Expression_Obj Parser::parse_map()-  {-    Expression_Obj 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; }--    Expression_Obj value = parse_space_list();--    map->append(key);-    map->append(value);--    while (lex_css< exactly<','> >())-    {-      // allow trailing commas - #495-      if (peek_css< exactly<')'> >(position))-      { break; }--      Expression_Obj key = parse_space_list();--      if (!(lex< exactly<':'> >()))-      { css_error("Invalid CSS", " after ", ": expected \":\", was "); }--      Expression_Obj value = parse_space_list();--      map->append(key);-      map->append(value);-    }--    ParserState ps = map->pstate();-    ps.offset = pstate - ps + pstate.offset;-    map->pstate(ps);--    return &map;-  }--  // 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!-  Expression_Obj Parser::parse_list(bool delayed)-  {-    return parse_comma_list(delayed);-  }--  // will return singletons unwrapped-  Expression_Obj Parser::parse_comma_list(bool delayed)-  {-    // check if we have an empty list-    // return the empty list as such-    if (peek_css< alternatives <-          // exactly<'!'>,-          exactly<';'>,-          exactly<'}'>,-          exactly<'{'>,-          exactly<')'>,-          exactly<':'>,-          end_of_file,-          exactly<ellipsis>,-          default_flag,-          global_flag-        > >(position))-    {-      // return an empty list (nothing to delay)-      return SASS_MEMORY_NEW(List, pstate, 0);-    }--    // now try to parse a space list-    Expression_Obj 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 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< alternatives <-            exactly<';'>,-            exactly<'}'>,-            exactly<'{'>,-            exactly<')'>,-            exactly<':'>,-            end_of_file,-            exactly<ellipsis>,-            default_flag,-            global_flag-          > >(position)-      ) { break; }-      // otherwise add another expression-      comma_list->append(parse_space_list());-    }-    // return the list-    return &comma_list;-  }-  // EO parse_comma_list--  // will return singletons unwrapped-  Expression_Obj Parser::parse_space_list()-  {-    Expression_Obj disj1 = parse_disjunction();-    // if it's a singleton, return it (don't wrap it)-    if (peek_css< alternatives <-          // exactly<'!'>,-          exactly<';'>,-          exactly<'}'>,-          exactly<'{'>,-          exactly<')'>,-          exactly<','>,-          exactly<':'>,-          end_of_file,-          exactly<ellipsis>,-          default_flag,-          global_flag-        > >(position)-    ) { return disj1; }--    List_Obj space_list = SASS_MEMORY_NEW(List, pstate, 2, SASS_SPACE);-    space_list->append(disj1);--    while (!(peek_css< alternatives <-               // exactly<'!'>,-               exactly<';'>,-               exactly<'}'>,-               exactly<'{'>,-               exactly<')'>,-               exactly<','>,-               exactly<':'>,-               end_of_file,-               exactly<ellipsis>,-               default_flag,-               global_flag-           > >(position)) && peek_css< optional_css_whitespace >() != end-    ) {-      // the space is parsed implicitly?-      space_list->append(parse_disjunction());-    }-    // return the list-    return &space_list;-  }-  // EO parse_space_list--  // parse logical OR operation-  Expression_Obj Parser::parse_disjunction()-  {-    advanceToNextToken();-    ParserState state(pstate);-    // parse the left hand side conjunction-    Expression_Obj conj = parse_conjunction();-    // parse multiple right hand sides-    std::vector<Expression_Obj> 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-    Expression_Obj ex = fold_operands(conj, operands, { Sass_OP::OR });-    state.offset = pstate - state + pstate.offset;-    ex->pstate(state);-    return ex;-  }-  // EO parse_disjunction--  // parse logical AND operation-  Expression_Obj Parser::parse_conjunction()-  {-    advanceToNextToken();-    ParserState state(pstate);-    // parse the left hand side relation-    Expression_Obj rel = parse_relation();-    // parse multiple right hand sides-    std::vector<Expression_Obj> 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-    Expression_Obj ex = fold_operands(rel, operands, { Sass_OP::AND });-    state.offset = pstate - state + pstate.offset;-    ex->pstate(state);-    return ex;-  }-  // EO parse_conjunction--  // parse comparison operations-  Expression_Obj Parser::parse_relation()-  {-    advanceToNextToken();-    ParserState state(pstate);-    // parse the left hand side expression-    Expression_Obj lhs = parse_expression();-    std::vector<Expression_Obj> operands;-    std::vector<Operand> 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<kwd_eq>()  ? Sass_OP::EQ-      : lex<kwd_neq>() ? Sass_OP::NEQ-      : lex<kwd_gte>() ? Sass_OP::GTE-      : lex<kwd_lte>() ? Sass_OP::LTE-      : lex<kwd_gt>()  ? Sass_OP::GT-      : lex<kwd_lt>()  ? 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());-      left_ws = peek < css_comments >() != NULL;-    }-    // 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 ...-    Expression_Obj ex = fold_operands(lhs, operands, operators);-    state.offset = pstate - state + pstate.offset;-    ex->pstate(state);-    return ex;-  }-  // 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-  Expression_Obj Parser::parse_expression()-  {-    advanceToNextToken();-    ParserState state(pstate);-    // parses multiple add and subtract operations-    // NOTE: make sure that identifiers starting with-    // NOTE: dashes do NOT count as subtract operation-    Expression_Obj lhs = parse_operators();-    // if it's a singleton, return it (don't wrap it)-    if (!(peek_css< exactly<'+'> >(position) ||-          // condition is a bit misterious, but some combinations should not be counted as operations-          (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; }--    std::vector<Expression_Obj> operands;-    std::vector<Operand> 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 (operands.size() == 0) return lhs;-    Expression_Obj ex = fold_operands(lhs, operands, operators);-    state.offset = pstate - state + pstate.offset;-    ex->pstate(state);-    return ex;-  }--  // parse addition and subtraction operations-  Expression_Obj Parser::parse_operators()-  {-    advanceToNextToken();-    ParserState state(pstate);-    Expression_Obj factor = parse_factor();-    // if it's a singleton, return it (don't wrap it)-    std::vector<Expression_Obj> operands; // factors-    std::vector<Operand> 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"); break;-      }-      operands.push_back(parse_factor());-      left_ws = peek < css_comments >();-    }-    // operands and operators to binary expression-    Expression_Obj ex = fold_operands(factor, operands, operators);-    state.offset = pstate - state + pstate.offset;-    ex->pstate(state);-    return ex;-  }-  // EO parse_operators---  // called from parse_operators-  // called from parse_value_schema-  Expression_Obj Parser::parse_factor()-  {-    lex < css_comments >(false);-    if (lex_css< exactly<'('> >()) {-      // parse_map may return a list-      Expression_Obj value = parse_map();-      // lex the expected closing parenthesis-      if (!lex_css< exactly<')'> >()) error("unclosed parenthesis", pstate);-      // expression can be evaluated-      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_Ptr schema = SASS_MEMORY_CAST(String_Schema, 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_Ptr ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::PLUS, &parse_factor());-      if (ex && ex->operand()) ex->is_delayed(ex->operand()->is_delayed());-      return ex;-    }-    else if (lex< exactly<'-'> >()) {-      Unary_Expression_Ptr ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::MINUS, &parse_factor());-      if (ex && ex->operand()) ex->is_delayed(ex->operand()->is_delayed());-      return ex;-    }-    else if (lex< sequence< kwd_not > >()) {-      Unary_Expression_Ptr ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::NOT, &parse_factor());-      if (ex && ex->operand()) ex->is_delayed(ex->operand()->is_delayed());-      return ex;-    }-    else if (peek < sequence < one_plus < alternatives < css_whitespace, exactly<'-'>, exactly<'+'> > >, number > >()) {-      if (parse_number_prefix()) return &parse_value(); // prefix is positive-      Unary_Expression_Ptr ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::MINUS, &parse_value());-      if (ex->operand()) ex->is_delayed(ex->operand()->is_delayed());-      return ex;-    }-    else {-      return parse_value();-    }-  }--  // parse one value for a list-  Expression_Obj Parser::parse_value()-  {-    lex< css_comments >(false);-    if (lex< ampersand >())-    {-      return SASS_MEMORY_NEW(Parent_Selector, 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 SASS_MEMORY_NEW(Textual, pstate, Textual::PERCENTAGE, lexed); }--    if (lex< sequence < number, lookahead< sequence < op, number > > > >())-    { return SASS_MEMORY_NEW(Textual, pstate, Textual::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 SASS_MEMORY_NEW(String_Constant, pstate, lexed);-    }--    if (lex< percentage >())-    { return SASS_MEMORY_NEW(Textual, pstate, Textual::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 SASS_MEMORY_NEW(Textual, pstate, Textual::HEX, 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 SASS_MEMORY_NEW(Textual, pstate, Textual::DIMENSION, lexed); }--    if (lex< sequence< static_component, one_plus< strict_identifier > > >())-    { return SASS_MEMORY_NEW(String_Constant, pstate, lexed); }--    if (lex< number >())-    { return SASS_MEMORY_NEW(Textual, pstate, Textual::NUMBER, lexed); }--    if (lex< variable >())-    { return SASS_MEMORY_NEW(Variable, pstate, Util::normalize_underscores(lexed)); }--    // Special case handling for `%` proceeding an interpolant.-    if (lex< sequence< exactly<'%'>, optional< percentage > > >())-    { return SASS_MEMORY_NEW(String_Constant, pstate, lexed); }--    css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was ");--    // unreachable statement-    return 0;-  }--  // this parses interpolation inside other strings-  // means the result should later be quoted again-  String_Obj Parser::parse_interpolated_chunk(Token chunk, bool constant)-  {-    const char* i = chunk.begin;-    // see if there any interpolants-    const char* p = constant ? find_first_in_interval< exactly<hash_lbrace> >(i, chunk.end) :-                    find_first_in_interval< exactly<hash_lbrace>, block_comment >(i, chunk.end);--    if (!p) {-      String_Quoted_Ptr str_quoted = SASS_MEMORY_NEW(String_Quoted, pstate, std::string(i, chunk.end));-      if (!constant && str_quoted->quote_mark()) str_quoted->quote_mark('*');-      return str_quoted;-    }--    String_Schema_Ptr schema = SASS_MEMORY_NEW(String_Schema, pstate);-    schema->is_interpolant(true);-    while (i < chunk.end) {-      p = constant ? find_first_in_interval< exactly<hash_lbrace> >(i, chunk.end) :-          find_first_in_interval< exactly<hash_lbrace>, 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, std::string(i, p)));-        }-        // we need to skip anything inside strings-        // create a new target in parser/prelexer-        if (peek < sequence < optional_spaces, exactly<rbrace> > >(p+2)) { position = p+2;-          css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was ");-        }-        const char* j = skip_over_scopes< exactly<hash_lbrace>, exactly<rbrace> >(p + 2, chunk.end); // find the closing brace-        if (j) { --j;-          // parse the interpolant and accumulate it-          Expression_Obj interp_node = Parser::from_token(Token(p+2, j), ctx, pstate, source).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(), pstate);-        }-      }-      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, std::string(i, chunk.end)));-        break;-      }-      ++ i;-    }--    return schema;-  }--  String_Constant_Obj 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;-    --str.end;-    --position;--    String_Constant_Ptr str_node = SASS_MEMORY_NEW(String_Constant, pstate, str.time_wspace());-    return str_node;-  }--  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<hash_lbrace>, block_comment >(str.begin, str.end);-    if (!p) {-      return SASS_MEMORY_NEW(String_Quoted, pstate, std::string(str.begin, str.end));-    }--    String_Schema_Ptr schema = SASS_MEMORY_NEW(String_Schema, pstate);-    while (i < str.end) {-      p = find_first_in_interval< exactly<hash_lbrace>, block_comment >(i, str.end);-      if (p) {-        if (i < p) {-          schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string(i, p))); // accumulate the preceding segment if it's nonempty-        }-        if (peek < sequence < optional_spaces, exactly<rbrace> > >(p+2)) { position = p+2;-          css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was ");-        }-        const char* j = skip_over_scopes< exactly<hash_lbrace>, exactly<rbrace> >(p+2, str.end); // find the closing brace-        if (j) {-          // parse the interpolant and accumulate it-          Expression_Obj interp_node = Parser::from_token(Token(p+2, j), ctx, pstate, source).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(), pstate);-        }-      }-      else { // no interpolants left; add the last segment if nonempty-        if (i < str.end) {-          schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string(i, str.end)));-        }-        break;-      }-    }-    return schema;-  }--  String_Obj Parser::parse_ie_keyword_arg()-  {-    String_Schema_Ptr 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 >()) kwd_arg->append(SASS_MEMORY_NEW(Textual, pstate, Textual::NUMBER, Util::normalize_decimals(lexed)));-    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<exactly<'}'>>()) {-      css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was ");-    }--    const char* e = 0;-    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 ");-        }-        Expression_Obj ex = 0;-        if (lex< re_static_expression >()) {-          ex = SASS_MEMORY_NEW(String_Constant, pstate, lexed);-        } else {-          ex = parse_list();-        }-        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< sequence < 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 >()) {-        std::string name(Util::normalize_underscores(lexed));-        schema->append(SASS_MEMORY_NEW(Variable, pstate, name));-      }-      // lex percentage value-      else if (lex< percentage >()) {-        schema->append(SASS_MEMORY_NEW(Textual, pstate, Textual::PERCENTAGE, lexed));-      }-      // lex dimension value-      else if (lex< dimension >()) {-        schema->append(SASS_MEMORY_NEW(Textual, pstate, Textual::DIMENSION, lexed));-      }-      // lex number value-      else if (lex< number >()) {-        schema->append( SASS_MEMORY_NEW(Textual, pstate, Textual::NUMBER, lexed));-      }-      // lex hex color value-      else if (lex< sequence < hex, negate < exactly < '-' > > > >()) {-        schema->append(SASS_MEMORY_NEW(Textual, pstate, Textual::HEX, 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, std::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<hash_lbrace>, block_comment >(id.begin, id.end);-    if (!p) {-      return SASS_MEMORY_NEW(String_Constant, pstate, std::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<hash_lbrace>, 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<rbrace> > >(p+2)) { position = p;-          css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was ");-        }-        const char* j = skip_over_scopes< exactly<hash_lbrace>, exactly<rbrace> >(p+2, id.end); // find the closing brace-        if (j) {-          // parse the interpolant and accumulate it-          Expression_Obj interp_node = Parser::from_token(Token(p+2, j), ctx, pstate, source).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(), pstate);-        }-      }-      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 >();-    std::string name(lexed);-    ParserState call_pos = pstate;-    lex< exactly<'('> >();-    ParserState 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()-  {-    std::string prefix("");-    if (lex< uri_prefix >()) {-      prefix = std::string(lexed);-    }--    lex < optional_spaces >();-    String_Obj url_string = parse_url_function_argument();--    std::string suffix("");-    if (lex< real_uri_suffix >()) {-      suffix = std::string(lexed);-    }--    std::string uri("");-    if (&url_string) {-      uri = url_string->to_string({ NESTED, 5 });-    }--    if (String_Schema_Ptr schema = dynamic_cast<String_Schema_Ptr>(&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 {-      std::string res = prefix + uri + suffix;-      return SASS_MEMORY_NEW(String_Constant, pstate, res);-    }-  }--  String_Obj Parser::parse_url_function_argument()-  {-    const char* p = position;--    std::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);-      }-      position = pp;-      return &parse_interpolated_chunk(Token(p, position));-    }-    else if (uri != "") {-      std::string res = Util::rtrim(uri);-      return SASS_MEMORY_NEW(String_Constant, pstate, res);-    }--    return 0;-  }--  Function_Call_Obj Parser::parse_function_call()-  {-    lex< identifier >();-    std::string name(lexed);--    ParserState call_pos = pstate;-    Arguments_Obj args = parse_arguments();-    return SASS_MEMORY_NEW(Function_Call, call_pos, name, args);-  }--  Function_Call_Schema_Obj Parser::parse_function_call_schema()-  {-    String_Obj name = parse_identifier_schema();-    ParserState source_position_of_call = pstate;-    Arguments_Obj args = parse_arguments();--    return SASS_MEMORY_NEW(Function_Call_Schema, source_position_of_call, name, args);-  }--  Content_Obj Parser::parse_content_directive()-  {-    return SASS_MEMORY_NEW(Content, pstate);-  }--  If_Obj Parser::parse_if_directive(bool else_if)-  {-    stack.push_back(Scope::Control);-    ParserState if_source_position = pstate;-    bool root = block_stack.back()->is_root();-    Expression_Obj predicate = parse_list();-    Block_Obj block = parse_block(root);-    Block_Obj alternative = NULL;--    // 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);-  }--  For_Obj Parser::parse_for_directive()-  {-    stack.push_back(Scope::Control);-    ParserState for_source_position = pstate;-    bool root = block_stack.back()->is_root();-    lex_variable();-    std::string var(Util::normalize_underscores(lexed));-    if (!lex< kwd_from >()) error("expected 'from' keyword in @for directive", pstate);-    Expression_Obj 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", pstate);-    Expression_Obj upper_bound = parse_expression();-    Block_Obj body = parse_block(root);-    stack.pop_back();-    return SASS_MEMORY_NEW(For, for_source_position, var, lower_bound, upper_bound, body, inclusive);-  }--  // helper to parse a var token-  Token Parser::lex_variable()-  {-    // peek for dollar sign first-    if (!peek< exactly <'$'> >()) {-      css_error("Invalid CSS", " after ", ": expected \"$\", was ");-    }-    // we expect a simple identifier as the call name-    if (!lex< sequence < exactly <'$'>, identifier > >()) {-      lex< exactly <'$'> >(); // move pstate and position up-      css_error("Invalid CSS", " after ", ": expected identifier, was ");-    }-    // return object-    return token;-  }-  // 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 token;-  }--  Each_Obj Parser::parse_each_directive()-  {-    stack.push_back(Scope::Control);-    ParserState each_source_position = pstate;-    bool root = block_stack.back()->is_root();-    std::vector<std::string> vars;-    lex_variable();-    vars.push_back(Util::normalize_underscores(lexed));-    while (lex< exactly<','> >()) {-      if (!lex< variable >()) error("@each directive requires an iteration variable", pstate);-      vars.push_back(Util::normalize_underscores(lexed));-    }-    if (!lex< kwd_in >()) error("expected 'in' keyword in @each directive", pstate);-    Expression_Obj list = parse_list();-    Block_Obj body = parse_block(root);-    stack.pop_back();-    return SASS_MEMORY_NEW(Each, each_source_position, vars, list, body);-  }--  // called after parsing `kwd_while_directive`-  While_Obj Parser::parse_while_directive()-  {-    stack.push_back(Scope::Control);-    bool root = block_stack.back()->is_root();-    // create the initial while call object-    While_Obj call = SASS_MEMORY_NEW(While, pstate, 0, 0);-    // parse mandatory predicate-    Expression_Obj predicate = parse_list();-    call->predicate(predicate);-    // parse mandatory block-    call->block(parse_block(root));-    // return ast node-    stack.pop_back();-    // return ast node-    return call.detach();-  }--  // EO parse_while_directive-  Media_Block_Obj Parser::parse_media_block()-  {-    stack.push_back(Scope::Media);-    Media_Block_Obj media_block = SASS_MEMORY_NEW(Media_Block, pstate, 0, 0);--    media_block->media_queries(parse_media_queries());--    Media_Block_Obj prev_media_block = last_media_block;-    last_media_block = &media_block;-    media_block->block(parse_css_block());-    last_media_block = &prev_media_block;-    stack.pop_back();-    return media_block.detach();-  }--  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_Ptr 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_Ptr schema = SASS_MEMORY_NEW(String_Schema, pstate);-      schema->append(&media_query->media_type());-      schema->append(SASS_MEMORY_NEW(String_Constant, pstate, " "));-      schema->append(&parse_identifier_schema());-      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_Expression_Obj Parser::parse_media_expression()-  {-    if (lex < identifier_schema >()) {-      String_Obj ss = parse_identifier_schema();-      return SASS_MEMORY_NEW(Media_Query_Expression, pstate, &ss, 0, true);-    }-    if (!lex_css< exactly<'('> >()) {-      error("media query expression must begin with '('", pstate);-    }-    Expression_Obj feature = 0;-    if (peek_css< exactly<')'> >()) {-      error("media feature required in media query expression", pstate);-    }-    feature = &parse_expression();-    Expression_Obj expression = 0;-    if (lex_css< exactly<':'> >()) {-      expression = &parse_list(DELAYED);-    }-    if (!lex_css< exactly<')'> >()) {-      error("unclosed parenthesis in media query expression", pstate);-    }-    return SASS_MEMORY_NEW(Media_Query_Expression, feature->pstate(), feature, expression);-  }--  // lexed after `kwd_supports_directive`-  // these are very similar to media blocks-  Supports_Block_Obj Parser::parse_supports_directive()-  {-    Supports_Condition_Obj cond = parse_supports_condition();-    // create the ast node object for the support queries-    Supports_Block_Obj query = SASS_MEMORY_NEW(Supports_Block, 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-  Supports_Condition_Obj Parser::parse_supports_condition()-  {-    lex < css_whitespace >();-    Supports_Condition_Obj cond = 0;-    if ((cond = parse_supports_negation())) return cond;-    if ((cond = parse_supports_operator())) return cond;-    if ((cond = parse_supports_interpolation())) return cond;-    return cond;-  }--  Supports_Condition_Obj Parser::parse_supports_negation()-  {-    if (!lex < kwd_not >()) return 0;-    Supports_Condition_Obj cond = parse_supports_condition_in_parens();-    return SASS_MEMORY_NEW(Supports_Negation, pstate, &cond);-  }--  Supports_Condition_Obj Parser::parse_supports_operator()-  {-    Supports_Condition_Obj cond = parse_supports_condition_in_parens();-    if (!&cond) return 0;--    while (true) {-      Supports_Operator::Operand op = Supports_Operator::OR;-      if (lex < kwd_and >()) { op = Supports_Operator::AND; }-      else if(!lex < kwd_or >()) { break; }--      lex < css_whitespace >();-      Supports_Condition_Obj right = parse_supports_condition_in_parens();--      // Supports_Condition_Ptr cc = SASS_MEMORY_NEW(Supports_Condition, *static_cast<Supports_Condition_Ptr>(cond));-      cond = SASS_MEMORY_NEW(Supports_Operator, pstate, &cond, &right, op);-    }-    return cond;-  }--  Supports_Condition_Obj Parser::parse_supports_interpolation()-  {-    if (!lex < interpolant >()) return 0;--    String_Obj interp = parse_interpolated_chunk(lexed);-    if (!interp) return 0;--    return SASS_MEMORY_NEW(Supports_Interpolation, pstate, &interp);-  }--  // TODO: This needs some major work. Although feature conditions-  // look like declarations their semantics differ significantly-  Supports_Condition_Obj Parser::parse_supports_declaration()-  {-    Supports_Condition_Ptr cond = 0;-    // parse something declaration like-    Declaration_Obj declaration = parse_declaration();-    if (!declaration) error("@supports condition expected declaration", pstate);-    cond = SASS_MEMORY_NEW(Supports_Declaration,-                     declaration->pstate(),-                     &declaration->property(),-                     declaration->value());-    // ToDo: maybe we need an additional error condition?-    return cond;-  }--  Supports_Condition_Obj Parser::parse_supports_condition_in_parens()-  {-    Supports_Condition_Obj interp = parse_supports_interpolation();-    if (&interp != 0) return interp;--    if (!lex < exactly <'('> >()) return 0;-    lex < css_whitespace >();--    Supports_Condition_Obj cond = parse_supports_condition();-    if (&cond != 0) {-      if (!lex < exactly <')'> >()) error("unclosed parenthesis in @supports declaration", pstate);-    } else {-      cond = parse_supports_declaration();-      if (!lex < exactly <')'> >()) error("unclosed parenthesis in @supports declaration", pstate);-    }-    lex < css_whitespace >();-    return cond;-  }--  At_Root_Block_Obj Parser::parse_at_root_block()-  {-    ParserState at_source_position = pstate;-    Block_Obj body = 0;-    At_Root_Query_Obj expr;-    Lookahead lookahead_result;-    LOCAL_FLAG(in_at_root, true);-    if (lex_css< exactly<'('> >()) {-      expr = parse_at_root_query();-    }-    if (peek_css < exactly<'{'> >()) {-      lex <optional_spaces>();-      body = &parse_block(true);-    }-    else if ((lookahead_result = lookahead_for_selector(position)).found) {-      Ruleset_Obj r = parse_ruleset(lookahead_result, false);-      body = SASS_MEMORY_NEW(Block, r->pstate(), 1, true);-      body->append(&r);-    }-    At_Root_Block_Obj at_root = SASS_MEMORY_NEW(At_Root_Block, at_source_position, body);-    if (&expr) at_root->expression(&expr);-    return at_root;-  }--  At_Root_Query_Obj Parser::parse_at_root_query()-  {-    if (peek< exactly<')'> >()) error("at-root feature required in at-root expression", pstate);--    if (!peek< alternatives< kwd_with_directive, kwd_without_directive > >()) {-      css_error("Invalid CSS", " after ", ": expected \"with\" or \"without\", was ");-    }--    Expression_Obj feature = parse_list();-    if (!lex_css< exactly<':'> >()) error("style declaration must contain a value", pstate);-    Expression_Obj expression = parse_list();-    List_Obj value = SASS_MEMORY_NEW(List, feature->pstate(), 1);--    if (expression->concrete_type() == Expression::LIST) {-        value = SASS_MEMORY_CAST(List, 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", pstate);-    return cond;-  }--  Directive_Obj Parser::parse_special_directive()-  {-    std::string kwd(lexed);--    if (lexed == "@else") error("Invalid CSS: @else must come after @if", pstate);--    Directive_Ptr at_rule = SASS_MEMORY_NEW(Directive, pstate, kwd);-    Lookahead lookahead = lookahead_for_include(position);-    if (lookahead.found && !lookahead.has_interpolants) {-      at_rule->selector(&parse_selector_list(true));-    }--    lex < css_comments >(false);--    if (lex < static_property >()) {-      at_rule->value(&parse_interpolated_chunk(Token(lexed)));-    } else if (!(peek < alternatives < exactly<'{'>, exactly<'}'>, exactly<';'> > >())) {-      at_rule->value(parse_list());-    }--    lex < css_comments >(false);--    if (peek< exactly<'{'> >()) {-      at_rule->block(parse_block());-    }--    return at_rule;-  }--  Directive_Obj Parser::parse_prefixed_directive()-  {-    std::string kwd(lexed);--    if (lexed == "@else") error("Invalid CSS: @else must come after @if", pstate);--    Directive_Obj at_rule = SASS_MEMORY_NEW(Directive, pstate, kwd);-    Lookahead lookahead = lookahead_for_include(position);-    if (lookahead.found && !lookahead.has_interpolants) {-      at_rule->selector(&parse_selector_list(true));-    }--    lex < css_comments >(false);--    if (lex < static_property >()) {-      at_rule->value(&parse_interpolated_chunk(Token(lexed)));-    } else if (!(peek < alternatives < exactly<'{'>, exactly<'}'>, exactly<';'> > >())) {-      at_rule->value(&parse_list());-    }--    lex < css_comments >(false);--    if (peek< exactly<'{'> >()) {-      at_rule->block(parse_block());-    }--    return at_rule;-  }---  Directive_Obj Parser::parse_directive()-  {-    Directive_Obj directive = SASS_MEMORY_NEW(Directive, pstate, lexed);-    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;-  }--  Expression_Obj Parser::lex_interpolation()-  {-    if (lex < interpolant >(true) != NULL) {-      return &parse_interpolated_chunk(lexed, true);-    }-    return 0;-  }--  Expression_Obj Parser::lex_interp_uri()-  {-    // create a string schema by lexing optional interpolations-    return lex_interp< re_string_uri_open, re_string_uri_close >();-  }--  Expression_Obj Parser::lex_interp_string()-  {-    Expression_Obj rv = 0;-    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;-  }--  Expression_Obj Parser::lex_almost_any_value_chars()-  {-    const char* match =-    lex <-      one_plus <-        alternatives <-          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 NULL;-  }--  Expression_Obj Parser::lex_almost_any_value_token()-  {-    Expression_Obj rv = 0;-    if (*position == 0) return 0;-    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;-    return rv;-  }--  String_Schema_Obj Parser::parse_almost_any_value()-  {--    String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate);-    if (*position == 0) return 0;-    lex < spaces >(false);-    Expression_Obj token = lex_almost_any_value_token();-    if (!token) return 0;-    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();-  }--  Warning_Obj 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.", pstate);-    }-    return SASS_MEMORY_NEW(Warning, pstate, parse_list(DELAYED));-  }--  Error_Obj 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.", pstate);-    }-    return SASS_MEMORY_NEW(Error, pstate, parse_list(DELAYED));-  }--  Debug_Obj 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.", pstate);-    }-    return SASS_MEMORY_NEW(Debug, 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)-    ) {-      while (p < q) {-        // did we have interpolations?-        if (*p == '#' && *(p+1) == '{') {-          rv.has_interpolants = true;-          p = q; break;-        }-        ++ p;-      }-      // store anyway  }---      // ToDo: remove-      rv.error = q;-      rv.position = q;-      // check expected opening bracket-      // only after successfull matching-      if (peek < exactly<'{'> >(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 <-              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;-    std::string encoding;-    bool utf_8 = false;-    switch ((unsigned char) source[0]) {-    case 0xEF:-      skip = check_bom_chars(source, end, utf_8_bom, 3);-      encoding = "UTF-8";-      utf_8 = true;-      break;-    case 0xFE:-      skip = check_bom_chars(source, end, utf_16_bom_be, 2);-      encoding = "UTF-16 (big endian)";-      break;-    case 0xFF:-      skip = check_bom_chars(source, end, utf_16_bom_le, 2);-      skip += (skip ? check_bom_chars(source, 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(source, end, utf_32_bom_be, 4);-      encoding = "UTF-32 (big endian)";-      break;-    case 0x2B:-      skip = check_bom_chars(source, end, utf_7_bom_1, 4)-           | check_bom_chars(source, end, utf_7_bom_2, 4)-           | check_bom_chars(source, end, utf_7_bom_3, 4)-           | check_bom_chars(source, end, utf_7_bom_4, 4)-           | check_bom_chars(source, end, utf_7_bom_5, 5);-      encoding = "UTF-7";-      break;-    case 0xF7:-      skip = check_bom_chars(source, end, utf_1_bom, 3);-      encoding = "UTF-1";-      break;-    case 0xDD:-      skip = check_bom_chars(source, end, utf_ebcdic_bom, 4);-      encoding = "UTF-EBCDIC";-      break;-    case 0x0E:-      skip = check_bom_chars(source, end, scsu_bom, 3);-      encoding = "SCSU";-      break;-    case 0xFB:-      skip = check_bom_chars(source, end, bocu_1_bom, 3);-      encoding = "BOCU-1";-      break;-    case 0x84:-      skip = check_bom_chars(source, end, gb_18030_bom, 4);-      encoding = "GB-18030";-      break;-    }-    if (skip > 0 && !utf_8) error("only UTF-8 documents are currently supported; your document appears to be " + encoding, pstate);-    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;-  }---  Expression_Obj Parser::fold_operands(Expression_Obj base, std::vector<Expression_Obj>& 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;-  }--  Expression_Obj Parser::fold_operands(Expression_Obj base, std::vector<Expression_Obj>& operands, std::vector<Operand>& ops, size_t i)-  {-    if (String_Schema_Ptr schema = dynamic_cast<String_Schema_Ptr>(&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)-        )) {-          Expression_Obj 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;-      }-    }--    for (size_t S = operands.size(); i < S; ++i) {-      if (String_Schema_Ptr schema = dynamic_cast<String_Schema_Ptr>(&operands[i])) {-        if (schema->has_interpolants()) {-          if (i + 1 < S) {-            Expression_Obj 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_Ptr b = static_cast<Binary_Expression_Ptr>(&base);-      if (b && ops[i].operand == Sass_OP::DIV && b->left()->is_delayed() && b->right()->is_delayed()) {-        base->is_delayed(true);-      }-    }-    // nested binary expression are never to be delayed-    if (Binary_Expression_Ptr b = dynamic_cast<Binary_Expression_Ptr>(&base)) {-      if (SASS_MEMORY_CAST(Binary_Expression, b->left())) base->set_delayed(false);-      if (SASS_MEMORY_CAST(Binary_Expression, b->right())) base->set_delayed(false);-    }-    return base;-  }--  void Parser::error(std::string msg, Position pos)-  {-    throw Exception::InvalidSass(ParserState(path, source, pos.line ? pos : before_token, Offset(0, 0)), msg);-  }--  // print a css parsing error with actual context information from parsed source-  void Parser::css_error(const std::string& msg, const std::string& prefix, const std::string& middle)-  {-    int max_len = 18;-    const char* end = this->end;-    while (*end != 0) ++ end;-    const char* pos = peek < optional_spaces >();--    const char* last_pos(pos);-    if (last_pos > source) {-      utf8::prior(last_pos, source);-    }-    // backup position to last significant char-    while (last_pos > source && last_pos < end) {-      if (!Prelexer::is_space(*last_pos)) break;-      utf8::prior(last_pos, source);-    }--    bool ellipsis_left = false;-    const char* pos_left(last_pos);-    const char* end_left(last_pos);--    utf8::next(pos_left, end);-    utf8::next(end_left, end);-    while (pos_left > source) {-      if (utf8::distance(pos_left, end_left) >= max_len) {-        utf8::prior(pos_left, source);-        ellipsis_left = *(pos_left) != '\n' &&-                        *(pos_left) != '\r';-        utf8::next(pos_left, end);-        break;-      }--      const char* prev = pos_left;-      utf8::prior(prev, source);-      if (*prev == '\r') break;-      if (*prev == '\n') break;-      pos_left = prev;-    }-    if (pos_left < source) {-      pos_left = source;-    }--    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 ++;--    std::string left(pos_left, end_left);-    std::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), pstate);+#include "parser.hpp"+#include "file.hpp"+#include "inspect.hpp"+#include "constants.hpp"+#include "util.hpp"+#include "prelexer.hpp"+#include "color_maps.hpp"+#include "sass/functions.h"+#include "error_handling.hpp"++// 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 <cstdlib>+#include <iostream>+#include <vector>+#include <typeinfo>++namespace Sass {+  using namespace Constants;+  using namespace Prelexer;++  Parser Parser::from_c_str(const char* beg, Context& ctx, Backtraces traces, ParserState pstate, const char* source)+  {+    pstate.offset.column = 0;+    pstate.offset.line = 0;+    Parser p(ctx, pstate, traces);+    p.source   = source ? source : beg;+    p.position = beg ? beg : p.source;+    p.end      = p.position + strlen(p.position);+    Block_Obj root = SASS_MEMORY_NEW(Block, pstate);+    p.block_stack.push_back(root);+    root->is_root(true);+    return p;+  }++  Parser Parser::from_c_str(const char* beg, const char* end, Context& ctx, Backtraces traces, ParserState pstate, const char* source)+  {+    pstate.offset.column = 0;+    pstate.offset.line = 0;+    Parser p(ctx, pstate, traces);+    p.source   = source ? source : beg;+    p.position = beg ? beg : p.source;+    p.end      = end ? end : p.position + strlen(p.position);+    Block_Obj root = SASS_MEMORY_NEW(Block, pstate);+    p.block_stack.push_back(root);+    root->is_root(true);+    return p;+  }++   void Parser::advanceToNextToken() {+      lex < css_comments >(false);+      // advance to position+      pstate += pstate.offset;+      pstate.offset.column = 0;+      pstate.offset.line = 0;+    }++  Selector_List_Obj Parser::parse_selector(const char* beg, Context& ctx, Backtraces traces, ParserState pstate, const char* source)+  {+    Parser p = Parser::from_c_str(beg, ctx, traces, pstate, source);+    // ToDo: ruby sass errors on parent references+    // ToDo: remap the source-map entries somehow+    return p.parse_selector_list(false);+  }++  bool Parser::peek_newline(const char* start)+  {+    return peek_linefeed(start ? start : position)+           && ! peek_css<exactly<'{'>>(start);+  }++  Parser Parser::from_token(Token t, Context& ctx, Backtraces traces, ParserState pstate, const char* source)+  {+    Parser p(ctx, pstate, traces);+    p.source   = source ? source : t.begin;+    p.position = t.begin ? t.begin : p.source;+    p.end      = t.end ? t.end : p.position + strlen(p.position);+    Block_Obj root = SASS_MEMORY_NEW(Block, pstate);+    p.block_stack.push_back(root);+    root->is_root(true);+    return p;+  }++  /* main entry point to parse root block */+  Block_Obj Parser::parse()+  {++    // 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 += 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, path, 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 ");+    }++    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)+  {++    // parse comments before block+    // lex < optional_css_comments >();++    // lex mandatory opener or error out+    if (!lex_css < exactly<'{'> >()) {+      css_error("Invalid CSS", " after ", ": expected \"{\", was ");+    }+    // 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 ");+    }++    // 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();++    return block;+  }++  // 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)+  {++    // 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;+    }+    // return success+    return true;+  }++  // 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.");+        }+      }+      // 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 (lex < kwd_extend >(true)) {+      Lookahead lookahead = lookahead_for_include(position);+      if (!lookahead.found) css_error("Invalid CSS", " after ", ": expected selector, was ");+      Selector_List_Obj target;+      if (!lookahead.has_interpolants) {+        target = parse_selector_list(true);+      }+      else {+        target = SASS_MEMORY_NEW(Selector_List, pstate);+        target->schema(parse_selector_schema(lookahead.found, true));+      }++      block->append(SASS_MEMORY_NEW(Extension, pstate, target));+    }++    // 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(parse_media_block()); }+    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(); }++    // generic at keyword (keep last)+    else if (lex< re_special_directive >(true)) { block->append(parse_special_directive()); }+    else if (lex< re_prefixed_directive >(true)) { block->append(parse_prefixed_directive()); }+    else if (lex< at_keyword >(true)) { block->append(parse_directive()); }++    else if (is_root && stack.back() != Scope::AtRoot /* && block->is_root() */) {+      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;+      }+    }+    // something matched+    return true;+  }+  // EO parse_block_nodes++  // parse imports inside the+  Import_Obj Parser::parse_import()+  {+    Import_Obj imp = SASS_MEMORY_NEW(Import, pstate);+    std::vector<std::pair<std::string,Function_Call_Obj>> to_import;+    bool first = true;+    do {+      while (lex< block_comment >());+      if (lex< quoted_string >()) {+        to_import.push_back(std::pair<std::string,Function_Call_Obj>(std::string(lexed), 0));+      }+      else if (lex< uri_prefix >()) {+        Arguments_Obj args = SASS_MEMORY_NEW(Arguments, pstate);+        Function_Call_Obj result = SASS_MEMORY_NEW(Function_Call, pstate, "url", args);++        if (lex< quoted_string >()) {+          Expression_Obj 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)) {+          Expression_Obj braced_url = parse_list(); // parse_interpolated_chunk(lexed);+          args->append(SASS_MEMORY_NEW(Argument, braced_url->pstate(), braced_url));+        }+        else {+          error("malformed URL");+        }+        if (!lex< exactly<')'> >()) error("URI is missing ')'");+        to_import.push_back(std::pair<std::string, Function_Call_Obj>("", 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), path, pstate, imp)) {+        // nobody wants it, so we do our import+        ctx.import_url(imp, location.first, path);+      }+    }++    return imp;+  }++  Definition_Obj Parser::parse_definition(Definition::Type which_type)+  {+    std::string which_str(lexed);+    if (!lex< identifier >()) error("invalid name in " + which_str + " definition");+    std::string name(Util::normalize_underscores(lexed));+    if (which_type == Definition::FUNCTION && (name == "and" || name == "or" || name == "not"))+    { error("Invalid function name \"" + name + "\"."); }+    ParserState 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;+  }++  Parameter_Obj Parser::parse_parameter()+  {+    if (peek< alternatives< exactly<','>, exactly< '{' >, exactly<';'> > >()) {+      css_error("Invalid CSS", " after ", ": expected variable (e.g. $foo), was ");+    }+    while (lex< alternatives < spaces, block_comment > >());+    lex < variable >();+    std::string name(Util::normalize_underscores(lexed));+    ParserState pos = pstate;+    Expression_Obj val;+    bool is_rest = false;+    while (lex< alternatives < spaces, block_comment > >());+    if (lex< exactly<':'> >()) { // there's a default value+      while (lex< block_comment >());+      val = parse_space_list();+    }+    else if (lex< exactly< ellipsis > >()) {+      is_rest = true;+    }+    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;+  }++  Argument_Obj Parser::parse_argument()+  {+    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 ");+    }++    Argument_Obj arg;+    if (peek_css< sequence < variable, optional_css_comments, exactly<':'> > >()) {+      lex_css< variable >();+      std::string name(Util::normalize_underscores(lexed));+      ParserState p = pstate;+      lex_css< exactly<':'> >();+      Expression_Obj val = parse_space_list();+      arg = SASS_MEMORY_NEW(Argument, p, val, name);+    }+    else {+      bool is_arglist = false;+      bool is_keyword = false;+      Expression_Obj val = parse_space_list();+      List_Ptr l = Cast<List>(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;+  }++  Assignment_Obj Parser::parse_assignment()+  {+    std::string name(Util::normalize_underscores(lexed));+    ParserState 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 ");+    }+    Expression_Obj 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);+  }++  // a ruleset connects a selector and a block+  Ruleset_Obj 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)+    Ruleset_Obj ruleset = SASS_MEMORY_NEW(Ruleset, pstate);+    // parse selector static or as schema to be evaluated later+    if (lookahead.parsable) ruleset->selector(parse_selector_list(false));+    else {+      Selector_List_Obj list = SASS_MEMORY_NEW(Selector_List, pstate);+      list->schema(parse_selector_schema(lookahead.position, false));+      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 sanity checks+    ruleset->is_root(is_root);+    // return AST Node+    return ruleset;+  }++  // 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_Ptr schema = SASS_MEMORY_NEW(String_Schema, pstate);+    // the selector schema is pretty much just a wrapper for the string schema+    Selector_Schema_Obj selector_schema = SASS_MEMORY_NEW(Selector_Schema, pstate, schema);+    selector_schema->connect_parent(chroot == false);+    selector_schema->media_block(last_media_block);++    // process until end+    while (i < end_of_selector) {+      // try to parse mutliple interpolants+      if (const char* p = find_first_in_interval< exactly<hash_lbrace>, block_comment >(i, end_of_selector)) {+        // accumulate the preceding segment if the position has advanced+        if (i < p) {+          std::string parsed(i, p);+          String_Constant_Obj str = SASS_MEMORY_NEW(String_Constant, pstate, parsed);+          pstate += Offset(parsed);+          str->update_pstate(pstate);+          schema->append(str);+        }++        // skip over all nested inner interpolations up to our own delimiter+        const char* j = skip_over_scopes< exactly<hash_lbrace>, exactly<rbrace> >(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<rbrace> > >(p+2)) {+          position = p+2;+          css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was ");+        }+        // pass inner expression to the parser to resolve nested interpolations+        pstate.add(p, p+2);+        Expression_Obj interpolant = Parser::from_c_str(p+2, j, ctx, traces, pstate).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.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) {+          std::string parsed(i, end_of_selector);+          String_Constant_Obj str = SASS_MEMORY_NEW(String_Constant, pstate, parsed);+          pstate += Offset(parsed);+          str->update_pstate(pstate);+          i = end_of_selector;+          schema->append(str);+        }+        // exit loop+      }+    }+    // EO until eos++    // update position+    position = i;++    // update for end position+    selector_schema->update_pstate(pstate);+    schema->update_pstate(pstate);++    after_token = before_token = pstate;++    // return parsed result+    return selector_schema.detach();+  }+  // EO parse_selector_schema++  void Parser::parse_charset_directive()+  {+    lex <+      sequence <+        quoted_string,+        optional_spaces,+        exactly <';'>+      >+    >();+  }++  // 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+    std::string name(Util::normalize_underscores(lexed));+    // create the initial mixin call object+    Mixin_Call_Obj call = SASS_MEMORY_NEW(Mixin_Call, pstate, name, 0, 0);+    // parse mandatory arguments+    call->arguments(parse_arguments());+    // parse optional block+    if (peek < exactly <'{'> >()) {+      call->block(parse_block());+    }+    // return ast node+    return call.detach();+  }+  // EO parse_include_directive++  // parse a list of complex selectors+  // this is the main entry point for most+  Selector_List_Obj Parser::parse_selector_list(bool chroot)+  {+    bool reloop;+    bool had_linefeed = false;+    NESTING_GUARD(nestings);+    Complex_Selector_Obj sel;+    Selector_List_Obj group = SASS_MEMORY_NEW(Selector_List, pstate);+    group->media_block(last_media_block);++    if (peek_css< alternatives < end_of_file, exactly <'{'>, exactly <','> > >()) {+      css_error("Invalid CSS", " after ", ": expected selector, was ");+    }++    do {+      reloop = false;++      had_linefeed = had_linefeed || peek_newline();++      if (peek_css< alternatives < class_char < selector_list_delims > > >())+        break; // in case there are superfluous commas at the end++      // now parse the complex selector+      sel = parse_complex_selector(chroot);++      if (!sel) return group.detach();++      sel->has_line_feed(had_linefeed);++      had_linefeed = false;++      while (peek_css< exactly<','> >())+      {+        lex< css_comments >(false);+        // consume everything up and including the comma separator+        reloop = lex< exactly<','> >() != 0;+        // remember line break (also between some commas)+        had_linefeed = had_linefeed || peek_newline();+        // remember line break (also between some commas)+      }+      group->append(sel);+    }+    while (reloop);+    while (lex_css< kwd_optional >()) {+      group->is_optional(true);+    }+    // update for end position+    group->update_pstate(pstate);+    if (sel) sel->last()->has_line_break(false);+    return group.detach();+  }+  // EO parse_selector_list++  // a complex selector combines a compound selector with another+  // complex selector, with one of four combinator operations.+  // the compound selector (head) is optional, since the combinator+  // can come first in the whole selector sequence (like `> DIV').+  Complex_Selector_Obj Parser::parse_complex_selector(bool chroot)+  {++    NESTING_GUARD(nestings);+    String_Obj reference = 0;+    lex < block_comment >();+    advanceToNextToken();+    Complex_Selector_Obj sel = SASS_MEMORY_NEW(Complex_Selector, pstate);++    if (peek < end_of_file >()) return 0;++    // parse the left hand side+    Compound_Selector_Obj lhs;+    // special case if it starts with combinator ([+~>])+    if (!peek_css< class_char < selector_combinator_ops > >()) {+      // parse the left hand side+      lhs = parse_compound_selector();+    }+++    // parse combinator between lhs and rhs+    Complex_Selector::Combinator combinator = Complex_Selector::ANCESTOR_OF;+    if      (lex< exactly<'+'> >()) combinator = Complex_Selector::ADJACENT_TO;+    else if (lex< exactly<'~'> >()) combinator = Complex_Selector::PRECEDES;+    else if (lex< exactly<'>'> >()) combinator = Complex_Selector::PARENT_OF;+    else if (lex< sequence < exactly<'/'>, negate < exactly < '*' > > > >()) {+      // comments are allowed, but not spaces?+      combinator = Complex_Selector::REFERENCE;+      if (!lex < re_reference_combinator >()) return 0;+      reference = SASS_MEMORY_NEW(String_Constant, pstate, lexed);+      if (!lex < exactly < '/' > >()) return 0; // ToDo: error msg?+    }++    if (!lhs && combinator == Complex_Selector::ANCESTOR_OF) return 0;++    // lex < block_comment >();+    sel->head(lhs);+    sel->combinator(combinator);+    sel->media_block(last_media_block);++    if (combinator == Complex_Selector::REFERENCE) sel->reference(reference);+    // has linfeed after combinator?+    sel->has_line_break(peek_newline());+    // sel->has_line_feed(has_line_feed);++    // check if we got the abort condition (ToDo: optimize)+    if (!peek_css< class_char < complex_selector_delims > >()) {+      // parse next selector in sequence+      sel->tail(parse_complex_selector(true));+    }++    // add a parent selector if we are not in a root+    // also skip adding parent ref if we only have refs+    if (!sel->has_parent_ref() && !chroot) {+      // create the objects to wrap parent selector reference+      Compound_Selector_Obj head = SASS_MEMORY_NEW(Compound_Selector, pstate);+      Parent_Selector_Ptr parent = SASS_MEMORY_NEW(Parent_Selector, pstate, false);+      parent->media_block(last_media_block);+      head->media_block(last_media_block);+      // add simple selector+      head->append(parent);+      // selector may not have any head yet+      if (!sel->head()) { sel->head(head); }+      // otherwise we need to create a new complex selector and set the old one as its tail+      else {+        sel = SASS_MEMORY_NEW(Complex_Selector, pstate, Complex_Selector::ANCESTOR_OF, head, sel);+        sel->media_block(last_media_block);+      }+      // peek for linefeed and remember result on head+      // if (peek_newline()) head->has_line_break(true);+    }++    sel->update_pstate(pstate);+    // complex selector+    return sel;+  }+  // EO parse_complex_selector++  // parse one compound selector, which is basically+  // a list of simple selectors (directly adjacent)+  // lex them exactly (without skipping white-space)+  Compound_Selector_Obj Parser::parse_compound_selector()+  {+    // init an empty compound selector wrapper+    Compound_Selector_Obj seq = SASS_MEMORY_NEW(Compound_Selector, pstate);+    seq->media_block(last_media_block);++    // skip initial white-space+    lex< css_whitespace >();++    // parse list+    while (true)+    {+      // remove all block comments (don't skip white-space)+      lex< delimited_by< slash_star, star_slash, false > >(false);+      // parse functional+      if (match < re_pseudo_selector >())+      {+        seq->append(parse_simple_selector());+      }+      // parse parent selector+      else if (lex< exactly<'&'> >(false))+      {+        // this produces a linefeed!?+        seq->has_parent_reference(true);+        seq->append(SASS_MEMORY_NEW(Parent_Selector, pstate));+        // parent selector only allowed at start+        // upcoming Sass may allow also trailing+        if (seq->length() > 1) {+          ParserState state(pstate);+          Simple_Selector_Obj cur = (*seq)[seq->length()-1];+          Simple_Selector_Obj prev = (*seq)[seq->length()-2];+          std::string sel(prev->to_string({ NESTED, 5 }));+          std::string found(cur->to_string({ NESTED, 5 }));+          if (lex < identifier >()) { found += std::string(lexed); }+          error("Invalid CSS after \"" + sel + "\": expected \"{\", was \"" + found + "\"\n\n"+            "\"" + found + "\" may only be used at the beginning of a compound selector.", state);+        }+      }+      // parse type selector+      else if (lex< re_type_selector >(false))+      {+        seq->append(SASS_MEMORY_NEW(Element_Selector, pstate, lexed));+      }+      // peek for abort conditions+      else if (peek< spaces >()) break;+      else if (peek< end_of_file >()) { break; }+      else if (peek_css < class_char < selector_combinator_ops > >()) break;+      else if (peek_css < class_char < complex_selector_delims > >()) break;+      // otherwise parse another simple selector+      else {+        Simple_Selector_Obj sel = parse_simple_selector();+        if (!sel) return 0;+        seq->append(sel);+      }+    }++    if (seq && !peek_css<alternatives<end_of_file,exactly<'{'>>>()) {+      seq->has_line_break(peek_newline());+    }++    // EO while true+    return seq;++  }+  // EO parse_compound_selector++  Simple_Selector_Obj Parser::parse_simple_selector()+  {+    lex < css_comments >(false);+    if (lex< class_name >()) {+      return SASS_MEMORY_NEW(Class_Selector, pstate, lexed);+    }+    else if (lex< id_name >()) {+      return SASS_MEMORY_NEW(Id_Selector, pstate, lexed);+    }+    else if (lex< alternatives < variable, number, static_reference_combinator > >()) {+      return SASS_MEMORY_NEW(Element_Selector, pstate, lexed);+    }+    else if (peek< pseudo_not >()) {+      return parse_negated_selector();+    }+    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 >()) {+      Placeholder_Selector_Ptr sel = SASS_MEMORY_NEW(Placeholder_Selector, pstate, lexed);+      sel->media_block(last_media_block);+      return sel;+    }+    else {+      css_error("Invalid CSS", " after ", ": expected selector, was ");+    }+    // failed+    return 0;+  }++  Wrapped_Selector_Obj Parser::parse_negated_selector()+  {+    lex< pseudo_not >();+    std::string name(lexed);+    ParserState nsource_position = pstate;+    Selector_List_Obj negated = parse_selector_list(true);+    if (!lex< exactly<')'> >()) {+      error("negated selector is missing ')'");+    }+    name.erase(name.size() - 1);+    return SASS_MEMORY_NEW(Wrapped_Selector, nsource_position, name, negated);+  }++  // a pseudo selector often starts with one or two colons+  // it can contain more selectors inside parentheses+  Simple_Selector_Obj Parser::parse_pseudo_selector() {+    if (lex< sequence<+          optional < pseudo_prefix >,+          // we keep the space within the name, strange enough+          // ToDo: refactor output to schedule the space for it+          // or do we really want to keep the real white-space?+          sequence< identifier, optional < block_comment >, exactly<'('> >+        > >())+    {++      std::string name(lexed);+      name.erase(name.size() - 1);+      ParserState p = pstate;++      // specially parse static stuff+      // ToDo: really everything static?+      if (peek_css <+            sequence <+              alternatives <+                static_value,+                binomial+              >,+              optional_css_whitespace,+              exactly<')'>+            >+          >()+      ) {+        lex_css< alternatives < static_value, binomial > >();+        String_Constant_Obj expr = SASS_MEMORY_NEW(String_Constant, pstate, lexed);+        if (lex_css< exactly<')'> >()) {+          expr->can_compress_whitespace(true);+          return SASS_MEMORY_NEW(Pseudo_Selector, p, name, expr);+        }+      }+      else if (Selector_List_Obj wrapped = parse_selector_list(true)) {+        if (wrapped && lex_css< exactly<')'> >()) {+          return SASS_MEMORY_NEW(Wrapped_Selector, p, name, wrapped);+        }+      }++    }+    // EO if pseudo selector++    else if (lex < sequence< optional < pseudo_prefix >, identifier > >()) {+      return SASS_MEMORY_NEW(Pseudo_Selector, pstate, lexed);+    }+    else if(lex < pseudo_prefix >()) {+      css_error("Invalid CSS", " after ", ": expected pseudoclass or pseudoelement, was ");+    }++    css_error("Invalid CSS", " after ", ": expected \")\", was ");++    // unreachable statement+    return 0;+  }++  const char* Parser::re_attr_sensitive_close(const char* src)+  {+    return alternatives < exactly<']'>, exactly<'/'> >(src);+  }++  const char* Parser::re_attr_insensitive_close(const char* src)+  {+    return sequence < insensitive<'i'>, re_attr_sensitive_close >(src);+  }++  Attribute_Selector_Obj Parser::parse_attribute_selector()+  {+    ParserState p = pstate;+    if (!lex_css< attribute_name >()) error("invalid attribute name in attribute selector");+    std::string name(lexed);+    if (lex_css< re_attr_sensitive_close >()) {+      return SASS_MEMORY_NEW(Attribute_Selector, p, name, "", 0, 0);+    }+    else if (lex_css< re_attr_insensitive_close >()) {+      char modifier = lexed.begin[0];+      return SASS_MEMORY_NEW(Attribute_Selector, p, name, "", 0, modifier);+    }+    if (!lex_css< alternatives< exact_match, class_match, dash_match,+                                prefix_match, suffix_match, substring_match > >()) {+      error("invalid operator in attribute selector for " + name);+    }+    std::string matcher(lexed);++    String_Obj value = 0;+    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!+    }+    else {+      error("expected a string constant or identifier in attribute selector for " + name);+    }++    if (lex_css< re_attr_sensitive_close >()) {+      return SASS_MEMORY_NEW(Attribute_Selector, p, name, matcher, value, 0);+    }+    else if (lex_css< re_attr_insensitive_close >()) {+      char modifier = lexed.begin[0];+      return SASS_MEMORY_NEW(Attribute_Selector, p, name, matcher, value, modifier);+    }+    error("unterminated attribute selector for " + name);+    return NULL; // to satisfy compilers (error must not return)+  }++  /* parse block comment and add to block */+  void Parser::parse_block_comments()+  {+    Block_Obj block = block_stack.back();++    while (lex< block_comment >()) {+      bool is_important = lexed.begin[2] == '!';+      // flag on second param is to skip loosely over comments+      String_Obj contents = parse_interpolated_chunk(lexed, true, false);+      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 std::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 std::string property(lexed);+      is_custom_property = property.compare(0, 2, "--") == 0;+      prop = SASS_MEMORY_NEW(String_Constant, pstate, lexed);+    }+    else {+      css_error("Invalid CSS", " after ", ": expected \"}\", was ");+    }+    bool is_indented = true;+    const std::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);+    }+    lex < css_comments >(false);+    if (peek_css< static_value >()) {+      return SASS_MEMORY_NEW(Declaration, prop->pstate(), prop, parse_static_value()/*, lex<kwd_important>()*/);+    }+    else {+      Expression_Obj 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 {+        value = parse_list(DELAYED);+        if (List_Ptr list = Cast<List>(value)) {+          if (!list->is_bracketed() && list->length() == 0 && !peek< exactly <'{'> >()) {+            css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was ");+          }+        }+      }+      lex < css_comments >(false);+      Declaration_Obj decl = SASS_MEMORY_NEW(Declaration, prop->pstate(), prop, value/*, lex<kwd_important>()*/);+      decl->is_indented(is_indented);+      decl->update_pstate(pstate);+      return decl;+    }+  }++  // parse +/- and return false if negative+  // this is never hit via spec tests+  bool Parser::parse_number_prefix()+  {+    bool positive = true;+    while(true) {+      if (lex < block_comment >()) continue;+      if (lex < number_prefix >()) continue;+      if (lex < exactly < '-' > >()) {+        positive = !positive;+        continue;+      }+      break;+    }+    return positive;+  }++  Expression_Obj Parser::parse_map()+  {+    NESTING_GUARD(nestings);+    Expression_Obj key = parse_list();+    List_Obj map = SASS_MEMORY_NEW(List, pstate, 0, SASS_HASH);++    // it's not a map so return the lexed value as a list value+    if (!lex_css< exactly<':'> >())+    { return key; }++    List_Obj l = Cast<List>(key);+    if (l && l->separator() == SASS_COMMA) {+      css_error("Invalid CSS", " after ", ": expected \")\", was ");+    }++    Expression_Obj 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);+    }++    ParserState ps = map->pstate();+    ps.offset = pstate - ps + pstate.offset;+    map->pstate(ps);++    return map;+  }++  Expression_Obj Parser::parse_bracket_list()+  {+    NESTING_GUARD(nestings);+    // check if we have an empty list+    // return the empty list as such+    if (peek_css< list_terminator >(position))+    {+      // return an empty list (nothing to delay)+      return SASS_MEMORY_NEW(List, pstate, 0, SASS_SPACE, false, true);+    }++    bool has_paren = peek_css< exactly<'('> >() != NULL;++    // now try to parse a space list+    Expression_Obj 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>(list);+      if (!l || l->is_bracketed() || has_paren) {+        List_Obj bracketed_list = SASS_MEMORY_NEW(List, pstate, 1, SASS_SPACE, false, true);+        bracketed_list->append(list);+        return bracketed_list;+      }+      l->is_bracketed(true);+      return l;+    }++    // if we got so far, we actually do have a comma list+    List_Obj bracketed_list = SASS_MEMORY_NEW(List, pstate, 2, SASS_COMMA, false, true);+    // wrap the first expression+    bracketed_list->append(list);++    while (lex_css< exactly<','> >())+    {+      // check for abort condition+      if (peek_css< list_terminator >(position)+      ) { break; }+      // otherwise add another expression+      bracketed_list->append(parse_space_list());+    }+    // return the list+    return bracketed_list;+  }++  // 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!+  Expression_Obj Parser::parse_list(bool delayed)+  {+    NESTING_GUARD(nestings);+    return parse_comma_list(delayed);+  }++  // will return singletons unwrapped+  Expression_Obj Parser::parse_comma_list(bool delayed)+  {+    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);+    }++    // now try to parse a space list+    Expression_Obj 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 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 the list+    return comma_list;+  }+  // EO parse_comma_list++  // will return singletons unwrapped+  Expression_Obj Parser::parse_space_list()+  {+    NESTING_GUARD(nestings);+    Expression_Obj 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());+    }+    // return the list+    return space_list;+  }+  // EO parse_space_list++  // parse logical OR operation+  Expression_Obj Parser::parse_disjunction()+  {+    NESTING_GUARD(nestings);+    advanceToNextToken();+    ParserState state(pstate);+    // parse the left hand side conjunction+    Expression_Obj conj = parse_conjunction();+    // parse multiple right hand sides+    std::vector<Expression_Obj> 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+    Expression_Obj ex = fold_operands(conj, operands, { Sass_OP::OR });+    state.offset = pstate - state + pstate.offset;+    ex->pstate(state);+    return ex;+  }+  // EO parse_disjunction++  // parse logical AND operation+  Expression_Obj Parser::parse_conjunction()+  {+    NESTING_GUARD(nestings);+    advanceToNextToken();+    ParserState state(pstate);+    // parse the left hand side relation+    Expression_Obj rel = parse_relation();+    // parse multiple right hand sides+    std::vector<Expression_Obj> 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+    Expression_Obj ex = fold_operands(rel, operands, { Sass_OP::AND });+    state.offset = pstate - state + pstate.offset;+    ex->pstate(state);+    return ex;+  }+  // EO parse_conjunction++  // parse comparison operations+  Expression_Obj Parser::parse_relation()+  {+    NESTING_GUARD(nestings);+    advanceToNextToken();+    ParserState state(pstate);+    // parse the left hand side expression+    Expression_Obj lhs = parse_expression();+    std::vector<Expression_Obj> operands;+    std::vector<Operand> 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<kwd_eq>()  ? Sass_OP::EQ+      : lex<kwd_neq>() ? Sass_OP::NEQ+      : lex<kwd_gte>() ? Sass_OP::GTE+      : lex<kwd_lte>() ? Sass_OP::LTE+      : lex<kwd_gt>()  ? Sass_OP::GT+      : lex<kwd_lt>()  ? 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 ...+    Expression_Obj ex = fold_operands(lhs, operands, operators);+    state.offset = pstate - state + pstate.offset;+    ex->pstate(state);+    return ex;+  }+  // 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+  Expression_Obj Parser::parse_expression()+  {+    NESTING_GUARD(nestings);+    advanceToNextToken();+    ParserState state(pstate);+    // parses multiple add and subtract operations+    // NOTE: make sure that identifiers starting with+    // NOTE: dashes do NOT count as subtract operation+    Expression_Obj lhs = parse_operators();+    // if it's a singleton, return it (don't wrap it)+    if (!(peek_css< exactly<'+'> >(position) ||+          // condition is a bit misterious, but some combinations should not be counted as operations+          (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; }++    std::vector<Expression_Obj> operands;+    std::vector<Operand> 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 (operands.size() == 0) return lhs;+    Expression_Obj ex = fold_operands(lhs, operands, operators);+    state.offset = pstate - state + pstate.offset;+    ex->pstate(state);+    return ex;+  }++  // parse addition and subtraction operations+  Expression_Obj Parser::parse_operators()+  {+    NESTING_GUARD(nestings);+    advanceToNextToken();+    ParserState state(pstate);+    Expression_Obj factor = parse_factor();+    // if it's a singleton, return it (don't wrap it)+    std::vector<Expression_Obj> operands; // factors+    std::vector<Operand> 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+    Expression_Obj ex = fold_operands(factor, operands, operators);+    state.offset = pstate - state + pstate.offset;+    ex->pstate(state);+    return ex;+  }+  // EO parse_operators+++  // called from parse_operators+  // called from parse_value_schema+  Expression_Obj Parser::parse_factor()+  {+    NESTING_GUARD(nestings);+    lex < css_comments >(false);+    if (lex_css< exactly<'('> >()) {+      // parse_map may return a list+      Expression_Obj 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+      Expression_Obj 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_Ptr schema = Cast<String_Schema>(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_Ptr ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::PLUS, parse_factor());+      if (ex && ex->operand()) ex->is_delayed(ex->operand()->is_delayed());+      return ex;+    }+    else if (lex< exactly<'-'> >()) {+      Unary_Expression_Ptr ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::MINUS, parse_factor());+      if (ex && ex->operand()) ex->is_delayed(ex->operand()->is_delayed());+      return ex;+    }+    else if (lex< exactly<'/'> >()) {+      Unary_Expression_Ptr ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::SLASH, parse_factor());+      if (ex && ex->operand()) ex->is_delayed(ex->operand()->is_delayed());+      return ex;+    }+    else if (lex< sequence< kwd_not > >()) {+      Unary_Expression_Ptr ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::NOT, parse_factor());+      if (ex && ex->operand()) ex->is_delayed(ex->operand()->is_delayed());+      return ex;+    }+    // this whole branch is never hit via spec tests+    else if (peek < sequence < one_plus < alternatives < css_whitespace, exactly<'-'>, exactly<'+'> > >, number > >()) {+      if (parse_number_prefix()) return parse_value(); // prefix is positive+      Unary_Expression_Ptr ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::MINUS, parse_value());+      if (ex->operand()) ex->is_delayed(ex->operand()->is_delayed());+      return ex;+    }+    else {+      return parse_value();+    }+  }++  bool number_has_zero(const std::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_Ptr Parser::lexed_number(const ParserState& pstate, const std::string& parsed)+  {+    Number_Ptr 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_Ptr Parser::lexed_percentage(const ParserState& pstate, const std::string& parsed)+  {+    Number_Ptr nr = SASS_MEMORY_NEW(Number,+                                    pstate,+                                    sass_strtod(parsed.c_str()),+                                    "%",+                                    true);+    nr->is_interpolant(false);+    nr->is_delayed(true);+    return nr;+  }++  Number_Ptr Parser::lexed_dimension(const ParserState& pstate, const std::string& parsed)+  {+    size_t L = parsed.length();+    size_t num_pos = parsed.find_first_not_of(" \n\r\t");+    if (num_pos == std::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 == std::string::npos) unit_pos = L;+    const std::string& num = parsed.substr(num_pos, unit_pos - num_pos);+    Number_Ptr nr = SASS_MEMORY_NEW(Number,+                                    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;+  }++  Expression_Ptr Parser::lexed_hex_color(const ParserState& pstate, const std::string& parsed)+  {+    Color_Ptr color = NULL;+    if (parsed[0] != '#') {+      return SASS_MEMORY_NEW(String_Quoted, pstate, parsed);+    }+    // chop off the '#'+    std::string hext(parsed.substr(1));+    if (parsed.length() == 4) {+      std::string r(2, parsed[1]);+      std::string g(2, parsed[2]);+      std::string b(2, parsed[3]);+      color = SASS_MEMORY_NEW(Color,+                               pstate,+                               static_cast<double>(strtol(r.c_str(), NULL, 16)),+                               static_cast<double>(strtol(g.c_str(), NULL, 16)),+                               static_cast<double>(strtol(b.c_str(), NULL, 16)),+                               1, // alpha channel+                               parsed);+    }+    else if (parsed.length() == 7) {+      std::string r(parsed.substr(1,2));+      std::string g(parsed.substr(3,2));+      std::string b(parsed.substr(5,2));+      color = SASS_MEMORY_NEW(Color,+                               pstate,+                               static_cast<double>(strtol(r.c_str(), NULL, 16)),+                               static_cast<double>(strtol(g.c_str(), NULL, 16)),+                               static_cast<double>(strtol(b.c_str(), NULL, 16)),+                               1, // alpha channel+                               parsed);+    }+    else if (parsed.length() == 9) {+      std::string r(parsed.substr(1,2));+      std::string g(parsed.substr(3,2));+      std::string b(parsed.substr(5,2));+      std::string a(parsed.substr(7,2));+      color = SASS_MEMORY_NEW(Color,+                               pstate,+                               static_cast<double>(strtol(r.c_str(), NULL, 16)),+                               static_cast<double>(strtol(g.c_str(), NULL, 16)),+                               static_cast<double>(strtol(b.c_str(), NULL, 16)),+                               static_cast<double>(strtol(a.c_str(), NULL, 16)) / 255,+                               parsed);+    }+    color->is_interpolant(false);+    color->is_delayed(false);+    return color;+  }++  // parse one value for a list+  Expression_Obj 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_Selector, 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 SASS_MEMORY_NEW(String_Constant, pstate, 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 >())+    {+      std::string s = lexed.to_string();++      deprecated(+        "The value \""+s+"\" is currently parsed as a string, but it will be parsed as a color in",+        "future versions of Sass. Use \"unquote('"+s+"')\" to continue parsing it as a string.",+        true, pstate+      );++      return SASS_MEMORY_NEW(String_Quoted, pstate, 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)); }++    // Special case handling for `%` proceeding an interpolant.+    if (lex< sequence< exactly<'%'>, optional< percentage > > >())+    { return SASS_MEMORY_NEW(String_Constant, pstate, lexed); }++    css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was ");++    // unreachable statement+    return 0;+  }++  // 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<hash_lbrace> >(i, chunk.end) :+                    find_first_in_interval< exactly<hash_lbrace>, block_comment >(i, chunk.end);++    if (!p) {+      String_Quoted_Ptr str_quoted = SASS_MEMORY_NEW(String_Quoted, pstate, std::string(i, chunk.end), 0, false, false, true, css);+      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<hash_lbrace> >(i, chunk.end) :+          find_first_in_interval< exactly<hash_lbrace>, 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, std::string(i, p), css));+        }+        // we need to skip anything inside strings+        // create a new target in parser/prelexer+        if (peek < sequence < optional_spaces, exactly<rbrace> > >(p+2)) { position = p+2;+          css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was ");+        }+        const char* j = skip_over_scopes< exactly<hash_lbrace>, exactly<rbrace> >(p + 2, chunk.end); // find the closing brace+        if (j) { --j;+          // parse the interpolant and accumulate it+          Expression_Obj interp_node = Parser::from_token(Token(p+2, j), ctx, traces, pstate, source).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, std::string(i, chunk.end), css));+        break;+      }+      ++ i;+    }++    return schema.detach();+  }++  String_Schema_Obj Parser::parse_css_variable_value(bool top_level)+  {+    String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate);+    String_Schema_Obj tok;+    if (!(tok = parse_css_variable_value_token(top_level))) {+      return NULL;+    }++    schema->concat(tok);+    while ((tok = parse_css_variable_value_token(top_level))) {+      schema->concat(tok);+    }++    return schema.detach();+  }++  String_Schema_Obj Parser::parse_css_variable_value_token(bool top_level)+  {+    String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate);+    if (+      (top_level && lex< css_variable_top_level_value >(false)) ||+      (!top_level && lex< css_variable_value >(false))+    ) {+      Token str(lexed);+      schema->append(SASS_MEMORY_NEW(String_Constant, pstate, str));+    }+    else if (Expression_Obj tok = lex_interpolation()) {+      if (String_Schema_Ptr s = Cast<String_Schema>(tok)) {+        schema->concat(s);+      } else {+        schema->append(tok);+      }+    }+    else if (lex< quoted_string >()) {+      Expression_Obj tok = parse_string();+      if (String_Schema_Ptr s = Cast<String_Schema>(tok)) {+        schema->concat(s);+      } else {+        schema->append(tok);+      }+    }+    else {+      if (peek< alternatives< exactly<'('>, exactly<'['>, exactly<'{'> > >()) {+        if (lex< exactly<'('> >()) {+          schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string("(")));+          if (String_Schema_Obj tok = parse_css_variable_value(false)) schema->concat(tok);+          if (!lex< exactly<')'> >()) css_error("Invalid CSS", " after ", ": expected \")\", was ");+          schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string(")")));+        }+        else if (lex< exactly<'['> >()) {+          schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string("[")));+          if (String_Schema_Obj tok = parse_css_variable_value(false)) schema->concat(tok);+          if (!lex< exactly<']'> >()) css_error("Invalid CSS", " after ", ": expected \"]\", was ");+          schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string("]")));+        }+        else if (lex< exactly<'{'> >()) {+          schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string("{")));+          if (String_Schema_Obj tok = parse_css_variable_value(false)) schema->concat(tok);+          if (!lex< exactly<'}'> >()) css_error("Invalid CSS", " after ", ": expected \"}\", was ");+          schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string("}")));+        }+      }+    }++    return schema->length() > 0 ? schema.detach() : NULL;+  }++  String_Constant_Obj 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;++    String_Constant_Ptr str_node = SASS_MEMORY_NEW(String_Constant, pstate, str.time_wspace());+    return str_node;+  }++  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<hash_lbrace>, block_comment >(str.begin, str.end);+    if (!p) {+      return SASS_MEMORY_NEW(String_Quoted, pstate, std::string(str.begin, str.end));+    }++    String_Schema_Ptr schema = SASS_MEMORY_NEW(String_Schema, pstate);+    while (i < str.end) {+      p = find_first_in_interval< exactly<hash_lbrace>, block_comment >(i, str.end);+      if (p) {+        if (i < p) {+          schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string(i, p))); // accumulate the preceding segment if it's nonempty+        }+        if (peek < sequence < optional_spaces, exactly<rbrace> > >(p+2)) { position = p+2;+          css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was ");+        }+        const char* j = skip_over_scopes< exactly<hash_lbrace>, exactly<rbrace> >(p+2, str.end); // find the closing brace+        if (j) {+          // parse the interpolant and accumulate it+          Expression_Obj interp_node = Parser::from_token(Token(p+2, j), ctx, traces, pstate, source).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, std::string(i, str.end)));+        }+        break;+      }+    }+    return schema;+  }++  String_Obj Parser::parse_ie_keyword_arg()+  {+    String_Schema_Ptr 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 >()) {+      std::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<exactly<'}'>>()) {+      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 ");+        }+        Expression_Obj ex;+        if (lex< re_static_expression >()) {+          ex = SASS_MEMORY_NEW(String_Constant, pstate, lexed);+        } else {+          ex = parse_list();+        }+        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< sequence < 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 >()) {+        std::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, std::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<hash_lbrace>, block_comment >(id.begin, id.end);+    if (!p) {+      return SASS_MEMORY_NEW(String_Constant, pstate, std::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<hash_lbrace>, 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<rbrace> > >(p+2)) { position = p;+          css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was ");+        }+        const char* j = skip_over_scopes< exactly<hash_lbrace>, exactly<rbrace> >(p+2, id.end); // find the closing brace+        if (j) {+          // parse the interpolant and accumulate it+          Expression_Obj interp_node = Parser::from_token(Token(p+2, j), ctx, traces, pstate, source).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 >();+    std::string name(lexed);+    ParserState call_pos = pstate;+    lex< exactly<'('> >();+    ParserState 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()+  {+    std::string prefix("");+    if (lex< uri_prefix >()) {+      prefix = std::string(lexed);+    }++    lex < optional_spaces >();+    String_Obj url_string = parse_url_function_argument();++    std::string suffix("");+    if (lex< real_uri_suffix >()) {+      suffix = std::string(lexed);+    }++    std::string uri("");+    if (url_string) {+      uri = url_string->to_string({ NESTED, 5 });+    }++    if (String_Schema_Ptr schema = Cast<String_Schema>(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 {+      std::string res = prefix + uri + suffix;+      return SASS_MEMORY_NEW(String_Constant, pstate, res);+    }+  }++  String_Obj Parser::parse_url_function_argument()+  {+    const char* p = position;++    std::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);+      }+      position = pp;+      return parse_interpolated_chunk(Token(p, position));+    }+    else if (uri != "") {+      std::string res = Util::rtrim(uri);+      return SASS_MEMORY_NEW(String_Constant, pstate, res);+    }++    return 0;+  }++  Function_Call_Obj Parser::parse_function_call()+  {+    lex< identifier >();+    std::string name(lexed);++    if (Util::normalize_underscores(name) == "content-exists" && stack.back() != Scope::Mixin)+    { error("Cannot call content-exists() except within a mixin."); }++    ParserState call_pos = pstate;+    Arguments_Obj args = parse_arguments();+    return SASS_MEMORY_NEW(Function_Call, call_pos, name, args);+  }++  Function_Call_Schema_Obj Parser::parse_function_call_schema()+  {+    String_Obj name = parse_identifier_schema();+    ParserState source_position_of_call = pstate;+    Arguments_Obj args = parse_arguments();++    return SASS_MEMORY_NEW(Function_Call_Schema, source_position_of_call, name, args);+  }++  Content_Obj Parser::parse_content_directive()+  {+    return SASS_MEMORY_NEW(Content, pstate);+  }++  If_Obj Parser::parse_if_directive(bool else_if)+  {+    stack.push_back(Scope::Control);+    ParserState if_source_position = pstate;+    bool root = block_stack.back()->is_root();+    Expression_Obj predicate = parse_list();+    Block_Obj block = parse_block(root);+    Block_Obj alternative = NULL;++    // 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);+  }++  For_Obj Parser::parse_for_directive()+  {+    stack.push_back(Scope::Control);+    ParserState for_source_position = pstate;+    bool root = block_stack.back()->is_root();+    lex_variable();+    std::string var(Util::normalize_underscores(lexed));+    if (!lex< kwd_from >()) error("expected 'from' keyword in @for directive");+    Expression_Obj 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");+    Expression_Obj upper_bound = parse_expression();+    Block_Obj body = parse_block(root);+    stack.pop_back();+    return SASS_MEMORY_NEW(For, for_source_position, var, lower_bound, upper_bound, body, inclusive);+  }++  // helper to parse a var token+  Token Parser::lex_variable()+  {+    // peek for dollar sign first+    if (!peek< exactly <'$'> >()) {+      css_error("Invalid CSS", " after ", ": expected \"$\", was ");+    }+    // we expect a simple identifier as the call name+    if (!lex< sequence < exactly <'$'>, identifier > >()) {+      lex< exactly <'$'> >(); // move pstate and position up+      css_error("Invalid CSS", " after ", ": expected identifier, was ");+    }+    // return object+    return token;+  }+  // 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 token;+  }++  Each_Obj Parser::parse_each_directive()+  {+    stack.push_back(Scope::Control);+    ParserState each_source_position = pstate;+    bool root = block_stack.back()->is_root();+    std::vector<std::string> 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");+    Expression_Obj list = parse_list();+    Block_Obj body = parse_block(root);+    stack.pop_back();+    return SASS_MEMORY_NEW(Each, each_source_position, vars, list, body);+  }++  // called after parsing `kwd_while_directive`+  While_Obj Parser::parse_while_directive()+  {+    stack.push_back(Scope::Control);+    bool root = block_stack.back()->is_root();+    // create the initial while call object+    While_Obj call = SASS_MEMORY_NEW(While, pstate, 0, 0);+    // parse mandatory predicate+    Expression_Obj predicate = parse_list();+    List_Obj l = Cast<List>(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();+  }++  // EO parse_while_directive+  Media_Block_Obj Parser::parse_media_block()+  {+    stack.push_back(Scope::Media);+    Media_Block_Obj media_block = SASS_MEMORY_NEW(Media_Block, pstate, 0, 0);++    media_block->media_queries(parse_media_queries());++    Media_Block_Obj prev_media_block = last_media_block;+    last_media_block = media_block;+    media_block->block(parse_css_block());+    last_media_block = prev_media_block;+    stack.pop_back();+    return media_block.detach();+  }++  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_Ptr 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_Ptr schema = SASS_MEMORY_NEW(String_Schema, pstate);+      schema->append(media_query->media_type());+      schema->append(SASS_MEMORY_NEW(String_Constant, pstate, " "));+      schema->append(parse_identifier_schema());+      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_Expression_Obj Parser::parse_media_expression()+  {+    if (lex < identifier_schema >()) {+      String_Obj ss = parse_identifier_schema();+      return SASS_MEMORY_NEW(Media_Query_Expression, pstate, ss, 0, true);+    }+    if (!lex_css< exactly<'('> >()) {+      error("media query expression must begin with '('");+    }+    Expression_Obj feature;+    if (peek_css< exactly<')'> >()) {+      error("media feature required in media query expression");+    }+    feature = parse_expression();+    Expression_Obj expression = 0;+    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+  Supports_Block_Obj Parser::parse_supports_directive()+  {+    Supports_Condition_Obj cond = parse_supports_condition();+    if (!cond) {+      css_error("Invalid CSS", " after ", ": expected @supports condition (e.g. (display: flexbox)), was ", false);+    }+    // create the ast node object for the support queries+    Supports_Block_Obj query = SASS_MEMORY_NEW(Supports_Block, 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+  Supports_Condition_Obj Parser::parse_supports_condition()+  {+    lex < css_whitespace >();+    Supports_Condition_Obj cond;+    if ((cond = parse_supports_negation())) return cond;+    if ((cond = parse_supports_operator())) return cond;+    if ((cond = parse_supports_interpolation())) return cond;+    return cond;+  }++  Supports_Condition_Obj Parser::parse_supports_negation()+  {+    if (!lex < kwd_not >()) return 0;+    Supports_Condition_Obj cond = parse_supports_condition_in_parens();+    return SASS_MEMORY_NEW(Supports_Negation, pstate, cond);+  }++  Supports_Condition_Obj Parser::parse_supports_operator()+  {+    Supports_Condition_Obj cond = parse_supports_condition_in_parens();+    if (cond.isNull()) return 0;++    while (true) {+      Supports_Operator::Operand op = Supports_Operator::OR;+      if (lex < kwd_and >()) { op = Supports_Operator::AND; }+      else if(!lex < kwd_or >()) { break; }++      lex < css_whitespace >();+      Supports_Condition_Obj right = parse_supports_condition_in_parens();++      // Supports_Condition_Ptr cc = SASS_MEMORY_NEW(Supports_Condition, *static_cast<Supports_Condition_Ptr>(cond));+      cond = SASS_MEMORY_NEW(Supports_Operator, pstate, cond, right, op);+    }+    return cond;+  }++  Supports_Condition_Obj Parser::parse_supports_interpolation()+  {+    if (!lex < interpolant >()) return 0;++    String_Obj interp = parse_interpolated_chunk(lexed);+    if (!interp) return 0;++    return SASS_MEMORY_NEW(Supports_Interpolation, pstate, interp);+  }++  // TODO: This needs some major work. Although feature conditions+  // look like declarations their semantics differ significantly+  Supports_Condition_Obj Parser::parse_supports_declaration()+  {+    Supports_Condition_Ptr cond;+    // parse something declaration like+    Expression_Obj feature = parse_expression();+    Expression_Obj expression = 0;+    if (lex_css< exactly<':'> >()) {+      expression = parse_list(DELAYED);+    }+    if (!feature || !expression) error("@supports condition expected declaration");+    cond = SASS_MEMORY_NEW(Supports_Declaration,+                     feature->pstate(),+                     feature,+                     expression);+    // ToDo: maybe we need an additional error condition?+    return cond;+  }++  Supports_Condition_Obj Parser::parse_supports_condition_in_parens()+  {+    Supports_Condition_Obj interp = parse_supports_interpolation();+    if (interp != 0) return interp;++    if (!lex < exactly <'('> >()) return 0;+    lex < css_whitespace >();++    Supports_Condition_Obj cond = parse_supports_condition();+    if (cond != 0) {+      if (!lex < exactly <')'> >()) error("unclosed parenthesis in @supports declaration");+    } else {+      cond = parse_supports_declaration();+      if (!lex < exactly <')'> >()) error("unclosed parenthesis in @supports declaration");+    }+    lex < css_whitespace >();+    return cond;+  }++  At_Root_Block_Obj Parser::parse_at_root_block()+  {+    stack.push_back(Scope::AtRoot);+    ParserState at_source_position = pstate;+    Block_Obj body = 0;+    At_Root_Query_Obj expr;+    Lookahead lookahead_result;+    if (lex_css< exactly<'('> >()) {+      expr = parse_at_root_query();+    }+    if (peek_css < exactly<'{'> >()) {+      lex <optional_spaces>();+      body = parse_block(true);+    }+    else if ((lookahead_result = lookahead_for_selector(position)).found) {+      Ruleset_Obj r = parse_ruleset(lookahead_result);+      body = SASS_MEMORY_NEW(Block, r->pstate(), 1, true);+      body->append(r);+    }+    At_Root_Block_Obj at_root = SASS_MEMORY_NEW(At_Root_Block, 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 ");+    }++    Expression_Obj feature = parse_list();+    if (!lex_css< exactly<':'> >()) error("style declaration must contain a value");+    Expression_Obj expression = parse_list();+    List_Obj value = SASS_MEMORY_NEW(List, feature->pstate(), 1);++    if (expression->concrete_type() == Expression::LIST) {+        value = Cast<List>(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;+  }++  Directive_Obj Parser::parse_special_directive()+  {+    std::string kwd(lexed);++    if (lexed == "@else") error("Invalid CSS: @else must come after @if");++    // this whole branch is never hit via spec tests++    Directive_Ptr at_rule = SASS_MEMORY_NEW(Directive, pstate, kwd);+    Lookahead lookahead = lookahead_for_include(position);+    if (lookahead.found && !lookahead.has_interpolants) {+      at_rule->selector(parse_selector_list(false));+    }++    lex < css_comments >(false);++    if (lex < static_property >()) {+      at_rule->value(parse_interpolated_chunk(Token(lexed)));+    } else if (!(peek < alternatives < exactly<'{'>, exactly<'}'>, exactly<';'> > >())) {+      at_rule->value(parse_list());+    }++    lex < css_comments >(false);++    if (peek< exactly<'{'> >()) {+      at_rule->block(parse_block());+    }++    return at_rule;+  }++  // this whole branch is never hit via spec tests+  Directive_Obj Parser::parse_prefixed_directive()+  {+    std::string kwd(lexed);++    if (lexed == "@else") error("Invalid CSS: @else must come after @if");++    Directive_Obj at_rule = SASS_MEMORY_NEW(Directive, pstate, kwd);+    Lookahead lookahead = lookahead_for_include(position);+    if (lookahead.found && !lookahead.has_interpolants) {+      at_rule->selector(parse_selector_list(false));+    }++    lex < css_comments >(false);++    if (lex < static_property >()) {+      at_rule->value(parse_interpolated_chunk(Token(lexed)));+    } else if (!(peek < alternatives < exactly<'{'>, exactly<'}'>, exactly<';'> > >())) {+      at_rule->value(parse_list());+    }++    lex < css_comments >(false);++    if (peek< exactly<'{'> >()) {+      at_rule->block(parse_block());+    }++    return at_rule;+  }+++  Directive_Obj Parser::parse_directive()+  {+    Directive_Obj directive = SASS_MEMORY_NEW(Directive, pstate, lexed);+    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;+  }++  Expression_Obj Parser::lex_interpolation()+  {+    if (lex < interpolant >(true) != NULL) {+      return parse_interpolated_chunk(lexed, true);+    }+    return 0;+  }++  Expression_Obj Parser::lex_interp_uri()+  {+    // create a string schema by lexing optional interpolations+    return lex_interp< re_string_uri_open, re_string_uri_close >();+  }++  Expression_Obj Parser::lex_interp_string()+  {+    Expression_Obj 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;+  }++  Expression_Obj Parser::lex_almost_any_value_chars()+  {+    const char* match =+    lex <+      one_plus <+        alternatives <+          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 NULL;+  }++  Expression_Obj Parser::lex_almost_any_value_token()+  {+    Expression_Obj rv;+    if (*position == 0) return 0;+    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;+    return rv;+  }++  String_Schema_Obj Parser::parse_almost_any_value()+  {++    String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate);+    if (*position == 0) return 0;+    lex < spaces >(false);+    Expression_Obj token = lex_almost_any_value_token();+    if (!token) return 0;+    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();+  }++  Warning_Obj 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(Warning, pstate, parse_list(DELAYED));+  }++  Error_Obj 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(Error, pstate, parse_list(DELAYED));+  }++  Debug_Obj 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(Debug, 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;+      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 == ':') {+          rv.is_custom_property = could_be_property || p+1 == q || peek< space >(p+1);+        }+        ++ p;+      }+      // store anyway  }+++      // ToDo: remove+      rv.error = q;+      rv.position = q;+      // check expected opening bracket+      // only after successfull 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;+    std::string encoding;+    bool utf_8 = false;+    switch ((unsigned char) source[0]) {+    case 0xEF:+      skip = check_bom_chars(source, end, utf_8_bom, 3);+      encoding = "UTF-8";+      utf_8 = true;+      break;+    case 0xFE:+      skip = check_bom_chars(source, end, utf_16_bom_be, 2);+      encoding = "UTF-16 (big endian)";+      break;+    case 0xFF:+      skip = check_bom_chars(source, end, utf_16_bom_le, 2);+      skip += (skip ? check_bom_chars(source, 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(source, end, utf_32_bom_be, 4);+      encoding = "UTF-32 (big endian)";+      break;+    case 0x2B:+      skip = check_bom_chars(source, end, utf_7_bom_1, 4)+           | check_bom_chars(source, end, utf_7_bom_2, 4)+           | check_bom_chars(source, end, utf_7_bom_3, 4)+           | check_bom_chars(source, end, utf_7_bom_4, 4)+           | check_bom_chars(source, end, utf_7_bom_5, 5);+      encoding = "UTF-7";+      break;+    case 0xF7:+      skip = check_bom_chars(source, end, utf_1_bom, 3);+      encoding = "UTF-1";+      break;+    case 0xDD:+      skip = check_bom_chars(source, end, utf_ebcdic_bom, 4);+      encoding = "UTF-EBCDIC";+      break;+    case 0x0E:+      skip = check_bom_chars(source, end, scsu_bom, 3);+      encoding = "SCSU";+      break;+    case 0xFB:+      skip = check_bom_chars(source, end, bocu_1_bom, 3);+      encoding = "BOCU-1";+      break;+    case 0x84:+      skip = check_bom_chars(source, 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;+  }+++  Expression_Obj Parser::fold_operands(Expression_Obj base, std::vector<Expression_Obj>& 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;+  }++  Expression_Obj Parser::fold_operands(Expression_Obj base, std::vector<Expression_Obj>& operands, std::vector<Operand>& ops, size_t i)+  {+    if (String_Schema_Ptr schema = Cast<String_Schema>(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)+        )) {+          Expression_Obj 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;+      }+    }++    for (size_t S = operands.size(); i < S; ++i) {+      if (String_Schema_Ptr schema = Cast<String_Schema>(operands[i])) {+        if (schema->has_interpolants()) {+          if (i + 1 < S) {+            // this whole branch is never hit via spec tests+            Expression_Obj 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_Ptr b = Cast<Binary_Expression>(base.ptr());+      if (b && ops[i].operand == Sass_OP::DIV && b->left()->is_delayed() && b->right()->is_delayed()) {+        base->is_delayed(true);+      }+    }+    // nested binary expression are never to be delayed+    if (Binary_Expression_Ptr b = Cast<Binary_Expression>(base)) {+      if (Cast<Binary_Expression>(b->left())) base->set_delayed(false);+      if (Cast<Binary_Expression>(b->right())) base->set_delayed(false);+    }+    return base;+  }++  void Parser::error(std::string msg, Position pos)+  {+    Position p(pos.line ? pos : before_token);+    ParserState pstate(path, source, p, Offset(0, 0));+    traces.push_back(Backtrace(pstate));+    throw Exception::InvalidSass(pstate, traces, msg);+  }++  void Parser::error(std::string msg)+  {+    error(msg, pstate);+  }++  // print a css parsing error with actual context information from parsed source+  void Parser::css_error(const std::string& msg, const std::string& prefix, const std::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 > source) {+      utf8::prior(last_pos, source);+    }+    // backup position to last significant char+    while (trim && last_pos > source && last_pos < end) {+      if (!Prelexer::is_space(*last_pos)) break;+      utf8::prior(last_pos, source);+    }++    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 > source) {+      if (utf8::distance(pos_left, end_left) >= max_len) {+        utf8::prior(pos_left, source);+        ellipsis_left = *(pos_left) != '\n' &&+                        *(pos_left) != '\r';+        utf8::next(pos_left, end);+        break;+      }++      const char* prev = pos_left;+      utf8::prior(prev, source);+      if (*prev == '\r') break;+      if (*prev == '\n') break;+      pos_left = prev;+    }+    if (pos_left < source) {+      pos_left = source;+    }++    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 ++;++    std::string left(pos_left, end_left);+    std::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;+    // Hotfix when source is null, probably due to interpolation parsing!?+    if (source == NULL || *source == 0) source = pstate.src;+    // now pass new message to the more generic error function+    error(msg + prefix + quote(left) + middle + quote(right));   }  }
libsass/src/parser.hpp view
@@ -10,12 +10,22 @@ #include "position.hpp" #include "prelexer.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; };  namespace Sass {@@ -23,7 +33,7 @@   class Parser : public ParserState {   public: -    enum Scope { Root, Mixin, Function, Media, Control, Properties, Rules };+    enum Scope { Root, Mixin, Function, Media, Control, Properties, Rules, AtRoot };      Context& ctx;     std::vector<Block_Obj> block_stack;@@ -35,23 +45,26 @@     Position before_token;     Position after_token;     ParserState pstate;-    int indentation;-+    Backtraces traces;+    size_t indentation;+    size_t nestings;      Token lexed;-    bool in_at_root; -    Parser(Context& ctx, const ParserState& pstate)+    Parser(Context& ctx, const ParserState& pstate, Backtraces traces)     : ParserState(pstate), ctx(ctx), block_stack(), stack(0), last_media_block(),-      source(0), position(0), end(0), before_token(pstate), after_token(pstate), pstate(pstate), indentation(0)-    { in_at_root = false; stack.push_back(Scope::Root); }+      source(0), position(0), end(0), before_token(pstate), after_token(pstate),+      pstate(pstate), traces(traces), indentation(0), nestings(0)+    { +      stack.push_back(Scope::Root);+    }      // static Parser from_string(const std::string& src, Context& ctx, ParserState pstate = ParserState("[STRING]"));-    static Parser from_c_str(const char* src, Context& ctx, ParserState pstate = ParserState("[CSTRING]"), const char* source = 0);-    static Parser from_c_str(const char* beg, const char* end, Context& ctx, ParserState pstate = ParserState("[CSTRING]"), const char* source = 0);-    static Parser from_token(Token t, Context& ctx, ParserState pstate = ParserState("[TOKEN]"), const char* source = 0);+    static Parser from_c_str(const char* src, Context& ctx, Backtraces, ParserState pstate = ParserState("[CSTRING]"), const char* source = 0);+    static Parser from_c_str(const char* beg, const char* end, Context& ctx, Backtraces, ParserState pstate = ParserState("[CSTRING]"), const char* source = 0);+    static Parser from_token(Token t, Context& ctx, Backtraces, ParserState pstate = ParserState("[TOKEN]"), const char* source = 0);     // special static parsers to convert strings into certain selectors-    static Selector_List_Obj parse_selector(const char* src, Context& ctx, ParserState pstate = ParserState("[SELECTOR]"), const char* source = 0);+    static Selector_List_Obj parse_selector(const char* src, Context& ctx, Backtraces, ParserState pstate = ParserState("[SELECTOR]"), const char* source = 0);  #ifdef __clang__ @@ -100,7 +113,7 @@      } -    // peek will only skip over space, tabs and line comment+    // match will not skip over space, tabs and line comment     // return the position where the lexer match will occur     template <Prelexer::prelexer mx>     const char* match(const char* start = 0)@@ -223,12 +236,14 @@  #endif +    void error(std::string msg);     void error(std::string msg, Position pos);     // generate message with given and expected sample     // text before and in the middle are configurable     void css_error(const std::string& msg,                    const std::string& prefix = " after ",-                   const std::string& middle = ", was: ");+                   const std::string& middle = ", was: ",+                   const bool trim = true);     void read_bom();      Block_Obj parse();@@ -240,10 +255,10 @@     Arguments_Obj parse_arguments();     Argument_Obj parse_argument();     Assignment_Obj parse_assignment();-    Ruleset_Obj parse_ruleset(Lookahead lookahead, bool is_root = false);-    Selector_Schema_Obj parse_selector_schema(const char* end_of_selector);-    Selector_List_Obj parse_selector_list(bool at_root = false);-    Complex_Selector_Obj parse_complex_selector(bool in_root = true);+    Ruleset_Obj parse_ruleset(Lookahead lookahead);+    Selector_List_Obj parse_selector_list(bool chroot);+    Complex_Selector_Obj parse_complex_selector(bool chroot);+    Selector_Schema_Obj parse_selector_schema(const char* end_of_selector, bool chroot);     Compound_Selector_Obj parse_compound_selector();     Simple_Selector_Obj parse_simple_selector();     Wrapped_Selector_Obj parse_negated_selector();@@ -257,6 +272,7 @@     bool parse_number_prefix();     Declaration_Obj parse_declaration();     Expression_Obj parse_map();+    Expression_Obj parse_bracket_list();     Expression_Obj parse_list(bool delayed = false);     Expression_Obj parse_comma_list(bool delayed = false);     Expression_Obj parse_space_list();@@ -272,9 +288,11 @@     Function_Call_Schema_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);+    String_Obj parse_interpolated_chunk(Token, bool constant = false, bool css = true);     String_Obj parse_string();     String_Constant_Obj parse_static_value();+    String_Schema_Obj parse_css_variable_value(bool top_level = true);+    String_Schema_Obj parse_css_variable_value_token(bool top_level = true);     String_Obj parse_ie_property();     String_Obj parse_ie_keyword_arg();     String_Schema_Obj parse_value_schema(const char* stop);@@ -340,15 +358,15 @@         schema->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed));         if (position[0] == '#' && position[1] == '{') {           Expression_Obj itpl = lex_interpolation();-          if (&itpl) schema->append(&itpl);+          if (!itpl.isNull()) schema->append(itpl);           while (lex < close >(false)) {             // std::cerr << "LEX [[" << std::string(lexed) << "]]\n";             schema->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed));             if (position[0] == '#' && position[1] == '{') {               Expression_Obj itpl = lex_interpolation();-              if (&itpl) schema->append(&itpl);+              if (!itpl.isNull()) schema->append(itpl);             } else {-              return &schema;+              return schema;             }           }         } else {@@ -357,6 +375,21 @@       }       return 0;     }++  public:+    static Number_Ptr lexed_number(const ParserState& pstate, const std::string& parsed);+    static Number_Ptr lexed_dimension(const ParserState& pstate, const std::string& parsed);+    static Number_Ptr lexed_percentage(const ParserState& pstate, const std::string& parsed);+    static Expression_Ptr lexed_hex_color(const ParserState& pstate, const std::string& parsed);+  private:+    Number_Ptr lexed_number(const std::string& parsed) { return lexed_number(pstate, parsed); };+    Number_Ptr lexed_dimension(const std::string& parsed) { return lexed_dimension(pstate, parsed); };+    Number_Ptr lexed_percentage(const std::string& parsed) { return lexed_percentage(pstate, parsed); };+    Expression_Ptr lexed_hex_color(const std::string& parsed) { return lexed_hex_color(pstate, parsed); };++    static const char* re_attr_sensitive_close(const char* src);+    static const char* re_attr_insensitive_close(const char* src);+   };    size_t check_bom_chars(const char* src, const char *end, const unsigned char* bom, size_t len);
libsass/src/plugins.cpp view
@@ -1,3 +1,8 @@+#include "sass.hpp"+#include <iostream>+#include "output.hpp"+#include "plugins.hpp"+ #ifdef _WIN32 #include <windows.h> #else@@ -7,15 +12,21 @@ #include <dlfcn.h> #endif -#include "sass.hpp"-#include <iostream>-#include "output.hpp"-#include "plugins.hpp"- namespace Sass {    Plugins::Plugins(void) { }-  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);+    }+  }    // check if plugin is compatible with this version   // plugins may be linked static against libsass@@ -57,20 +68,23 @@         // try to get import address for "libsass_load_functions"         if (LOAD_LIB_FN(__plugin_load_fns__, plugin_load_functions, "libsass_load_functions"))         {-          Sass_Function_List fns = plugin_load_functions();+          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         }         // 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();+          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"))         {-          Sass_Importer_List imps = plugin_load_headers();+          Sass_Importer_List imps = plugin_load_headers(), _p = imps;           while (imps && *imps) { headers.push_back(*imps); ++ imps; }+          sass_free_memory(_p); // only delete the container, items not yet         }         // success         return true;@@ -153,7 +167,11 @@       struct dirent *dirp;       if((dp  = opendir(path.c_str())) == NULL) return -1;       while ((dirp = readdir(dp)) != NULL) {-        if (!ends_with(dirp->d_name, ".so")) continue;+        #if __APPLE__+          if (!ends_with(dirp->d_name, ".dylib")) continue;+        #else+          if (!ends_with(dirp->d_name, ".so")) continue;+        #endif         if (load_plugin(path + dirp->d_name)) ++ loaded;       }       closedir(dp);
libsass/src/position.cpp view
@@ -4,6 +4,11 @@ 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)   {@@ -32,7 +37,6 @@    // increase offset by given string (mostly called by lexer)   // increase line counter and count columns on the last line-  // ToDo: make the col count utf8 aware   Offset Offset::add(const char* begin, const char* end)   {     if (end == 0) return *this;@@ -42,9 +46,23 @@         // start new line         column = 0;       } else {-        ++ column;+        // 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;+        // skip over 10xxxxxx+        // is 1st bit not set+        if ((chr & 128) == 0) {+          // regular ascii char+          column += 1;+        }+        // is 2nd bit not set+        else if ((chr & 64) == 0) {+          // first utf8 byte+          column += 1;+        }       }-      ++begin;+      ++ begin;     }     return *this;   }
libsass/src/position.hpp view
@@ -11,6 +11,7 @@   class Offset {      public: // c-tor+      Offset(const char chr);       Offset(const char* string);       Offset(const std::string& text);       Offset(const size_t line, const size_t column);@@ -85,7 +86,7 @@      size_t length()    const { return end - begin; }     std::string ws_before() const { return std::string(prefix, begin); }-    std::string to_string() const { return std::string(begin, end); }+    const std::string to_string() const { return std::string(begin, end); }     std::string time_wspace() const {       std::string str(to_string());       std::string whitespaces(" \t\f\v\n\r");
libsass/src/prelexer.cpp view
@@ -1,6 +1,5 @@ #include "sass.hpp" #include <cctype>-#include <cstddef> #include <iostream> #include <iomanip> #include "util.hpp"@@ -438,6 +437,10 @@         optional <           sequence <           exactly <'/'>,+          negate < sequence <+            exactly < calc_fn_kwd >,+            exactly < '(' >+          > >,           multiple_units         > >       >(src);@@ -577,7 +580,7 @@     const char* value_combinations(const char* src) {       // `2px-2px` is invalid combo       bool was_number = false;-      const char* pos = src;+      const char* pos;       while (src) {         if ((pos = alternatives < quoted_string, identifier, percentage, hex >(src))) {           was_number = false;@@ -643,10 +646,7 @@             >,             sequence <               negate <-                sequence <-                  exactly < url_kwd >,-                  exactly <'('>-                >+                uri_prefix               >,               neg_class_char <                 almost_any_value_class@@ -998,7 +998,17 @@                           digits>(src);     }     const char* number(const char* src) {-      return sequence< optional<sign>, unsigned_number>(src);+      return sequence<+          optional<sign>,+          unsigned_number,+          optional<+            sequence<+              exactly<'e'>,+              optional<sign>,+              unsigned_number+            >+          >+        >(src);     }     const char* coefficient(const char* src) {       return alternatives< sequence< optional<sign>, digits >,@@ -1037,7 +1047,7 @@     const char* hexa(const char* src) {       const char* p = sequence< exactly<'#'>, one_plus<xdigit> >(src);       ptrdiff_t len = p - src;-      return (len != 4 && len != 7 && len != 9) ? 0 : p;+      return (len != 5 && len != 9) ? 0 : p;     }     const char* hex0(const char* src) {       const char* p = sequence< exactly<'0'>, exactly<'x'>, one_plus<xdigit> >(src);@@ -1047,7 +1057,7 @@      /* no longer used - remove?     const char* rgb_prefix(const char* src) {-      return word<rgb_kwd>(src);+      return word<rgb_fn_kwd>(src);     }*/     // Match CSS uri specifiers. @@ -1161,7 +1171,7 @@     }     // Match the CSS negation pseudo-class.     const char* pseudo_not(const char* src) {-      return word< pseudo_not_kwd >(src);+      return word< pseudo_not_fn_kwd >(src);     }     // Match CSS 'odd' and 'even' keywords for functional pseudo-classes.     const char* even(const char* src) {@@ -1269,7 +1279,7 @@             optional_css_whitespace,             exactly<'='>,             optional_css_whitespace,-            alternatives< variable, identifier_schema, identifier, quoted_string, number, hexa >,+            alternatives< variable, identifier_schema, identifier, quoted_string, number, hex, hexa >,             zero_plus< sequence<               optional_css_whitespace,               exactly<','>,@@ -1279,7 +1289,7 @@                 optional_css_whitespace,                 exactly<'='>,                 optional_css_whitespace,-                alternatives< variable, identifier_schema, identifier, quoted_string, number, hexa >+                alternatives< variable, identifier_schema, identifier, quoted_string, number, hex, hexa >               >             > >           > >,@@ -1314,6 +1324,7 @@           identifier,           quoted_string,           number,+          hex,           hexa,           sequence <             exactly < '(' >,@@ -1420,7 +1431,29 @@       >(src);     } +    const char* list_terminator(const char* src) {+      return alternatives <+        exactly<';'>,+        exactly<'}'>,+        exactly<'{'>,+        exactly<')'>,+        exactly<']'>,+        exactly<':'>,+        end_of_file,+        exactly<ellipsis>,+        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 >,@@ -1428,6 +1461,16 @@     //   >(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);     }@@ -1478,6 +1521,7 @@                            static_string,                            percentage,                            hex,+                           hexa,                            exactly<'|'>,                            // exactly<'+'>,                            sequence < number, unit_identifier >,@@ -1545,6 +1589,40 @@                       >(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 < '(' >,@@ -1578,7 +1656,7 @@             class_char < selector_lookahead_ops >,             // match selector combinators /[>+~]/             class_char < selector_combinator_ops >,-            // match attribute compare operators+            // match pseudo selectors             sequence <               exactly <'('>,               optional_spaces,@@ -1586,6 +1664,7 @@               optional_spaces,               exactly <')'>             >,+            // match attribute compare operators             alternatives <               exact_match, class_match, dash_match,               prefix_match, suffix_match, substring_match@@ -1604,12 +1683,21 @@                 // class match                 exactly <'.'>,                 // single or double colon-                optional < pseudo_prefix >+                sequence <+                  optional < pseudo_prefix >,+                  // fix libsass issue 2376+                  negate < uri_prefix >+                >               >,               // accept hypens in token               one_plus < sequence <                 // can start with hyphens-                zero_plus < exactly<'-'> >,+                zero_plus <+                  sequence <+                    exactly <'-'>,+                    optional_spaces+                  >+                >,                 // now the main token                 alternatives <                   kwd_optional,@@ -1636,10 +1724,7 @@       return sequence< optional<namespace_schema>, identifier>(src);     }     const char* re_type_selector(const char* src) {-      return alternatives< type_selector, universal, quoted_string, dimension, percentage, number, identifier_alnums >(src);-    }-    const char* re_type_selector2(const char* src) {-      return alternatives< type_selector, universal, quoted_string, dimension, percentage, number, identifier_alnums >(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);
libsass/src/prelexer.hpp view
@@ -46,7 +46,7 @@       src = exactly<beg>(src);       if (!src) return 0;       const char* stop;-      while (1) {+      while (true) {         if (!*src) return 0;         stop = exactly<end>(src);         if (stop && (!esc || *(src - 1) != '\\')) return stop;@@ -139,7 +139,7 @@       src = exactly<beg>(src);       if (!src) return 0;       const char* stop;-      while (1) {+      while (true) {         if (!*src) return 0;         stop = exactly<end>(src);         if (stop && (!esc || *(src - 1) != '\\')) return stop;@@ -264,7 +264,6 @@     const char* kwd_while_directive(const char* src);      const char* re_nothing(const char* src);-    const char* re_type_selector2(const char* src);      const char* re_special_fun(const char* src); @@ -355,6 +354,10 @@     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);@@ -362,6 +365,7 @@     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);@@ -375,6 +379,9 @@     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<char c>
libsass/src/remove_placeholders.cpp view
@@ -6,13 +6,12 @@  namespace Sass { -    Remove_Placeholders::Remove_Placeholders(Context& ctx)-    : ctx(ctx)+    Remove_Placeholders::Remove_Placeholders()     { }      void Remove_Placeholders::operator()(Block_Ptr b) {         for (size_t i = 0, L = b->length(); i < L; ++i) {-            Statement_Ptr st = &b->at(i);+            Statement_Ptr st = b->at(i);             st->perform(this);         }     }@@ -34,19 +33,19 @@      void Remove_Placeholders::operator()(Ruleset_Ptr r) {         // Create a new selector group without placeholders-        Selector_List_Obj sl = SASS_MEMORY_CAST(Selector_List, r->selector());+        Selector_List_Obj sl = Cast<Selector_List>(r->selector());          if (sl) {           // Set the new placeholder selector list-          r->selector(remove_placeholders(&sl));+          r->selector(remove_placeholders(sl));           // Remove placeholders in wrapped selectors           for (Complex_Selector_Obj cs : sl->elements()) {             while (cs) {               if (cs->head()) {                 for (Simple_Selector_Obj& ss : cs->head()->elements()) {-                  if (Wrapped_Selector_Ptr ws = SASS_MEMORY_CAST(Wrapped_Selector, ss)) {-                    if (Selector_List_Ptr sl = SASS_MEMORY_CAST(Selector_List, ws->selector())) {-                      Selector_List_Ptr clean = remove_placeholders(sl);+                  if (Wrapped_Selector_Ptr ws = Cast<Wrapped_Selector>(ss)) {+                    if (Selector_List_Ptr wsl = Cast<Selector_List>(ws->selector())) {+                      Selector_List_Ptr clean = remove_placeholders(wsl);                       // also clean superflous parent selectors                       // probably not really the correct place                       clean->remove_parent_selectors();@@ -72,10 +71,10 @@     }      void Remove_Placeholders::operator()(Media_Block_Ptr m) {-        operator()(&m->block());+        operator()(m->block());     }     void Remove_Placeholders::operator()(Supports_Block_Ptr m) {-        operator()(&m->block());+        operator()(m->block());     }      void Remove_Placeholders::operator()(Directive_Ptr a) {
libsass/src/remove_placeholders.hpp view
@@ -9,19 +9,15 @@ namespace Sass {  -    class Context;-     class Remove_Placeholders : public Operation_CRTP<void, Remove_Placeholders> { -        Context&          ctx;-         void fallback_impl(AST_Node_Ptr n) {}      public:       Selector_List_Ptr remove_placeholders(Selector_List_Ptr);      public:-        Remove_Placeholders(Context&);+        Remove_Placeholders();         ~Remove_Placeholders() { }          void operator()(Block_Ptr);
libsass/src/sass.cpp view
@@ -7,7 +7,24 @@ #include "sass.h" #include "file.hpp" #include "util.hpp"+#include "sass_context.hpp"+#include "sass_functions.hpp" +namespace Sass {++  // helper to convert string list to vector+  std::vector<std::string> list2vec(struct string_list* cur)+  {+    std::vector<std::string> list;+    while (cur) {+      list.push_back(cur->string);+      cur = cur->next;+    }+    return list;+  }++}+ extern "C" {   using namespace Sass; @@ -49,11 +66,50 @@     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 std::vector<std::string>& incs = compiler->cpp_ctx->include_paths;+    // create the vector with paths to lookup+    std::vector<std::string> 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+    std::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 std::vector<std::string>& incs = compiler->cpp_ctx->include_paths;+    // create the vector with paths to lookup+    std::vector<std::string> 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+    std::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!-  char* ADDCALL sass_resolve_file (const char* file, const char* paths[])+  // this has the original resolve logic for sass include+  char* ADDCALL sass_find_include (const char* file, struct Sass_Options* opt)   {-    std::string resolved(File::find_file(file, paths));+    std::vector<std::string> vec(list2vec(opt->include_paths));+    std::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)+  {+    std::vector<std::string> vec(list2vec(opt->include_paths));+    std::string resolved(File::find_file(file, vec));     return sass_copy_c_string(resolved.c_str());   } 
libsass/src/sass.hpp view
@@ -90,10 +90,13 @@   // Precision for fractional numbers   int precision; +  // Do not compress colors in selectors+  bool in_selector;+   // initialization list (constructor with defaults)   Sass_Inspect_Options(Sass_Output_Style style = Sass::NESTED,-                       int precision = 5)-  : output_style(style), precision(precision)+                       int precision = 5, bool in_selector = false)+  : output_style(style), precision(precision), in_selector(in_selector)   { }  };
libsass/src/sass_context.cpp view
@@ -35,7 +35,6 @@     catch (Exception::Base& e) {       std::stringstream msg_stream;       std::string cwd(Sass::File::get_cwd());-       std::string msg_prefix(e.errtype());       bool got_newline = false;       msg_stream << msg_prefix << ": ";@@ -55,41 +54,49 @@         ++msg;       }       if (!got_newline) msg_stream << "\n";-      if (e.import_stack) {-        for (size_t i = 1; i < e.import_stack->size() - 1; ++i) {-          std::string path((*e.import_stack)[i]->imp_path);-          std::string rel_path(Sass::File::abs2rel(path, cwd, cwd));-          msg_stream << std::string(msg_prefix.size() + 2, ' ');-          msg_stream << (i == 1 ? " on line " : " from line ");-          msg_stream << e.pstate.line + 1 << " of " << rel_path << "\n";-        }-      }-      else {++      if (e.traces.empty()) {+        // we normally should have some traces, still here as a fallback         std::string rel_path(Sass::File::abs2rel(e.pstate.path, cwd, cwd));         msg_stream << std::string(msg_prefix.size() + 2, ' ');         msg_stream << " on line " << e.pstate.line + 1 << " of " << rel_path << "\n";       }+      else {+        std::string rel_path(Sass::File::abs2rel(e.pstate.path, cwd, cwd));+        msg_stream << traces_to_string(e.traces, "        ");+      }        // now create the code trace (ToDo: maybe have util functions?)       if (e.pstate.line != std::string::npos && e.pstate.column != std::string::npos) {-        size_t line = e.pstate.line;+        size_t lines = e.pstate.line;         const char* line_beg = e.pstate.src;-        while (line_beg && *line_beg && line) {-          if (*line_beg == '\n') --line;-          ++line_beg;+        // scan through src until target line+        // move line_beg pointer to line start+        while (line_beg && *line_beg && lines != 0) {+          if (*line_beg == '\n') --lines;+          utf8::unchecked::next(line_beg);          }         const char* line_end = line_beg;+        // move line_end before next newline character         while (line_end && *line_end && *line_end != '\n') {           if (*line_end == '\n') break;           if (*line_end == '\r') break;-          line_end++;+          utf8::unchecked::next(line_end);          }-        size_t max_left = 42; size_t max_right = 78;-        size_t move_in = e.pstate.column > max_left ? e.pstate.column - max_left : 0;-        size_t shorten = (line_end - line_beg) - move_in > max_right ?-          (line_end - line_beg) - move_in - max_right : 0;-        msg_stream << ">> " << std::string(line_beg + move_in, line_end - shorten) << "\n";-        msg_stream << "   " << std::string(e.pstate.column - move_in, '-') << "^\n";+        if (line_end && *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 (e.pstate.column > line_len) left_chars = e.pstate.column;+        if (e.pstate.column > left_chars) move_in = e.pstate.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);+        std::string sanitized; std::string marker(e.pstate.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();@@ -199,7 +206,6 @@   static int handle_errors(Sass_Context* c_ctx) {     try { return handle_error(c_ctx); }     catch (...) { return handle_error(c_ctx); }-    return c_ctx->error_status;   }    static Block_Obj sass_parse_block(Sass_Compiler* compiler) throw()@@ -272,10 +278,14 @@   #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_ACCESSOR(type, option, def) \-    type ADDCALL sass_option_get_##option (struct Sass_Options* options) { return safe_str(options->option, def); } \+  #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; }@@ -327,7 +337,9 @@       c_ctx->error_column = std::string::npos;        // allocate a new compiler instance-      Sass_Compiler* compiler = (struct Sass_Compiler*) calloc(1, sizeof(struct Sass_Compiler));+      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@@ -468,7 +480,7 @@     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);+    compiler->root = sass_parse_block(compiler);     // success     return 0;   }@@ -518,30 +530,10 @@   static void sass_clear_options (struct Sass_Options* options)   {     if (options == 0) return;-    // Deallocate custom functions-    if (options->c_functions) {-      Sass_Function_List this_func_data = options->c_functions;-      while (this_func_data && *this_func_data) {-        free(*this_func_data);-        ++this_func_data;-      }-    }-    // Deallocate custom headers-    if (options->c_headers) {-      Sass_Importer_List this_head_data = options->c_headers;-      while (this_head_data && *this_head_data) {-        free(*this_head_data);-        ++this_head_data;-      }-    }-    // Deallocate custom importers-    if (options->c_importers) {-      Sass_Importer_List this_imp_data = options->c_importers;-      while (this_imp_data && *this_imp_data) {-        free(*this_imp_data);-        ++this_imp_data;-      }-    }+    // Deallocate custom functions, headers and importes+    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;@@ -573,11 +565,6 @@     free(options->include_path);     free(options->source_map_file);     free(options->source_map_root);-    // Free custom functions-    free(options->c_functions);-    // Free custom importers-    free(options->c_importers);-    free(options->c_headers);     // Reset our pointers     options->input_path = 0;     options->output_path = 0;@@ -674,6 +661,10 @@   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)@@ -693,10 +684,10 @@   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*, plugin_path, 0);-  IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(const char*, include_path, 0);   IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(const char*, source_map_file, 0);   IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(const char*, source_map_root, 0); @@ -738,6 +729,23 @@       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)
libsass/src/sass_context.hpp view
@@ -1,9 +1,8 @@ #ifndef SASS_SASS_CONTEXT_H #define SASS_SASS_CONTEXT_H -#include "sass.h"-#include "sass.hpp"-#include "context.hpp"+#include "sass/base.h"+#include "sass/context.h" #include "ast_fwd_decl.hpp"  // sass config options structure@@ -33,7 +32,7 @@   char* input_path;    // The output path is used for source map-  // generation. Libsass will not write to+  // generation. LibSass will not write to   // this file, it is just used to create   // information in source-maps etc.   char* output_path;
libsass/src/sass_functions.cpp view
@@ -2,6 +2,7 @@ #include <cstring> #include "util.hpp" #include "context.hpp"+#include "values.hpp" #include "sass/functions.h" #include "sass_functions.hpp" @@ -17,12 +18,30 @@   {     Sass_Function_Entry cb = (Sass_Function_Entry) calloc(1, sizeof(Sass_Function));     if (cb == 0) return 0;-    cb->signature = signature;+    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; }@@ -57,6 +76,18 @@     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; } @@ -124,6 +155,37 @@     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_Ptr ex = Cast<Expression>((*env->frame)[name]);+    return ex != NULL ? ast_node_to_sass_value(ex) : NULL;+  }+  void ADDCALL sass_env_set_lexical (Sass_Env_Frame env, const char* name, union Sass_Value* val) {+    (*env->frame)[name] = sass_value_to_ast_node(val);+  }+  union Sass_Value* ADDCALL sass_env_get_local (Sass_Env_Frame env, const char* name) {+    Expression_Ptr ex = Cast<Expression>(env->frame->get_local(name));+    return ex != NULL ? ast_node_to_sass_value(ex) : NULL;+  }+  void ADDCALL sass_env_set_local (Sass_Env_Frame env, const char* name, union Sass_Value* val) {+    env->frame->set_local(name, sass_value_to_ast_node(val));+  }+  union Sass_Value* ADDCALL sass_env_get_global (Sass_Env_Frame env, const char* name) {+    Expression_Ptr ex = Cast<Expression>(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
libsass/src/sass_functions.hpp view
@@ -2,10 +2,12 @@ #define SASS_SASS_FUNCTIONS_H  #include "sass.h"+#include "environment.hpp"+#include "functions.hpp"  // Struct to hold custom function callback struct Sass_Function {-  const char*      signature;+  char*            signature;   Sass_Function_Fn function;   void*            cookie; };@@ -20,6 +22,22 @@   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
libsass/src/sass_util.cpp view
@@ -37,7 +37,7 @@       end     end   */-  Node paths(const Node& arrs, Context& ctx) {+  Node paths(const Node& arrs) {      Node loopStart = Node::createCollection();     loopStart.collection()->push_back(Node::createCollection());@@ -108,7 +108,7 @@     return flattened   end   */-  Node flatten(Node& arr, Context& ctx, int n) {+  Node flatten(Node& arr, int n) {     if (n != -1 && n == 0) {       return arr;     }@@ -124,7 +124,7 @@       if (e.isCollection()) {        	// e.collection().got_line_feed = e.got_line_feed;-      	Node recurseFlattened = flatten(e, ctx, n - 1);+      	Node recurseFlattened = flatten(e, n - 1);        	if(e.got_line_feed) {       		 flattened.got_line_feed = e.got_line_feed;
libsass/src/sass_util.hpp view
@@ -28,7 +28,7 @@     #     #  [1, 4, 5],     #     #  [2, 4, 5]]   */-  Node paths(const Node& arrs, Context& ctx);+  Node paths(const Node& arrs);     /*@@ -40,7 +40,7 @@     bool operator()(const Node& one, const Node& two, Node& out) const {       // TODO: Is this the correct C++ interpretation?       // block ||= proc {|a, b| a == b && a}-      if (nodesEqual(one, two, true)) {+      if (one == two) {         out = one;         return true;       }@@ -139,7 +139,7 @@   http://en.wikipedia.org/wiki/Longest_common_subsequence_problem   */   template<typename ComparatorType>-  Node lcs(Node& x, Node& y, const ComparatorType& comparator, Context& ctx) {+  Node lcs(Node& x, Node& y, const ComparatorType& comparator) {     DEBUG_PRINTLN(LCS, "LCS: X=" << x << " Y=" << y)      Node newX = Node::createCollection();@@ -169,7 +169,7 @@   # @param n [int] The number of levels to flatten   # @return [NodeCollection] The flattened array   */-  Node flatten(Node& arr, Context& ctx, int n = -1);+  Node flatten(Node& arr, int n = -1);     /*
libsass/src/sass_values.cpp view
@@ -4,6 +4,7 @@ #include "util.hpp" #include "eval.hpp" #include "values.hpp"+#include "operators.hpp" #include "sass/values.h" #include "sass_values.hpp" @@ -54,6 +55,8 @@   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; }@@ -130,13 +133,14 @@     return v;   } -  union Sass_Value* ADDCALL sass_make_list(size_t len, enum Sass_Separator sep)+  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;@@ -218,6 +222,7 @@         case SASS_WARNING: {                 free(val->error.message);         }   break;+        default: break;     }      free(val);@@ -233,26 +238,26 @@     switch(val->unknown.tag) {         case SASS_NULL: {                 return sass_make_null();-        }   break;+        }         case SASS_BOOLEAN: {                 return sass_make_boolean(val->boolean.value);-        }   break;+        }         case SASS_NUMBER: {                 return sass_make_number(val->number.value, val->number.unit);-        }   break;+        }         case SASS_COLOR: {                 return sass_make_color(val->color.r, val->color.g, val->color.b, val->color.a);-        }   break;+        }         case SASS_STRING: {                 return sass_string_is_quoted(val) ? sass_make_qstring(val->string.value) : sass_make_string(val->string.value);-        }   break;+        }         case SASS_LIST: {-                union Sass_Value* list = sass_make_list(val->list.length, val->list.separator);+                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;-        }   break;+        }         case SASS_MAP: {                 union Sass_Value* map = sass_make_map(val->map.length);                 for (i = 0; i < val->map.length; i++) {@@ -260,13 +265,14 @@                     map->map.pairs[i].value = sass_clone_value(val->map.pairs[i].value);                 }                 return map;-        }   break;+        }         case SASS_ERROR: {                 return sass_make_error(val->error.message);-        }   break;+        }         case SASS_WARNING: {                 return sass_make_warning(val->warning.message);-        }   break;+        }+        default: break;     }      return 0;@@ -284,7 +290,7 @@   union Sass_Value* ADDCALL sass_value_op (enum Sass_OP op, const union Sass_Value* a, const union Sass_Value* b)   { -    Sass::Value_Ptr rv = 0;+    Sass::Value_Ptr rv;      try { @@ -294,41 +300,41 @@        // see if it's a relational expression       switch(op) {-        case Sass_OP::EQ:  return sass_make_boolean(Eval::eq(&lhs, &rhs));-        case Sass_OP::NEQ: return sass_make_boolean(!Eval::eq(&lhs, &rhs));-        case Sass_OP::GT:  return sass_make_boolean(!Eval::lt(&lhs, &rhs, "gt") && !Eval::eq(&lhs, &rhs));-        case Sass_OP::GTE: return sass_make_boolean(!Eval::lt(&lhs, &rhs, "gte"));-        case Sass_OP::LT:  return sass_make_boolean(Eval::lt(&lhs, &rhs, "lt"));-        case Sass_OP::LTE: return sass_make_boolean(Eval::lt(&lhs, &rhs, "lte") || Eval::eq(&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;+        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)) {-        Number_Ptr_Const l_n = SASS_MEMORY_CAST(Number, lhs);-        Number_Ptr_Const r_n = SASS_MEMORY_CAST(Number, rhs);-        rv = Eval::op_numbers(op, *l_n, *r_n, options);+        Number_Ptr_Const l_n = Cast<Number>(lhs);+        Number_Ptr_Const r_n = Cast<Number>(rhs);+        rv = Operators::op_numbers(op, *l_n, *r_n, options, l_n->pstate());       }       else if (sass_value_is_number(a) && sass_value_is_color(a)) {-        Number_Ptr_Const l_n = SASS_MEMORY_CAST(Number, lhs);-        Color_Ptr_Const r_c = SASS_MEMORY_CAST(Color, rhs);-        rv = Eval::op_number_color(op, *l_n, *r_c, options);+        Number_Ptr_Const l_n = Cast<Number>(lhs);+        Color_Ptr_Const r_c = Cast<Color>(rhs);+        rv = Operators::op_number_color(op, *l_n, *r_c, options, l_n->pstate());       }       else if (sass_value_is_color(a) && sass_value_is_number(b)) {-        Color_Ptr_Const l_c = SASS_MEMORY_CAST(Color, lhs);-        Number_Ptr_Const r_n = SASS_MEMORY_CAST(Number, rhs);-        rv = Eval::op_color_number(op, *l_c, *r_n, options);+        Color_Ptr_Const l_c = Cast<Color>(lhs);+        Number_Ptr_Const r_n = Cast<Number>(rhs);+        rv = Operators::op_color_number(op, *l_c, *r_n, options, l_c->pstate());       }       else if (sass_value_is_color(a) && sass_value_is_color(b)) {-        Color_Ptr_Const l_c = SASS_MEMORY_CAST(Color, lhs);-        Color_Ptr_Const r_c = SASS_MEMORY_CAST(Color, rhs);-        rv = Eval::op_colors(op, *l_c, *r_c, options);+        Color_Ptr_Const l_c = Cast<Color>(lhs);+        Color_Ptr_Const r_c = Cast<Color>(rhs);+        rv = Operators::op_colors(op, *l_c, *r_c, options, l_c->pstate());       }       else /* convert other stuff to string and apply operation */ {-        Value_Ptr l_v = SASS_MEMORY_CAST(Value, lhs);-        Value_Ptr r_v = SASS_MEMORY_CAST(Value, rhs);-        rv = Eval::op_strings(op, *l_v, *r_v, options);+        Value_Ptr l_v = Cast<Value>(lhs);+        Value_Ptr r_v = Cast<Value>(rhs);+        rv = Operators::op_strings(op, *l_v, *r_v, options, l_v->pstate());       }        // ToDo: maybe we should should return null value?@@ -346,9 +352,6 @@     catch (std::string& e) { return sass_make_error(e.c_str()); }     catch (const char* e) { return sass_make_error(e); }     catch (...) { return sass_make_error("unknown"); }--    return 0;-   }  }
libsass/src/sass_values.hpp view
@@ -35,6 +35,7 @@ struct Sass_List {   enum Sass_Tag       tag;   enum Sass_Separator separator;+  bool                is_bracketed;   size_t              length;   // null terminated "array"   union Sass_Value**  values;
libsass/src/source_map.cpp view
@@ -2,7 +2,6 @@ #include <string> #include <sstream> #include <iostream>-#include <cstddef> #include <iomanip>  #include "ast.hpp"@@ -25,9 +24,9 @@      json_append_member(json_srcmap, "version", json_mknumber(3)); -    const char *include = file.c_str();-    JsonNode *json_include = json_mkstring(include);-    json_append_member(json_srcmap, "file", json_include);+    const char *file_name = file.c_str();+    JsonNode *json_file_name = json_mkstring(file_name);+    json_append_member(json_srcmap, "file", json_file_name);      // pass-through sourceRoot option     if (!ctx.source_map_root.empty()) {@@ -35,35 +34,34 @@       json_append_member(json_srcmap, "sourceRoot", root);     } -    JsonNode *json_includes = json_mkarray();+    JsonNode *json_sources = json_mkarray();     for (size_t i = 0; i < source_index.size(); ++i) {-      std::string include(links[source_index[i]]);+      std::string source(links[source_index[i]]);       if (ctx.c_options.source_map_file_urls) {-        include = File::rel2abs(include);+        source = File::rel2abs(source);         // check for windows abs path-        if (include[0] == '/') {+        if (source[0] == '/') {           // ends up with three slashes-          include = "file://" + include;+          source = "file://" + source;         } else {           // needs an additional slash-          include = "file:///" + include;+          source = "file:///" + source;         }       }-      const char* inc = include.c_str();-      JsonNode *json_include = json_mkstring(inc);-      json_append_element(json_includes, json_include);+      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_includes);+    json_append_member(json_srcmap, "sources", json_sources); -    if (include_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);       }-      if (json_contents->children.head)-        json_append_member(json_srcmap, "sourcesContent", json_contents);+      json_append_member(json_srcmap, "sourcesContent", json_contents);     }      JsonNode *json_names = json_mkarray();@@ -138,7 +136,7 @@         }       }     }-    // will adjust the offset+    // adjust the buffer offset     prepend(Offset(out.buffer));     // now add the new mappings     VECTOR_UNSHIFT(mappings, out.smap.mappings);
libsass/src/subset_map.cpp view
@@ -4,22 +4,20 @@  namespace Sass { -  void Subset_Map::put(const Compound_Selector_Obj& sel, const Subset_Map_Val& value)+  void Subset_Map::put(const Compound_Selector_Obj& sel, const SubSetMapPair& value)   {     if (sel->empty()) throw std::runtime_error("internal error: subset map keys may not be empty");     size_t index = values_.size();     values_.push_back(value);     for (size_t i = 0, S = sel->length(); i < S; ++i)     {-      hash_[(*sel)[i]].push_back(std::make_pair(&sel, index));+      hash_[(*sel)[i]].push_back(std::make_pair(sel, index));     }   } -  std::vector<Subset_Map_Val> Subset_Map::get_kv(const Compound_Selector_Obj& sel)+  std::vector<SubSetMapPair> Subset_Map::get_kv(const Compound_Selector_Obj& sel)   {-    // std::vector<Subset_Map_Key> s = sel->to_str_vec();-    // std::set<std::string> dict(s.begin(), s.end());-    std::unordered_set<Simple_Selector_Obj, HashSimpleSelector, CompareSimpleSelector> dict(sel->begin(), sel->end());+    SimpleSelectorDict dict(sel->begin(), sel->end()); // XXX Set     std::vector<size_t> indices;     for (size_t i = 0, S = sel->length(); i < S; ++i) {       if (!hash_.count((*sel)[i])) {@@ -42,14 +40,14 @@     std::vector<size_t>::iterator indices_end = unique(indices.begin(), indices.end());     indices.resize(distance(indices.begin(), indices_end)); -    std::vector<Subset_Map_Val> results;+    std::vector<SubSetMapPair> results;     for (size_t i = 0, S = indices.size(); i < S; ++i) {       results.push_back(values_[indices[i]]);     }     return results;   } -  std::vector<Subset_Map_Val> Subset_Map::get_v(const Compound_Selector_Obj& sel)+  std::vector<SubSetMapPair> Subset_Map::get_v(const Compound_Selector_Obj& sel)   {     return get_kv(sel);   }
libsass/src/subset_map.hpp view
@@ -60,15 +60,15 @@    class Subset_Map {   private:-    std::vector<Subset_Map_Val> values_;-    std::map<Simple_Selector_Obj, std::vector<std::pair<Compound_Selector_Obj, size_t> > > hash_;+    std::vector<SubSetMapPair> values_;+    std::map<Simple_Selector_Obj, std::vector<std::pair<Compound_Selector_Obj, size_t> >, OrderNodes > hash_;   public:-    void put(const Compound_Selector_Obj& sel, const Subset_Map_Val& value);-    std::vector<Subset_Map_Val> get_kv(const Compound_Selector_Obj& s);-    std::vector<Subset_Map_Val> get_v(const Compound_Selector_Obj& s);+    void put(const Compound_Selector_Obj& sel, const SubSetMapPair& value);+    std::vector<SubSetMapPair> get_kv(const Compound_Selector_Obj& s);+    std::vector<SubSetMapPair> get_v(const Compound_Selector_Obj& s);     bool empty() { return values_.empty(); }     void clear() { values_.clear(); hash_.clear(); }-    const std::vector<Subset_Map_Val> values(void) { return values_; }+    const std::vector<SubSetMapPair> values(void) { return values_; }   };  }
libsass/src/to_c.cpp view
@@ -36,7 +36,7 @@    union Sass_Value* To_C::operator()(List_Ptr l)   {-    union Sass_Value* v = sass_make_list(l->length(), l->separator());+    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));     }@@ -57,7 +57,7 @@    union Sass_Value* To_C::operator()(Arguments_Ptr a)   {-    union Sass_Value* v = sass_make_list(a->length(), SASS_COMMA);+    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));     }
libsass/src/to_value.cpp view
@@ -9,8 +9,6 @@     // throw a runtime error if this happens     // we want a well defined set of possible nodes     throw std::runtime_error("invalid node for to_value");-    // mute warning-    return 0;   }    // Custom_Error is a valid value@@ -62,7 +60,8 @@                                l->pstate(),                                l->length(),                                l->separator(),-                               l->is_arglist());+                               l->is_arglist(),+                               l->is_bracketed());     for (size_t i = 0, L = l->length(); i < L; ++i) {       ll->append((*l)[i]->perform(this));     }@@ -77,6 +76,12 @@    // Null is a valid value   Value_Ptr To_Value::operator()(Null_Ptr n)+  {+    return n;+  }++  // Function is a valid value+  Value_Ptr To_Value::operator()(Function_Ptr n)   {     return n;   }
libsass/src/to_value.hpp view
@@ -34,6 +34,7 @@     Value_Ptr operator()(List_Ptr);     Value_Ptr operator()(Map_Ptr);     Value_Ptr operator()(Null_Ptr);+    Value_Ptr operator()(Function_Ptr);      // convert to string via `To_String`     Value_Ptr operator()(Selector_List_Ptr);
libsass/src/units.cpp view
@@ -1,6 +1,7 @@ #include "sass.hpp" #include <stdexcept> #include "units.hpp"+#include "error_handling.hpp"  namespace Sass { @@ -53,12 +54,12 @@   {     switch (unit & 0xFF00)     {-      case UnitClass::LENGTH:      return UnitClass::LENGTH; break;-      case UnitClass::ANGLE:       return UnitClass::ANGLE; break;-      case UnitClass::TIME:        return UnitClass::TIME; break;-      case UnitClass::FREQUENCY:   return UnitClass::FREQUENCY; break;-      case UnitClass::RESOLUTION:  return UnitClass::RESOLUTION; break;-      default:                     return UnitClass::INCOMMENSURABLE; break;+      case UnitClass::LENGTH:      return UnitClass::LENGTH;+      case UnitClass::ANGLE:       return UnitClass::ANGLE;+      case UnitClass::TIME:        return UnitClass::TIME;+      case UnitClass::FREQUENCY:   return UnitClass::FREQUENCY;+      case UnitClass::RESOLUTION:  return UnitClass::RESOLUTION;+      default:                     return UnitClass::INCOMMENSURABLE;     }   }; @@ -66,15 +67,28 @@   {     switch (unit & 0xFF00)     {-      case UnitClass::LENGTH:      return "LENGTH"; break;-      case UnitClass::ANGLE:       return "ANGLE"; break;-      case UnitClass::TIME:        return "TIME"; break;-      case UnitClass::FREQUENCY:   return "FREQUENCY"; break;-      case UnitClass::RESOLUTION:  return "RESOLUTION"; break;-      default:                     return "INCOMMENSURABLE"; break;+      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)+  {+    switch (unit)+    {+      case UnitClass::LENGTH:      return UnitType::PX;+      case UnitClass::ANGLE:       return UnitType::DEG;+      case UnitClass::TIME:        return UnitType::SEC;+      case UnitClass::FREQUENCY:   return UnitType::HERTZ;+      case UnitClass::RESOLUTION:  return UnitType::DPI;+      default:                     return UnitType::UNKNOWN;+    }+  };+   UnitType string_to_unit(const std::string& s)   {     // size units@@ -107,29 +121,29 @@   {     switch (unit) {       // size units-      case UnitType::PX:      return "px"; break;-      case UnitType::PT:      return "pt"; break;-      case UnitType::PC:      return "pc"; break;-      case UnitType::MM:      return "mm"; break;-      case UnitType::CM:      return "cm"; break;-      case UnitType::IN:      return "in"; break;+      case UnitType::PX:      return "px";+      case UnitType::PT:      return "pt";+      case UnitType::PC:      return "pc";+      case UnitType::MM:      return "mm";+      case UnitType::CM:      return "cm";+      case UnitType::IN:      return "in";       // angle units-      case UnitType::DEG:     return "deg"; break;-      case UnitType::GRAD:    return "grad"; break;-      case UnitType::RAD:     return "rad"; break;-      case UnitType::TURN:    return "turn"; break;+      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"; break;-      case UnitType::MSEC:    return "ms"; break;+      case UnitType::SEC:     return "s";+      case UnitType::MSEC:    return "ms";       // frequency units-      case UnitType::HERTZ:   return "Hz"; break;-      case UnitType::KHERTZ:  return "kHz"; break;+      case UnitType::HERTZ:   return "Hz";+      case UnitType::KHERTZ:  return "kHz";       // resolutions units-      case UnitType::DPI:     return "dpi"; break;-      case UnitType::DPCM:    return "dpcm"; break;-      case UnitType::DPPX:    return "dppx"; break;+      case UnitType::DPI:     return "dpi";+      case UnitType::DPCM:    return "dpcm";+      case UnitType::DPPX:    return "dppx";       // for unknown units-      default:                return ""; break;+      default:                return "";     }   } @@ -161,7 +175,7 @@   }    // throws incompatibleUnits exceptions-  double conversion_factor(const std::string& s1, const std::string& s2, bool strict)+  double conversion_factor(const std::string& s1, const std::string& s2)   {     // assert for same units     if (s1 == s2) return 1;@@ -171,27 +185,317 @@     // query unit group types     UnitClass t1 = get_unit_type(u1);     UnitClass t2 = get_unit_type(u2);+    // return the conversion factor+    return conversion_factor(u1, u2, t1, t2);+  }++  // throws incompatibleUnits exceptions+  double conversion_factor(UnitType u1, UnitType u2, UnitClass t1, UnitClass t2)+  {+    // can't convert between groups+    if (t1 != t2) return 0;     // get absolute offset     // used for array acces     size_t i1 = u1 - t1;     size_t i2 = u2 - t2;-    // error if units are not of the same group-    // don't error for multiplication and division-    if (strict && t1 != t2) throw incompatibleUnits(u1, u2);-    // only process known units-    if (u1 != UNKNOWN && u2 != UNKNOWN) {-      switch (t1) {-        case UnitClass::LENGTH:            return size_conversion_factors[i1][i2]; break;-        case UnitClass::ANGLE:             return angle_conversion_factors[i1][i2]; break;-        case UnitClass::TIME:              return time_conversion_factors[i1][i2]; break;-        case UnitClass::FREQUENCY:         return frequency_conversion_factors[i1][i2]; break;-        case UnitClass::RESOLUTION:        return resolution_conversion_factors[i1][i2]; break;-        // ToDo: should we throw error here?-        case UnitClass::INCOMMENSURABLE:   return 0; break;-      }+    // process known units+    switch (t1) {+      case LENGTH:+        return size_conversion_factors[i1][i2];+      case ANGLE:+        return angle_conversion_factors[i1][i2];+      case TIME:+        return time_conversion_factors[i1][i2];+      case FREQUENCY:+        return frequency_conversion_factors[i1][i2];+      case RESOLUTION:+        return resolution_conversion_factors[i1][i2];+      case INCOMMENSURABLE:+        return 0;     }     // fallback     return 0;+  }++  double convert_units(const std::string& lhs, const std::string& rhs, int& lhsexp, int& rhsexp)+  {+    double f = 0;+    // do not convert same ones+    if (lhs == rhs) return 0;+    // skip already canceled out unit+    if (lhsexp == 0) return 0;+    if (rhsexp == 0) return 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;+    // query unit group types+    UnitClass clhs = get_unit_type(ulhs);+    UnitClass crhs = get_unit_type(urhs);+    // skip units we cannot convert+    if (clhs != crhs) return 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+      f = std::pow(f, lhsexp);+      rhsexp += lhsexp;+      lhsexp = 0;+    }+    else {+      // get the conversion factor for units+      f = conversion_factor(ulhs, urhs, clhs, crhs);+      // right hand side has been consumned+      f = std::pow(f, rhsexp);+      lhsexp += rhsexp;+      rhsexp = 0;+    }+    return f;+  }++  bool Units::operator< (const Units& rhs) const+  {+    return (numerators < rhs.numerators) &&+           (denominators < rhs.denominators);+  }+  bool Units::operator== (const Units& rhs) const+  {+    return (numerators == rhs.numerators) &&+           (denominators == rhs.denominators);+  }++  double Units::normalize()+  {++    size_t iL = numerators.size();+    size_t nL = denominators.size();++    // the final conversion factor+    double factor = 1;++    for (size_t i = 0; i < iL; i++) {+      std::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);+      if (ulhs == umain) continue;+      double f(conversion_factor(umain, ulhs, clhs, clhs));+      if (f == 0) throw std::runtime_error("INVALID");+      numerators[i] = unit_to_string(umain);+      factor /= f;+    }++    for (size_t n = 0; n < nL; n++) {+      std::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);+      if (urhs == umain) continue;+      double f(conversion_factor(umain, urhs, crhs, crhs));+      if (f == 0) throw std::runtime_error("INVALID");+      denominators[n] = unit_to_string(umain);+      factor /= f;+    }++    std::sort (numerators.begin(), numerators.end());+    std::sort (denominators.begin(), denominators.end());++    // return for conversion+    return factor;+  }++  double Units::reduce()+  {++    size_t iL = numerators.size();+    size_t nL = denominators.size();++    // have less than two units?+    if (iL + nL < 2) return 1;++    // 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<std::string, int> exponents;++    // initialize by summing up occurences in unit vectors+    // this will already cancel out equivalent units (e.q. px/px)+    for (size_t i = 0; i < iL; i ++) exponents[numerators[i]] += 1;+    for (size_t n = 0; n < nL; n ++) exponents[denominators[n]] -= 1;++    // the final conversion factor+    double factor = 1;++    // convert between compatible units+    for (size_t i = 0; i < iL; i++) {+      for (size_t n = 0; n < nL; n++) {+        std::string &lhs = numerators[i], &rhs = denominators[n];+        int &lhsexp = exponents[lhs], &rhsexp = exponents[rhs];+        double f(convert_units(lhs, rhs, lhsexp, rhsexp));+        if (f == 0) continue;+        factor /= f;+      }+    }++    // now we can build up the new unit arrays+    numerators.clear();+    denominators.clear();++    // recreate sorted units vectors+    for (auto exp : exponents) {+      int &exponent = exp.second;+      while (exponent > 0 && exponent --)+        numerators.push_back(exp.first);+      while (exponent < 0 && exponent ++)+        denominators.push_back(exp.first);+    }++    // return for conversion+    return factor;++  }++  std::string Units::unit() const+  {+    std::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];+    }+    if (nL != 0) u += '/';+    for (size_t n = 0; n < nL; n += 1) {+      if (n) u += '*';+      u += denominators[n];+    }+    return u;+  }++  bool Units::is_unitless() const+  {+    return numerators.empty() &&+           denominators.empty();+  }++  bool Units::is_valid_css_unit() const+  {+    return numerators.size() <= 1 &&+           denominators.size() == 0;+  }++  // this does not cover all cases (multiple prefered units)+  double Units::convert_factor(const Units& r) const+  {++    std::vector<std::string> miss_nums(0);+    std::vector<std::string> miss_dens(0);+    // create copy since we need these for state keeping+    std::vector<std::string> r_nums(r.numerators);+    std::vector<std::string> r_dens(r.denominators);++    auto l_num_it = numerators.begin();+    auto l_num_end = numerators.end();++    bool l_unitless = is_unitless();+    auto r_unitless = r.is_unitless();++    // overall conversion+    double factor = 1;++    // process all left numerators+    while (l_num_it != l_num_end)+    {+      // get and increment afterwards+      const std::string l_num = *(l_num_it ++);++      auto r_num_it = r_nums.begin(), r_num_end = r_nums.end();++      bool found = false;+      // search for compatible numerator+      while (r_num_it != r_num_end)+      {+        // get and increment afterwards+        const std::string r_num = *(r_num_it);+        // get possible conversion factor for units+        double conversion = conversion_factor(l_num, r_num);+        // skip incompatible numerator+        if (conversion == 0) {+          ++ r_num_it;+          continue;+        }+        // apply to global factor+        factor *= conversion;+        // remove item from vector+        r_nums.erase(r_num_it);+        // found numerator+        found = true;+        break;+      }+      // maybe we did not find any+      // left numerator is leftover+      if (!found) miss_nums.push_back(l_num);+    }++    auto l_den_it = denominators.begin();+    auto l_den_end = denominators.end();++    // process all left denominators+    while (l_den_it != l_den_end)+    {+      // get and increment afterwards+      const std::string l_den = *(l_den_it ++);++      auto r_den_it = r_dens.begin();+      auto r_den_end = r_dens.end();++      bool found = false;+      // search for compatible denominator+      while (r_den_it != r_den_end)+      {+        // get and increment afterwards+        const std::string r_den = *(r_den_it);+        // get possible converstion factor for units+        double conversion = conversion_factor(l_den, r_den);+        // skip incompatible denominator+        if (conversion == 0) {+          ++ r_den_it;+          continue;+        }+        // apply to global factor+        factor /= conversion;+        // remove item from vector+        r_dens.erase(r_den_it);+        // found denominator+        found = true;+        break;+      }+      // maybe we did not find any+      // left denominator is leftover+      if (!found) miss_dens.push_back(l_den);+    }++    // check left-overs (ToDo: might cancel out?)+    if (miss_nums.size() > 0 && !r_unitless) {+      throw Exception::IncompatibleUnits(r, *this);+    }+    else if (miss_dens.size() > 0 && !r_unitless) {+      throw Exception::IncompatibleUnits(r, *this);+    }+    else if (r_nums.size() > 0 && !l_unitless) {+      throw Exception::IncompatibleUnits(r, *this);+    }+    else if (r_dens.size() > 0 && !l_unitless) {+      throw Exception::IncompatibleUnits(r, *this);+    }++    return factor;   }  }
libsass/src/units.hpp view
@@ -4,6 +4,7 @@ #include <cmath> #include <string> #include <sstream>+#include <vector>  namespace Sass { @@ -52,40 +53,56 @@    }; +  class Units {+  public:+    std::vector<std::string> numerators;+    std::vector<std::string> denominators;+  public:+    // default constructor+    Units() :+      numerators(),+      denominators()+    { }+    // copy constructor+    Units(const Units* ptr) :+      numerators(ptr->numerators),+      denominators(ptr->denominators)+    { }+    // convert to string+    std::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+    double reduce();+    // normalize units for compare+    // returns conversion factor+    double normalize();+    // compare operations+    bool operator< (const Units& rhs) const;+    bool operator== (const Units& rhs) const;+    // factor to convert into given units+    double convert_factor(const Units&) const;+  };+   extern const double size_conversion_factors[6][6];   extern const double angle_conversion_factors[4][4];   extern const double time_conversion_factors[2][2];   extern const double frequency_conversion_factors[2][2];   extern const double resolution_conversion_factors[3][3]; +  UnitType get_main_unit(const UnitClass unit);   enum Sass::UnitType string_to_unit(const std::string&);   const char* unit_to_string(Sass::UnitType unit);   enum Sass::UnitClass get_unit_type(Sass::UnitType unit);   std::string get_unit_class(Sass::UnitType unit);   std::string unit_to_class(const std::string&);   // throws incompatibleUnits exceptions-  double conversion_factor(const std::string&, const std::string&, bool = true);--  class incompatibleUnits: public std::exception-  {-    public:-      const char* msg;-      incompatibleUnits(Sass::UnitType a, Sass::UnitType b)-      : exception()-      {-        std::stringstream ss;-        ss << "Incompatible units: ";-        ss << "'" << unit_to_string(a) << "' and ";-        ss << "'" << unit_to_string(b) << "'";-        // hold on to string on stack!-        std::string str(ss.str());-        msg = str.c_str();-      }-      virtual const char* what() const throw()-      {-        return msg;-      }-  };+  double conversion_factor(const std::string&, const std::string&);+  double conversion_factor(UnitType, UnitType, UnitClass, UnitClass);+  double convert_units(const std::string&, const std::string&, int&, int&);  } 
libsass/src/utf8/checked.h view
@@ -193,6 +193,13 @@             utf8::next(it, end);     } +    template <typename octet_iterator, typename distance_type>+    void retreat (octet_iterator& it, distance_type n, octet_iterator start)+    {+        for (distance_type i = 0; i < n; ++i)+            utf8::prior(it, start);+    }+     template <typename octet_iterator>     typename std::iterator_traits<octet_iterator>::difference_type     distance (octet_iterator first, octet_iterator last)
libsass/src/utf8/unchecked.h view
@@ -116,6 +116,13 @@                 utf8::unchecked::next(it);         } +        template <typename octet_iterator, typename distance_type>+        void retreat (octet_iterator& it, distance_type n)+        {+            for (distance_type i = 0; i < n; ++i)+                utf8::unchecked::prior(it);+        }+         template <typename octet_iterator>         typename std::iterator_traits<octet_iterator>::difference_type         distance (octet_iterator first, octet_iterator last)
libsass/src/utf8_string.cpp view
@@ -28,7 +28,7 @@     size_t offset_at_position(const string& str, size_t position) {       string::const_iterator it = str.begin();       utf8::advance(it, position, str.end());-      return distance(str.begin(), it);+      return std::distance(str.begin(), it);     }      // function that returns number of bytes in a character at offset
libsass/src/util.cpp view
@@ -9,11 +9,22 @@  #include <cmath> #include <stdint.h>+#if defined(_MSC_VER) && _MSC_VER >= 1800 && _MSC_VER < 1900 && defined(_M_X64)+#include <mutex>+#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 (fmod(val, 1) - 0.5 > - std::pow(0.1, precision + 1)) return std::ceil(val);     else if (fmod(val, 1) - 0.5 > std::pow(0.1, precision)) return std::floor(val);@@ -24,7 +35,7 @@   }    /* Locale unspecific atof function. */-  double sass_atof(const char *str)+  double sass_strtod(const char *str)   {     char separator = *(localeconv()->decimal_point);     if(separator != '.'){@@ -37,13 +48,13 @@         // of the string. This is slower but it is thread safe.         char *copy = sass_copy_c_string(str);         *(copy + (found - str)) = separator;-        double res = atof(copy);+        double res = strtod(copy, NULL);         free(copy);         return res;       }     } -    return atof(str);+    return strtod(str, NULL);   }    // helper for safe access to c_ctx@@ -85,8 +96,9 @@   }    // read css string (handle multiline DELIM)-  std::string read_css_string(const std::string& str)+  std::string read_css_string(const std::string& str, bool css)   {+    if (!css) return str;     std::string out("");     bool esc = false;     for (auto i : str) {@@ -169,6 +181,23 @@     return out;   } +  std::string escape_string(const std::string& str)+  {+    std::string out("");+    for (auto i : str) {+      if (i == '\n') {+        out += "\\n";+      } else if (i == '\r') {+        out += "\\r";+      } else if (i == '\t') {+        out += "\\t";+      } else {+        out += i;+      }+    }+    return out;+  }+   std::string comment_to_string(const std::string& text)   {     std::string str = "";@@ -221,6 +250,75 @@     return quote_mark;   } +  std::string read_hex_escapes(const std::string& s)+  {++    std::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] && isxdigit(s[i + len])) ++ len;++        if (len > 1) {++          // convert the extracted hex string to code point value+          // ToDo: Maybe we could do this without creating a substring+          uint32_t cp = strtol(s.substr (i + 1, len - 1).c_str(), NULL, 16);++          if (s[i + len] == ' ') ++ len;++          // assert invalid code points+          if (cp == 0) cp = 0xFFFD;+          // replace bell character+          // if (cp == '\n') cp = 32;++          // use a very simple approach to convert via utf8 lib+          // maybe there is a more elegant way; maybe we shoud+          // convert the whole output from string to a stream!?+          // allocate memory for utf8 char and convert to utf8+          unsigned char u[5] = {0,0,0,0,0}; utf8::append(cp, u);+          for(size_t m = 0; m < 5 && u[m]; m++) result.push_back(u[m]);++          // skip some more chars?+          i += len - 1; skipped = false;++        }++        else {++          skipped = false;++          result.push_back(s[i]);++        }++      }++      else {++        result.push_back(s[i]);++      }++    }++    return result;++  }+   std::string unquote(const std::string& s, char* qd, bool keep_utf8_sequences, bool strict)   { @@ -281,7 +379,7 @@           // convert the whole output from string to a stream!?           // allocate memory for utf8 char and convert to utf8           unsigned char u[5] = {0,0,0,0,0}; utf8::append(cp, u);-          for(size_t m = 0; u[m] && m < 5; m++) unq.push_back(u[m]);+          for(size_t m = 0; m < 5 && u[m]; m++) unq.push_back(u[m]);            // skip some more chars?           i += len - 1; skipped = false;@@ -445,7 +543,7 @@        Block_Obj b = r->block(); -      Selector_List_Ptr sl = SASS_MEMORY_CAST(Selector_List, r->selector());+      Selector_List_Ptr sl = Cast<Selector_List>(r->selector());       bool hasSelectors = sl ? sl->length() > 0 : false;        if (!hasSelectors) {@@ -456,16 +554,16 @@       bool hasPrintableChildBlocks = false;       for (size_t i = 0, L = b->length(); i < L; ++i) {         Statement_Obj stm = b->at(i);-        if (dynamic_cast<Directive_Ptr>(&stm)) {+        if (Cast<Directive>(stm)) {           return true;-        } else if (Declaration_Ptr d = dynamic_cast<Declaration_Ptr>(&stm)) {+        } else if (Declaration_Ptr d = Cast<Declaration>(stm)) {           return isPrintable(d, style);-        } else if (dynamic_cast<Has_Block_Ptr>(&stm)) {-          Block_Obj pChildBlock = ((Has_Block_Ptr)&stm)->block();-          if (isPrintable(&pChildBlock, style)) {+        } else if (Has_Block_Ptr p = Cast<Has_Block>(stm)) {+          Block_Obj pChildBlock = p->block();+          if (isPrintable(pChildBlock, style)) {             hasPrintableChildBlocks = true;           }-        } else if (Comment_Ptr c = dynamic_cast<Comment_Ptr>(&stm)) {+        } else if (Comment_Ptr c = Cast<Comment>(stm)) {           // keep for uncompressed           if (style != COMPRESSED) {             hasDeclarations = true;@@ -499,8 +597,8 @@     bool isPrintable(Declaration_Ptr d, Sass_Output_Style style)     {       Expression_Obj val = d->value();-      if (String_Quoted_Obj sq = SASS_MEMORY_CAST(String_Quoted, val)) return isPrintable(&sq, style);-      if (String_Constant_Obj sc = SASS_MEMORY_CAST(String_Constant, val)) return isPrintable(&sc, style);+      if (String_Quoted_Obj sq = Cast<String_Quoted>(val)) return isPrintable(sq.ptr(), style);+      if (String_Constant_Obj sc = Cast<String_Constant>(val)) return isPrintable(sc.ptr(), style);       return true;     } @@ -511,19 +609,19 @@        Block_Obj b = f->block(); -//      bool hasSelectors = f->selector() && static_cast<Selector_List_Ptr>(f->selector())->length() > 0;-       bool hasDeclarations = false;       bool hasPrintableChildBlocks = false;       for (size_t i = 0, L = b->length(); i < L; ++i) {         Statement_Obj stm = b->at(i);-        if (dynamic_cast<Declaration_Ptr>(&stm) || dynamic_cast<Directive_Ptr>(&stm)) {+        if (Cast<Declaration>(stm) || Cast<Directive>(stm)) {           hasDeclarations = true;         }-        else if (dynamic_cast<Has_Block_Ptr>(&stm)) {-          Block_Obj pChildBlock = ((Has_Block_Ptr)&stm)->block();-          if (isPrintable(&pChildBlock, style)) {-            hasPrintableChildBlocks = true;+        else if (Has_Block_Ptr b = Cast<Has_Block>(stm)) {+          Block_Obj pChildBlock = b->block();+          if (!b->is_invisible()) {+            if (isPrintable(pChildBlock, style)) {+              hasPrintableChildBlocks = true;+            }           }         } @@ -542,34 +640,32 @@       if (b == 0) return false;       for (size_t i = 0, L = b->length(); i < L; ++i) {         Statement_Obj stm = b->at(i);-        if (dynamic_cast<Directive_Ptr>(&stm)) return true;-        else if (dynamic_cast<Declaration_Ptr>(&stm)) return true;-        else if (dynamic_cast<Comment_Ptr>(&stm)) {-          Comment_Ptr c = (Comment_Ptr) &stm;+        if (Cast<Directive>(stm)) return true;+        else if (Cast<Declaration>(stm)) return true;+        else if (Comment_Ptr c = Cast<Comment>(stm)) {           if (isPrintable(c, style)) {             return true;           }         }-        else if (dynamic_cast<Ruleset_Ptr>(&stm)) {-          Ruleset_Ptr r = (Ruleset_Ptr) &stm;+        else if (Ruleset_Ptr r = Cast<Ruleset>(stm)) {           if (isPrintable(r, style)) {             return true;           }         }-        else if (dynamic_cast<Supports_Block_Ptr>(&stm)) {-          Supports_Block_Ptr f = (Supports_Block_Ptr) &stm;+        else if (Supports_Block_Ptr f = Cast<Supports_Block>(stm)) {           if (isPrintable(f, style)) {             return true;           }         }-        else if (dynamic_cast<Media_Block_Ptr>(&stm)) {-          Media_Block_Ptr m = (Media_Block_Ptr) &stm;-          if (isPrintable(m, style)) {+        else if (Media_Block_Ptr mb = Cast<Media_Block>(stm)) {+          if (isPrintable(mb, style)) {             return true;           }         }-        else if (dynamic_cast<Has_Block_Ptr>(&stm) && isPrintable(((Has_Block_Ptr)&stm)->block(), style)) {-          return true;+        else if (Has_Block_Ptr b = Cast<Has_Block>(stm)) {+          if (isPrintable(b->block(), style)) {+            return true;+          }         }       }       return false;@@ -596,35 +692,33 @@        for (size_t i = 0, L = b->length(); i < L; ++i) {         Statement_Obj stm = b->at(i);-        if (dynamic_cast<Declaration_Ptr>(&stm) || dynamic_cast<Directive_Ptr>(&stm)) {+        if (Cast<Declaration>(stm) || Cast<Directive>(stm)) {           return true;         }-        else if (dynamic_cast<Comment_Ptr>(&stm)) {-          Comment_Ptr c = (Comment_Ptr) &stm;+        else if (Comment_Ptr c = Cast<Comment>(stm)) {           if (isPrintable(c, style)) {             return true;           }         }-        else if (dynamic_cast<Ruleset_Ptr>(&stm)) {-          Ruleset_Ptr r = (Ruleset_Ptr) &stm;+        else if (Ruleset_Ptr r = Cast<Ruleset>(stm)) {           if (isPrintable(r, style)) {             return true;           }         }-        else if (dynamic_cast<Supports_Block_Ptr>(&stm)) {-          Supports_Block_Ptr f = (Supports_Block_Ptr) &stm;+        else if (Supports_Block_Ptr f = Cast<Supports_Block>(stm)) {           if (isPrintable(f, style)) {             return true;           }         }-        else if (dynamic_cast<Media_Block_Ptr>(&stm)) {-          Media_Block_Ptr m = (Media_Block_Ptr) &stm;+        else if (Media_Block_Ptr m = Cast<Media_Block>(stm)) {           if (isPrintable(m, style)) {             return true;           }         }-        else if (dynamic_cast<Has_Block_Ptr>(&stm) && isPrintable(((Has_Block_Ptr)&stm)->block(), style)) {-          return true;+        else if (Has_Block_Ptr b = Cast<Has_Block>(stm)) {+          if (isPrintable(b->block(), style)) {+            return true;+          }         }       } 
libsass/src/util.hpp view
@@ -18,14 +18,16 @@     } while (0)    double round(double val, size_t precision = 0);-  double sass_atof(const char* str);+  double sass_strtod(const char* str);   const char* safe_str(const char *, const char* = "");   void free_string_array(char **);   char **copy_strings(const std::vector<std::string>&, char ***, int = 0);-  std::string read_css_string(const std::string& str);+  std::string read_css_string(const std::string& str, bool css = true);   std::string evacuate_escapes(const std::string& str);   std::string string_to_output(const std::string& str);   std::string comment_to_string(const std::string& text);+  std::string read_hex_escapes(const std::string& str);+  std::string escape_string(const std::string& str);   void newline_to_space(std::string& str);    std::string quote(const std::string&, char q = 0);
libsass/src/values.cpp view
@@ -11,32 +11,32 @@   {     if (val->concrete_type() == Expression::NUMBER)     {-      Number_Ptr_Const res = dynamic_cast<Number_Ptr_Const>(val);+      Number_Ptr_Const res = Cast<Number>(val);       return sass_make_number(res->value(), res->unit().c_str());     }     else if (val->concrete_type() == Expression::COLOR)     {-      Color_Ptr_Const col = dynamic_cast<Color_Ptr_Const>(val);+      Color_Ptr_Const col = Cast<Color>(val);       return sass_make_color(col->r(), col->g(), col->b(), col->a());     }     else if (val->concrete_type() == Expression::LIST)     {-      List_Ptr_Const l = dynamic_cast<List_Ptr_Const>(val);-      union Sass_Value* list = sass_make_list(l->size(), l->separator());+      List_Ptr_Const l = Cast<List>(val);+      union Sass_Value* list = sass_make_list(l->size(), l->separator(), l->is_bracketed());       for (size_t i = 0, L = l->length(); i < L; ++i) {         Expression_Obj obj = l->at(i);-        auto val = ast_node_to_sass_value(&obj);+        auto val = ast_node_to_sass_value(obj);         sass_list_set_value(list, i, val);       }       return list;     }     else if (val->concrete_type() == Expression::MAP)     {-      Map_Ptr_Const m = dynamic_cast<Map_Ptr_Const>(val);+      Map_Ptr_Const m = Cast<Map>(val);       union Sass_Value* map = sass_make_map(m->length());       size_t i = 0; for (Expression_Obj key : m->keys()) {-        sass_map_set_key(map, i, ast_node_to_sass_value(&key));-        sass_map_set_value(map, i, ast_node_to_sass_value(&m->at(key)));+        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;@@ -47,16 +47,16 @@     }     else if (val->concrete_type() == Expression::BOOLEAN)     {-      Boolean_Ptr_Const res = dynamic_cast<Boolean_Ptr_Const>(val);+      Boolean_Ptr_Const res = Cast<Boolean>(val);       return sass_make_boolean(res->value());     }     else if (val->concrete_type() == Expression::STRING)     {-      if (String_Quoted_Ptr_Const qstr = dynamic_cast<String_Quoted_Ptr_Const>(val))+      if (String_Quoted_Ptr_Const qstr = Cast<String_Quoted>(val))       {         return sass_make_qstring(qstr->value().c_str());       }-      else if (String_Constant_Ptr_Const cstr = dynamic_cast<String_Constant_Ptr_Const>(val))+      else if (String_Constant_Ptr_Const cstr = Cast<String_Constant>(val))       {         return sass_make_string(cstr->value().c_str());       }@@ -73,12 +73,10 @@                                ParserState("[C-VALUE]"),                                sass_number_get_value(val),                                sass_number_get_unit(val));-      break;       case SASS_BOOLEAN:         return SASS_MEMORY_NEW(Boolean,                                ParserState("[C-VALUE]"),                                sass_boolean_get_value(val));-      break;       case SASS_COLOR:         return SASS_MEMORY_NEW(Color,                                ParserState("[C-VALUE]"),@@ -86,18 +84,15 @@                                sass_color_get_g(val),                                sass_color_get_b(val),                                sass_color_get_a(val));-      break;       case SASS_STRING:         if (sass_string_is_quoted(val)) {           return SASS_MEMORY_NEW(String_Quoted,                                  ParserState("[C-VALUE]"),                                  sass_string_get_value(val));-        } else {-          return SASS_MEMORY_NEW(String_Constant,+        }+        return SASS_MEMORY_NEW(String_Constant,                                  ParserState("[C-VALUE]"),                                  sass_string_get_value(val));-        }-      break;       case SASS_LIST: {         List_Ptr l = SASS_MEMORY_NEW(List,                                   ParserState("[C-VALUE]"),@@ -106,9 +101,9 @@         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;       }-      break;       case SASS_MAP: {         Map_Ptr m = SASS_MEMORY_NEW(Map, ParserState("[C-VALUE]"));         for (size_t i = 0, L = sass_map_get_length(val); i < L; ++i) {@@ -118,20 +113,17 @@         }         return m;       }-      break;       case SASS_NULL:         return SASS_MEMORY_NEW(Null, ParserState("[C-VALUE]"));-      break;       case SASS_ERROR:         return SASS_MEMORY_NEW(Custom_Error,                                ParserState("[C-VALUE]"),                                sass_error_get_message(val));-      break;       case SASS_WARNING:         return SASS_MEMORY_NEW(Custom_Warning,                                ParserState("[C-VALUE]"),                                sass_warning_get_message(val));-      break;+      default: break;     }     return 0;   }
tests/Main.hs view
@@ -35,7 +35,7 @@ #ifndef EXTERNAL_LIBSASS     it "should report correct version" $ do         str <- peekCString libsass_version-        str `shouldBe` "3.4.3"+        str `shouldBe` "3.5.2" #endif      it "should support quoted strings" $ withCString "sample" $ \cstr -> do