packages feed

hlibsass (empty) → 0.1.0.0

raw patch · 114 files changed

+29434/−0 lines, 114 filesdep +basedep +hlibsassdep +hspecbuild-type:Customsetup-changed

Dependencies added: base, hlibsass, hspec

Files

+ Binding/Libsass.hs view
@@ -0,0 +1,19 @@+-- |+-- This is the main module of the library - it reexports rest of the library, so+-- you should only import this one.++{-# LANGUAGE ForeignFunctionInterface #-}+module Binding.Libsass+  (+    libsass_version+  , module Libsass+  ) where++import           Binding.Libsass.Context   as Libsass+import           Binding.Libsass.Functions as Libsass+import           Binding.Libsass.Types     as Libsass+import           Binding.Libsass.Wrappers  as Libsass+import           Binding.Libsass.Values    as Libsass+import           Foreign.C.String++foreign import ccall unsafe "sass.h" libsass_version :: CString
+ Binding/Libsass/Context.hs view
@@ -0,0 +1,358 @@+{-# LANGUAGE ForeignFunctionInterface #-}+module Binding.Libsass.Context where++import           Binding.Libsass.Types+import           Foreign+import           Foreign.C.String+import           Foreign.C.Types++foreign import ccall unsafe "sass_context.h" sass_make_options+    :: IO (Ptr SassOptions)++foreign import ccall unsafe "sass_context.h" sass_make_file_context+    :: CString+    -> IO (Ptr SassFileContext)++-- | Creates and initializes a data context, ie. context that parses string+--   instead of a file.+--+--   WARNING! The string that this function takes is released by the libsass+--   during cleanup. You must not deallocate it.+foreign import ccall unsafe "sass_context.h" sass_make_data_context+    :: CString+    -> IO (Ptr SassDataContext)++foreign import ccall safe "sass_context.h" sass_compile_file_context+    :: Ptr SassFileContext+    -> IO CInt++foreign import ccall safe "sass_context.h" sass_compile_data_context+    :: Ptr SassDataContext+    -> IO CInt++foreign import ccall unsafe "sass_context.h" sass_make_file_compiler+    :: Ptr SassFileContext+    -> IO (Ptr SassCompiler)++foreign import ccall unsafe "sass_context.h" sass_make_data_compiler+    :: Ptr SassDataContext+    -> IO (Ptr SassCompiler)++foreign import ccall safe "sass_context.h" sass_compiler_parse+    :: Ptr SassCompiler+    -> IO CInt++foreign import ccall safe "sass_context.h" sass_compiler_execute+    :: Ptr SassCompiler+    -> IO CInt++foreign import ccall unsafe "sass_context.h" sass_delete_compiler+    :: Ptr SassCompiler+    -> IO ()++foreign import ccall unsafe "sass_context.h" sass_delete_file_context+    :: Ptr SassFileContext+    -> IO ()++foreign import ccall unsafe "sass_context.h" sass_delete_data_context+    :: Ptr SassDataContext+    -> IO ()++foreign import ccall unsafe "sass_context.h" sass_file_context_get_context+    :: Ptr SassFileContext+    -> Ptr SassContext++foreign import ccall unsafe "sass_context.h" sass_data_context_get_context+    :: Ptr SassDataContext+    -> Ptr SassContext++foreign import ccall unsafe "sass_context.h" sass_context_get_options+    :: Ptr SassContext+    -> Ptr SassOptions++foreign import ccall unsafe "sass_context.h" sass_file_context_get_options+    :: Ptr SassFileContext+    -> Ptr SassOptions++foreign import ccall unsafe "sass_context.h" sass_data_context_get_options+    :: Ptr SassDataContext+    -> Ptr SassOptions++foreign import ccall unsafe "sass_context.h" sass_file_context_set_options+    :: Ptr SassFileContext+    -> Ptr SassOptions+    -> IO ()++foreign import ccall unsafe "sass_context.h" sass_data_context_set_options+    :: Ptr SassDataContext+    -> Ptr SassOptions+    -> IO ()++foreign import ccall unsafe "sass_context.h" sass_option_get_precision+    :: Ptr SassOptions+    -> IO CInt++foreign import ccall unsafe "sass_context.h" sass_option_get_output_style+    :: Ptr SassOptions+    -> IO CInt -- ^ Returns 'SassOutputStyle'++foreign import ccall unsafe "sass_context.h" sass_option_get_source_comments+    :: Ptr SassOptions+    -> IO Bool++foreign import ccall unsafe "sass_context.h" sass_option_get_source_map_embed+    :: Ptr SassOptions+    -> IO Bool++foreign import ccall unsafe "sass_context.h" sass_option_get_source_map_contents+    :: Ptr SassOptions+    -> IO Bool++foreign import ccall unsafe "sass_context.h" sass_option_get_omit_source_map_url+    :: Ptr SassOptions+    -> IO Bool++foreign import ccall unsafe "sass_context.h" sass_option_get_is_indented_syntax_src+    :: Ptr SassOptions+    -> IO Bool++foreign import ccall unsafe "sass_context.h" sass_option_get_indent+    :: Ptr SassOptions+    -> IO CString++foreign import ccall unsafe "sass_context.h" sass_option_get_linefeed+    :: Ptr SassOptions+    -> IO CString++foreign import ccall unsafe "sass_context.h" sass_option_get_input_path+    :: Ptr SassOptions+    -> IO CString++foreign import ccall unsafe "sass_context.h" sass_option_get_output_path+    :: 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++foreign import ccall unsafe "sass_context.h" sass_option_get_source_map_root+    :: Ptr SassOptions+    -> IO CString++foreign import ccall unsafe "sass_context.h" sass_option_get_c_headers+     :: Ptr SassOptions+     -> IO SassImporterList++foreign import ccall unsafe "sass_context.h" sass_option_get_c_importers+     :: Ptr SassOptions+     -> IO SassImporterList++foreign import ccall unsafe "sass_context.h" sass_option_get_c_functions+     :: Ptr SassOptions+     -> IO SassFunctionList++foreign import ccall unsafe "sass_context.h" sass_option_set_precision+    :: Ptr SassOptions+    -> CInt+    -> IO ()++foreign import ccall unsafe "sass_context.h" sass_option_set_output_style+    :: Ptr SassOptions+    -> CInt -- ^ 'SassOutputStyle'+    -> IO ()++foreign import ccall unsafe "sass_context.h" sass_option_set_source_comments+    :: Ptr SassOptions+    -> Bool+    -> IO ()++foreign import ccall unsafe "sass_context.h" sass_option_set_source_map_embed+    :: Ptr SassOptions+    -> Bool+    -> IO ()++foreign import ccall unsafe "sass_context.h" sass_option_set_source_map_contents+    :: Ptr SassOptions+    -> Bool+    -> IO ()++foreign import ccall unsafe "sass_context.h" sass_option_set_omit_source_map_url+    :: Ptr SassOptions+    -> Bool+    -> IO ()++foreign import ccall unsafe "sass_context.h" sass_option_set_is_indented_syntax_src+    :: Ptr SassOptions+    -> Bool+    -> IO ()++foreign import ccall unsafe "sass_context.h" sass_option_set_indent+    :: Ptr SassOptions+    -> CString+    -> IO ()++foreign import ccall unsafe "sass_context.h" sass_option_set_linefeed+    :: Ptr SassOptions+    -> CString+    -> IO ()++foreign import ccall unsafe "sass_context.h" sass_option_set_input_path+    :: Ptr SassOptions+    -> CString+    -> IO ()++foreign import ccall unsafe "sass_context.h" sass_option_set_output_path+    :: Ptr SassOptions+    -> CString+    -> IO ()++foreign import ccall unsafe "sass_context.h" sass_option_set_plugin_path+    :: Ptr SassOptions+    -> CString+    -> IO ()++foreign import ccall unsafe "sass_context.h" sass_option_set_include_path+    :: Ptr SassOptions+    -> CString+    -> IO ()++foreign import ccall unsafe "sass_context.h" sass_option_set_source_map_file+    :: Ptr SassOptions+    -> CString+    -> IO ()++foreign import ccall unsafe "sass_context.h" sass_option_set_source_map_root+    :: Ptr SassOptions+    -> CString+    -> IO ()++foreign import ccall unsafe "sass_context.h" sass_option_set_c_headers+    :: Ptr SassOptions+    -> SassImporterList+    -> IO ()++foreign import ccall unsafe "sass_context.h" sass_option_set_c_importers+    :: Ptr SassOptions+    -> SassImporterList+    -> IO ()++foreign import ccall unsafe "sass_context.h" sass_option_set_c_functions+    :: Ptr SassOptions+    -> SassFunctionList+    -> IO ()++foreign import ccall unsafe "sass_context.h" sass_context_get_output_string+    :: Ptr SassContext+    -> IO CString++foreign import ccall unsafe "sass_context.h" sass_context_get_error_status+    :: Ptr SassContext+    -> IO CInt++foreign import ccall unsafe "sass_context.h" sass_context_get_error_json+    :: Ptr SassContext+    -> IO CString++foreign import ccall unsafe "sass_context.h" sass_context_get_error_text+    :: Ptr SassContext+    -> IO CString++foreign import ccall unsafe "sass_context.h" sass_context_get_error_message+    :: Ptr SassContext+    -> IO CString++foreign import ccall unsafe "sass_context.h" sass_context_get_error_file+    :: Ptr SassContext+    -> IO CString++foreign import ccall unsafe "sass_context.h" sass_context_get_error_src+    :: Ptr SassContext+    -> IO CString++foreign import ccall unsafe "sass_context.h" sass_context_get_error_line+    :: Ptr SassContext+    -> IO CSize++foreign import ccall unsafe "sass_context.h" sass_context_get_error_column+    :: Ptr SassContext+    -> IO CSize++foreign import ccall unsafe "sass_context.h" sass_context_get_source_map_string+    :: Ptr SassContext+    -> IO CString++foreign import ccall unsafe "sass_context.h" sass_context_get_included_files+    :: Ptr SassContext+    -> IO (Ptr CString)++foreign import ccall unsafe "sass_context.h" sass_context_get_included_files_size+    :: Ptr SassContext+    -> IO CSize++foreign import ccall unsafe "sass_context.h" sass_context_take_error_json+    :: Ptr SassContext+    -> IO CString++foreign import ccall unsafe "sass_context.h" sass_context_take_error_text+    :: Ptr SassContext+    -> IO CString++foreign import ccall unsafe "sass_context.h" sass_context_take_error_message+    :: Ptr SassContext+    -> IO CString++foreign import ccall unsafe "sass_context.h" sass_context_take_error_file+    :: Ptr SassContext+    -> IO CString++foreign import ccall unsafe "sass_context.h" sass_context_take_output_string+    :: Ptr SassContext+    -> IO CString++foreign import ccall unsafe "sass_context.h" sass_context_take_source_map_string+    :: Ptr SassContext+    -> IO CString++foreign import ccall unsafe "sass_context.h" sass_context_take_included_files+    :: Ptr SassContext+    -> IO (Ptr CString)++foreign import ccall unsafe "sass_context.h" sass_compiler_get_state+    :: Ptr SassCompiler+    -> IO CInt -- ^ Retruns 'SassCompilerState'++foreign import ccall unsafe "sass_context.h" sass_compiler_get_context+    :: Ptr SassCompiler+    -> IO (Ptr SassContext)++foreign import ccall unsafe "sass_context.h" sass_compiler_get_import_stack_size+    :: Ptr SassCompiler+    -> IO CSize++foreign import ccall unsafe "sass_context.h" sass_compiler_get_last_import+    :: Ptr SassCompiler+    -> IO SassImportEntry++foreign import ccall unsafe "sass_context.h" sass_compiler_get_import_entry+    :: Ptr SassCompiler+    -> CSize+    -> IO SassImportEntry++foreign import ccall unsafe "sass_context.h" sass_option_push_plugin_path+    :: Ptr SassOptions+    -> CString+    -> IO ()++foreign import ccall unsafe "sass_context.h" sass_option_push_include_path+    :: Ptr SassOptions+    -> CString+    -> IO ()
+ Binding/Libsass/Functions.hs view
@@ -0,0 +1,156 @@+{-# LANGUAGE ForeignFunctionInterface #-}+module Binding.Libsass.Functions where++import           Binding.Libsass.Types+import           Foreign+import           Foreign.C++foreign import ccall unsafe "sass_function.h" sass_make_importer_list+    :: CSize+    -> IO SassImporterList++-- Libsass lacks implementation of these functions.++-- foreign import ccall unsafe "sass_function.h" sass_importer_get_list_entry+--     :: SassImporterList+--     -> CSize+--     -> IO SassImporterEntry++-- foreign import ccall unsafe "sass_function.h" sass_importer_set_list_entry+--     :: SassImporterList+--     -> CSize+--     -> SassImporterEntry+--     -> IO ()++foreign import ccall unsafe "sass_function.h" sass_make_importer+    :: SassImporterFn+    -> CDouble+    -> Ptr ()+    -> IO SassImporterEntry++foreign import ccall unsafe "sass_function.h" sass_importer_get_function+    :: SassImporterEntry+    -> IO SassImporterFn++foreign import ccall unsafe "sass_function.h" sass_importer_get_priority+    :: SassImporterEntry+    -> IO CDouble+foreign import ccall unsafe "sass_function.h" sass_importer_get_cookie+    :: SassImporterEntry+    -> IO (Ptr ())++foreign import ccall unsafe "sass_function.h" sass_delete_importer+    :: SassImporterEntry+    -> IO ()++foreign import ccall unsafe "sass_function.h" sass_make_import_list+    :: CSize+    -> IO SassImportList++foreign import ccall unsafe "sass_function.h" sass_make_import_entry+    :: CString+    -> CString+    -> CString+    -> IO SassImportEntry++foreign import ccall unsafe "sass_function.h" sass_make_import+    :: CString+    -> CString+    -> CString+    -> CString+    -> IO SassImportEntry++foreign import ccall unsafe "sass_function.h" sass_import_set_error+    :: SassImportEntry+    -> CString+    -> CSize+    -> CSize+    -> IO SassImportEntry++foreign import ccall unsafe "sass_function.h" sass_import_set_list_entry+    :: SassImportList+    -> CSize+    -> SassImportEntry+    -> IO ()++foreign import ccall unsafe "sass_function.h" sass_import_get_list_entry+    :: SassImportList+    -> CSize+    -> IO SassImportEntry++foreign import ccall unsafe "sass_function.h" sass_import_get_path+    :: SassImportEntry+    -> IO CString++foreign import ccall unsafe "sass_function.h" sass_import_get_base+    :: SassImportEntry+    -> IO CString++foreign import ccall unsafe "sass_function.h" sass_import_get_source+    :: SassImportEntry+    -> IO CString++foreign import ccall unsafe "sass_function.h" sass_import_get_srcmap+    :: SassImportEntry+    -> IO CString++foreign import ccall unsafe "sass_function.h" sass_import_take_source+    :: SassImportEntry+    -> IO CString++foreign import ccall unsafe "sass_function.h" sass_import_take_srcmap+    :: SassImportEntry+    -> IO CString++foreign import ccall unsafe "sass_function.h" sass_import_get_error_line+    :: SassImportEntry+    -> IO CSize++foreign import ccall unsafe "sass_function.h" sass_import_get_error_column+    :: SassImportEntry+    -> IO CSize++foreign import ccall unsafe "sass_function.h" sass_import_get_error_message+    :: SassImportEntry+    -> IO CString++foreign import ccall unsafe "sass_function.h" sass_delete_import_list+    :: SassImportList+    -> IO ()++foreign import ccall unsafe "sass_function.h" sass_delete_import+    :: SassImportEntry+    -> IO ()++foreign import ccall unsafe "sass_function.h" sass_make_function_list+    :: CSize+    -> IO SassFunctionList++foreign import ccall unsafe "sass_function.h" sass_make_function+    :: CString+    -> SassFunctionFn+    -> Ptr ()+    -> IO SassFunctionEntry++foreign import ccall unsafe "sass_function.h" sass_function_get_list_entry+    :: SassFunctionList+    -> CSize+    -> IO SassFunctionEntry++foreign import ccall unsafe "sass_function.h" sass_function_set_list_entry+    :: SassFunctionList+    -> CSize+    -> SassFunctionEntry+    -> IO ()++foreign import ccall unsafe "sass_function.h" sass_function_get_signature+    :: SassFunctionEntry+    -> IO CString++foreign import ccall unsafe "sass_function.h" sass_function_get_function+    :: SassFunctionEntry+    -> IO SassFunctionFn++foreign import ccall unsafe "sass_function.h" sass_function_get_cookie+    :: SassFunctionEntry+    -> IO (Ptr ())
+ Binding/Libsass/Types.hs view
@@ -0,0 +1,127 @@+{-# LANGUAGE EmptyDataDecls #-}+module Binding.Libsass.Types where++import           Foreign+import           Foreign.C.String++-- From sass.h++-- | Defines output style of compiled CSS.+data SassOutputStyle = SassStyleNested+                     | SassStyleExpanded+                     | SassStyleCompact+                     | SassStyleCompressed+                     deriving (Eq, Show)++instance Enum SassOutputStyle where+    fromEnum SassStyleNested     = 0+    fromEnum SassStyleExpanded   = 1+    fromEnum SassStyleCompact    = 2+    fromEnum SassStyleCompressed = 3++    toEnum 0 = SassStyleNested+    toEnum 1 = SassStyleExpanded+    toEnum 2 = SassStyleCompact+    toEnum 3 = SassStyleCompressed+    toEnum u = error ("SassOutputStyle.toEnum: Cannot match " ++ show u)++-- From sass_context.h++data SassCompiler+data SassOptions+data SassContext+data SassFileContext+data SassDataContext++data SassCompilerState = SassCompilerCreated+                       | SassCompilerParsed+                       | SassCompilerExecuted+                       deriving (Show, Eq)++instance Enum SassCompilerState where+    fromEnum SassCompilerCreated  = 0+    fromEnum SassCompilerParsed   = 1+    fromEnum SassCompilerExecuted = 2++    toEnum 0 = SassCompilerCreated+    toEnum 1 = SassCompilerParsed+    toEnum 2 = SassCompilerExecuted+    toEnum u = error ("SassCompilerState.toEnum: Cannot match " ++ show u)++-- From sass_functions.h++data SassImport+data SassImporter+data SassFunction++type SassImportEntry = Ptr SassImport+type SassImportList = Ptr (Ptr SassImport)+type SassImporterEntry = Ptr SassImporter+type SassImporterList = Ptr (Ptr SassImporter)++type SassImporterFnType = +     CString+  -> SassImporterEntry+  -> Ptr SassCompiler+  -> IO SassImportList+type SassImporterFn = FunPtr SassImporterFnType++type SassFunctionEntry = Ptr SassFunction+type SassFunctionList = Ptr (Ptr SassFunction)++type SassFunctionFnType =+     Ptr SassValue+  -> SassFunctionEntry+  -> Ptr SassOptions+  -> IO (Ptr SassValue)+type SassFunctionFn = FunPtr SassFunctionFnType++-- From sass_values.h++data SassValue++data SassTag = SassBoolean+             | SassNumber+             | SassColor+             | SassString+             | SassList+             | SassMap+             | SassNull+             | SassError+             | SassWarning+             deriving (Eq, Show)++instance Enum SassTag where+    fromEnum SassBoolean = 0+    fromEnum SassNumber  = 1+    fromEnum SassColor   = 2+    fromEnum SassString  = 3+    fromEnum SassList    = 4+    fromEnum SassMap     = 5+    fromEnum SassNull    = 6+    fromEnum SassError   = 7+    fromEnum SassWarning = 8++    toEnum 0 = SassBoolean+    toEnum 1 = SassNumber+    toEnum 2 = SassColor+    toEnum 3 = SassString+    toEnum 4 = SassList+    toEnum 5 = SassMap+    toEnum 6 = SassNull+    toEnum 7 = SassError+    toEnum 8 = SassWarning+    toEnum u = error ("SassTag.toEnum: Cannot match " ++ show u)++-- ^ Separator used in Sass lists.+data SassSeparator = SassSeparatorComma+                   | SassSeparatorSpace+                   deriving (Eq, Show)++instance Enum SassSeparator where+    fromEnum SassSeparatorComma = 0+    fromEnum SassSeparatorSpace  = 1++    toEnum 0 = SassSeparatorComma+    toEnum 1 = SassSeparatorSpace+    toEnum u = error ("SassSeparator.toEnum: Cannot match " ++ show u)
+ Binding/Libsass/Values.hs view
@@ -0,0 +1,234 @@+{-# LANGUAGE ForeignFunctionInterface #-}+module Binding.Libsass.Values where++import           Binding.Libsass.Types+import           Foreign+import           Foreign.C++foreign import ccall unsafe "sass_values.h" sass_value_get_tag+    :: Ptr SassValue+    -> IO CInt -- ^ Returns 'SassTag'++foreign import ccall unsafe "sass_values.h" sass_value_is_null+    :: Ptr SassValue+    -> IO Bool++foreign import ccall unsafe "sass_values.h" sass_value_is_number+    :: Ptr SassValue+    -> IO Bool++foreign import ccall unsafe "sass_values.h" sass_value_is_string+    :: Ptr SassValue+    -> IO Bool++foreign import ccall unsafe "sass_values.h" sass_value_is_boolean+    :: Ptr SassValue+    -> IO Bool++foreign import ccall unsafe "sass_values.h" sass_value_is_color+    :: Ptr SassValue+    -> IO Bool++foreign import ccall unsafe "sass_values.h" sass_value_is_list+    :: Ptr SassValue+    -> IO Bool++foreign import ccall unsafe "sass_values.h" sass_value_is_map+    :: Ptr SassValue+    -> IO Bool++foreign import ccall unsafe "sass_values.h" sass_value_is_error+    :: Ptr SassValue+    -> IO Bool++foreign import ccall unsafe "sass_values.h" sass_value_is_warning+    :: Ptr SassValue+    -> IO Bool++foreign import ccall unsafe "sass_values.h" sass_number_get_value+    :: Ptr SassValue+    -> IO CDouble++foreign import ccall unsafe "sass_values.h" sass_number_set_value+    :: Ptr SassValue+    -> CDouble+    -> IO ()++foreign import ccall unsafe "sass_values.h" sass_number_get_unit+    :: Ptr SassValue+    -> IO CString++foreign import ccall unsafe "sass_values.h" sass_number_set_unit+    :: Ptr SassValue+    -> CString+    -> IO ()++foreign import ccall unsafe "sass_values.h" sass_string_get_value+    :: Ptr SassValue+    -> IO CString++foreign import ccall unsafe "sass_values.h" sass_string_set_value+    :: Ptr SassValue+    -> CString+    -> IO ()++foreign import ccall unsafe "sass_values.h" sass_boolean_get_value+    :: Ptr SassValue+    -> IO Bool++foreign import ccall unsafe "sass_values.h" sass_boolean_set_value+    :: Ptr SassValue+    -> Bool+    -> IO ()++foreign import ccall unsafe "sass_values.h" sass_color_get_r+    :: Ptr SassValue+    -> IO CDouble++foreign import ccall unsafe "sass_values.h" sass_color_set_r+    :: Ptr SassValue+    -> CDouble+    -> IO ()++foreign import ccall unsafe "sass_values.h" sass_color_get_g+    :: Ptr SassValue+    -> IO CDouble++foreign import ccall unsafe "sass_values.h" sass_color_set_g+    :: Ptr SassValue+    -> CDouble+    -> IO ()++foreign import ccall unsafe "sass_values.h" sass_color_get_b+    :: Ptr SassValue+    -> IO CDouble++foreign import ccall unsafe "sass_values.h" sass_color_set_b+    :: Ptr SassValue+    -> CDouble+    -> IO ()++foreign import ccall unsafe "sass_values.h" sass_color_get_a+    :: Ptr SassValue+    -> IO CDouble++foreign import ccall unsafe "sass_values.h" sass_color_set_a+    :: Ptr SassValue+    -> CDouble+    -> IO ()++foreign import ccall unsafe "sass_values.h" sass_list_get_length+    :: Ptr SassValue+    -> IO CSize++foreign import ccall unsafe "sass_values.h" sass_list_get_separator+    :: Ptr SassValue+    -> IO CInt -- ^ Retrurns 'SassSeparator'++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_value+    :: Ptr SassValue+    -> CSize+    -> IO (Ptr SassValue)++foreign import ccall unsafe "sass_values.h" sass_list_set_value+    :: Ptr SassValue+    -> CSize+    -> Ptr SassValue+    -> IO ()++foreign import ccall unsafe "sass_values.h" sass_map_get_length+    :: Ptr SassValue+    -> IO CSize++foreign import ccall unsafe "sass_values.h" sass_map_get_key+    :: Ptr SassValue+    -> CSize+    -> IO (Ptr SassValue)++foreign import ccall unsafe "sass_values.h" sass_map_set_key+    :: Ptr SassValue+    -> CSize+    -> Ptr SassValue+    -> IO ()++foreign import ccall unsafe "sass_values.h" sass_map_get_value+    :: Ptr SassValue+    -> CSize+    -> IO (Ptr SassValue)++foreign import ccall unsafe "sass_values.h" sass_map_set_value+    :: Ptr SassValue+    -> CSize+    -> Ptr SassValue+    -> IO ()++foreign import ccall unsafe "sass_values.h" sass_error_get_message+    :: Ptr SassValue+    -> IO CString++foreign import ccall unsafe "sass_values.h" sass_error_set_message+    :: Ptr SassValue+    -> CString+    -> IO ()++foreign import ccall unsafe "sass_values.h" sass_warning_get_message+    :: Ptr SassValue+    -> IO CString++foreign import ccall unsafe "sass_values.h" sass_warning_set_message+    :: Ptr SassValue+    -> CString+    -> IO ()++foreign import ccall unsafe "sass_values.h" sass_make_null+    :: IO (Ptr SassValue)++foreign import ccall unsafe "sass_values.h" sass_make_boolean+    :: Bool+    -> IO (Ptr SassValue)++foreign import ccall unsafe "sass_values.h" sass_make_string+    :: CString+    -> IO (Ptr SassValue)++foreign import ccall unsafe "sass_values.h" sass_make_number+    :: CDouble+    -> CString+    -> IO (Ptr SassValue)++foreign import ccall unsafe "sass_values.h" sass_make_color+    :: CDouble+    -> CDouble+    -> CDouble+    -> CDouble+    -> IO (Ptr SassValue)++foreign import ccall unsafe "sass_values.h" sass_make_list+    :: CSize+    -> CInt -- ^ 'SassSeparator'+    -> IO (Ptr SassValue)++foreign import ccall unsafe "sass_values.h" sass_make_map+    :: CSize+    -> IO (Ptr SassValue)++foreign import ccall unsafe "sass_values.h" sass_make_error+    :: CString+    -> IO (Ptr SassValue)++foreign import ccall unsafe "sass_values.h" sass_make_warning+    :: CString+    -> IO (Ptr SassValue)++foreign import ccall unsafe "sass_values.h" sass_delete_value+    :: Ptr SassValue+    -> IO ()++foreign import ccall unsafe "sass_values.h" sass_clone_value+    :: Ptr SassValue+    -> IO (Ptr SassValue)
+ Binding/Libsass/Wrappers.hs view
@@ -0,0 +1,34 @@+{-# LANGUAGE ForeignFunctionInterface #-}+module Binding.Libsass.Wrappers where++import           Binding.Libsass.Types+import           Foreign++foreign import ccall safe "wrapper" mkSassImporterFn+    :: SassImporterFnType+    -> IO SassImporterFn++foreign import ccall safe "wrapper" mkSassFunctionFn+    :: SassFunctionFnType+    -> IO SassFunctionFn++foreign import ccall "sass_context.h &sass_delete_compiler"+    p_sass_delete_compiler :: FunPtr (Ptr SassCompiler -> IO ())++foreign import ccall "sass_context.h &sass_delete_file_context"+    p_sass_delete_file_context :: FunPtr (Ptr SassFileContext -> IO ())++foreign import ccall "sass_context.h &sass_delete_data_context"+    p_sass_delete_data_context :: FunPtr (Ptr SassDataContext -> IO ())++foreign import ccall "sass_function.h &sass_delete_import_list"+    p_sass_delete_import_list :: FunPtr (SassImportList -> IO ())++foreign import ccall "sass_function.h &sass_delete_import"+    p_sass_delete_import :: FunPtr (SassImportEntry -> IO ())++foreign import ccall "sass_function.h &sass_delete_importer"+    p_sass_delete_importer :: FunPtr (SassImporterEntry -> IO ())++foreign import ccall  "sass_values.h &sass_delete_value"+    p_sass_delete_value :: FunPtr (Ptr SassValue -> IO ())
+ LICENSE view
@@ -0,0 +1,20 @@+Copyright (c) 2015 Jakub Fijałkowski++Permission is hereby granted, free of charge, to any person obtaining+a copy of this software and associated documentation files (the+"Software"), to deal in the Software without restriction, including+without limitation the rights to use, copy, modify, merge, publish,+distribute, sublicense, and/or sell copies of the Software, and to+permit persons to whom the Software is furnished to do so, subject to+the following conditions:++The above copyright notice and this permission notice shall be included+in all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ Setup.hs view
@@ -0,0 +1,55 @@+import           Data.Maybe                         (fromJust)+import           Distribution.PackageDescription+import           Distribution.Simple+import           Distribution.Simple.LocalBuildInfo (InstallDirs (..),+                                                     LocalBuildInfo (..),+                                                     absoluteInstallDirs,+                                                     localPkgDescr)+import           Distribution.Simple.Setup+import           Distribution.Simple.Utils          (rawSystemExit)+import           System.Directory                   (getCurrentDirectory)++main = defaultMainWithHooks simpleUserHooks+  {+    preConf = \a f -> makeLibsass a f >> preConf simpleUserHooks a f+  , copyHook = copyLibsass+  , confHook = \a f -> confHook simpleUserHooks a f >>= updateExtraLibDirs+  , postClean = cleanLibsass+  }++makeLibsass :: Args -> ConfigFlags -> IO ()+makeLibsass _ flags =+    rawSystemExit (fromFlag $ configVerbosity flags) "env"+        ["make", "--directory=libsass"]++updateExtraLibDirs :: LocalBuildInfo -> IO LocalBuildInfo+updateExtraLibDirs localBuildInfo = do+    let packageDescription = localPkgDescr localBuildInfo+        lib = fromJust $ library packageDescription+        libBuild = libBuildInfo lib+    dir <- getCurrentDirectory+    return localBuildInfo {+        localPkgDescr = packageDescription {+            library = Just $ lib {+                libBuildInfo = libBuild {+                    extraLibDirs = (dir ++ "/libsass/lib") :+                        extraLibDirs libBuild+                }+            }+        }+    }++copyLibsass :: PackageDescription -> LocalBuildInfo -> UserHooks -> CopyFlags+            -> IO ()+copyLibsass pkg_descr lbi _ flags = do+    let libPref = libdir . absoluteInstallDirs pkg_descr lbi+                . fromFlag . copyDest+                $ flags+    rawSystemExit (fromFlag $ copyVerbosity flags) "cp"+        ["libsass/lib/libsass.a", libPref]+++cleanLibsass :: Args -> CleanFlags -> PackageDescription -> () -> IO ()+cleanLibsass _ flags _ _ =+    rawSystemExit (fromFlag $ cleanVerbosity flags) "env"+        ["make", "--directory=libsass", "clean"]
+ hlibsass.cabal view
@@ -0,0 +1,57 @@+name:                hlibsass+version:             0.1.0.0+license:             MIT+license-file:        LICENSE+author:              Jakub Fijałkowski <fiolek94@gmail.com>+maintainer:          fiolek94@gmail.com+category:            Foreign+build-type:          Custom+cabal-version:       >= 1.10+stability:           experimental+synopsis:            Low-level bindings to libsass+description:+  This package provides (very) low level bindings to libsass. It uses libsass+  compiled to static library, so if you use this in your project, you have to+  link your project with C++ runtime(which libsass does automatically - links+  with libstd++).+  For documentation visit <https://github.com/sass/libsass>.++extra-source-files:+  libsass/*.cpp+  libsass/*.c+  libsass/*.h+  libsass/*.hpp+  libsass/b64/*.h+  libsass/posix/*.h+  libsass/posix/*.c+  libsass/utf8/*.h+  libsass/Makefile+  libsass/LICENSE++source-repository head+  type:              git+  location:          git://github.com/jakubfijalkowski/hlibsass.git++library+  exposed-modules:+      Binding.Libsass+    , Binding.Libsass.Types+    , Binding.Libsass.Wrappers+    , Binding.Libsass.Context+    , Binding.Libsass.Functions+    , Binding.Libsass.Values+  build-depends:       base >= 4.7 && < 5+  hs-source-dirs:      .+  default-language:    Haskell2010+  default-extensions:  ForeignFunctionInterface+  extra-libraries:     sass+  ghc-options:         -Wall+  extra-libraries:     stdc++, sass++test-suite tests+  hs-source-dirs:      tests+  main-is:             Main.hs+  type:                exitcode-stdio-1.0+  ghc-options:         -Wall+  build-depends:       base >= 4.7 && < 5, hspec, hlibsass+  default-language:    Haskell2010
+ libsass/LICENSE view
@@ -0,0 +1,25 @@++Copyright (C) 2012 by Hampton Catlin++Permission is hereby granted, free of charge, to any person obtaining a copy of+this software and associated documentation files (the "Software"), to deal in+the Software without restriction, including without limitation the rights to+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies+of the Software, and to permit persons to whom the Software is furnished to do+so, subject to the following conditions:++The above copyright notice and this permission notice shall be included in all+copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+SOFTWARE.+++The following files in the spec were taken from the original Ruby Sass project which+is copyright Hampton Catlin, Nathan Weizenbaum, and Chris Eppstein and under+the same license.
+ libsass/Makefile view
@@ -0,0 +1,242 @@+CC       ?= gcc+CXX      ?= g+++RM       ?= rm -f+CP       ?= cp -a+MKDIR    ?= mkdir+WINDRES  ?= windres+CFLAGS   ?= -Wall -O2+CXXFLAGS ?= -Wall -O2+LDFLAGS  ?= -Wall -O2 -Wl,--no-undefined+CAT      ?= $(if $(filter $(OS),Windows_NT),type,cat)++ifneq (,$(findstring /cygdrive/,$(PATH)))+	UNAME := Cygwin+else+	ifneq (,$(findstring WINDOWS,$(PATH)))+		UNAME := Windows+	else+		ifneq (,$(findstring mingw32,$(MAKE)))+			UNAME := MinGW+		else+			ifneq (,$(findstring MINGW32,$(shell uname -s)))+				UNAME = MinGW+			else+				UNAME := $(shell uname -s)+			endif+		endif+	endif+endif++ifeq "$(LIBSASS_VERSION)" ""+	ifneq "$(wildcard ./.git/ )" ""+		LIBSASS_VERSION ?= $(shell git describe --abbrev=4 --dirty --always --tags)+	endif+endif++ifeq "$(LIBSASS_VERSION)" ""+	ifneq ("$(wildcard VERSION)","")+		LIBSASS_VERSION ?= $(shell $(CAT) VERSION)+	endif+endif++ifneq "$(LIBSASS_VERSION)" ""+	CFLAGS   += -DLIBSASS_VERSION="\"$(LIBSASS_VERSION)\""+	CXXFLAGS += -DLIBSASS_VERSION="\"$(LIBSASS_VERSION)\""+endif++# enable mandatory flag+ifeq (MinGW,$(UNAME))+	CXXFLAGS += -std=gnu++0x+	LDFLAGS  += -std=gnu++0x+else+	CXXFLAGS += -std=c++0x+	LDFLAGS  += -std=c++0x+endif++ifneq "$(SASS_LIBSASS_PATH)" ""+	CFLAGS   += -I $(SASS_LIBSASS_PATH)+	CXXFLAGS += -I $(SASS_LIBSASS_PATH)+endif++ifneq "$(EXTRA_CFLAGS)" ""+	CFLAGS   += $(EXTRA_CFLAGS)+endif+ifneq "$(EXTRA_CXXFLAGS)" ""+	CXXFLAGS += $(EXTRA_CXXFLAGS)+endif+ifneq "$(EXTRA_LDFLAGS)" ""+	LDFLAGS  += $(EXTRA_LDFLAGS)+endif++LDLIBS = -lstdc++ -lm+ifeq ($(UNAME),Darwin)+	CFLAGS += -stdlib=libc+++	CXXFLAGS += -stdlib=libc+++	LDFLAGS += -stdlib=libc+++endif++ifneq (MinGW,$(UNAME))+	LDFLAGS += -ldl+	LDLIBS += -ldl+endif++ifneq ($(BUILD),shared)+	BUILD = static+endif++ifeq (,$(PREFIX))+	ifeq (,$(TRAVIS_BUILD_DIR))+		PREFIX = /usr/local+	else+		PREFIX = $(TRAVIS_BUILD_DIR)+	endif+endif++SASS_SASSC_PATH ?= sassc+SASS_SPEC_PATH ?= sass-spec+SASS_SPEC_SPEC_DIR ?= spec+SASSC_BIN = $(SASS_SASSC_PATH)/bin/sassc+RUBY_BIN = ruby++ifeq (MinGW,$(UNAME))+	SASSC_BIN = $(SASS_SASSC_PATH)/bin/sassc.exe+endif+ifeq (Windows,$(UNAME))+	SASSC_BIN = $(SASS_SASSC_PATH)/bin/sassc.exe+endif++SOURCES = \+	ast.cpp \+	base64vlq.cpp \+	bind.cpp \+	constants.cpp \+	context.cpp \+	contextualize.cpp \+	contextualize_eval.cpp \+	cssize.cpp \+	listize.cpp \+	error_handling.cpp \+	eval.cpp \+	expand.cpp \+	extend.cpp \+	file.cpp \+	functions.cpp \+	inspect.cpp \+	lexer.cpp \+	node.cpp \+	json.cpp \+	emitter.cpp \+	output.cpp \+	parser.cpp \+	plugins.cpp \+	position.cpp \+	prelexer.cpp \+	remove_placeholders.cpp \+	sass.cpp \+	sass_util.cpp \+	sass_values.cpp \+	sass_context.cpp \+	sass_functions.cpp \+	sass_interface.cpp \+	sass2scss.cpp \+	source_map.cpp \+	to_c.cpp \+	to_string.cpp \+	units.cpp \+	utf8_string.cpp \+	util.cpp++CSOURCES = cencode.c++RESOURCES =++LIBRARIES = lib/libsass.so++ifeq (MinGW,$(UNAME))+	ifeq (shared,$(BUILD))+		CFLAGS    += -D ADD_EXPORTS+		CXXFLAGS  += -D ADD_EXPORTS+		LIBRARIES += lib/libsass.dll+		RESOURCES += res/resource.rc+	endif+else+	CFLAGS   += -fPIC+	CXXFLAGS += -fPIC+	LDFLAGS  += -fPIC+endif++OBJECTS = $(SOURCES:.cpp=.o)+COBJECTS = $(CSOURCES:.c=.o)+RCOBJECTS = $(RESOURCES:.rc=.o)++DEBUG_LVL ?= NONE++all: $(BUILD)++debug: $(BUILD)++debug-static: LDFLAGS := -g $(filter-out -O2,$(LDFLAGS))+debug-static: CFLAGS := -g -DDEBUG -DDEBUG_LVL="$(DEBUG_LVL)" $(filter-out -O2,$(CFLAGS))+debug-static: CXXFLAGS := -g -DDEBUG -DDEBUG_LVL="$(DEBUG_LVL)" $(filter-out -O2,$(CXXFLAGS))+debug-static: static++debug-shared: LDFLAGS := -g $(filter-out -O2,$(LDFLAGS))+debug-shared: CFLAGS := -g -DDEBUG -DDEBUG_LVL="$(DEBUG_LVL)" $(filter-out -O2,$(CFLAGS))+debug-shared: CXXFLAGS := -g -DDEBUG -DDEBUG_LVL="$(DEBUG_LVL)" $(filter-out -O2,$(CXXFLAGS))+debug-shared: shared++static: lib/libsass.a+shared: $(LIBRARIES)++lib:+	$(MKDIR) lib++lib/libsass.a: lib $(COBJECTS) $(OBJECTS)+	$(AR) rcvs $@ $(COBJECTS) $(OBJECTS)++lib/libsass.so: lib $(COBJECTS) $(OBJECTS)+	$(CXX) -shared $(LDFLAGS) -o $@ $(COBJECTS) $(OBJECTS) $(LDLIBS)++lib/libsass.dll: lib $(COBJECTS) $(OBJECTS) $(RCOBJECTS)+	$(CXX) -shared $(LDFLAGS) -o $@ $(COBJECTS) $(OBJECTS) $(RCOBJECTS) $(LDLIBS) -s -Wl,--subsystem,windows,--out-implib,lib/libsass.a++%.o: %.c+	$(CC) $(CFLAGS) -c -o $@ $<++%.o: %.rc+	$(WINDRES) -i $< -o $@++%.o: %.cpp+	$(CXX) $(CXXFLAGS) -c -o $@ $<++%: %.o static+	$(CXX) $(CXXFLAGS) -o $@ $+ $(LDFLAGS) $(LDLIBS)++install: install-$(BUILD)++install-static: lib/libsass.a+	install -pm0755 $< $(DESTDIR)$(PREFIX)/$<++install-shared: lib/libsass.so+	install -pm0755 $< $(DESTDIR)$(PREFIX)/$<++$(SASSC_BIN): $(BUILD)+	cd $(SASS_SASSC_PATH) && $(MAKE)++sassc: $(SASSC_BIN)+	$(SASSC_BIN) -v++test: $(SASSC_BIN)+	$(RUBY_BIN) $(SASS_SPEC_PATH)/sass-spec.rb -c $(SASSC_BIN) -s $(LOG_FLAGS) $(SASS_SPEC_PATH)/$(SASS_SPEC_SPEC_DIR)++test_build: $(SASSC_BIN)+	$(RUBY_BIN) $(SASS_SPEC_PATH)/sass-spec.rb -c $(SASSC_BIN) -s --ignore-todo $(LOG_FLAGS) $(SASS_SPEC_PATH)/$(SASS_SPEC_SPEC_DIR)++test_issues: $(SASSC_BIN)+	$(RUBY_BIN) $(SASS_SPEC_PATH)/sass-spec.rb -c $(SASSC_BIN) $(LOG_FLAGS) $(SASS_SPEC_PATH)/spec/issues++clean:+	$(RM) $(RCOBJECTS) $(COBJECTS) $(OBJECTS) $(LIBRARIES) lib/*.a lib/*.so lib/*.dll lib/*.la+++.PHONY: all debug debug-static debug-shared static shared install install-static install-shared sassc clean
+ libsass/ast.cpp view
@@ -0,0 +1,591 @@+#include "ast.hpp"+#include "context.hpp"+#include "to_string.hpp"+#include <set>+#include <algorithm>+#include <iostream>++namespace Sass {+  using namespace std;++  bool Compound_Selector::operator<(const Compound_Selector& rhs) const+  {+    To_String to_string;+    // ugly+    return const_cast<Compound_Selector*>(this)->perform(&to_string) <+           const_cast<Compound_Selector&>(rhs).perform(&to_string);+  }++  bool Complex_Selector::operator<(const Complex_Selector& rhs) const+  {+    To_String to_string;+    return const_cast<Complex_Selector*>(this)->perform(&to_string) <+           const_cast<Complex_Selector&>(rhs).perform(&to_string);+  }++  bool Complex_Selector::operator==(const Complex_Selector& rhs) const {+  	// TODO: We have to access the tail directly using tail_ since ADD_PROPERTY doesn't provide a const version.++  	const Complex_Selector* pOne = this;+    const Complex_Selector* pTwo = &rhs;++    // Consume any empty references at the beginning of the Complex_Selector+    if (pOne->combinator() == Complex_Selector::ANCESTOR_OF && pOne->head()->is_empty_reference()) {+    	pOne = pOne->tail_;+    }+    if (pTwo->combinator() == Complex_Selector::ANCESTOR_OF && pTwo->head()->is_empty_reference()) {+    	pTwo = pTwo->tail_;+    }++    while (pOne && pTwo) {+    	if (pOne->combinator() != pTwo->combinator()) {+      	return false;+      }++      if (*(pOne->head()) != *(pTwo->head())) {+      	return false;+      }++    	pOne = pOne->tail_;+      pTwo = pTwo->tail_;+    }++    return pOne == NULL && pTwo == NULL;+  }++  Compound_Selector* Compound_Selector::unify_with(Compound_Selector* rhs, Context& ctx)+  {+    Compound_Selector* unified = rhs;+    for (size_t i = 0, L = length(); i < L; ++i)+    {+      if (!unified) break;+      else          unified = (*this)[i]->unify_with(unified, ctx);+    }+    return unified;+  }++  bool Simple_Selector::operator==(const Simple_Selector& rhs) const+  {+  	// Compare the string representations for equality.++  	// Cast away const here. To_String should take a const object, but it doesn't.+  	Simple_Selector* pLHS = const_cast<Simple_Selector*>(this);+    Simple_Selector* pRHS = const_cast<Simple_Selector*>(&rhs);++    To_String to_string;+    return pLHS->perform(&to_string) == pRHS->perform(&to_string);+  }++  bool Simple_Selector::operator<(const Simple_Selector& rhs) const {+		// Use the string representation for ordering.++  	// Cast away const here. To_String should take a const object, but it doesn't.+  	Simple_Selector* pLHS = const_cast<Simple_Selector*>(this);+    Simple_Selector* pRHS = const_cast<Simple_Selector*>(&rhs);++    To_String to_string;+    return pLHS->perform(&to_string) < pRHS->perform(&to_string);+  }++  Compound_Selector* Simple_Selector::unify_with(Compound_Selector* rhs, Context& ctx)+  {+    To_String to_string(&ctx);+    for (size_t i = 0, L = rhs->length(); i < L; ++i)+    { if (perform(&to_string) == (*rhs)[i]->perform(&to_string)) return rhs; }++    // check for pseudo elements because they need to come 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 ((typeid(*(*rhs)[i]) == typeid(Pseudo_Selector) || typeid(*(*rhs)[i]) == typeid(Wrapped_Selector)) && (*rhs)[L-1]->is_pseudo_element())+        { found = true; break; }+      }+    }+    else+    {+      for (i = 0, L = rhs->length(); i < L; ++i)+      {+        if (typeid(*(*rhs)[i]) == typeid(Pseudo_Selector) || typeid(*(*rhs)[i]) == typeid(Wrapped_Selector))+        { found = true; break; }+      }+    }+    if (!found)+    {+      Compound_Selector* cpy = new (ctx.mem) Compound_Selector(*rhs);+      (*cpy) << this;+      return cpy;+    }+    Compound_Selector* cpy = new (ctx.mem) Compound_Selector(rhs->pstate());+    for (size_t j = 0; j < i; ++j)+    { (*cpy) << (*rhs)[j]; }+    (*cpy) << this;+    for (size_t j = i; j < L; ++j)+    { (*cpy) << (*rhs)[j]; }+    return cpy;+  }++  Compound_Selector* Type_Selector::unify_with(Compound_Selector* rhs, Context& ctx)+  {+    // TODO: handle namespaces++    // if the rhs is empty, just return a copy of this+    if (rhs->length() == 0) {+      Compound_Selector* cpy = new (ctx.mem) Compound_Selector(rhs->pstate());+      (*cpy) << this;+      return cpy;+    }++    // if this is a universal selector and rhs is not empty, just return the rhs+    if (name() == "*")+    { return new (ctx.mem) Compound_Selector(*rhs); }+++    Simple_Selector* rhs_0 = (*rhs)[0];+    // otherwise, this is a tag name+    if (typeid(*rhs_0) == typeid(Type_Selector))+    {+      // if rhs is universal, just return this tagname + rhs's qualifiers+      if (static_cast<Type_Selector*>(rhs_0)->name() == "*")+      {+        Compound_Selector* cpy = new (ctx.mem) Compound_Selector(rhs->pstate());+        (*cpy) << this;+        for (size_t i = 1, L = rhs->length(); i < L; ++i)+        { (*cpy) << (*rhs)[i]; }+        return cpy;+      }+      // if rhs is another tag name and it matches this, return rhs+      else if (static_cast<Type_Selector*>(rhs_0)->name() == name())+      { return new (ctx.mem) Compound_Selector(*rhs); }+      // else the tag names don't match; return nil+      else+      { return 0; }+    }+    // else it's a tag name and a bunch of qualifiers -- just append them+    Compound_Selector* cpy = new (ctx.mem) Compound_Selector(rhs->pstate());+    (*cpy) << this;+    (*cpy) += rhs;+    return cpy;+  }++  Compound_Selector* Selector_Qualifier::unify_with(Compound_Selector* rhs, Context& ctx)+  {+    if (name()[0] == '#')+    {+      for (size_t i = 0, L = rhs->length(); i < L; ++i)+      {+        Simple_Selector* rhs_i = (*rhs)[i];+        if (typeid(*rhs_i) == typeid(Selector_Qualifier) &&+            static_cast<Selector_Qualifier*>(rhs_i)->name()[0] == '#' &&+            static_cast<Selector_Qualifier*>(rhs_i)->name() != name())+          return 0;+      }+    }+    rhs->has_line_break(has_line_break());+    return Simple_Selector::unify_with(rhs, ctx);+  }++  Compound_Selector* Pseudo_Selector::unify_with(Compound_Selector* rhs, Context& ctx)+  {+    if (is_pseudo_element())+    {+      for (size_t i = 0, L = rhs->length(); i < L; ++i)+      {+        Simple_Selector* rhs_i = (*rhs)[i];+        if (typeid(*rhs_i) == typeid(Pseudo_Selector) &&+            static_cast<Pseudo_Selector*>(rhs_i)->is_pseudo_element() &&+            static_cast<Pseudo_Selector*>(rhs_i)->name() != name())+        { return 0; }+      }+    }+    return Simple_Selector::unify_with(rhs, ctx);+  }++  bool Compound_Selector::is_superselector_of(Compound_Selector* rhs)+  {+    To_String to_string;++    Simple_Selector* lbase = base();+    Simple_Selector* rbase = rhs->base();++    // Check if pseudo-elements are the same between the selectors++    set<string> lpsuedoset, rpsuedoset;+    for (size_t i = 0, L = length(); i < L; ++i)+    {+    	if ((*this)[i]->is_pseudo_element()) {+      	string pseudo((*this)[i]->perform(&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()) {+      	string pseudo((*rhs)[i]->perform(&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;+    }++		// Check the Simple_Selectors++    set<string> lset, rset;++    if (!lbase) // no lbase; just see if the left-hand qualifiers are a subset of the right-hand selector+    {+      for (size_t i = 0, L = length(); i < L; ++i)+      { lset.insert((*this)[i]->perform(&to_string)); }+      for (size_t i = 0, L = rhs->length(); i < L; ++i)+      { rset.insert((*rhs)[i]->perform(&to_string)); }+      return includes(rset.begin(), rset.end(), lset.begin(), lset.end());+    }+    else { // there's an lbase+      for (size_t i = 1, L = length(); i < L; ++i)+      { lset.insert((*this)[i]->perform(&to_string)); }+      if (rbase)+      {+        if (lbase->perform(&to_string) != rbase->perform(&to_string)) // if there's an rbase, make sure they match+        { return false; }+        else // the bases do match, so compare qualifiers+        {+          for (size_t i = 1, L = rhs->length(); i < L; ++i)+          { rset.insert((*rhs)[i]->perform(&to_string)); }+          return includes(rset.begin(), rset.end(), lset.begin(), lset.end());+        }+      }+    }+    // catch-all+    return false;+  }++  bool Compound_Selector::operator==(const Compound_Selector& rhs) const {+    To_String to_string;++    // Check if pseudo-elements are the same between the selectors++    set<string> lpsuedoset, rpsuedoset;+    for (size_t i = 0, L = length(); i < L; ++i)+    {+    	if ((*this)[i]->is_pseudo_element()) {+      	string pseudo((*this)[i]->perform(&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()) {+      	string pseudo(rhs[i]->perform(&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;+    }++		// Check the base++    const Simple_Selector* const lbase = base();+    const Simple_Selector* const rbase = rhs.base();++    if ((lbase && !rbase) ||+    	(!lbase && rbase) ||+      ((lbase && rbase) && (*lbase != *rbase))) {+			return false;+    }+++    // Check the rest of the SimpleSelectors+    // Use string representations. We can't create a set of Simple_Selector pointers because std::set == std::set is going to call ==+    // on the pointers to determine equality. I don't know of a way to pass in a comparison object. The one you can specify as part of+    // the template type is used for ordering, but not equality. We also can't just put in non-pointer Simple_Selectors because the+    // class is intended to be subclassed, and we'd get splicing.++    set<string> lset, rset;++    for (size_t i = 0, L = length(); i < L; ++i)+    { lset.insert((*this)[i]->perform(&to_string)); }+    for (size_t i = 0, L = rhs.length(); i < L; ++i)+    { rset.insert(rhs[i]->perform(&to_string)); }++    return lset == rset;+  }++  bool Complex_Selector_Pointer_Compare::operator() (const Complex_Selector* const pLeft, const Complex_Selector* const pRight) const {+    return *pLeft < *pRight;+  }++  bool Complex_Selector::is_superselector_of(Compound_Selector* rhs)+  {+    if (length() != 1)+    { return false; }+    return base()->is_superselector_of(rhs);+  }++  bool Complex_Selector::is_superselector_of(Complex_Selector* rhs)+  {+    Complex_Selector* lhs = this;+    To_String to_string;+    // check for selectors with leading or trailing combinators+    if (!lhs->head() || !rhs->head())+    { return false; }+    Complex_Selector* l_innermost = lhs->innermost();+    if (l_innermost->combinator() != Complex_Selector::ANCESTOR_OF && !l_innermost->tail())+    { return false; }+    Complex_Selector* r_innermost = rhs->innermost();+    if (r_innermost->combinator() != Complex_Selector::ANCESTOR_OF && !r_innermost->tail())+    { 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->base()); }++    bool found = false;+    Complex_Selector* marker = rhs;+    for (size_t i = 0, L = rhs->length(); i < L; ++i) {+      if (i == L-1)+      { return false; }+      if (lhs->head()->is_superselector_of(marker->head()))+      { 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()+  {+    // TODO: make this iterative+    if (!tail()) return 1;+    return 1 + tail()->length();+  }++  Compound_Selector* Complex_Selector::base()+  {+    if (!tail()) return head();+    else return tail()->base();+  }++  Complex_Selector* Complex_Selector::context(Context& ctx)+  {+    if (!tail()) return 0;+    if (!head()) return tail()->context(ctx);+    Complex_Selector* cpy = new (ctx.mem) Complex_Selector(pstate(), combinator(), head(), tail()->context(ctx));+    cpy->media_block(media_block());+    cpy->last_block(last_block());+    return cpy;+  }++  Complex_Selector* Complex_Selector::innermost()+  {+    if (!tail()) return this;+    else         return tail()->innermost();+  }++  Complex_Selector::Combinator Complex_Selector::clear_innermost()+  {+    Combinator c;+    if (!tail() || tail()->length() == 1)+    { c = combinator(); combinator(ANCESTOR_OF); tail(0); }+    else+    { c = tail()->clear_innermost(); }+    return c;+  }++  void Complex_Selector::set_innermost(Complex_Selector* val, Combinator c)+  {+    if (!tail())+    { tail(val); combinator(c); }+    else+    { tail()->set_innermost(val, c); }+  }++  Complex_Selector* Complex_Selector::clone(Context& ctx) const+  {+    Complex_Selector* cpy = new (ctx.mem) Complex_Selector(*this);+    if (tail()) cpy->tail(tail()->clone(ctx));+    return cpy;+  }++  Complex_Selector* Complex_Selector::cloneFully(Context& ctx) const+  {+    Complex_Selector* cpy = new (ctx.mem) Complex_Selector(*this);++    if (head()) {+    	cpy->head(head()->clone(ctx));+    }++    if (tail()) {+      cpy->tail(tail()->cloneFully(ctx));+    }++    return cpy;+  }++  Compound_Selector* Compound_Selector::clone(Context& ctx) const+  {+    Compound_Selector* cpy = new (ctx.mem) Compound_Selector(*this);+    return cpy;+  }++++  /* not used anymore - remove?+  Selector_Placeholder* Selector::find_placeholder()+  {+    return 0;+  }*/++  void Selector_List::adjust_after_pushing(Complex_Selector* c)+  {+    if (c->has_reference())   has_reference(true);+    if (c->has_placeholder()) has_placeholder(true);++#ifdef DEBUG+    To_String to_string;+    this->mCachedSelector(this->perform(&to_string));+#endif+  }++  /* not used anymore - remove?+  Selector_Placeholder* Selector_List::find_placeholder()+  {+    if (has_placeholder()) {+      for (size_t i = 0, L = length(); i < L; ++i) {+        if ((*this)[i]->has_placeholder()) return (*this)[i]->find_placeholder();+      }+    }+    return 0;+  }*/++  /* not used anymore - remove?+  Selector_Placeholder* Complex_Selector::find_placeholder()+  {+    if (has_placeholder()) {+      if (head() && head()->has_placeholder()) return head()->find_placeholder();+      else if (tail() && tail()->has_placeholder()) return tail()->find_placeholder();+    }+    return 0;+  }*/++  /* not used anymore - remove?+  Selector_Placeholder* Compound_Selector::find_placeholder()+  {+    if (has_placeholder()) {+      for (size_t i = 0, L = length(); i < L; ++i) {+        if ((*this)[i]->has_placeholder()) return (*this)[i]->find_placeholder();+      }+      // return this;+    }+    return 0;+  }*/++  /* not used anymore - remove?+  Selector_Placeholder* Selector_Placeholder::find_placeholder()+  {+    return this;+  }*/++  vector<string> Compound_Selector::to_str_vec()+  {+    To_String to_string;+    vector<string> result;+    result.reserve(length());+    for (size_t i = 0, L = length(); i < L; ++i)+    { result.push_back((*this)[i]->perform(&to_string)); }+    return result;+  }++  Compound_Selector* Compound_Selector::minus(Compound_Selector* rhs, Context& ctx)+  {+    To_String to_string(&ctx);+    Compound_Selector* result = new (ctx.mem) Compound_Selector(pstate());++    // not very efficient because it needs to preserve order+    for (size_t i = 0, L = length(); i < L; ++i)+    {+      bool found = false;+      string thisSelector((*this)[i]->perform(&to_string));+      for (size_t j = 0, M = rhs->length(); j < M; ++j)+      {+        if (thisSelector == (*rhs)[j]->perform(&to_string))+        {+          found = true;+          break;+        }+      }+      if (!found) (*result) << (*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((*iterator)->clone(ctx));+    }+  }++  /* not used anymore - remove?+  vector<Compound_Selector*> Complex_Selector::to_vector()+  {+    vector<Compound_Selector*> result;+    Compound_Selector* h = head();+    Complex_Selector* t = tail();+    if (h) result.push_back(h);+    while (t)+    {+      h = t->head();+      t = t->tail();+      if (h) result.push_back(h);+    }+    return result;+  }*/++}+
+ libsass/ast.hpp view
@@ -0,0 +1,2168 @@+#ifndef SASS_AST_H+#define SASS_AST_H++#include <set>+#include <deque>+#include <vector>+#include <string>+#include <sstream>+#include <iostream>+#include <typeinfo>+#include <algorithm>+#include <unordered_map>++#ifdef __clang__++/*+ * There are some overloads used here that trigger the clang overload+ * hiding warning. Specifically:+ *+ * Type type() which hides string type() from Expression+ *+ * and+ *+ * Block* block() which hides virtual Block* block() from Statement+ *+ */++#pragma clang diagnostic push+#pragma clang diagnostic ignored "-Woverloaded-virtual"++#endif++#include "util.hpp"+#include "units.hpp"+#include "context.hpp"+#include "position.hpp"+#include "constants.hpp"+#include "operation.hpp"+#include "position.hpp"+#include "inspect.hpp"+#include "source_map.hpp"+#include "environment.hpp"+#include "error_handling.hpp"+#include "ast_def_macros.hpp"+#include "ast_fwd_decl.hpp"+#include "to_string.hpp"+#include "source_map.hpp"++#include "sass.h"+#include "sass_values.h"+#include "sass_functions.h"++namespace Sass {+  using namespace std;++  //////////////////////////////////////////////////////////+  // Abstract base class for all abstract syntax tree nodes.+  //////////////////////////////////////////////////////////+  class AST_Node {+    ADD_PROPERTY(ParserState, pstate);+  public:+    AST_Node(ParserState pstate)+    : pstate_(pstate)+    { }+    virtual ~AST_Node() = 0;+    // virtual Block* block() { return 0; }+  public:+    Offset off() { return pstate(); };+    Position pos() { return pstate(); };+    ATTACH_OPERATIONS();+  };+  inline AST_Node::~AST_Node() { }+++  //////////////////////////////////////////////////////////////////////+  // Abstract base class for expressions. This side of the AST hierarchy+  // represents elements in value contexts, which exist primarily to be+  // evaluated and returned.+  //////////////////////////////////////////////////////////////////////+  class Expression : public AST_Node {+  public:+    enum Concrete_Type {+      NONE,+      BOOLEAN,+      NUMBER,+      COLOR,+      STRING,+      LIST,+      MAP,+      SELECTOR,+      NULL_VAL,+      NUM_TYPES+    };+  private:+    // expressions in some contexts shouldn't be evaluated+    ADD_PROPERTY(bool, is_delayed);+    ADD_PROPERTY(bool, is_expanded);+    ADD_PROPERTY(bool, is_interpolant);+    ADD_PROPERTY(Concrete_Type, concrete_type);+  public:+    Expression(ParserState pstate,+               bool d = false, bool e = false, bool i = false, Concrete_Type ct = NONE)+    : AST_Node(pstate),+      is_delayed_(d),+      is_expanded_(d),+      is_interpolant_(i),+      concrete_type_(ct)+    { }+    virtual operator bool() { return true; }+    virtual ~Expression() { };+    virtual string type() { return ""; /* TODO: raise an error? */ }+    virtual bool is_invisible() { return false; }+    static string type_name() { return ""; }+    virtual bool is_false() { return false; }+    virtual bool operator==( Expression& rhs) const { return false; }+    virtual size_t hash() { return 0; }+  };+}+++/////////////////////////////////////////////////////////////////////////////+// Hash method specializations for unordered_map to work with Sass::Expression+/////////////////////////////////////////////////////////////////////////////++namespace std {+  template<>+  struct hash<Sass::Expression*>+  {+    size_t operator()(Sass::Expression* s) const+    {+      return s->hash();+    }+  };+  template<>+  struct equal_to<Sass::Expression*>+  {+    bool operator()( Sass::Expression* lhs,  Sass::Expression* rhs) const+    {+      return *lhs == *rhs;+    }+  };+}++namespace Sass {+  using namespace std;++  /////////////////////////////////////////////////////////////////////////////+  // Mixin class for AST nodes that should behave like vectors. Uses the+  // "Template Method" design pattern to allow subclasses to adjust their flags+  // when certain objects are pushed.+  /////////////////////////////////////////////////////////////////////////////+  template <typename T>+  class Vectorized {+    vector<T> elements_;+  protected:+    size_t hash_;+    void reset_hash() { hash_ = 0; }+    virtual void adjust_after_pushing(T element) { }+  public:+    Vectorized(size_t s = 0) : elements_(vector<T>())+    { elements_.reserve(s); }+    virtual ~Vectorized() = 0;+    size_t length() const   { return elements_.size(); }+    bool empty() const      { return elements_.empty(); }+    T last()                { return elements_.back(); }+    T& operator[](size_t i) { return elements_[i]; }+    const T& operator[](size_t i) const { return elements_[i]; }+    Vectorized& operator<<(T element)+    {+      reset_hash();+      elements_.push_back(element);+      adjust_after_pushing(element);+      return *this;+    }+    Vectorized& operator+=(Vectorized* v)+    {+      for (size_t i = 0, L = v->length(); i < L; ++i) *this << (*v)[i];+      return *this;+    }+    Vectorized& unshift(T element)+    {+      elements_.insert(elements_.begin(), element);+      return *this;+    }+    vector<T>& elements() { return elements_; }+    const vector<T>& elements() const { return elements_; }+    vector<T>& elements(vector<T>& e) { elements_ = e; return elements_; }+  };+  template <typename T>+  inline Vectorized<T>::~Vectorized() { }++  /////////////////////////////////////////////////////////////////////////////+  // Mixin class for AST nodes that should behave like a hash table. Uses an+  // extra <vector> internally to maintain insertion order for interation.+  /////////////////////////////////////////////////////////////////////////////+  class Hashed {+  private:+    unordered_map<Expression*, Expression*> elements_;+    vector<Expression*> list_;+  protected:+    size_t hash_;+    Expression* duplicate_key_;+    void reset_hash() { hash_ = 0; }+    void reset_duplicate_key() { duplicate_key_ = 0; }+    virtual void adjust_after_pushing(std::pair<Expression*, Expression*> p) { }+  public:+    Hashed(size_t s = 0) : elements_(unordered_map<Expression*, Expression*>(s)), list_(vector<Expression*>())+    { elements_.reserve(s); list_.reserve(s); reset_duplicate_key(); }+    virtual ~Hashed();+    size_t length() const                  { return list_.size(); }+    bool empty() const                     { return list_.empty(); }+    bool has(Expression* k) const          { return elements_.count(k) == 1; }+    Expression* at(Expression* k) const    { return elements_.at(k); }+    bool has_duplicate_key() const         { return duplicate_key_ != 0; }+    Expression* get_duplicate_key() const  { return duplicate_key_; }+    Hashed& operator<<(pair<Expression*, Expression*> p)+    {+      reset_hash();++      if (!has(p.first)) list_.push_back(p.first);+      else if (!duplicate_key_) duplicate_key_ = p.first;++      elements_[p.first] = p.second;++      adjust_after_pushing(p);+      return *this;+    }+    Hashed& operator+=(Hashed* h)+    {+      if (length() == 0) {+        this->elements_ = h->elements_;+        this->list_ = h->list_;+        return *this;+      }++      for (auto key : h->keys()) {+        *this << make_pair(key, h->at(key));+      }++      reset_duplicate_key();+      return *this;+    }+    const unordered_map<Expression*, Expression*>& pairs() const { return elements_; }+    const vector<Expression*>& keys() const { return list_; }+  };+  inline Hashed::~Hashed() { }+++  /////////////////////////////////////////////////////////////////////////+  // Abstract base class for statements. This side of the AST hierarchy+  // represents elements in expansion contexts, which exist primarily to be+  // rewritten and macro-expanded.+  /////////////////////////////////////////////////////////////////////////+  class Statement : public AST_Node {+  public:+    enum Statement_Type {+      NONE,+      RULESET,+      MEDIA,+      DIRECTIVE,+      FEATURE,+      ATROOT,+      BUBBLE,+      KEYFRAMERULE+    };+  private:+    ADD_PROPERTY(Block*, block);+    ADD_PROPERTY(Statement_Type, statement_type);+    ADD_PROPERTY(size_t, tabs);+    ADD_PROPERTY(bool, group_end);+  public:+    Statement(ParserState pstate, Statement_Type st = NONE, size_t t = 0)+    : AST_Node(pstate), statement_type_(st), tabs_(t), group_end_(false)+     { }+    virtual ~Statement() = 0;+    // needed for rearranging nested rulesets during CSS emission+    virtual bool   is_hoistable() { return false; }+    virtual bool   is_invisible() { return false; }+    virtual bool   bubbles() { return false; }+    virtual Block* block()  { return 0; }+  };+  inline Statement::~Statement() { }++  ////////////////////////+  // Blocks of statements.+  ////////////////////////+  class Block : public Statement, public Vectorized<Statement*> {+    ADD_PROPERTY(bool, is_root);+    // needed for properly formatted CSS emission+    ADD_PROPERTY(bool, has_hoistable);+    ADD_PROPERTY(bool, has_non_hoistable);+  protected:+    void adjust_after_pushing(Statement* s)+    {+      if (s->is_hoistable()) has_hoistable_     = true;+      else                   has_non_hoistable_ = true;+    };+  public:+    Block(ParserState pstate, size_t s = 0, bool r = false)+    : Statement(pstate),+      Vectorized<Statement*>(s),+      is_root_(r), has_hoistable_(false), has_non_hoistable_(false)+    { }+    Block* block() { return this; }+    ATTACH_OPERATIONS();+  };++  ////////////////////////////////////////////////////////////////////////+  // Abstract base class for statements that contain blocks of statements.+  ////////////////////////////////////////////////////////////////////////+  class Has_Block : public Statement {+    ADD_PROPERTY(Block*, block);+  public:+    Has_Block(ParserState pstate, Block* b)+    : Statement(pstate), block_(b)+    { }+    virtual ~Has_Block() = 0;+  };+  inline Has_Block::~Has_Block() { }++  /////////////////////////////////////////////////////////////////////////////+  // Rulesets (i.e., sets of styles headed by a selector and containing a block+  // of style declarations.+  /////////////////////////////////////////////////////////////////////////////+  class Ruleset : public Has_Block {+    ADD_PROPERTY(Selector*, selector);+  public:+    Ruleset(ParserState pstate, Selector* s, Block* b)+    : Has_Block(pstate, b), selector_(s)+    { statement_type(RULESET); }+    bool is_invisible();+    // nested rulesets need to be hoisted out of their enclosing blocks+    bool is_hoistable() { return true; }+    ATTACH_OPERATIONS();+  };++  /////////////////////////////////////////////////////////+  // Nested declaration sets (i.e., namespaced properties).+  /////////////////////////////////////////////////////////+  class Propset : public Has_Block {+    ADD_PROPERTY(String*, property_fragment);+  public:+    Propset(ParserState pstate, String* pf, Block* b = 0)+    : Has_Block(pstate, b), property_fragment_(pf)+    { }+    ATTACH_OPERATIONS();+  };++  /////////////////+  // Bubble.+  /////////////////+  class Bubble : public Statement {+    ADD_PROPERTY(Statement*, node);+    ADD_PROPERTY(bool, group_end);+  public:+    Bubble(ParserState pstate, Statement* n, Statement* g = 0, size_t t = 0)+    : Statement(pstate, Statement::BUBBLE, t), node_(n), group_end_(g == 0)+    { }+    bool bubbles() { return true; }+    ATTACH_OPERATIONS();+  };++  /////////////////+  // Media queries.+  /////////////////+  class Media_Block : public Has_Block {+    ADD_PROPERTY(List*, media_queries);+    ADD_PROPERTY(Selector*, selector);+  public:+    Media_Block(ParserState pstate, List* mqs, Block* b)+    : Has_Block(pstate, b), media_queries_(mqs), selector_(0)+    { statement_type(MEDIA); }+    Media_Block(ParserState pstate, List* mqs, Block* b, Selector* s)+    : Has_Block(pstate, b), media_queries_(mqs), selector_(s)+    { statement_type(MEDIA); }+    bool bubbles() { return true; }+    bool is_hoistable() { return true; }+    bool is_invisible() {+      bool is_invisible = true;+      for (size_t i = 0, L = block()->length(); i < L && is_invisible; i++)+        is_invisible &= (*block())[i]->is_invisible();+      return is_invisible;+    }+    ATTACH_OPERATIONS();+  };++  ///////////////////+  // Feature queries.+  ///////////////////+  class Feature_Block : public Has_Block {+    ADD_PROPERTY(Feature_Query*, feature_queries);+    ADD_PROPERTY(Selector*, selector);+  public:+    Feature_Block(ParserState pstate, Feature_Query* fqs, Block* b)+    : Has_Block(pstate, b), feature_queries_(fqs), selector_(0)+    { statement_type(FEATURE); }+    bool is_hoistable() { return true; }+    bool bubbles() { return true; }+    ATTACH_OPERATIONS();+  };++  ///////////////////////////////////////////////////////////////////////+  // At-rules -- arbitrary directives beginning with "@" that may have an+  // optional statement block.+  ///////////////////////////////////////////////////////////////////////+  class At_Rule : public Has_Block {+    ADD_PROPERTY(string, keyword);+    ADD_PROPERTY(Selector*, selector);+    ADD_PROPERTY(Expression*, value);+  public:+    At_Rule(ParserState pstate, string kwd, Selector* sel = 0, Block* b = 0)+    : Has_Block(pstate, b), keyword_(kwd), selector_(sel), value_(0) // set value manually if needed+    { statement_type(DIRECTIVE); }+    bool bubbles() { return is_keyframes() || is_media(); }+    bool is_media() {+      return keyword_.compare("@-webkit-media") == 0 ||+             keyword_.compare("@-moz-media") == 0 ||+             keyword_.compare("@-o-media") == 0 ||+             keyword_.compare("@media") == 0;+    }+    bool is_keyframes() {+      return keyword_.compare("@-webkit-keyframes") == 0 ||+             keyword_.compare("@-moz-keyframes") == 0 ||+             keyword_.compare("@-o-keyframes") == 0 ||+             keyword_.compare("@keyframes") == 0;+    }+    ATTACH_OPERATIONS();+  };++  ///////////////////////////////////////////////////////////////////////+  // Keyframe-rules -- the child blocks of "@keyframes" nodes.+  ///////////////////////////////////////////////////////////////////////+  class Keyframe_Rule : public Has_Block {+    ADD_PROPERTY(Selector*, selector);+  public:+    Keyframe_Rule(ParserState pstate, Block* b)+    : Has_Block(pstate, b), selector_(0)+    { statement_type(KEYFRAMERULE); }+    ATTACH_OPERATIONS();+  };++  ////////////////////////////////////////////////////////////////////////+  // Declarations -- style rules consisting of a property name and values.+  ////////////////////////////////////////////////////////////////////////+  class Declaration : public Statement {+    ADD_PROPERTY(String*, property);+    ADD_PROPERTY(Expression*, value);+    ADD_PROPERTY(bool, is_important);+  public:+    Declaration(ParserState pstate,+                String* prop, Expression* val, bool i = false)+    : Statement(pstate), property_(prop), value_(val), is_important_(i)+    { }+    ATTACH_OPERATIONS();+  };++  /////////////////////////////////////+  // Assignments -- variable and value.+  /////////////////////////////////////+  class Assignment : public Statement {+    ADD_PROPERTY(string, variable);+    ADD_PROPERTY(Expression*, value);+    ADD_PROPERTY(bool, is_default);+    ADD_PROPERTY(bool, is_global);+  public:+    Assignment(ParserState pstate,+               string var, Expression* val,+               bool is_default = false,+               bool is_global = false)+    : Statement(pstate), variable_(var), value_(val), is_default_(is_default), is_global_(is_global)+    { }+    ATTACH_OPERATIONS();+  };++  ////////////////////////////////////////////////////////////////////////////+  // Import directives. CSS and Sass import lists can be intermingled, so it's+  // necessary to store a list of each in an Import node.+  ////////////////////////////////////////////////////////////////////////////+  class Import : public Statement {+    vector<string>         files_;+    vector<Expression*>    urls_;+  public:+    Import(ParserState pstate)+    : Statement(pstate),+      files_(vector<string>()), urls_(vector<Expression*>())+    { }+    vector<string>&         files() { return files_; }+    vector<Expression*>& urls()     { return urls_; }+    ATTACH_OPERATIONS();+  };++  class Import_Stub : public Statement {+    ADD_PROPERTY(string, file_name);+  public:+    Import_Stub(ParserState pstate, string f)+    : Statement(pstate), file_name_(f)+    { }+    ATTACH_OPERATIONS();+  };++  //////////////////////////////+  // The Sass `@warn` directive.+  //////////////////////////////+  class Warning : public Statement {+    ADD_PROPERTY(Expression*, message);+  public:+    Warning(ParserState pstate, Expression* msg)+    : Statement(pstate), message_(msg)+    { }+    ATTACH_OPERATIONS();+  };++  ///////////////////////////////+  // The Sass `@error` directive.+  ///////////////////////////////+  class Error : public Statement {+    ADD_PROPERTY(Expression*, message);+  public:+    Error(ParserState pstate, Expression* msg)+    : Statement(pstate), message_(msg)+    { }+    ATTACH_OPERATIONS();+  };++  ///////////////////////////////+  // The Sass `@debug` directive.+  ///////////////////////////////+  class Debug : public Statement {+    ADD_PROPERTY(Expression*, value);+  public:+    Debug(ParserState pstate, Expression* val)+    : Statement(pstate), value_(val)+    { }+    ATTACH_OPERATIONS();+  };++  ///////////////////////////////////////////+  // CSS comments. These may be interpolated.+  ///////////////////////////////////////////+  class Comment : public Statement {+    ADD_PROPERTY(String*, text);+    ADD_PROPERTY(bool, is_important);+  public:+    Comment(ParserState pstate, String* txt, bool is_important)+    : Statement(pstate), text_(txt), is_important_(is_important)+    { }+    ATTACH_OPERATIONS();+  };++  ////////////////////////////////////+  // The Sass `@if` control directive.+  ////////////////////////////////////+  class If : public Statement {+    ADD_PROPERTY(Expression*, predicate);+    ADD_PROPERTY(Block*, consequent);+    ADD_PROPERTY(Block*, alternative);+  public:+    If(ParserState pstate, Expression* pred, Block* con, Block* alt = 0)+    : Statement(pstate), predicate_(pred), consequent_(con), alternative_(alt)+    { }+    ATTACH_OPERATIONS();+  };++  /////////////////////////////////////+  // The Sass `@for` control directive.+  /////////////////////////////////////+  class For : public Has_Block {+    ADD_PROPERTY(string, variable);+    ADD_PROPERTY(Expression*, lower_bound);+    ADD_PROPERTY(Expression*, upper_bound);+    ADD_PROPERTY(bool, is_inclusive);+  public:+    For(ParserState pstate,+        string var, Expression* lo, Expression* hi, Block* b, bool inc)+    : Has_Block(pstate, b),+      variable_(var), lower_bound_(lo), upper_bound_(hi), is_inclusive_(inc)+    { }+    ATTACH_OPERATIONS();+  };++  //////////////////////////////////////+  // The Sass `@each` control directive.+  //////////////////////////////////////+  class Each : public Has_Block {+    ADD_PROPERTY(vector<string>, variables);+    ADD_PROPERTY(Expression*, list);+  public:+    Each(ParserState pstate, vector<string> vars, Expression* lst, Block* b)+    : Has_Block(pstate, b), variables_(vars), list_(lst)+    { }+    ATTACH_OPERATIONS();+  };++  ///////////////////////////////////////+  // The Sass `@while` control directive.+  ///////////////////////////////////////+  class While : public Has_Block {+    ADD_PROPERTY(Expression*, predicate);+  public:+    While(ParserState pstate, Expression* pred, Block* b)+    : Has_Block(pstate, b), predicate_(pred)+    { }+    ATTACH_OPERATIONS();+  };++  /////////////////////////////////////////////////////////////+  // The @return directive for use inside SassScript functions.+  /////////////////////////////////////////////////////////////+  class Return : public Statement {+    ADD_PROPERTY(Expression*, value);+  public:+    Return(ParserState pstate, Expression* val)+    : Statement(pstate), value_(val)+    { }+    ATTACH_OPERATIONS();+  };++  ////////////////////////////////+  // The Sass `@extend` directive.+  ////////////////////////////////+  class Extension : public Statement {+    ADD_PROPERTY(Selector*, selector);+  public:+    Extension(ParserState pstate, Selector* s)+    : Statement(pstate), selector_(s)+    { }+    ATTACH_OPERATIONS();+  };++  /////////////////////////////////////////////////////////////////////////////+  // Definitions for both mixins and functions. The two cases are distinguished+  // by a type tag.+  /////////////////////////////////////////////////////////////////////////////+  struct Backtrace;+  typedef Environment<AST_Node*> Env;+  typedef const char* Signature;+  typedef Expression* (*Native_Function)(Env&, Env&, Context&, Signature, ParserState, Backtrace*);+  typedef const char* Signature;+  class Definition : public Has_Block {+  public:+    enum Type { MIXIN, FUNCTION };+    ADD_PROPERTY(string, name);+    ADD_PROPERTY(Parameters*, parameters);+    ADD_PROPERTY(Env*, environment);+    ADD_PROPERTY(Type, type);+    ADD_PROPERTY(Native_Function, native_function);+    ADD_PROPERTY(Sass_Function_Entry, c_function);+    ADD_PROPERTY(void*, cookie);+    ADD_PROPERTY(Context*, ctx);+    ADD_PROPERTY(bool, is_overload_stub);+    ADD_PROPERTY(Signature, signature);+  public:+    Definition(ParserState pstate,+               string n,+               Parameters* params,+               Block* b,+               Context* ctx,+               Type t)+    : Has_Block(pstate, b),+      name_(n),+      parameters_(params),+      environment_(0),+      type_(t),+      native_function_(0),+      c_function_(0),+      cookie_(0),+      ctx_(ctx),+      is_overload_stub_(false),+      signature_(0)+    { }+    Definition(ParserState pstate,+               Signature sig,+               string n,+               Parameters* params,+               Native_Function func_ptr,+               Context* ctx,+               bool overload_stub = false)+    : Has_Block(pstate, 0),+      name_(n),+      parameters_(params),+      environment_(0),+      type_(FUNCTION),+      native_function_(func_ptr),+      c_function_(0),+      cookie_(0),+      ctx_(ctx),+      is_overload_stub_(overload_stub),+      signature_(sig)+    { }+    Definition(ParserState pstate,+               Signature sig,+               string n,+               Parameters* params,+               Sass_Function_Entry c_func,+               Context* ctx,+               bool whatever,+               bool whatever2)+    : Has_Block(pstate, 0),+      name_(n),+      parameters_(params),+      environment_(0),+      type_(FUNCTION),+      native_function_(0),+      c_function_(c_func),+      cookie_(sass_function_get_cookie(c_func)),+      ctx_(ctx),+      is_overload_stub_(false),+      signature_(sig)+    { }+    ATTACH_OPERATIONS();+  };++  //////////////////////////////////////+  // Mixin calls (i.e., `@include ...`).+  //////////////////////////////////////+  class Mixin_Call : public Has_Block {+    ADD_PROPERTY(string, name);+    ADD_PROPERTY(Arguments*, arguments);+  public:+    Mixin_Call(ParserState pstate, string n, Arguments* args, Block* b = 0)+    : Has_Block(pstate, b), name_(n), arguments_(args)+    { }+    ATTACH_OPERATIONS();+  };++  ///////////////////////////////////////////////////+  // The @content directive for mixin content blocks.+  ///////////////////////////////////////////////////+  class Content : public Statement {+  public:+    Content(ParserState pstate) : Statement(pstate) { }+    ATTACH_OPERATIONS();+  };++  ///////////////////////////////////////////////////////////////////////+  // Lists of values, both comma- and space-separated (distinguished by a+  // type-tag.) Also used to represent variable-length argument lists.+  ///////////////////////////////////////////////////////////////////////+  class List : public Expression, public Vectorized<Expression*> {+    void adjust_after_pushing(Expression* e) { is_expanded(false); }+  public:+    enum Separator { SPACE, COMMA };+  private:+    ADD_PROPERTY(Separator, separator);+    ADD_PROPERTY(bool, is_arglist);+  public:+    List(ParserState pstate,+         size_t size = 0, Separator sep = SPACE, bool argl = false)+    : Expression(pstate),+      Vectorized<Expression*>(size),+      separator_(sep), is_arglist_(argl)+    { concrete_type(LIST); }+    string type() { return is_arglist_ ? "arglist" : "list"; }+    static string type_name() { return "list"; }+    bool is_invisible() { return !length(); }+    Expression* value_at_index(size_t i);++    virtual bool operator==(Expression& rhs) const+    {+      try+      {+        List& l = dynamic_cast<List&>(rhs);+        if (!(l && length() == l.length() && separator() == l.separator())) return false;+        for (size_t i = 0, L = l.length(); i < L; ++i)+          if (!(*(elements()[i]) == *(l[i]))) return false;+        return true;+      }+      catch (std::bad_cast&)+      {+        return false;+      }+      catch (...) { throw; }++    }++    virtual size_t hash()+    {+      if (hash_ > 0) return hash_;++      hash_ = std::hash<string>()(separator() == COMMA ? "comma" : "space");++      for (size_t i = 0, L = length(); i < L; ++i)+        hash_ ^= (elements()[i])->hash();++      return hash_;+    }++    ATTACH_OPERATIONS();+  };++  ///////////////////////////////////////////////////////////////////////+  // Key value paris.+  ///////////////////////////////////////////////////////////////////////+  class Map : public Expression, public Hashed {+    void adjust_after_pushing(std::pair<Expression*, Expression*> p) { is_expanded(false); }+  public:+    Map(ParserState pstate,+         size_t size = 0)+    : Expression(pstate),+      Hashed(size)+    { concrete_type(MAP); }+    string type() { return "map"; }+    static string type_name() { return "map"; }+    bool is_invisible() { return !length(); }++    virtual bool operator==(Expression& rhs) const+    {+      try+      {+        Map& m = dynamic_cast<Map&>(rhs);+        if (!(m && length() == m.length())) return false;+        for (auto key : keys())+          if (!(*at(key) == *m.at(key))) return false;+        return true;+      }+      catch (std::bad_cast&)+      {+        return false;+      }+      catch (...) { throw; }+    }++    virtual size_t hash()+    {+      if (hash_ > 0) return hash_;++      for (auto key : keys())+        hash_ ^= key->hash() ^ at(key)->hash();++      return hash_;+    }++    ATTACH_OPERATIONS();+  };++  //////////////////////////////////////////////////////////////////////////+  // Binary expressions. Represents logical, relational, and arithmetic+  // operations. Templatized to avoid large switch statements and repetitive+  // subclassing.+  //////////////////////////////////////////////////////////////////////////+  class Binary_Expression : public Expression {+  public:+    enum Type {+      AND, OR,                   // logical connectives+      EQ, NEQ, GT, GTE, LT, LTE, // arithmetic relations+      ADD, SUB, MUL, DIV, MOD,   // arithmetic functions+      NUM_OPS                    // so we know how big to make the op table+    };+  private:+    ADD_PROPERTY(Type, type);+    ADD_PROPERTY(Expression*, left);+    ADD_PROPERTY(Expression*, right);+  public:+    Binary_Expression(ParserState pstate,+                      Type t, Expression* lhs, Expression* rhs)+    : Expression(pstate), type_(t), left_(lhs), right_(rhs)+    { }+    ATTACH_OPERATIONS();+  };++  ////////////////////////////////////////////////////////////////////////////+  // Arithmetic negation (logical negation is just an ordinary function call).+  ////////////////////////////////////////////////////////////////////////////+  class Unary_Expression : public Expression {+  public:+    enum Type { PLUS, MINUS, NOT };+  private:+    ADD_PROPERTY(Type, type);+    ADD_PROPERTY(Expression*, operand);+  public:+    Unary_Expression(ParserState pstate, Type t, Expression* o)+    : Expression(pstate), type_(t), operand_(o)+    { }+    ATTACH_OPERATIONS();+  };++  ////////////////////////////////////////////////////////////+  // Individual argument objects for mixin and function calls.+  ////////////////////////////////////////////////////////////+  class Argument : public Expression {+    ADD_PROPERTY(Expression*, value);+    ADD_PROPERTY(string, name);+    ADD_PROPERTY(bool, is_rest_argument);+    ADD_PROPERTY(bool, is_keyword_argument);+    size_t hash_;+  public:+    Argument(ParserState pstate, Expression* val, string n = "", bool rest = false, bool keyword = false)+    : Expression(pstate), value_(val), name_(n), is_rest_argument_(rest), is_keyword_argument_(keyword), hash_(0)+    {+      if (!name_.empty() && is_rest_argument_) {+        error("variable-length argument may not be passed by name", pstate);+      }+    }++    virtual bool operator==(Expression& rhs) const+    {+      try+      {+        Argument& m = dynamic_cast<Argument&>(rhs);+        if (!(m && name() == m.name())) return false;+        return *value() == *value();+      }+      catch (std::bad_cast&)+      {+        return false;+      }+      catch (...) { throw; }+    }++    virtual size_t hash()+    {+      if (hash_ > 0) return hash_;++      hash_ = std::hash<string>()(name()) ^ value()->hash();++      return hash_;+    }++    ATTACH_OPERATIONS();+  };++  ////////////////////////////////////////////////////////////////////////+  // Argument lists -- in their own class to facilitate context-sensitive+  // error checking (e.g., ensuring that all ordinal arguments precede all+  // named arguments).+  ////////////////////////////////////////////////////////////////////////+  class Arguments : public Expression, public Vectorized<Argument*> {+    ADD_PROPERTY(bool, has_named_arguments);+    ADD_PROPERTY(bool, has_rest_argument);+    ADD_PROPERTY(bool, has_keyword_argument);+  protected:+    void adjust_after_pushing(Argument* 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());+        }+      }+    }+  public:+    Arguments(ParserState pstate)+    : Expression(pstate),+      Vectorized<Argument*>(),+      has_named_arguments_(false),+      has_rest_argument_(false),+      has_keyword_argument_(false)+    { }+    ATTACH_OPERATIONS();+  };++  //////////////////+  // Function calls.+  //////////////////+  class Function_Call : public Expression {+    ADD_PROPERTY(string, name);+    ADD_PROPERTY(Arguments*, arguments);+    ADD_PROPERTY(void*, cookie);+    size_t hash_;+  public:+    Function_Call(ParserState pstate, string n, Arguments* args, void* cookie)+    : Expression(pstate), name_(n), arguments_(args), cookie_(cookie), hash_(0)+    { concrete_type(STRING); }+    Function_Call(ParserState pstate, string n, Arguments* args)+    : Expression(pstate), name_(n), arguments_(args), cookie_(0), hash_(0)+    { concrete_type(STRING); }++    virtual bool operator==(Expression& rhs) const+    {+      try+      {+        Function_Call& m = dynamic_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;+        return true;+      }+      catch (std::bad_cast&)+      {+        return false;+      }+      catch (...) { throw; }+    }++    virtual size_t hash()+    {+      if (hash_ > 0) return hash_;++      hash_ = std::hash<string>()(name());+      for (auto argument : arguments()->elements())+        hash_ ^= argument->hash();++      return hash_;+    }++    ATTACH_OPERATIONS();+  };++  /////////////////////////+  // Function call schemas.+  /////////////////////////+  class Function_Call_Schema : public Expression {+    ADD_PROPERTY(String*, name);+    ADD_PROPERTY(Arguments*, arguments);+  public:+    Function_Call_Schema(ParserState pstate, String* n, Arguments* args)+    : Expression(pstate), name_(n), arguments_(args)+    { concrete_type(STRING); }+    ATTACH_OPERATIONS();+  };++  ///////////////////////+  // Variable references.+  ///////////////////////+  class Variable : public Expression {+    ADD_PROPERTY(string, name);+  public:+    Variable(ParserState pstate, string n)+    : Expression(pstate), name_(n)+    { }++    virtual bool operator==(Expression& rhs) const+    {+      try+      {+        Variable& e = dynamic_cast<Variable&>(rhs);+        return e && name() == e.name();+      }+      catch (std::bad_cast&)+      {+        return false;+      }+      catch (...) { throw; }+    }++    virtual size_t hash()+    {+      return std::hash<string>()(name());+    }++    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_PROPERTY(Type, type);+    ADD_PROPERTY(string, value);+    size_t hash_;+  public:+    Textual(ParserState pstate, Type t, string val)+    : Expression(pstate, true), type_(t), value_(val),+      hash_(0)+    { }++    virtual bool operator==(Expression& rhs) const+    {+      try+      {+        Textual& e = dynamic_cast<Textual&>(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<string>()(value_) ^ std::hash<int>()(type_);+      return hash_;+    }++    ATTACH_OPERATIONS();+  };++  ////////////////////////////////////////////////+  // Numbers, percentages, dimensions, and colors.+  ////////////////////////////////////////////////+  class Number : public Expression {+    ADD_PROPERTY(double, value);+    ADD_PROPERTY(bool, zero);+    vector<string> numerator_units_;+    vector<string> denominator_units_;+    size_t hash_;+  public:+    Number(ParserState pstate, double val, string u = "", bool zero = true)+    : Expression(pstate),+      value_(val),+      zero_(zero),+      numerator_units_(vector<string>()),+      denominator_units_(vector<string>()),+      hash_(0)+    {+      if (!u.empty()) numerator_units_.push_back(u);+      concrete_type(NUMBER);+    }+    bool            zero()              { return zero_; }+    vector<string>& numerator_units()   { return numerator_units_; }+    vector<string>& denominator_units() { return denominator_units_; }+    string type() { return "number"; }+    static string type_name() { return "number"; }+    string unit() const+    {+      stringstream 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.str();+    }+    bool is_unitless()+    { return numerator_units_.empty() && denominator_units_.empty(); }+    void normalize(string to = "")+    {+      // (multiple passes because I'm too tired to think up something clever)+      // Find a unit to convert everything to, if one isn't provided.+      if (to.empty()) {+        for (size_t i = 0, S = numerator_units_.size(); i < S; ++i) {+          string u(numerator_units_[i]);+          if (string_to_unit(u) == INCOMMENSURABLE) {+            continue;+          }+          else {+            to = u;+            break;+          }+        }+      }+      if (to.empty()) {+        for (size_t i = 0, S = denominator_units_.size(); i < S; ++i) {+          string u(denominator_units_[i]);+          if (string_to_unit(u) == INCOMMENSURABLE) {+            continue;+          }+          else {+            to = u;+            break;+          }+        }+      }+      // Now loop through again and do all the conversions.+      for (size_t i = 0, S = numerator_units_.size(); i < S; ++i) {+        string from(numerator_units_[i]);+        if (string_to_unit(from) == INCOMMENSURABLE) continue;+        value_ *= conversion_factor(from, to);+        numerator_units_[i] = to;+      }+      for (size_t i = 0, S = denominator_units_.size(); i < S; ++i) {+        string from(denominator_units_[i]);+        if (string_to_unit(from) == INCOMMENSURABLE) continue;+        value_ /= conversion_factor(from, to);+        denominator_units_[i] = to;+      }+      // Now divide out identical units in the numerator and denominator.+      vector<string> ncopy;+      ncopy.reserve(numerator_units_.size());+      for (vector<string>::iterator n = numerator_units_.begin();+           n != numerator_units_.end();+           ++n) {+        vector<string>::iterator d = find(denominator_units_.begin(),+                                          denominator_units_.end(),+                                          *n);+        if (d != denominator_units_.end()) {+          denominator_units_.erase(d);+        }+        else {+          ncopy.push_back(*n);+        }+      }+      numerator_units_ = ncopy;+      // Sort the units to make them pretty and, well, normal.+      sort(numerator_units_.begin(), numerator_units_.end());+      sort(denominator_units_.begin(), denominator_units_.end());+    }+    // useful for making one number compatible with another+    string find_convertible_unit() const+    {+      for (size_t i = 0, S = numerator_units_.size(); i < S; ++i) {+        string u(numerator_units_[i]);+        if (string_to_unit(u) != INCOMMENSURABLE) return u;+      }+      for (size_t i = 0, S = denominator_units_.size(); i < S; ++i) {+        string u(denominator_units_[i]);+        if (string_to_unit(u) != INCOMMENSURABLE) return u;+      }+      return string();+    }++    virtual bool operator==(Expression& rhs) const+    {+      try+      {+        Number& e(dynamic_cast<Number&>(rhs));+        if (!e) return false;+        e.normalize(find_convertible_unit());+        return unit() == e.unit() && value() == e.value();+      }+      catch (std::bad_cast&)+      {+        return false;+      }+      catch (...) { throw; }+    }++    virtual size_t hash()+    {+      if (hash_ == 0) hash_ = std::hash<double>()(value_);+      return hash_;+    }++    ATTACH_OPERATIONS();+  };++  //////////+  // Colors.+  //////////+  class Color : public Expression {+    ADD_PROPERTY(double, r);+    ADD_PROPERTY(double, g);+    ADD_PROPERTY(double, b);+    ADD_PROPERTY(double, a);+    ADD_PROPERTY(bool, sixtuplet);+    ADD_PROPERTY(string, disp);+    size_t hash_;+  public:+    Color(ParserState pstate, double r, double g, double b, double a = 1, bool sixtuplet = true, const string disp = "")+    : Expression(pstate), r_(r), g_(g), b_(b), a_(a), sixtuplet_(sixtuplet), disp_(disp),+      hash_(0)+    { concrete_type(COLOR); }+    string type() { return "color"; }+    static string type_name() { return "color"; }++    virtual bool operator==(Expression& rhs) const+    {+      try+      {+        Color& c = (dynamic_cast<Color&>(rhs));+        return c && r() == c.r() && g() == c.g() && b() == c.b() && a() == c.a();+      }+      catch (std::bad_cast&)+      {+        return false;+      }+      catch (...) { throw; }+    }++    virtual size_t hash()+    {+      if (hash_ == 0) hash_ = std::hash<double>()(r_) ^ std::hash<double>()(g_) ^ std::hash<double>()(b_) ^ std::hash<double>()(a_);+      return hash_;+    }++    ATTACH_OPERATIONS();+  };++  ////////////+  // Booleans.+  ////////////+  class Boolean : public Expression {+    ADD_PROPERTY(bool, value);+    size_t hash_;+  public:+    Boolean(ParserState pstate, bool val)+    : Expression(pstate), value_(val),+      hash_(0)+    { concrete_type(BOOLEAN); }+    virtual operator bool() { return value_; }+    string type() { return "bool"; }+    static string type_name() { return "bool"; }+    virtual bool is_false() { return !value_; }++    virtual bool operator==(Expression& rhs) const+    {+      try+      {+        Boolean& e = dynamic_cast<Boolean&>(rhs);+        return e && value() == e.value();+      }+      catch (std::bad_cast&)+      {+        return false;+      }+      catch (...) { throw; }+    }++    virtual size_t hash()+    {+      if (hash_ == 0) hash_ = std::hash<bool>()(value_);+      return hash_;+    }++    ATTACH_OPERATIONS();+  };++  ////////////////////////////////////////////////////////////////////////+  // Abstract base class for Sass string values. Includes interpolated and+  // "flat" strings.+  ////////////////////////////////////////////////////////////////////////+  class String : public Expression {+    ADD_PROPERTY(bool, sass_fix_1291);+  public:+    String(ParserState pstate, bool delayed = false, bool sass_fix_1291 = false)+    : Expression(pstate, delayed), sass_fix_1291_(sass_fix_1291)+    { concrete_type(STRING); }+    static string type_name() { return "string"; }+    virtual ~String() = 0;+    ATTACH_OPERATIONS();+  };+  inline String::~String() { };++  ///////////////////////////////////////////////////////////////////////+  // Interpolated strings. Meant to be reduced to flat strings during the+  // evaluation phase.+  ///////////////////////////////////////////////////////////////////////+  class String_Schema : public String, public Vectorized<Expression*> {+    ADD_PROPERTY(bool, has_interpolants);+    size_t hash_;+  public:+    String_Schema(ParserState pstate, size_t size = 0, bool has_interpolants = false)+    : String(pstate), Vectorized<Expression*>(size), has_interpolants_(has_interpolants), hash_(0)+    { }+    string type() { return "string"; }+    static string type_name() { return "string"; }++    virtual bool operator==(Expression& rhs) const+    {+      try+      {+        String_Schema& e = dynamic_cast<String_Schema&>(rhs);+        if (!(e && length() == e.length())) return false;+        for (size_t i = 0, L = length(); i < L; ++i)+          if (!((*this)[i] == e[i])) return false;+        return true;+      }+      catch (std::bad_cast&)+      {+        return false;+      }+      catch (...) { throw; }+    }++    virtual size_t hash()+    {+      if (hash_ > 0) return hash_;++      for (auto string : elements())+        hash_ ^= string->hash();++      return hash_;+    }++    ATTACH_OPERATIONS();+  };++  ////////////////////////////////////////////////////////+  // Flat strings -- the lowest level of raw textual data.+  ////////////////////////////////////////////////////////+  class String_Constant : public String {+    ADD_PROPERTY(char, quote_mark);+    ADD_PROPERTY(string, value);+  protected:+    size_t hash_;+  public:+    String_Constant(ParserState pstate, string val)+    : String(pstate), quote_mark_(0), value_(read_css_string(val)), hash_(0)+    { }+    String_Constant(ParserState pstate, const char* beg)+    : String(pstate), quote_mark_(0), value_(read_css_string(string(beg))), hash_(0)+    { }+    String_Constant(ParserState pstate, const char* beg, const char* end)+    : String(pstate), quote_mark_(0), value_(read_css_string(string(beg, end-beg))), hash_(0)+    { }+    String_Constant(ParserState pstate, const Token& tok)+    : String(pstate), quote_mark_(0), value_(read_css_string(string(tok.begin, tok.end))), hash_(0)+    { }+    string type() { return "string"; }+    static string type_name() { return "string"; }++    virtual bool operator==(Expression& rhs) const+    {+      try+      {+        String_Constant& e = dynamic_cast<String_Constant&>(rhs);+        return e && value_ == e.value_;+      }+      catch (std::bad_cast&)+      {+        return false;+      }+      catch (...) { throw; }+    }++    virtual size_t hash()+    {+      if (hash_ == 0) hash_ = std::hash<string>()(value_);+      return hash_;+    }++    // static char auto_quote() { return '*'; }+    static char double_quote() { return '"'; }+    static char single_quote() { return '\''; }++    ATTACH_OPERATIONS();+  };++  ////////////////////////////////////////////////////////+  // Possibly quoted string (unquote on instantiation)+  ////////////////////////////////////////////////////////+  class String_Quoted : public String_Constant {+  public:+    String_Quoted(ParserState pstate, string val)+    : String_Constant(pstate, val)+    {+      value_ = unquote(value_, &quote_mark_);+    }+    ATTACH_OPERATIONS();+  };++  /////////////////+  // Media queries.+  /////////////////+  class Media_Query : public Expression,+                      public Vectorized<Media_Query_Expression*> {+    ADD_PROPERTY(String*, media_type);+    ADD_PROPERTY(bool, is_negated);+    ADD_PROPERTY(bool, is_restricted);+  public:+    Media_Query(ParserState pstate,+                String* t = 0, size_t s = 0, bool n = false, bool r = false)+    : Expression(pstate), Vectorized<Media_Query_Expression*>(s),+      media_type_(t), is_negated_(n), is_restricted_(r)+    { }+    ATTACH_OPERATIONS();+  };++  ////////////////////////////////////////////////////+  // Media expressions (for use inside media queries).+  ////////////////////////////////////////////////////+  class Media_Query_Expression : public Expression {+    ADD_PROPERTY(Expression*, feature);+    ADD_PROPERTY(Expression*, value);+    ADD_PROPERTY(bool, is_interpolated);+  public:+    Media_Query_Expression(ParserState pstate,+                           Expression* f, Expression* v, bool i = false)+    : Expression(pstate), feature_(f), value_(v), is_interpolated_(i)+    { }+    ATTACH_OPERATIONS();+  };++  ///////////////////+  // Feature queries.+  ///////////////////+  class Feature_Query : public Expression, public Vectorized<Feature_Query_Condition*> {+  public:+    Feature_Query(ParserState pstate, size_t s = 0)+    : Expression(pstate), Vectorized<Feature_Query_Condition*>(s)+    { }+    ATTACH_OPERATIONS();+  };++  ////////////////////////////////////////////////////////+  // Feature expressions (for use inside feature queries).+  ////////////////////////////////////////////////////////+  class Feature_Query_Condition : public Expression, public Vectorized<Feature_Query_Condition*> {+  public:+    enum Operand { NONE, AND, OR, NOT };+  private:+    ADD_PROPERTY(String*, feature);+    ADD_PROPERTY(Expression*, value);+    ADD_PROPERTY(Operand, operand);+    ADD_PROPERTY(bool, is_root);+  public:+    Feature_Query_Condition(ParserState pstate, size_t s = 0, String* f = 0,+                            Expression* v = 0, Operand o = NONE, bool r = false)+    : Expression(pstate), Vectorized<Feature_Query_Condition*>(s),+      feature_(f), value_(v), operand_(o), is_root_(r)+    { }+    ATTACH_OPERATIONS();+  };++  /////////////////////////////////////////////////+  // At root expressions (for use inside @at-root).+  /////////////////////////////////////////////////+  class At_Root_Expression : public Expression {+  private:+    ADD_PROPERTY(String*, feature);+    ADD_PROPERTY(Expression*, value);+    ADD_PROPERTY(bool, is_interpolated);+  public:+    At_Root_Expression(ParserState pstate, String* f = 0, Expression* v = 0, bool i = false)+    : Expression(pstate), feature_(f), value_(v), is_interpolated_(i)+    { }+    bool exclude(string str)+    {+      To_String to_string;+      bool with = feature() && unquote(feature()->perform(&to_string)).compare("with") == 0;+      List* l = static_cast<List*>(value());+      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]->perform(&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]->perform(&to_string));+          if (v.compare("all") == 0 || v == str) return true;+        }+        return false;+      }+    }+    ATTACH_OPERATIONS();+  };++  ///////////+  // At-root.+  ///////////+  class At_Root_Block : public Has_Block {+    ADD_PROPERTY(At_Root_Expression*, expression);+  public:+    At_Root_Block(ParserState pstate, Block* b = 0, At_Root_Expression* e = 0)+    : Has_Block(pstate, b), expression_(e)+    { statement_type(ATROOT); }+    bool is_hoistable() { return true; }+    bool bubbles() { return true; }+    bool exclude_node(Statement* s) {+      if (s->statement_type() == Statement::DIRECTIVE)+      {+        return expression()->exclude(static_cast<At_Rule*>(s)->keyword().erase(0, 1));+      }+      if (s->statement_type() == Statement::MEDIA)+      {+        return expression()->exclude("media");+      }+      if (s->statement_type() == Statement::RULESET)+      {+        return expression()->exclude("rule");+      }+      if (s->statement_type() == Statement::FEATURE)+      {+        return expression()->exclude("supports");+      }+      if (static_cast<At_Rule*>(s)->is_keyframes())+      {+        return expression()->exclude("keyframes");+      }+      return false;+    }+    ATTACH_OPERATIONS();+  };++  //////////////////+  // The null value.+  //////////////////+  class Null : public Expression {+  public:+    Null(ParserState pstate) : Expression(pstate) { concrete_type(NULL_VAL); }+    string type() { return "null"; }+    static string type_name() { return "null"; }+    bool is_invisible() { return true; }+    operator bool() { return false; }+    bool is_false() { return true; }++    virtual bool operator==(Expression& rhs) const+    {+      return rhs.concrete_type() == NULL_VAL;+    }++    virtual size_t hash()+    {+      return 0;+    }++    ATTACH_OPERATIONS();+  };++  /////////////////////////////////+  // Thunks for delayed evaluation.+  /////////////////////////////////+  class Thunk : public Expression {+    ADD_PROPERTY(Expression*, expression);+    ADD_PROPERTY(Env*, environment);+  public:+    Thunk(ParserState pstate, Expression* exp, Env* env = 0)+    : Expression(pstate), expression_(exp), environment_(env)+    { }+  };++  /////////////////////////////////////////////////////////+  // Individual parameter objects for mixins and functions.+  /////////////////////////////////////////////////////////+  class Parameter : public AST_Node {+    ADD_PROPERTY(string, name);+    ADD_PROPERTY(Expression*, default_value);+    ADD_PROPERTY(bool, is_rest_parameter);+  public:+    Parameter(ParserState pstate,+              string n, Expression* 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);+      }+    }+    ATTACH_OPERATIONS();+  };++  /////////////////////////////////////////////////////////////////////////+  // Parameter lists -- in their own class to facilitate context-sensitive+  // error checking (e.g., ensuring that all optional parameters follow all+  // required parameters).+  /////////////////////////////////////////////////////////////////////////+  class Parameters : public AST_Node, public Vectorized<Parameter*> {+    ADD_PROPERTY(bool, has_optional_parameters);+    ADD_PROPERTY(bool, has_rest_parameter);+  protected:+    void adjust_after_pushing(Parameter* p)+    {+      if (p->default_value()) {+        if (has_rest_parameter_) {+          error("optional parameters may not be combined with variable-length parameters", p->pstate());+        }+        has_optional_parameters_ = true;+      }+      else if (p->is_rest_parameter()) {+        if (has_rest_parameter_) {+          error("functions and mixins cannot have more than one variable-length parameter", p->pstate());+        }+        has_rest_parameter_ = true;+      }+      else {+        if (has_rest_parameter_) {+          error("required parameters must precede variable-length parameters", p->pstate());+        }+        if (has_optional_parameters_) {+          error("required parameters must precede optional parameters", p->pstate());+        }+      }+    }+  public:+    Parameters(ParserState pstate)+    : AST_Node(pstate),+      Vectorized<Parameter*>(),+      has_optional_parameters_(false),+      has_rest_parameter_(false)+    { }+    ATTACH_OPERATIONS();+  };++  //////////////////////////////////////////////////////////////////////////////////////////+  // Additional method on Lists to retrieve values directly or from an encompassed Argument.+  //////////////////////////////////////////////////////////////////////////////////////////+  inline Expression* List::value_at_index(size_t i) { return is_arglist_ ? ((Argument*)(*this)[i])->value() : (*this)[i]; }++  ////////////+  // The Parent Selector Expression.+  ////////////+  class Parent_Selector : public Expression {+    ADD_PROPERTY(Selector*, selector);+  public:+    Parent_Selector(ParserState pstate, Selector* r = 0)+    : Expression(pstate), selector_(r)+    { concrete_type(SELECTOR); }+    virtual Selector* selector() { return selector_; }+    string type() { return "selector"; }+    static string type_name() { return "selector"; }++    ATTACH_OPERATIONS();+  };++  /////////////////////////////////////////+  // Abstract base class for CSS selectors.+  /////////////////////////////////////////+  class Selector : public AST_Node {+    ADD_PROPERTY(bool, has_reference);+    ADD_PROPERTY(bool, has_placeholder);+    // line break before list separator+    ADD_PROPERTY(bool, has_line_feed);+    // line break after list separator+    ADD_PROPERTY(bool, has_line_break);+    // maybe we have optional flag+    ADD_PROPERTY(bool, is_optional);+    // parent block pointers+    ADD_PROPERTY(Block*, last_block);+    ADD_PROPERTY(Media_Block*, media_block);+  public:+    Selector(ParserState pstate, bool r = false, bool h = false)+    : AST_Node(pstate),+      has_reference_(r),+      has_placeholder_(h),+      has_line_feed_(false),+      has_line_break_(false),+      is_optional_(false),+      media_block_(0)+    { }+    virtual ~Selector() = 0;+    // virtual Selector_Placeholder* find_placeholder();+    virtual unsigned long specificity() {+      return Constants::Specificity_Universal;+    };+  };+  inline Selector::~Selector() { }++  /////////////////////////////////////////////////////////////////////////+  // Interpolated selectors -- the interpolated String will be expanded and+  // re-parsed into a normal selector class.+  /////////////////////////////////////////////////////////////////////////+  class Selector_Schema : public Selector {+    ADD_PROPERTY(String*, contents);+  public:+    Selector_Schema(ParserState pstate, String* c)+    : Selector(pstate), contents_(c)+    { }+    ATTACH_OPERATIONS();+  };++  ////////////////////////////////////////////+  // Abstract base class for simple selectors.+  ////////////////////////////////////////////+  class Simple_Selector : public Selector {+  public:+    Simple_Selector(ParserState pstate)+    : Selector(pstate)+    { }+    virtual ~Simple_Selector() = 0;+    virtual Compound_Selector* unify_with(Compound_Selector*, Context&);+    virtual bool is_pseudo_element() { return false; }+    virtual bool is_pseudo_class() { return false; }++    bool operator==(const Simple_Selector& rhs) const;+    inline bool operator!=(const Simple_Selector& rhs) const { return !(*this == rhs); }++    bool operator<(const Simple_Selector& rhs) const;+  };+  inline Simple_Selector::~Simple_Selector() { }++  /////////////////////////////////////+  // Parent references (i.e., the "&").+  /////////////////////////////////////+  class Selector_Reference : public Simple_Selector {+    ADD_PROPERTY(Selector*, selector);+  public:+    Selector_Reference(ParserState pstate, Selector* r = 0)+    : Simple_Selector(pstate), selector_(r)+    { has_reference(true); }+    virtual unsigned long specificity()+    {+      if (!selector()) return 0;+      return selector()->specificity();+    }+    ATTACH_OPERATIONS();+  };++  /////////////////////////////////////////////////////////////////////////+  // Placeholder selectors (e.g., "%foo") for use in extend-only selectors.+  /////////////////////////////////////////////////////////////////////////+  class Selector_Placeholder : public Simple_Selector {+    ADD_PROPERTY(string, name);+  public:+    Selector_Placeholder(ParserState pstate, string n)+    : Simple_Selector(pstate), name_(n)+    { has_placeholder(true); }+    // virtual Selector_Placeholder* find_placeholder();+    ATTACH_OPERATIONS();+  };++  /////////////////////////////////////////////////////////////////////+  // Type selectors (and the universal selector) -- e.g., div, span, *.+  /////////////////////////////////////////////////////////////////////+  class Type_Selector : public Simple_Selector {+    ADD_PROPERTY(string, name);+  public:+    Type_Selector(ParserState pstate, string n)+    : Simple_Selector(pstate), name_(n)+    { }+    virtual unsigned long specificity()+    {+      // ToDo: What is the specificity of the star selector?+      if (name() == "*") return Constants::Specificity_Universal;+      else               return Constants::Specificity_Type;+    }+    virtual Compound_Selector* unify_with(Compound_Selector*, Context&);+    ATTACH_OPERATIONS();+  };++  ////////////////////////////////////////////////+  // Selector qualifiers -- i.e., classes and ids.+  ////////////////////////////////////////////////+  class Selector_Qualifier : public Simple_Selector {+    ADD_PROPERTY(string, name);+  public:+    Selector_Qualifier(ParserState pstate, string n)+    : Simple_Selector(pstate), name_(n)+    { }+    virtual unsigned long specificity()+    {+      if (name()[0] == '#') return Constants::Specificity_ID;+      if (name()[0] == '.') return Constants::Specificity_Class;+      else                  return Constants::Specificity_Type;+    }+    virtual Compound_Selector* unify_with(Compound_Selector*, Context&);+    ATTACH_OPERATIONS();+  };++  ///////////////////////////////////////////////////+  // Attribute selectors -- e.g., [src*=".jpg"], etc.+  ///////////////////////////////////////////////////+  class Attribute_Selector : public Simple_Selector {+    ADD_PROPERTY(string, name);+    ADD_PROPERTY(string, matcher);+    ADD_PROPERTY(String*, value); // might be interpolated+  public:+    Attribute_Selector(ParserState pstate, string n, string m, String* v)+    : Simple_Selector(pstate), name_(n), matcher_(m), value_(v)+    { }+    virtual unsigned long specificity()+    {+      return Constants::Specificity_Attr;+    }+    ATTACH_OPERATIONS();+  };++  //////////////////////////////////////////////////////////////////+  // Pseudo selectors -- e.g., :first-child, :nth-of-type(...), etc.+  //////////////////////////////////////////////////////////////////+  /* '::' starts a pseudo-element, ':' a pseudo-class */+  /* Except :first-line, :first-letter, :before and :after */+  /* Note that pseudo-elements are restricted to one per selector */+  /* and occur only in the last simple_selector_sequence. */+  inline bool is_pseudo_class_element(const string& name)+  {+    return name == ":before"       ||+           name == ":after"        ||+           name == ":first-line"   ||+           name == ":first-letter";+  }++  class Pseudo_Selector : public Simple_Selector {+    ADD_PROPERTY(string, name);+    ADD_PROPERTY(String*, expression);+  public:+    Pseudo_Selector(ParserState pstate, string n, String* expr = 0)+    : Simple_Selector(pstate), name_(n), expression_(expr)+    { }++    // 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.+    // For compatibility with existing style sheets, user agents must also+    // accept the previous one-colon notation for pseudo-elements introduced+    // in CSS levels 1 and 2 (namely, :first-line, :first-letter, :before and+    // :after). This compatibility is not allowed for the new pseudo-elements+    // introduced in this specification.+    virtual bool is_pseudo_element()+    {+      return (name_[0] == ':' && name_[1] == ':')+             || is_pseudo_class_element(name_);+    }+    virtual unsigned long specificity()+    {+      if (is_pseudo_element())+        return Constants::Specificity_Type;+      return Constants::Specificity_Pseudo;+    }+    virtual Compound_Selector* unify_with(Compound_Selector*, Context&);+    ATTACH_OPERATIONS();+  };++  /////////////////////////////////////////////////+  // Wrapped selector -- pseudo selector that takes a list of selectors as argument(s) e.g., :not(:first-of-type), :-moz-any(ol p.blah, ul, menu, dir)+  /////////////////////////////////////////////////+  class Wrapped_Selector : public Simple_Selector {+    ADD_PROPERTY(string, name);+    ADD_PROPERTY(Selector*, selector);+  public:+    Wrapped_Selector(ParserState pstate, string n, Selector* sel)+    : Simple_Selector(pstate), name_(n), selector_(sel)+    { }+    // Selectors inside the negation pseudo-class are counted like any+    // other, but the negation itself does not count as a pseudo-class.+    virtual unsigned long specificity()+    {+      return selector_ ? selector_->specificity() : 0;+    }+    ATTACH_OPERATIONS();+  };++  struct Complex_Selector_Pointer_Compare {+    bool operator() (const Complex_Selector* const pLeft, const Complex_Selector* const pRight) const;+  };++  ////////////////////////////////////////////////////////////////////////////+  // Simple selector sequences. Maintains flags indicating whether it contains+  // any parent references or placeholders, to simplify expansion.+  ////////////////////////////////////////////////////////////////////////////+  typedef set<Complex_Selector*, Complex_Selector_Pointer_Compare> SourcesSet;+  class Compound_Selector : public Selector, public Vectorized<Simple_Selector*> {+  private:+    SourcesSet sources_;+  protected:+    void adjust_after_pushing(Simple_Selector* s)+    {+      if (s->has_reference())   has_reference(true);+      if (s->has_placeholder()) has_placeholder(true);+    }+  public:+    Compound_Selector(ParserState pstate, size_t s = 0)+    : Selector(pstate),+      Vectorized<Simple_Selector*>(s)+    { }++    Compound_Selector* unify_with(Compound_Selector* rhs, Context& ctx);+    // virtual Selector_Placeholder* find_placeholder();+    Simple_Selector* base()+    {+      // Implement non-const in terms of const. Safe to const_cast since this method is non-const+      return const_cast<Simple_Selector*>(static_cast<const Compound_Selector*>(this)->base());+    }+    const Simple_Selector* base() const {+      if (length() > 0 && typeid(*(*this)[0]) == typeid(Type_Selector))+        return (*this)[0];+      return 0;+    }+    bool is_superselector_of(Compound_Selector* rhs);+    virtual unsigned long specificity()+    {+      int sum = 0;+      for (size_t i = 0, L = length(); i < L; ++i)+      { sum += (*this)[i]->specificity(); }+      return sum;+    }+    bool is_empty_reference()+    {+      return length() == 1 &&+             typeid(*(*this)[0]) == typeid(Selector_Reference) &&+             !static_cast<Selector_Reference*>((*this)[0])->selector();+    }+    vector<string> to_str_vec(); // sometimes need to convert to a flat "by-value" data structure++    bool operator<(const Compound_Selector& rhs) const;++    bool operator==(const Compound_Selector& rhs) const;+    inline bool operator!=(const Compound_Selector& rhs) const { return !(*this == rhs); }++    SourcesSet& sources() { return sources_; }+    void clearSources() { sources_.clear(); }+    void mergeSources(SourcesSet& sources, Context& ctx);++    Compound_Selector* clone(Context&) const; // does not clone the Simple_Selector*s++    Compound_Selector* minus(Compound_Selector* rhs, Context& ctx);+    ATTACH_OPERATIONS();+  };++  ////////////////////////////////////////////////////////////////////////////+  // General selectors -- i.e., simple sequences combined with one of the four+  // CSS selector combinators (">", "+", "~", and whitespace). Essentially a+  // linked list.+  ////////////////////////////////////////////////////////////////////////////+  class Complex_Selector : public Selector {+  public:+    enum Combinator { ANCESTOR_OF, PARENT_OF, PRECEDES, ADJACENT_TO };+  private:+    ADD_PROPERTY(Combinator, combinator);+    ADD_PROPERTY(Compound_Selector*, head);+    ADD_PROPERTY(Complex_Selector*, tail);+  public:+    Complex_Selector(ParserState pstate,+                     Combinator c,+                     Compound_Selector* h,+                     Complex_Selector* t)+    : Selector(pstate), combinator_(c), head_(h), tail_(t)+    {+      if ((h && h->has_reference())   || (t && t->has_reference()))   has_reference(true);+      if ((h && h->has_placeholder()) || (t && t->has_placeholder())) has_placeholder(true);+    }+    Compound_Selector* base();+    Complex_Selector* context(Context&);+    Complex_Selector* innermost();+    size_t length();+    bool is_superselector_of(Compound_Selector*);+    bool is_superselector_of(Complex_Selector*);+    // virtual Selector_Placeholder* find_placeholder();+    Combinator clear_innermost();+    void set_innermost(Complex_Selector*, Combinator);+    virtual unsigned long specificity() const+    {+      int sum = 0;+      if (head()) sum += head()->specificity();+      if (tail()) sum += tail()->specificity();+      return sum;+    }+    bool operator<(const Complex_Selector& rhs) const;+    bool operator==(const Complex_Selector& rhs) const;+    inline bool operator!=(const Complex_Selector& rhs) const { return !(*this == rhs); }+    SourcesSet 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;++      Compound_Selector* pHead = head();+      Complex_Selector*  pTail = tail();++      if (pHead) {+        SourcesSet& headSources = pHead->sources();+        srcs.insert(headSources.begin(), headSources.end());+      }++      if (pTail) {+        SourcesSet tailSources = pTail->sources();+        srcs.insert(tailSources.begin(), tailSources.end());+      }++      return srcs;+    }+    void addSources(SourcesSet& sources, Context& ctx) {+      // members.map! {|m| m.is_a?(SimpleSequence) ? m.with_more_sources(sources) : m}+      Complex_Selector* pIter = this;+      while (pIter) {+        Compound_Selector* pHead = pIter->head();++        if (pHead) {+          pHead->mergeSources(sources, ctx);+        }++        pIter = pIter->tail();+      }+    }+    void clearSources() {+      Complex_Selector* pIter = this;+      while (pIter) {+        Compound_Selector* pHead = pIter->head();++        if (pHead) {+          pHead->clearSources();+        }++        pIter = pIter->tail();+      }+    }+    Complex_Selector* clone(Context&) const;      // does not clone Compound_Selector*s+    Complex_Selector* cloneFully(Context&) const; // clones Compound_Selector*s+    // vector<Compound_Selector*> to_vector();+    ATTACH_OPERATIONS();+  };++  typedef deque<Complex_Selector*> ComplexSelectorDeque;++  ///////////////////////////////////+  // Comma-separated selector groups.+  ///////////////////////////////////+  class Selector_List : public Selector, public Vectorized<Complex_Selector*> {+#ifdef DEBUG+    ADD_PROPERTY(string, mCachedSelector);+#endif+    ADD_PROPERTY(vector<string>, wspace);+  protected:+    void adjust_after_pushing(Complex_Selector* c);+  public:+    Selector_List(ParserState pstate, size_t s = 0)+    : Selector(pstate), Vectorized<Complex_Selector*>(s), wspace_(0)+    { }+    // virtual Selector_Placeholder* find_placeholder();+    virtual unsigned long specificity()+    {+      unsigned long sum = 0;+      for (size_t i = 0, L = length(); i < L; ++i)+      { sum += (*this)[i]->specificity(); }+      return sum;+    }+    // vector<Complex_Selector*> members() { return elements_; }+    ATTACH_OPERATIONS();+  };++  inline bool Ruleset::is_invisible() {+    bool is_invisible = true;+    Selector_List* sl = static_cast<Selector_List*>(selector());+    for (size_t i = 0, L = sl->length(); i < L && is_invisible; ++i)+      is_invisible &= (*sl)[i]->has_placeholder();+    return is_invisible;+  }+++  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;+    }+  }++}++#ifdef __clang__++#pragma clang diagnostic pop++#endif++#endif
+ libsass/ast_def_macros.hpp view
@@ -0,0 +1,21 @@+#ifndef SASS_AST_DEF_MACROS_H+#define SASS_AST_DEF_MACROS_H++#define ATTACH_OPERATIONS()\+virtual void perform(Operation<void>* op) { (*op)(this); }\+virtual AST_Node* perform(Operation<AST_Node*>* op) { return (*op)(this); }\+virtual Statement* perform(Operation<Statement*>* op) { return (*op)(this); }\+virtual Expression* perform(Operation<Expression*>* op) { return (*op)(this); }\+virtual Selector* perform(Operation<Selector*>* op) { return (*op)(this); }\+virtual string perform(Operation<string>* op) { return (*op)(this); }\+virtual Sass_Value* perform(Operation<Sass_Value*>* op) { return (*op)(this); }++#define ADD_PROPERTY(type, name)\+protected:\+  type name##_;\+public:\+  type name() const        { return name##_; }\+  type name(type name##__) { return name##_ = name##__; }\+private:++#endif
+ libsass/ast_factory.hpp view
@@ -0,0 +1,92 @@+#ifndef SASS_AST_FACTORY_H+#define SASS_AST_FACTORY_H++#include <vector>++#include "ast.hpp"++namespace Sass {+  using namespace std;++  class AST_Factory {+    vector<AST_Node*> nodes;+  public:+    // statements+    Block* new_Block(string p, size_t l, size_t s = 0, bool r = false);+    Ruleset* new_Ruleset(string p, size_t l, Selector* s, Block* b);+    Propset* new_Propset(string p, size_t l, String* pf, Block* b);+    Feature_Query* new_Feature_Query(string p, size_t l, Feature_Query* f, Block* b);+    Media_Query* new_Media_Query(string p, size_t l, List* q, Block* b);+    At_Root_Block* new_At_Root_Block(string p, size_t l, Selector* sel, Block* b);+    At_Rule* new_At_Rule(string p, size_t l, string kwd, Selector* sel, Block* b);+    Keyframe_Rule* new_Keyframe_Rule(string p, size_t l, Block* b);+    Declaration* new_Declaration(string p, size_t l, String* prop, List* vals);+    Assignment* new_Assignment(string p, size_t l, string var, Expression* val, bool guarded = false);+    Import<Function_Call*>* new_CSS_Import(string p, size_t l, Function_Call* loc);+    Import<String*>* new_SASS_Import(string p, size_t l, String* loc);+    Warning* new_Warning(string p, size_t l, Expression* msg);+    Error* new_Error(string p, size_t l, Expression* msg);+    Debug* new_Debug(string p, size_t l, Expression* val);+    Comment* new_Comment(string p, size_t l, String* txt);+    If* new_If(string p, size_t l, Expression* pred, Block* con, Block* alt = 0);+    For* new_For(string p, size_t l, string var, Expression* lo, Expression* hi, Block* b, bool inc);+    Each* new_Each(string p, size_t l, vector<string> vars, Expression* lst, Block* b);+    While* new_While(string p, size_t l, Expression* pred, Block* b);+    Extension* new_Extension(string p, size_t l, Selector* s);+    Definition<MIXIN>* new_Mixin_Definition(string p, size_t l, string n, Parameters* params, Block* b);+    Definition<FUNCTION>* new_Function_Definition(string p, size_t l, string n, Parameters* params, Block* b);+    Mixin_Call* new_Mixin_Call(string p, size_t l, string n, Arguments* args, Block* b = 0);+    // expressions+    List* new_List(string p, size_t l, size_t size = 0, List::Separator sep = List::space, bool argl = false);+    Map* new_Map(string p, size_t l, size_t size = 0);+    Binary_Expression<AND>* new_And(string p, size_t l, Expression* lhs, Expression* rhs);+    Binary_Expression<OR>* new_Or(string p, size_t l, Expression* lhs, Expression* rhs);+    Binary_Expression<EQ>* new_Eq(string p, size_t l, Expression* lhs, Expression* rhs);+    Binary_Expression<NEQ>* new_Neq(string p, size_t l, Expression* lhs, Expression* rhs);+    Binary_Expression<GT>* new_Gt(string p, size_t l, Expression* lhs, Expression* rhs);+    Binary_Expression<GTE>* new_Gte(string p, size_t l, Expression* lhs, Expression* rhs);+    Binary_Expression<LT>* new_Lt(string p, size_t l, Expression* lhs, Expression* rhs);+    Binary_Expression<LTE>* new_Lte(string p, size_t l, Expression* lhs, Expression* rhs);+    Binary_Expression<ADD>* new_Add(string p, size_t l, Expression* lhs, Expression* rhs);+    Binary_Expression<SUB>* new_Sub(string p, size_t l, Expression* lhs, Expression* rhs);+    Binary_Expression<MUL>* new_Mul(string p, size_t l, Expression* lhs, Expression* rhs);+    Binary_Expression<DIV>* new_Div(string p, size_t l, Expression* lhs, Expression* rhs);+    Negation* new_Negation(string p, size_t l, Expression* o);+    Function_Call* new_Function_Call(string p, size_t l, String* n, Arguments* args);+    Variable* new_Variable(string p, size_t l, string n);+    Textual<NUMBER>* new_Textual_Number(string p, size_t l, string val);+    Textual<PERCENTAGE>* new_Textual_Percentage(string p, size_t l, string val);+    Textual<DIMENSION>* new_Textual_Dimension(string p, size_t l, string val);+    Textual<HEX>* new_Textual_Hex(string p, size_t l, string val);+    Number* new_Number(string p, size_t l, double val);+    Percentage* new_Percentage(string p, size_t l, double val);+    Dimension* new_Dimension(string p, size_t l, double val, string unit);+    Color* new_Color(string p, size_t l, double r, double g, double b, double a = 1, string disp = "");+    Boolean* new_Boolean(string p, size_t l, bool val);+    String_Schema* new_String_Schema(string p, size_t l, size_t size = 0);+    String_Constant* new_String_Constant(string p, size_t l, string val);+    String_Constant* new_String_Constant(string p, size_t l, const char* beg);+    String_Constant* new_String_Constant(string p, size_t l, const char* beg, const char* end);+    Feature_Query_Condition* new_Feature_Query_Condition(string p, size_t l, String* f, Expression* v);+    Media_Expression* new_Media_Expression(string p, size_t l, String* f, Expression* v);+    Parent_Selector* new_Parent_Selector(string p, size_t l, Selector* s);+    // parameters and arguments+    Parameter* new_Parameter(string p, size_t l, string n, Expression* def = 0, bool rest = false);+    Parameters* new_Parameters(string p, size_t l);+    Argument* new_Argument(string p, size_t l, Expression* val, string n = "", bool rest = false);+    Arguments* new_Arguments(string p, size_t l);+    // selectors+    Selector_Schema* new_Selector_Schema(string p, size_t l, String* c);+    Attribute_Selector* new_Attribute_Selector(string p, size_t l, string n, string m, String* v);+    Simple_Selector* new_Simple_Selector(string p, size_t l, string c);+    Reference_Selector* new_Reference_Selector(string p, size_t l);+    Placeholder_Selector* new_Placeholder_Selector(string p, size_t l, string n);+    Pseudo_Selector* new_Pseudo_Selector(string p, size_t l, string n, Expression* expr = 0);+    Wrapped_Selector* new_Wrapped_Selector(string p, size_t l, string n, Simple_Base* sel);+    Compound_Selector* new_Compound_Selector(string p, size_t l, size_t s = 0);+    Complex_Selector* new_Complex_Selector(string p, size_t l, Complex_Selector::Combinator c, Complex_Selector* ctx, Compound_Selector* sel);+    Selector_List* new_Selector_List(string p, size_t l, size_t s = 0);+  };+}++#endif
+ libsass/ast_fwd_decl.hpp view
@@ -0,0 +1,85 @@+#ifndef SASS_AST_FWD_DECL_H+#define SASS_AST_FWD_DECL_H++/////////////////////////////////////////////+// Forward declarations for the AST visitors.+/////////////////////////////////////////////+namespace Sass {++  enum Output_Style { NESTED, EXPANDED, COMPACT, COMPRESSED, FORMATTED };++  class AST_Node;+  // statements+  class Statement;+  class Block;+  class Ruleset;+  class Propset;+  class Bubble;+  class Media_Block;+  class Feature_Block;+  class At_Rule;+  class Keyframe_Rule;+  class At_Root_Block;+  class Declaration;+  class Assignment;+  class Import;+  class Import_Stub;+  class Warning;+  class Error;+  class Debug;+  class Comment;+  class If;+  class For;+  class Each;+  class While;+  class Return;+  class Content;+  class Extension;+  class Definition;+  class Mixin_Call;+  // expressions+  class Expression;+  class List;+  class Map;+  class Binary_Expression;+  class Unary_Expression;+  class Function_Call;+  class Function_Call_Schema;+  class Variable;+  class Textual;+  class Number;+  class Color;+  class Boolean;+  class String_Schema;+  class String;+  class String_Constant;+  class String_Quoted;+  class Media_Query;+  class Media_Query_Expression;+  class Feature_Query;+  class Feature_Query_Condition;+  class At_Root_Expression;+  class Null;+  class Parent_Selector;+  // parameters and arguments+  class Parameter;+  class Parameters;+  class Argument;+  class Arguments;+  // selectors+  class Selector;+  class Selector_Schema;+  class Selector_Reference;+  class Selector_Placeholder;+  class Type_Selector;+  class Selector_Qualifier;+  class Attribute_Selector;+  class Pseudo_Selector;+  class Wrapped_Selector;+  class Compound_Selector;+  class Complex_Selector;+  class Selector_List;++}++#endif
+ libsass/b64/cencode.h view
@@ -0,0 +1,32 @@+/*+cencode.h - c header for a base64 encoding algorithm++This is part of the libb64 project, and has been placed in the public domain.+For details, see http://sourceforge.net/projects/libb64+*/++#ifndef BASE64_CENCODE_H+#define BASE64_CENCODE_H++typedef enum+{+	step_A, step_B, step_C+} base64_encodestep;++typedef struct+{+	base64_encodestep step;+	char result;+	int stepcount;+} base64_encodestate;++void base64_init_encodestate(base64_encodestate* state_in);++char base64_encode_value(char value_in);++int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in);++int base64_encode_blockend(char* code_out, base64_encodestate* state_in);++#endif /* BASE64_CENCODE_H */+
+ libsass/b64/encode.h view
@@ -0,0 +1,77 @@+// :mode=c++:+/*+encode.h - c++ wrapper for a base64 encoding algorithm++This is part of the libb64 project, and has been placed in the public domain.+For details, see http://sourceforge.net/projects/libb64+*/+#ifndef BASE64_ENCODE_H+#define BASE64_ENCODE_H++#include <iostream>++namespace base64+{+	extern "C"+	{+		#include "cencode.h"+	}++	struct encoder+	{+		base64_encodestate _state;+		int _buffersize;++		encoder(int buffersize_in = BUFFERSIZE)+		: _buffersize(buffersize_in)+		{}++		int encode(char value_in)+		{+			return base64_encode_value(value_in);+		}++		int encode(const char* code_in, const int length_in, char* plaintext_out)+		{+			return base64_encode_block(code_in, length_in, plaintext_out, &_state);+		}++		int encode_end(char* plaintext_out)+		{+			return base64_encode_blockend(plaintext_out, &_state);+		}++		void encode(std::istream& istream_in, std::ostream& ostream_in)+		{+			base64_init_encodestate(&_state);+			//+			const int N = _buffersize;+			char* plaintext = new char[N];+			char* code = new char[2*N];+			int plainlength;+			int codelength;++			do+			{+				istream_in.read(plaintext, N);+				plainlength = static_cast<int>(istream_in.gcount());+				//+				codelength = encode(plaintext, plainlength, code);+				ostream_in.write(code, codelength);+			}+			while (istream_in.good() && plainlength > 0);++			codelength = encode_end(code);+			ostream_in.write(code, codelength);+			//+			base64_init_encodestate(&_state);++			delete [] code;+			delete [] plaintext;+		}+	};++} // namespace base64++#endif // BASE64_ENCODE_H+
+ libsass/backtrace.hpp view
@@ -0,0 +1,77 @@+#ifndef SASS_BACKTRACE_H+#define SASS_BACKTRACE_H++#include <sstream>++#include "file.hpp"+#include "position.hpp"++namespace Sass {++  using namespace std;++  struct Backtrace {++    Backtrace*  parent;+    ParserState pstate;+    string      caller;++    Backtrace(Backtrace* prn, ParserState pstate, string c)+    : parent(prn),+      pstate(pstate),+      caller(c)+    { }++    string to_string(bool warning = false)+    {+      size_t i = -1;+      stringstream ss;+      string cwd(Sass::File::get_cwd());+      Backtrace* this_point = this;++      if (!warning) ss << endl << "Backtrace:";+      // the first tracepoint (which is parent-less) is an empty placeholder+      while (this_point->parent) {++        // make path relative to the current directory+        string rel_path(Sass::File::resolve_relative_path(this_point->pstate.path, cwd, cwd));++        if (warning) {+          ss << endl+             << "\t"+             << (++i == 0 ? "on" : "from")+             << " line "+             << this_point->pstate.line+             << " of "+             << rel_path;+        } else {+          ss << endl+             << "\t"+             << rel_path+             << ":"+             << this_point->pstate.line+             << 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;+    }++  };++}++#endif
+ libsass/base64vlq.cpp view
@@ -0,0 +1,43 @@+#include "base64vlq.hpp"++namespace Sass {++  string Base64VLQ::encode(const int number) const+  {+    string encoded = "";++    int vlq = to_vlq_signed(number);++    do {+      int digit = vlq & VLQ_BASE_MASK;+      vlq >>= VLQ_BASE_SHIFT;+      if (vlq > 0) {+        digit |= VLQ_CONTINUATION_BIT;+      }+      encoded += base64_encode(digit);+    } while (vlq > 0);++    return encoded;+  }++  char Base64VLQ::base64_encode(const int number) const+  {+    int index = number;+    if (index < 0) index = 0;+    if (index > 63) index = 63;+    return CHARACTERS[index];+  }++  int Base64VLQ::to_vlq_signed(const int number) const+  {+    return (number < 0) ? ((-number) << 1) + 1 : (number << 1) + 0;+  }++  const char* Base64VLQ::CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";++  const int Base64VLQ::VLQ_BASE_SHIFT = 5;+  const int Base64VLQ::VLQ_BASE = 1 << VLQ_BASE_SHIFT;+  const int Base64VLQ::VLQ_BASE_MASK = VLQ_BASE - 1;+  const int Base64VLQ::VLQ_CONTINUATION_BIT = VLQ_BASE;++}
+ libsass/base64vlq.hpp view
@@ -0,0 +1,31 @@+#ifndef SASS_BASE64VLQ_H+#define SASS_BASE64VLQ_H++#include <string>++namespace Sass {+  using std::string;++  class Base64VLQ {++  public:++    string encode(const int number) const;++  private:++    char base64_encode(const int number) const;++    int to_vlq_signed(const int number) const;++    static const char* CHARACTERS;++    static const int VLQ_BASE_SHIFT;+    static const int VLQ_BASE;+    static const int VLQ_BASE_MASK;+    static const int VLQ_CONTINUATION_BIT;+  };++}++#endif
+ libsass/bind.cpp view
@@ -0,0 +1,186 @@+#include "bind.hpp"+#include "ast.hpp"+#include "context.hpp"+#include "eval.hpp"+#include <map>+#include <iostream>+#include <sstream>+#include "to_string.hpp"++namespace Sass {+  using namespace std;++  void bind(string callee, Parameters* ps, Arguments* as, Context& ctx, Env* env, Eval* eval)+  {+    map<string, Parameter*> param_map;++    // Set up a map to ensure named arguments refer to actual parameters. Also+    // eval each default value left-to-right, wrt env, populating env as we go.+    for (size_t i = 0, L = ps->length(); i < L; ++i) {+      Parameter*  p = (*ps)[i];+      param_map[p->name()] = p;+      // if (p->default_value()) {+      //   env->local_frame()[p->name()] = p->default_value()->perform(eval->with(env));+      // }+    }++    // plug in all args; if we have leftover params, deal with it later+    size_t ip = 0, LP = ps->length();+    size_t ia = 0, LA = as->length();+    while (ia < LA) {+      if (ip >= LP) {+        stringstream msg;+        msg << callee << " only takes " << LP << " arguments; "+            << "given " << LA;+        error(msg.str(), as->pstate());+      }+      Parameter* p = (*ps)[ip];+      Argument*  a = (*as)[ia];++      // If the current parameter is the rest parameter, process and break the loop+      if (p->is_rest_parameter()) {+        if (a->is_rest_argument()) {+          // rest param and rest arg -- just add one to the other+          if (env->has_local(p->name())) {+            *static_cast<List*>(env->local_frame()[p->name()])+            += static_cast<List*>(a->value());+          }+          else {+            env->local_frame()[p->name()] = a->value();+          }+        } else {++          // copy all remaining arguments into the rest parameter, preserving names+          List* arglist = new (ctx.mem) List(p->pstate(),+                                             0,+                                             List::COMMA,+                                             true);+          env->local_frame()[p->name()] = arglist;+          while (ia < LA) {+            a = (*as)[ia];+            (*arglist) << new (ctx.mem) Argument(a->pstate(),+                                                 a->value(),+                                                 a->name(),+                                                 false);+            ++ia;+          }+        }+        ++ip;+        break;+      }++      // If the current argument is the rest argument, extract a value for processing+      else if (a->is_rest_argument()) {+        // normal param and rest arg+        List* arglist = static_cast<List*>(a->value());+        // empty rest arg - treat all args as default values+        if (!arglist->length()) {+          break;+        }+        // otherwise move one of the rest args into the param, converting to argument if necessary+        if (arglist->is_arglist()) {+          a = static_cast<Argument*>((*arglist)[0]);+        } else {+          Expression* a_to_convert = (*arglist)[0];+          a = new (ctx.mem) Argument(a_to_convert->pstate(), a_to_convert, "", false);+        }+        arglist->elements().erase(arglist->elements().begin());+        if (!arglist->length() || (!arglist->is_arglist() && ip + 1 == LP)) {+          ++ia;+        }+      } else if (a->is_keyword_argument()) {+        Map* argmap = static_cast<Map*>(a->value());++        for (auto key : argmap->keys()) {+          string name = "$" + unquote(static_cast<String_Constant*>(key)->value());++          if (!param_map.count(name)) {+            stringstream msg;+            msg << callee << " has no parameter named " << name;+            error(msg.str(), a->pstate());+          }+          env->local_frame()[name] = argmap->at(key);+        }+        ++ia;+        continue;+      } else {+        ++ia;+      }++      if (a->name().empty()) {+        if (env->has_local(p->name())) {+          stringstream msg;+          msg << "parameter " << p->name()+          << " provided more than once in call to " << callee;+          error(msg.str(), a->pstate());+        }+        // ordinal arg -- bind it to the next param+        env->local_frame()[p->name()] = a->value();+        ++ip;+      }+      else {+        // named arg -- bind it to the appropriately named param+        if (!param_map.count(a->name())) {+          stringstream msg;+          msg << callee << " has no parameter named " << a->name();+          error(msg.str(), a->pstate());+        }+        if (param_map[a->name()]->is_rest_parameter()) {+          stringstream msg;+          msg << "argument " << a->name() << " of " << callee+              << "cannot be used as named argument";+          error(msg.str(), a->pstate());+        }+        if (env->has_local(a->name())) {+          stringstream msg;+          msg << "parameter " << p->name()+              << "provided more than once in call to " << callee;+          error(msg.str(), a->pstate());+        }+        env->local_frame()[a->name()] = a->value();+      }+    }++    // If we make it here, we're out of args but may have leftover params.+    // That's only okay if they have default values, or were already bound by+    // named arguments, or if it's a single rest-param.+    for (size_t i = ip; i < LP; ++i) {+      To_String to_string(&ctx);+      Parameter* leftover = (*ps)[i];+      // cerr << "env for default params:" << endl;+      // env->print();+      // cerr << "********" << endl;+      if (!env->has_local(leftover->name())) {+        if (leftover->is_rest_parameter()) {+          env->local_frame()[leftover->name()] = new (ctx.mem) List(leftover->pstate(),+                                                                      0,+                                                                      List::COMMA,+                                                                      true);+        }+        else if (leftover->default_value()) {+          // make sure to eval the default value in the env that we've been populating+          Env* old_env = eval->env;+          Backtrace* old_bt = eval->backtrace;+          Contextualize* old_context = eval->contextualize;+          Expression* dv = leftover->default_value()->perform(eval->with(env, eval->backtrace));+          eval->env = old_env;+          eval->backtrace = old_bt;+          eval->contextualize = old_context;+          // dv->perform(&to_string);+          env->local_frame()[leftover->name()] = dv;+        }+        else {+          // param is unbound and has no default value -- error+          stringstream msg;+          msg << "required parameter " << leftover->name()+              << " is missing in call to " << callee;+          error(msg.str(), as->pstate());+        }+      }+    }++    return;+  }+++}
+ libsass/bind.hpp view
@@ -0,0 +1,18 @@+#ifndef SASS_BIND_H+#define SASS_BIND_H++#include <string>+#include "environment.hpp"++namespace Sass {+  class   AST_Node;+  class   Parameters;+  class   Arguments;+  class   Context;+  class   Eval;+  typedef Environment<AST_Node*> Env;++  void bind(string caller, Parameters*, Arguments*, Context&, Env*, Eval*);+}++#endif
+ libsass/cencode.c view
@@ -0,0 +1,102 @@+/*+cencoder.c - c source to a base64 encoding algorithm implementation++This is part of the libb64 project, and has been placed in the public domain.+For details, see http://sourceforge.net/projects/libb64+*/++#include "b64/cencode.h"++void base64_init_encodestate(base64_encodestate* state_in)+{+	state_in->step = step_A;+	state_in->result = 0;+	state_in->stepcount = 0;+}++char base64_encode_value(char value_in)+{+	static const char* encoding = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";+	if (value_in > 63) return '=';+	return encoding[(int)value_in];+}++int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in)+{+	const char* plainchar = plaintext_in;+	const char* const plaintextend = plaintext_in + length_in;+	char* codechar = code_out;+	char result;+	char fragment;++	result = state_in->result;++	switch (state_in->step)+	{+		while (1)+		{+	case step_A:+			if (plainchar == plaintextend)+			{+				state_in->result = result;+				state_in->step = step_A;+				return codechar - code_out;+			}+			fragment = *plainchar++;+			result = (fragment & 0x0fc) >> 2;+			*codechar++ = base64_encode_value(result);+			result = (fragment & 0x003) << 4;+	case step_B:+			if (plainchar == plaintextend)+			{+				state_in->result = result;+				state_in->step = step_B;+				return codechar - code_out;+			}+			fragment = *plainchar++;+			result |= (fragment & 0x0f0) >> 4;+			*codechar++ = base64_encode_value(result);+			result = (fragment & 0x00f) << 2;+	case step_C:+			if (plainchar == plaintextend)+			{+				state_in->result = result;+				state_in->step = step_C;+				return codechar - code_out;+			}+			fragment = *plainchar++;+			result |= (fragment & 0x0c0) >> 6;+			*codechar++ = base64_encode_value(result);+			result  = (fragment & 0x03f) >> 0;+			*codechar++ = base64_encode_value(result);++			++(state_in->stepcount);+		}+	}+	/* control should not reach here */+	return codechar - code_out;+}++int base64_encode_blockend(char* code_out, base64_encodestate* state_in)+{+	char* codechar = code_out;++	switch (state_in->step)+	{+	case step_B:+		*codechar++ = base64_encode_value(state_in->result);+		*codechar++ = '=';+		*codechar++ = '=';+		break;+	case step_C:+		*codechar++ = base64_encode_value(state_in->result);+		*codechar++ = '=';+		break;+	case step_A:+		break;+	}+	*codechar++ = '\n';++	return codechar - code_out;+}+
+ libsass/color_names.hpp view
@@ -0,0 +1,327 @@+#ifndef SASS_COLOR_NAMES_H+#define SASS_COLOR_NAMES_H++namespace Sass {++  /*+   * Color names are processed in order. For color names with alternate aliases+   * (i.e. gray & grey), the canonical name should be listed last, so it takes+   * precedence.+   */+  const char* color_names[] =+  {+    "aliceblue",+    "antiquewhite",+    "aqua",+    "aquamarine",+    "azure",+    "beige",+    "bisque",+    "black",+    "blanchedalmond",+    "blue",+    "blueviolet",+    "brown",+    "burlywood",+    "cadetblue",+    "chartreuse",+    "chocolate",+    "coral",+    "cornflowerblue",+    "cornsilk",+    "crimson",+    "cyan",+    "darkblue",+    "darkcyan",+    "darkgoldenrod",+    "darkgrey",+    "darkgray",+    "darkgreen",+    "darkkhaki",+    "darkmagenta",+    "darkolivegreen",+    "darkorange",+    "darkorchid",+    "darkred",+    "darksalmon",+    "darkseagreen",+    "darkslateblue",+    "darkslategrey",+    "darkslategray",+    "darkturquoise",+    "darkviolet",+    "deeppink",+    "deepskyblue",+    "dimgrey",+    "dimgray",+    "dodgerblue",+    "firebrick",+    "floralwhite",+    "forestgreen",+    "fuchsia",+    "gainsboro",+    "ghostwhite",+    "gold",+    "goldenrod",+    "grey",+    "gray",+    "green",+    "greenyellow",+    "honeydew",+    "hotpink",+    "indianred",+    "indigo",+    "ivory",+    "khaki",+    "lavender",+    "lavenderblush",+    "lawngreen",+    "lemonchiffon",+    "lightblue",+    "lightcoral",+    "lightcyan",+    "lightgoldenrodyellow",+    "lightgrey",+    "lightgray",+    "lightgreen",+    "lightpink",+    "lightsalmon",+    "lightseagreen",+    "lightskyblue",+    "lightslategrey",+    "lightslategray",+    "lightsteelblue",+    "lightyellow",+    "lime",+    "limegreen",+    "linen",+    "magenta",+    "maroon",+    "mediumaquamarine",+    "mediumblue",+    "mediumorchid",+    "mediumpurple",+    "mediumseagreen",+    "mediumslateblue",+    "mediumspringgreen",+    "mediumturquoise",+    "mediumvioletred",+    "midnightblue",+    "mintcream",+    "mistyrose",+    "moccasin",+    "navajowhite",+    "navy",+    "oldlace",+    "olive",+    "olivedrab",+    "orange",+    "orangered",+    "orchid",+    "palegoldenrod",+    "palegreen",+    "paleturquoise",+    "palevioletred",+    "papayawhip",+    "peachpuff",+    "peru",+    "pink",+    "plum",+    "powderblue",+    "purple",+    "red",+    "rosybrown",+    "royalblue",+    "saddlebrown",+    "salmon",+    "sandybrown",+    "seagreen",+    "seashell",+    "sienna",+    "silver",+    "skyblue",+    "slateblue",+    "slategrey",+    "slategray",+    "snow",+    "springgreen",+    "steelblue",+    "tan",+    "teal",+    "thistle",+    "tomato",+    "turquoise",+    "violet",+    "wheat",+    "white",+    "whitesmoke",+    "yellow",+    "yellowgreen",+    // rebeccapurple+    "rebeccapurple",+    // transparent+    "transparent",+    // sentinel value+    0+  };++  const double color_values[] =+  {+    0xf0, 0xf8, 0xff, 1,+    0xfa, 0xeb, 0xd7, 1,+    0x00, 0xff, 0xff, 1,+    0x7f, 0xff, 0xd4, 1,+    0xf0, 0xff, 0xff, 1,+    0xf5, 0xf5, 0xdc, 1,+    0xff, 0xe4, 0xc4, 1,+    0x00, 0x00, 0x00, 1,+    0xff, 0xeb, 0xcd, 1,+    0x00, 0x00, 0xff, 1,+    0x8a, 0x2b, 0xe2, 1,+    0xa5, 0x2a, 0x2a, 1,+    0xde, 0xb8, 0x87, 1,+    0x5f, 0x9e, 0xa0, 1,+    0x7f, 0xff, 0x00, 1,+    0xd2, 0x69, 0x1e, 1,+    0xff, 0x7f, 0x50, 1,+    0x64, 0x95, 0xed, 1,+    0xff, 0xf8, 0xdc, 1,+    0xdc, 0x14, 0x3c, 1,+    0x00, 0xff, 0xff, 1,+    0x00, 0x00, 0x8b, 1,+    0x00, 0x8b, 0x8b, 1,+    0xb8, 0x86, 0x0b, 1,+    0xa9, 0xa9, 0xa9, 1,+    0xa9, 0xa9, 0xa9, 1,+    0x00, 0x64, 0x00, 1,+    0xbd, 0xb7, 0x6b, 1,+    0x8b, 0x00, 0x8b, 1,+    0x55, 0x6b, 0x2f, 1,+    0xff, 0x8c, 0x00, 1,+    0x99, 0x32, 0xcc, 1,+    0x8b, 0x00, 0x00, 1,+    0xe9, 0x96, 0x7a, 1,+    0x8f, 0xbc, 0x8f, 1,+    0x48, 0x3d, 0x8b, 1,+    0x2f, 0x4f, 0x4f, 1,+    0x2f, 0x4f, 0x4f, 1,+    0x00, 0xce, 0xd1, 1,+    0x94, 0x00, 0xd3, 1,+    0xff, 0x14, 0x93, 1,+    0x00, 0xbf, 0xff, 1,+    0x69, 0x69, 0x69, 1,+    0x69, 0x69, 0x69, 1,+    0x1e, 0x90, 0xff, 1,+    0xb2, 0x22, 0x22, 1,+    0xff, 0xfa, 0xf0, 1,+    0x22, 0x8b, 0x22, 1,+    0xff, 0x00, 0xff, 1,+    0xdc, 0xdc, 0xdc, 1,+    0xf8, 0xf8, 0xff, 1,+    0xff, 0xd7, 0x00, 1,+    0xda, 0xa5, 0x20, 1,+    0x80, 0x80, 0x80, 1,+    0x80, 0x80, 0x80, 1,+    0x00, 0x80, 0x00, 1,+    0xad, 0xff, 0x2f, 1,+    0xf0, 0xff, 0xf0, 1,+    0xff, 0x69, 0xb4, 1,+    0xcd, 0x5c, 0x5c, 1,+    0x4b, 0x00, 0x82, 1,+    0xff, 0xff, 0xf0, 1,+    0xf0, 0xe6, 0x8c, 1,+    0xe6, 0xe6, 0xfa, 1,+    0xff, 0xf0, 0xf5, 1,+    0x7c, 0xfc, 0x00, 1,+    0xff, 0xfa, 0xcd, 1,+    0xad, 0xd8, 0xe6, 1,+    0xf0, 0x80, 0x80, 1,+    0xe0, 0xff, 0xff, 1,+    0xfa, 0xfa, 0xd2, 1,+    0xd3, 0xd3, 0xd3, 1,+    0xd3, 0xd3, 0xd3, 1,+    0x90, 0xee, 0x90, 1,+    0xff, 0xb6, 0xc1, 1,+    0xff, 0xa0, 0x7a, 1,+    0x20, 0xb2, 0xaa, 1,+    0x87, 0xce, 0xfa, 1,+    0x77, 0x88, 0x99, 1,+    0x77, 0x88, 0x99, 1,+    0xb0, 0xc4, 0xde, 1,+    0xff, 0xff, 0xe0, 1,+    0x00, 0xff, 0x00, 1,+    0x32, 0xcd, 0x32, 1,+    0xfa, 0xf0, 0xe6, 1,+    0xff, 0x00, 0xff, 1,+    0x80, 0x00, 0x00, 1,+    0x66, 0xcd, 0xaa, 1,+    0x00, 0x00, 0xcd, 1,+    0xba, 0x55, 0xd3, 1,+    0x93, 0x70, 0xdb, 1,+    0x3c, 0xb3, 0x71, 1,+    0x7b, 0x68, 0xee, 1,+    0x00, 0xfa, 0x9a, 1,+    0x48, 0xd1, 0xcc, 1,+    0xc7, 0x15, 0x85, 1,+    0x19, 0x19, 0x70, 1,+    0xf5, 0xff, 0xfa, 1,+    0xff, 0xe4, 0xe1, 1,+    0xff, 0xe4, 0xb5, 1,+    0xff, 0xde, 0xad, 1,+    0x00, 0x00, 0x80, 1,+    0xfd, 0xf5, 0xe6, 1,+    0x80, 0x80, 0x00, 1,+    0x6b, 0x8e, 0x23, 1,+    0xff, 0xa5, 0x00, 1,+    0xff, 0x45, 0x00, 1,+    0xda, 0x70, 0xd6, 1,+    0xee, 0xe8, 0xaa, 1,+    0x98, 0xfb, 0x98, 1,+    0xaf, 0xee, 0xee, 1,+    0xdb, 0x70, 0x93, 1,+    0xff, 0xef, 0xd5, 1,+    0xff, 0xda, 0xb9, 1,+    0xcd, 0x85, 0x3f, 1,+    0xff, 0xc0, 0xcb, 1,+    0xdd, 0xa0, 0xdd, 1,+    0xb0, 0xe0, 0xe6, 1,+    0x80, 0x00, 0x80, 1,+    0xff, 0x00, 0x00, 1,+    0xbc, 0x8f, 0x8f, 1,+    0x41, 0x69, 0xe1, 1,+    0x8b, 0x45, 0x13, 1,+    0xfa, 0x80, 0x72, 1,+    0xf4, 0xa4, 0x60, 1,+    0x2e, 0x8b, 0x57, 1,+    0xff, 0xf5, 0xee, 1,+    0xa0, 0x52, 0x2d, 1,+    0xc0, 0xc0, 0xc0, 1,+    0x87, 0xce, 0xeb, 1,+    0x6a, 0x5a, 0xcd, 1,+    0x70, 0x80, 0x90, 1,+    0x70, 0x80, 0x90, 1,+    0xff, 0xfa, 0xfa, 1,+    0x00, 0xff, 0x7f, 1,+    0x46, 0x82, 0xb4, 1,+    0xd2, 0xb4, 0x8c, 1,+    0x00, 0x80, 0x80, 1,+    0xd8, 0xbf, 0xd8, 1,+    0xff, 0x63, 0x47, 1,+    0x40, 0xe0, 0xd0, 1,+    0xee, 0x82, 0xee, 1,+    0xf5, 0xde, 0xb3, 1,+    0xff, 0xff, 0xff, 1,+    0xf5, 0xf5, 0xf5, 1,+    0xff, 0xff, 0x00, 1,+    0x9a, 0xcd, 0x32, 1,+    // rebeccapurple+    0x66, 0x33, 0x99, 1,+    // transparent+    0x00, 0x00, 0x00, 0,+    // sentinel value+    0xfff+  };++}++#endif
+ libsass/constants.cpp view
@@ -0,0 +1,166 @@+#include "constants.hpp"++namespace Sass {+  namespace Constants {++    // https://github.com/sass/libsass/issues/592+    // https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity+    // https://github.com/sass/sass/issues/1495#issuecomment-61189114+    extern const unsigned long Specificity_Star = 0;+    extern const unsigned long Specificity_Universal = 1 << 0;+    extern const unsigned long Specificity_Type = 1 << 8;+    extern const unsigned long Specificity_Class = 1 << 16;+    extern const unsigned long Specificity_Attr = 1 << 16;+    extern const unsigned long Specificity_Pseudo = 1 << 16;+    extern const unsigned long Specificity_ID = 1 << 24;++    // sass keywords+    extern const char at_root_kwd[]       = "@at-root";+    extern const char import_kwd[]        = "@import";+    extern const char mixin_kwd[]         = "@mixin";+    extern const char function_kwd[]      = "@function";+    extern const char return_kwd[]        = "@return";+    extern const char include_kwd[]       = "@include";+    extern const char content_kwd[]       = "@content";+    extern const char extend_kwd[]        = "@extend";+    extern const char if_kwd[]            = "@if";+    extern const char else_kwd[]          = "@else";+    extern const char if_after_else_kwd[] = "if";+    extern const char for_kwd[]           = "@for";+    extern const char from_kwd[]          = "from";+    extern const char to_kwd[]            = "to";+    extern const char through_kwd[]       = "through";+    extern const char each_kwd[]          = "@each";+    extern const char in_kwd[]            = "in";+    extern const char while_kwd[]         = "@while";+    extern const char warn_kwd[]          = "@warn";+    extern const char error_kwd[]         = "@error";+    extern const char debug_kwd[]         = "@debug";+    extern const char default_kwd[]       = "default";+    extern const char global_kwd[]        = "global";+    extern const char null_kwd[]          = "null";+    extern const char optional_kwd[]      = "optional";+    extern const char with_kwd[]          = "with";+    extern const char without_kwd[]       = "without";+    extern const char all_kwd[]           = "all";+    extern const char rule_kwd[]          = "rule";++    // css standard units+    extern const char em_kwd[]   = "em";+    extern const char ex_kwd[]   = "ex";+    extern const char px_kwd[]   = "px";+    extern const char cm_kwd[]   = "cm";+    extern const char mm_kwd[]   = "mm";+    extern const char pt_kwd[]   = "pt";+    extern const char pc_kwd[]   = "pc";+    extern const char deg_kwd[]  = "deg";+    extern const char rad_kwd[]  = "rad";+    extern const char grad_kwd[] = "grad";+    extern const char turn_kwd[] = "turn";+    extern const char ms_kwd[]   = "ms";+    extern const char s_kwd[]    = "s";+    extern const char Hz_kwd[]   = "Hz";+    extern const char kHz_kwd[]  = "kHz";++    // vendor prefixes+    extern const char vendor_opera_kwd[]    = "-o-";+    extern const char vendor_webkit_kwd[]   = "-webkit-";+    extern const char vendor_mozilla_kwd[]  = "-moz-";+    extern const char vendor_ms_kwd[]       = "-ms-";+    extern const char vendor_khtml_kwd[]    = "-khtml-";++    // css functions and keywords+    extern const char charset_kwd[]      = "@charset";+    extern const char media_kwd[]        = "@media";+    extern const char supports_kwd[]     = "@supports";+    extern const char keyframes_kwd[]    = "keyframes";+    extern const char only_kwd[]         = "only";+    extern const char rgb_kwd[]          = "rgb(";+    extern const char url_kwd[]          = "url(";+    extern const char important_kwd[]    = "important";+    extern const char pseudo_not_kwd[]   = ":not(";+    extern const char even_kwd[]         = "even";+    extern const char odd_kwd[]          = "odd";+    extern const char progid_kwd[]       = "progid";+    extern const char expression_kwd[]   = "expression";+    extern const char calc_kwd[]         = "calc(";+    extern const char moz_calc_kwd[]     = "-moz-calc(";+    extern const char webkit_calc_kwd[]  = "-webkit-calc(";+    extern const char ms_calc_kwd[]      = "-ms-calc(";++    // css selector keywords+    extern const char sel_deep_kwd[] = "/deep/";++    // css attribute-matching operators+    extern const char tilde_equal[]  = "~=";+    extern const char pipe_equal[]   = "|=";+    extern const char caret_equal[]  = "^=";+    extern const char dollar_equal[] = "$=";+    extern const char star_equal[]   = "*=";++    // relational & logical operators and constants+    extern const char and_kwd[]   = "and";+    extern const char or_kwd[]    = "or";+    extern const char not_kwd[]   = "not";+    extern const char gt[]        = ">";+    extern const char gte[]       = ">=";+    extern const char lt[]        = "<";+    extern const char lte[]       = "<=";+    extern const char eq[]        = "==";+    extern const char neq[]       = "!=";+    extern const char true_kwd[]  = "true";+    extern const char false_kwd[] = "false";++    // miscellaneous punctuation and delimiters+    extern const char percent_str[]     = "%";+    extern const char empty_str[]       = "";+    extern const char slash_slash[]     = "//";+    extern const char slash_star[]      = "/*";+    extern const char star_slash[]      = "*/";+    extern const char hash_lbrace[]     = "#{";+    extern const char rbrace[]          = "}";+    extern const char rparen[]          = ")";+    extern const char sign_chars[]      = "-+";+    extern const char hyphen[]          = "-";+    extern const char ellipsis[]        = "...";+    // extern const char url_space_chars[] = " \t\r\n\f";+    extern const char escape_chars[]    = " -~"; // need to include unicode spaces too+    // type names+    extern const char numeric_name[]    = "numeric value";+    extern const char number_name[]     = "number";+    extern const char percentage_name[] = "percentage";+    extern const char dimension_name[]  = "numeric dimension";+    extern const char string_name[]     = "string";+    extern const char bool_name[]       = "bool";+    extern const char color_name[]      = "color";+    extern const char list_name[]       = "list";+    extern const char map_name[]        = "map";+    extern const char arglist_name[]    = "arglist";++    // constants for uri parsing (RFC 3986 Appendix A.)+    extern const char uri_chars[]  = ":;/?!$%&#@[]{}'\"*+-.,_=";++    // some specific constant character classes+    // they must be static to be useable by lexer+    extern const char static_ops[]      = "*/%";++    // byte order marks+    // (taken from http://en.wikipedia.org/wiki/Byte_order_mark)+    extern const unsigned char utf_8_bom[]      = { 0xEF, 0xBB, 0xBF };+    extern const unsigned char utf_16_bom_be[]  = { 0xFE, 0xFF };+    extern const unsigned char utf_16_bom_le[]  = { 0xFF, 0xFE };+    extern const unsigned char utf_32_bom_be[]  = { 0x00, 0x00, 0xFE, 0xFF };+    extern const unsigned char utf_32_bom_le[]  = { 0xFF, 0xFE, 0x00, 0x00 };+    extern const unsigned char utf_7_bom_1[]    = { 0x2B, 0x2F, 0x76, 0x38 };+    extern const unsigned char utf_7_bom_2[]    = { 0x2B, 0x2F, 0x76, 0x39 };+    extern const unsigned char utf_7_bom_3[]    = { 0x2B, 0x2F, 0x76, 0x2B };+    extern const unsigned char utf_7_bom_4[]    = { 0x2B, 0x2F, 0x76, 0x2F };+    extern const unsigned char utf_7_bom_5[]    = { 0x2B, 0x2F, 0x76, 0x38, 0x2D };+    extern const unsigned char utf_1_bom[]      = { 0xF7, 0x64, 0x4C };+    extern const unsigned char utf_ebcdic_bom[] = { 0xDD, 0x73, 0x66, 0x73 };+    extern const unsigned char scsu_bom[]       = { 0x0E, 0xFE, 0xFF };+    extern const unsigned char bocu_1_bom[]     = { 0xFB, 0xEE, 0x28 };+    extern const unsigned char gb_18030_bom[]   = { 0x84, 0x31, 0x95, 0x33 };++  }+}
+ libsass/constants.hpp view
@@ -0,0 +1,170 @@+#ifndef SASS_CONSTANTS_H+#define SASS_CONSTANTS_H++namespace Sass {+  namespace Constants {++    // https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity+    // The following list of selectors is by increasing specificity:+    extern const unsigned long Specificity_Star;+    extern const unsigned long Specificity_Universal;+    extern const unsigned long Specificity_Type;+    extern const unsigned long Specificity_Class;+    extern const unsigned long Specificity_Attr;+    extern const unsigned long Specificity_Pseudo;+    extern const unsigned long Specificity_ID;++    // sass keywords+    extern const char at_root_kwd[];+    extern const char import_kwd[];+    extern const char mixin_kwd[];+    extern const char function_kwd[];+    extern const char return_kwd[];+    extern const char include_kwd[];+    extern const char content_kwd[];+    extern const char extend_kwd[];+    extern const char if_kwd[];+    extern const char else_kwd[];+    extern const char if_after_else_kwd[];+    extern const char for_kwd[];+    extern const char from_kwd[];+    extern const char to_kwd[];+    extern const char through_kwd[];+    extern const char each_kwd[];+    extern const char in_kwd[];+    extern const char while_kwd[];+    extern const char warn_kwd[];+    extern const char error_kwd[];+    extern const char debug_kwd[];+    extern const char default_kwd[];+    extern const char global_kwd[];+    extern const char null_kwd[];+    extern const char optional_kwd[];+    extern const char with_kwd[];+    extern const char without_kwd[];+    extern const char all_kwd[];+    extern const char rule_kwd[];++    // css standard units+    extern const char em_kwd[];+    extern const char ex_kwd[];+    extern const char px_kwd[];+    extern const char cm_kwd[];+    extern const char mm_kwd[];+    extern const char pt_kwd[];+    extern const char pc_kwd[];+    extern const char deg_kwd[];+    extern const char rad_kwd[];+    extern const char grad_kwd[];+    extern const char turn_kwd[];+    extern const char ms_kwd[];+    extern const char s_kwd[];+    extern const char Hz_kwd[];+    extern const char kHz_kwd[];++    // vendor prefixes+    extern const char vendor_opera_kwd[];+    extern const char vendor_webkit_kwd[];+    extern const char vendor_mozilla_kwd[];+    extern const char vendor_ms_kwd[];+    extern const char vendor_khtml_kwd[];++    // css functions and keywords+    extern const char charset_kwd[];+    extern const char media_kwd[];+    extern const char supports_kwd[];+    extern const char keyframes_kwd[];+    extern const char only_kwd[];+    extern const char rgb_kwd[];+    extern const char url_kwd[];+    extern const char image_url_kwd[];+    extern const char important_kwd[];+    extern const char pseudo_not_kwd[];+    extern const char even_kwd[];+    extern const char odd_kwd[];+    extern const char progid_kwd[];+    extern const char expression_kwd[];+    extern const char calc_kwd[];+    extern const char moz_calc_kwd[];+    extern const char webkit_calc_kwd[];+    extern const char ms_calc_kwd[];++    // css selector keywords+    extern const char sel_deep_kwd[];++    // css attribute-matching operators+    extern const char tilde_equal[];+    extern const char pipe_equal[];+    extern const char caret_equal[];+    extern const char dollar_equal[];+    extern const char star_equal[];++    // relational & logical operators and constants+    extern const char and_kwd[];+    extern const char or_kwd[];+    extern const char not_kwd[];+    extern const char gt[];+    extern const char gte[];+    extern const char lt[];+    extern const char lte[];+    extern const char eq[];+    extern const char neq[];+    extern const char true_kwd[];+    extern const char false_kwd[];++    // miscellaneous punctuation and delimiters+    extern const char percent_str[];+    extern const char empty_str[];+    extern const char slash_slash[];+    extern const char slash_star[];+    extern const char star_slash[];+    extern const char hash_lbrace[];+    extern const char rbrace[];+    extern const char rparen[];+    extern const char sign_chars[];+    extern const char hyphen[];+    extern const char ellipsis[];+    // extern const char url_space_chars[];+    extern const char escape_chars[];++    // type names+    extern const char numeric_name[];+    extern const char number_name[];+    extern const char percentage_name[];+    extern const char dimension_name[];+    extern const char string_name[];+    extern const char bool_name[];+    extern const char color_name[];+    extern const char list_name[];+    extern const char map_name[];+    extern const char arglist_name[];++    // constants for uri parsing (RFC 3986 Appendix A.)+    extern const char uri_chars[];++    // some specific constant character classes+    // they must be static to be useable by lexer+    extern const char static_ops[];++    // byte order marks+    // (taken from http://en.wikipedia.org/wiki/Byte_order_mark)+    extern const unsigned char utf_8_bom[];+    extern const unsigned char utf_16_bom_be[];+    extern const unsigned char utf_16_bom_le[];+    extern const unsigned char utf_32_bom_be[];+    extern const unsigned char utf_32_bom_le[];+    extern const unsigned char utf_7_bom_1[];+    extern const unsigned char utf_7_bom_2[];+    extern const unsigned char utf_7_bom_3[];+    extern const unsigned char utf_7_bom_4[];+    extern const unsigned char utf_7_bom_5[];+    extern const unsigned char utf_1_bom[];+    extern const unsigned char utf_ebcdic_bom[];+    extern const unsigned char scsu_bom[];+    extern const unsigned char bocu_1_bom[];+    extern const unsigned char gb_18030_bom[];++  }+}++#endif
+ libsass/context.cpp view
@@ -0,0 +1,560 @@+#ifdef _WIN32+#define PATH_SEP ';'+#else+#define PATH_SEP ':'+#endif++#include "ast.hpp"+#include "util.hpp"+#include "sass.h"+#include "context.hpp"+#include "plugins.hpp"+#include "constants.hpp"+#include "parser.hpp"+#include "file.hpp"+#include "inspect.hpp"+#include "output.hpp"+#include "expand.hpp"+#include "eval.hpp"+#include "contextualize.hpp"+#include "contextualize_eval.hpp"+#include "cssize.hpp"+#include "listize.hpp"+#include "extend.hpp"+#include "remove_placeholders.hpp"+#include "color_names.hpp"+#include "functions.hpp"+#include "backtrace.hpp"+#include "sass2scss.h"+#include "prelexer.hpp"+#include "emitter.hpp"++#include <string>+#include <cstdlib>+#include <cstring>+#include <iomanip>+#include <sstream>+#include <iostream>++namespace Sass {+  using namespace Constants;+  using namespace File;+  using namespace Sass;+  using std::cerr;+  using std::endl;++  Sass_Queued::Sass_Queued(const string& load_path, const string& abs_path, const char* source)+  {+    this->load_path = load_path;+    this->abs_path = abs_path;+    this->source = source;+  }++  inline bool sort_importers (const Sass_Importer_Entry& i, const Sass_Importer_Entry& j)+  { return sass_importer_get_priority(i) > sass_importer_get_priority(j); }++  Context::Context(Context::Data initializers)+  : // Output(this),+    head_imports(0),+    mem(Memory_Manager<AST_Node>()),+    c_options               (initializers.c_options()),+    c_compiler              (initializers.c_compiler()),+    source_c_str            (initializers.source_c_str()),+    sources                 (vector<const char*>()),+    plugin_paths            (initializers.plugin_paths()),+    include_paths           (initializers.include_paths()),+    queue                   (vector<Sass_Queued>()),+    style_sheets            (map<string, Block*>()),+    emitter (this),+    c_headers               (vector<Sass_Importer_Entry>()),+    c_importers             (vector<Sass_Importer_Entry>()),+    c_functions             (vector<Sass_Function_Entry>()),+    indent                  (initializers.indent()),+    linefeed                (initializers.linefeed()),+    input_path              (make_canonical_path(initializers.input_path())),+    output_path             (make_canonical_path(initializers.output_path())),+    source_comments         (initializers.source_comments()),+    output_style            (initializers.output_style()),+    source_map_file         (make_canonical_path(initializers.source_map_file())),+    source_map_root         (initializers.source_map_root()), // pass-through+    source_map_embed        (initializers.source_map_embed()),+    source_map_contents     (initializers.source_map_contents()),+    omit_source_map_url     (initializers.omit_source_map_url()),+    is_indented_syntax_src  (initializers.is_indented_syntax_src()),+    names_to_colors         (map<string, Color*>()),+    colors_to_names         (map<int, string>()),+    precision               (initializers.precision()),+    plugins(),+    subset_map              (Subset_Map<string, pair<Complex_Selector*, Compound_Selector*> >())+  {++    cwd = get_cwd();++    // enforce some safe defaults+    // used to create relative file links+    if (input_path == "") input_path = "stdin";+    if (output_path == "") output_path = "stdout";++    include_paths.push_back(cwd);+    collect_include_paths(initializers.include_paths_c_str());+    // collect_include_paths(initializers.include_paths_array());+    collect_plugin_paths(initializers.plugin_paths_c_str());+    // collect_plugin_paths(initializers.plugin_paths_array());++    setup_color_map();++    for (size_t i = 0, S = plugin_paths.size(); i < S; ++i) {+      plugins.load_plugins(plugin_paths[i]);+    }++    for(auto fn : plugins.get_functions()) {+      c_functions.push_back(fn);+    }+    for(auto fn : plugins.get_headers()) {+      c_headers.push_back(fn);+    }+    for(auto fn : plugins.get_importers()) {+      c_importers.push_back(fn);+    }++    sort (c_headers.begin(), c_headers.end(), sort_importers);+    sort (c_importers.begin(), c_importers.end(), sort_importers);+    string entry_point = initializers.entry_point();+    if (!entry_point.empty()) {+      string result(add_file(entry_point));+      if (result.empty()) {+        throw "File to read not found or unreadable: " + entry_point;+      }+    }++    emitter.set_filename(resolve_relative_path(output_path, source_map_file, cwd));++  }++  void Context::add_c_function(Sass_Function_Entry function)+  {+    c_functions.push_back(function);+  }+  void Context::add_c_header(Sass_Importer_Entry header)+  {+    c_headers.push_back(header);+    // need to sort the array afterwards (no big deal)+    sort (c_headers.begin(), c_headers.end(), sort_importers);+  }+  void Context::add_c_importer(Sass_Importer_Entry importer)+  {+    c_importers.push_back(importer);+    // need to sort the array afterwards (no big deal)+    sort (c_importers.begin(), c_importers.end(), sort_importers);+  }++  Context::~Context()+  {+    // everything that gets put into sources will be freed by us+    for (size_t n = 0; n < import_stack.size(); ++n) sass_delete_import(import_stack[n]);+    // sources are allocated by strdup or malloc (overtaken from C code)+    for (size_t i = 0; i < sources.size(); ++i) free((void*)sources[i]);+    // clear inner structures (vectors)+    sources.clear(); import_stack.clear();+  }++  void Context::setup_color_map()+  {+    size_t i = 0;+    while (color_names[i]) {+      string name(color_names[i]);+      Color* value = new (mem) Color(ParserState("[COLOR TABLE]"),+                                     color_values[i*4],+                                     color_values[i*4+1],+                                     color_values[i*4+2],+                                     color_values[i*4+3]);+      names_to_colors[name] = value;+      // only map fully opaque colors+      if (color_values[i*4+3] >= 1) {+        int numval = static_cast<int>(color_values[i*4])*0x10000;+        numval += static_cast<int>(color_values[i*4+1])*0x100;+        numval += static_cast<int>(color_values[i*4+2]);+        colors_to_names[numval] = name;+      }+      ++i;+    }+  }++  void Context::collect_include_paths(const char* paths_str)+  {++    if (paths_str) {+      const char* beg = paths_str;+      const char* end = Prelexer::find_first<PATH_SEP>(beg);++      while (end) {+        string path(beg, end - beg);+        if (!path.empty()) {+          if (*path.rbegin() != '/') path += '/';+          include_paths.push_back(path);+        }+        beg = end + 1;+        end = Prelexer::find_first<PATH_SEP>(beg);+      }++      string path(beg);+      if (!path.empty()) {+        if (*path.rbegin() != '/') path += '/';+        include_paths.push_back(path);+      }+    }+  }++  void Context::collect_include_paths(const char** paths_array)+  {+    if (paths_array) {+      for (size_t i = 0; paths_array[i]; i++) {+        collect_include_paths(paths_array[i]);+      }+    }+  }++  void Context::collect_plugin_paths(const char* paths_str)+  {++    if (paths_str) {+      const char* beg = paths_str;+      const char* end = Prelexer::find_first<PATH_SEP>(beg);++      while (end) {+        string path(beg, end - beg);+        if (!path.empty()) {+          if (*path.rbegin() != '/') path += '/';+          plugin_paths.push_back(path);+        }+        beg = end + 1;+        end = Prelexer::find_first<PATH_SEP>(beg);+      }++      string path(beg);+      if (!path.empty()) {+        if (*path.rbegin() != '/') path += '/';+        plugin_paths.push_back(path);+      }+    }+  }++  void Context::collect_plugin_paths(const char** paths_array)+  {+    if (paths_array) {+      for (size_t i = 0; paths_array[i]; i++) {+        collect_plugin_paths(paths_array[i]);+      }+    }+  }+  void Context::add_source(string load_path, string abs_path, const char* contents)+  {+    sources.push_back(contents);+    included_files.push_back(abs_path);+    queue.push_back(Sass_Queued(load_path, abs_path, contents));+    emitter.add_source_index(sources.size() - 1);+    include_links.push_back(resolve_relative_path(abs_path, source_map_file, cwd));+  }++  // Add a new import file to the context+  string Context::add_file(const string& file)+  {+    using namespace File;+    string path(make_canonical_path(file));+    string resolved(find_file(path, include_paths));+    if (resolved == "") return resolved;+    if (char* contents = read_file(resolved)) {+      add_source(path, resolved, contents);+      style_sheets[path] = 0;+      return path;+    }+    return string("");+  }++  // Add a new import file to the context+  // This has some previous directory context+  string Context::add_file(const string& base, const string& file)+  {+    using namespace File;+    string path(make_canonical_path(file));+    string base_file(join_paths(base, path));+    string resolved(resolve_file(base_file));+    if (style_sheets.count(base_file)) return base_file;+    if (char* contents = read_file(resolved)) {+      add_source(base_file, resolved, contents);+      style_sheets[base_file] = 0;+      return base_file;+    }+    // now go the regular code path+    return add_file(path);+  }++  void register_function(Context&, Signature sig, Native_Function f, Env* env);+  void register_function(Context&, Signature sig, Native_Function f, size_t arity, Env* env);+  void register_overload_stub(Context&, string name, Env* env);+  void register_built_in_functions(Context&, Env* env);+  void register_c_functions(Context&, Env* env, Sass_Function_List);+  void register_c_function(Context&, Env* env, Sass_Function_Entry);++  char* Context::compile_block(Block* root)+  {+    if (!root) return 0;+    root->perform(&emitter);+    emitter.finalize();+    OutputBuffer emitted = emitter.get_buffer();+    string output = emitted.buffer;+    if (source_map_file != "" && !omit_source_map_url) {+      output += linefeed + format_source_mapping_url(source_map_file);+    }+    return sass_strdup(output.c_str());+  }++  Block* Context::parse_file()+  {+    Block* root = 0;+    for (size_t i = 0; i < queue.size(); ++i) {+      Sass_Import_Entry import = sass_make_import(+        queue[i].load_path.c_str(),+        queue[i].abs_path.c_str(),+        0, 0+      );+      import_stack.push_back(import);+      Parser p(Parser::from_c_str(queue[i].source, *this, ParserState(queue[i].abs_path, queue[i].source, i)));+      Block* ast = p.parse();+      sass_delete_import(import_stack.back());+      import_stack.pop_back();+      if (i == 0) root = ast;+      style_sheets[queue[i].load_path] = ast;+    }+    if (root == 0) return 0;+    Env tge;+    Backtrace backtrace(0, ParserState("", 0), "");+    register_built_in_functions(*this, &tge);+    for (size_t i = 0, S = c_functions.size(); i < S; ++i) {+      register_c_function(*this, &tge, c_functions[i]);+    }+    Contextualize contextualize(*this, &tge, &backtrace);+    Listize listize(*this);+    Eval eval(*this, &contextualize, &listize, &tge, &backtrace);+    Contextualize_Eval contextualize_eval(*this, &eval, &tge, &backtrace);+    Expand expand(*this, &eval, &contextualize_eval, &tge, &backtrace);+    Cssize cssize(*this, &tge, &backtrace);+    root = root->perform(&expand)->block();+    root = root->perform(&cssize)->block();+    if (!subset_map.empty()) {+      Extend extend(*this, subset_map);+      root->perform(&extend);+    }++    Remove_Placeholders remove_placeholders(*this);+    root->perform(&remove_placeholders);++    return root;+  }++  Block* Context::parse_string()+  {+    if (!source_c_str) return 0;+    queue.clear();+    if(is_indented_syntax_src) {+      char * contents = sass2scss(source_c_str, SASS2SCSS_PRETTIFY_1 | SASS2SCSS_KEEP_COMMENT);+      add_source(input_path, input_path, contents);+      delete [] source_c_str;+      return parse_file();+    }+    add_source(input_path, input_path, source_c_str);+    return parse_file();+  }++  char* Context::compile_file()+  {+    // returns NULL if something fails+    return compile_block(parse_file());+  }++  char* Context::compile_string()+  {+    // returns NULL if something fails+    return compile_block(parse_string());+  }++  string Context::format_source_mapping_url(const string& file)+  {+    string url = resolve_relative_path(file, output_path, cwd);+    if (source_map_embed) {+      string map = emitter.generate_source_map(*this);+      istringstream is( map );+      ostringstream buffer;+      base64::encoder E;+      E.encode(is, buffer);+      url = "data:application/json;base64," + buffer.str();+      url.erase(url.size() - 1);+    }+    return "/*# sourceMappingURL=" + url + " */";+  }++  char* Context::generate_source_map()+  {+    if (source_map_file == "") return 0;+    char* result = 0;+    string map = emitter.generate_source_map(*this);+    result = sass_strdup(map.c_str());+    return result;+  }+++  std::vector<std::string> Context::get_included_files(size_t skip)+  {+      vector<string> includes = included_files;+      if (includes.size() == 0) return includes;+      std::sort( includes.begin() + skip, includes.end() );+      includes.erase( includes.begin(), includes.begin() + skip );+      includes.erase( std::unique( includes.begin(), includes.end() ), includes.end() );+      // the skip solution seems more robust, as we may have real files named stdin+      // includes.erase( std::remove( includes.begin(), includes.end(), "stdin" ), includes.end() );+      return includes;+  }++  string Context::get_cwd()+  {+    return Sass::File::get_cwd();+  }++  void register_function(Context& ctx, Signature sig, Native_Function f, Env* env)+  {+    Definition* def = make_native_function(sig, f, ctx);+    def->environment(env);+    (*env)[def->name() + "[f]"] = def;+  }++  void register_function(Context& ctx, Signature sig, Native_Function f, size_t arity, Env* env)+  {+    Definition* def = make_native_function(sig, f, ctx);+    stringstream ss;+    ss << def->name() << "[f]" << arity;+    def->environment(env);+    (*env)[ss.str()] = def;+  }++  void register_overload_stub(Context& ctx, string name, Env* env)+  {+    Definition* stub = new (ctx.mem) Definition(ParserState("[built-in function]"),+                                            0,+                                            name,+                                            0,+                                            0,+                                            &ctx,+                                            true);+    (*env)[name + "[f]"] = stub;+  }+++  void register_built_in_functions(Context& ctx, Env* env)+  {+    using namespace Functions;+    // RGB Functions+    register_function(ctx, rgb_sig, rgb, env);+    register_overload_stub(ctx, "rgba", env);+    register_function(ctx, rgba_4_sig, rgba_4, 4, env);+    register_function(ctx, rgba_2_sig, rgba_2, 2, env);+    register_function(ctx, red_sig, red, env);+    register_function(ctx, green_sig, green, env);+    register_function(ctx, blue_sig, blue, env);+    register_function(ctx, mix_sig, mix, env);+    // HSL Functions+    register_function(ctx, hsl_sig, hsl, env);+    register_function(ctx, hsla_sig, hsla, env);+    register_function(ctx, hue_sig, hue, env);+    register_function(ctx, saturation_sig, saturation, env);+    register_function(ctx, lightness_sig, lightness, env);+    register_function(ctx, adjust_hue_sig, adjust_hue, env);+    register_function(ctx, lighten_sig, lighten, env);+    register_function(ctx, darken_sig, darken, env);+    register_function(ctx, saturate_sig, saturate, env);+    register_function(ctx, desaturate_sig, desaturate, env);+    register_function(ctx, grayscale_sig, grayscale, env);+    register_function(ctx, complement_sig, complement, env);+    register_function(ctx, invert_sig, invert, env);+    // Opacity Functions+    register_function(ctx, alpha_sig, alpha, env);+    register_function(ctx, opacity_sig, alpha, env);+    register_function(ctx, opacify_sig, opacify, env);+    register_function(ctx, fade_in_sig, opacify, env);+    register_function(ctx, transparentize_sig, transparentize, env);+    register_function(ctx, fade_out_sig, transparentize, env);+    // Other Color Functions+    register_function(ctx, adjust_color_sig, adjust_color, env);+    register_function(ctx, scale_color_sig, scale_color, env);+    register_function(ctx, change_color_sig, change_color, env);+    register_function(ctx, ie_hex_str_sig, ie_hex_str, env);+    // String Functions+    register_function(ctx, unquote_sig, sass_unquote, env);+    register_function(ctx, quote_sig, sass_quote, env);+    register_function(ctx, str_length_sig, str_length, env);+    register_function(ctx, str_insert_sig, str_insert, env);+    register_function(ctx, str_index_sig, str_index, env);+    register_function(ctx, str_slice_sig, str_slice, env);+    register_function(ctx, to_upper_case_sig, to_upper_case, env);+    register_function(ctx, to_lower_case_sig, to_lower_case, env);+    // Number Functions+    register_function(ctx, percentage_sig, percentage, env);+    register_function(ctx, round_sig, round, env);+    register_function(ctx, ceil_sig, ceil, env);+    register_function(ctx, floor_sig, floor, env);+    register_function(ctx, abs_sig, abs, env);+    register_function(ctx, min_sig, min, env);+    register_function(ctx, max_sig, max, env);+    register_function(ctx, random_sig, random, env);+    // List Functions+    register_function(ctx, length_sig, length, env);+    register_function(ctx, nth_sig, nth, env);+    register_function(ctx, set_nth_sig, set_nth, env);+    register_function(ctx, index_sig, index, env);+    register_function(ctx, join_sig, join, env);+    register_function(ctx, append_sig, append, env);+    register_function(ctx, compact_sig, compact, env);+    register_function(ctx, zip_sig, zip, env);+    register_function(ctx, list_separator_sig, list_separator, env);+    // Map Functions+    register_function(ctx, map_get_sig, map_get, env);+    register_function(ctx, map_merge_sig, map_merge, env);+    register_function(ctx, map_remove_sig, map_remove, env);+    register_function(ctx, map_keys_sig, map_keys, env);+    register_function(ctx, map_values_sig, map_values, env);+    register_function(ctx, map_has_key_sig, map_has_key, env);+    register_function(ctx, keywords_sig, keywords, env);+    // Introspection Functions+    register_function(ctx, type_of_sig, type_of, env);+    register_function(ctx, unit_sig, unit, env);+    register_function(ctx, unitless_sig, unitless, env);+    register_function(ctx, comparable_sig, comparable, env);+    register_function(ctx, variable_exists_sig, variable_exists, env);+    register_function(ctx, global_variable_exists_sig, global_variable_exists, env);+    register_function(ctx, function_exists_sig, function_exists, env);+    register_function(ctx, mixin_exists_sig, mixin_exists, env);+    register_function(ctx, feature_exists_sig, feature_exists, env);+    register_function(ctx, call_sig, call, env);+    // Boolean Functions+    register_function(ctx, not_sig, sass_not, env);+    register_function(ctx, if_sig, sass_if, env);+    // Misc Functions+    register_function(ctx, inspect_sig, inspect, env);+    register_function(ctx, unique_id_sig, unique_id, env);+  }++  void register_c_functions(Context& ctx, Env* env, Sass_Function_List descrs)+  {+    while (descrs && *descrs) {+      register_c_function(ctx, env, *descrs);+      ++descrs;+    }+  }+  void register_c_function(Context& ctx, Env* env, Sass_Function_Entry descr)+  {+    Definition* def = make_c_function(descr, ctx);+    def->environment(env);+    (*env)[def->name() + "[f]"] = def;+  }+++}
+ libsass/context.hpp view
@@ -0,0 +1,157 @@+#ifndef SASS_CONTEXT_H+#define SASS_CONTEXT_H++#include <string>+#include <vector>+#include <map>++#define BUFFERSIZE 255+#include "b64/encode.h"++#include "ast_fwd_decl.hpp"+#include "kwd_arg_macros.hpp"+#include "memory_manager.hpp"+#include "environment.hpp"+#include "source_map.hpp"+#include "subset_map.hpp"+#include "output.hpp"+#include "plugins.hpp"+#include "sass_functions.h"++struct Sass_Function;++namespace Sass {+  using namespace std;+  struct Sass_Queued {+    string abs_path;+    string load_path;+    const char* source;+  public:+    Sass_Queued(const string& load_path, const string& abs_path, const char* source);+  };++  class Context {+  public:+    size_t head_imports;+    Memory_Manager<AST_Node> mem;++    struct Sass_Options* c_options;+    struct Sass_Compiler* c_compiler;+    const char* source_c_str;++    // c-strs containing Sass file contents+    // we will overtake ownership of memory+    vector<const char*> sources;+    // absolute paths to includes+    vector<string> included_files;+    // relative links to includes+    vector<string> include_links;+    // vectors above have same size++    vector<string> plugin_paths; // relative paths to load plugins+    vector<string> include_paths; // lookup paths for includes+    vector<Sass_Queued> queue; // queue of files to be parsed+    map<string, Block*> style_sheets; // map of paths to ASTs+    // SourceMap source_map;+    Output emitter;++    vector<Sass_Importer_Entry> c_headers;+    vector<Sass_Importer_Entry> c_importers;+    vector<Sass_Function_Entry> c_functions;++    void add_c_header(Sass_Importer_Entry header);+    void add_c_importer(Sass_Importer_Entry importer);+    void add_c_function(Sass_Function_Entry function);++    string       indent; // String to be used for indentation+    string       linefeed; // String to be used for line feeds+    string       input_path; // for relative paths in src-map+    string       output_path; // for relative paths to the output+    bool         source_comments; // for inline debug comments in css output+    Output_Style output_style; // output style for the generated css code+    string       source_map_file; // path to source map file (enables feature)+    string       source_map_root; // path for sourceRoot property (pass-through)+    bool         source_map_embed; // embed in sourceMappingUrl (as data-url)+    bool         source_map_contents; // insert included contents into source map+    bool         omit_source_map_url; // disable source map comment in css output+    bool         is_indented_syntax_src; // treat source string as sass++    // overload import calls+    vector<Sass_Import_Entry> import_stack;++    map<string, Color*> names_to_colors;+    map<int, string>    colors_to_names;++    size_t precision; // precision for outputting fractional numbers++    KWD_ARG_SET(Data) {+      KWD_ARG(Data, struct Sass_Options*, c_options);+      KWD_ARG(Data, struct Sass_Compiler*, c_compiler);+      KWD_ARG(Data, const char*,     source_c_str);+      KWD_ARG(Data, string,          entry_point);+      KWD_ARG(Data, string,          input_path);+      KWD_ARG(Data, string,          output_path);+      KWD_ARG(Data, string,          indent);+      KWD_ARG(Data, string,          linefeed);+      KWD_ARG(Data, const char*,     include_paths_c_str);+      KWD_ARG(Data, const char*,     plugin_paths_c_str);+      // KWD_ARG(Data, const char**,    include_paths_array);+      // KWD_ARG(Data, const char**,    plugin_paths_array);+      KWD_ARG(Data, vector<string>,  include_paths);+      KWD_ARG(Data, vector<string>,  plugin_paths);+      KWD_ARG(Data, bool,            source_comments);+      KWD_ARG(Data, Output_Style,    output_style);+      KWD_ARG(Data, string,          source_map_file);+      KWD_ARG(Data, string,          source_map_root);+      KWD_ARG(Data, bool,            omit_source_map_url);+      KWD_ARG(Data, bool,            is_indented_syntax_src);+      KWD_ARG(Data, size_t,          precision);+      KWD_ARG(Data, bool,            source_map_embed);+      KWD_ARG(Data, bool,            source_map_contents);+    };++    Context(Data);+    ~Context();+    static string get_cwd();+    void setup_color_map();++    Block* parse_file();+    Block* parse_string();+    void add_source(string, string, const char*);++    string add_file(const string& file);+    string add_file(const string& base, const string& file);+++    // allow to optionally overwrite the input path+    // default argument for input_path is string("stdin")+    // usefull to influence the source-map generating etc.+    char* compile_file();+    char* compile_string();+    char* compile_block(Block* root);+    char* generate_source_map();++    vector<string> get_included_files(size_t skip = 0);++  private:+    void collect_plugin_paths(const char* paths_str);+    void collect_plugin_paths(const char** paths_array);+    void collect_include_paths(const char* paths_str);+    void collect_include_paths(const char** paths_array);+    string format_source_mapping_url(const string& file);++    string cwd;+    Plugins plugins;++    // void register_built_in_functions(Env* env);+    // void register_function(Signature sig, Native_Function f, Env* env);+    // void register_function(Signature sig, Native_Function f, size_t arity, Env* env);+    // void register_overload_stub(string name, Env* env);++  public:+    Subset_Map<string, pair<Complex_Selector*, Compound_Selector*> > subset_map;+  };++}++#endif
+ libsass/contextualize.cpp view
@@ -0,0 +1,141 @@+#include "contextualize.hpp"+#include "ast.hpp"+#include "eval.hpp"+#include "backtrace.hpp"+#include "to_string.hpp"+#include "parser.hpp"++namespace Sass {++  Contextualize::Contextualize(Context& ctx, Env* env, Backtrace* bt, Selector* placeholder, Selector* extender)+  : ctx(ctx), env(env), backtrace(bt), parent(0), placeholder(placeholder), extender(extender)+  { }++  Contextualize::~Contextualize() { }++  Selector* Contextualize::fallback_impl(AST_Node* n)+  { return parent; }++  Contextualize* Contextualize::with(Selector* s, Env* e, Backtrace* bt, Selector* p, Selector* ex)+  {+    parent = s;+    env = e;+    backtrace = bt;+    placeholder = p;+    extender = ex;+    return this;+  }++  Selector* Contextualize::operator()(Selector_List* s)+  {+    Selector_List* p = static_cast<Selector_List*>(parent);+    Selector_List* ss = 0;+    if (p) {+      ss = new (ctx.mem) Selector_List(s->pstate(), p->length() * s->length());+      for (size_t i = 0, L = p->length(); i < L; ++i) {+        for (size_t j = 0, L = s->length(); j < L; ++j) {+          parent = (*p)[i];+          Complex_Selector* comb = static_cast<Complex_Selector*>((*s)[j]->perform(this));+          if (parent->has_line_feed()) comb->has_line_feed(true);+          if (comb) *ss << comb;+        }+      }+    }+    else {+      ss = new (ctx.mem) Selector_List(s->pstate(), s->length());+      for (size_t j = 0, L = s->length(); j < L; ++j) {+        Complex_Selector* comb = static_cast<Complex_Selector*>((*s)[j]->perform(this));+        if (comb) *ss << comb;+      }+    }+    return ss->length() ? ss : 0;+  }++  Selector* Contextualize::operator()(Complex_Selector* s)+  {+    To_String to_string(&ctx);+    Complex_Selector* ss = new (ctx.mem) Complex_Selector(*s);+    // ss->last_block(s->last_block());+    // ss->media_block(s->media_block());+    Compound_Selector* new_head = 0;+    Complex_Selector* new_tail = 0;+    if (ss->head()) {+      new_head = static_cast<Compound_Selector*>(s->head()->perform(this));+      ss->head(new_head);+    }+    if (ss->tail()) {+      new_tail = static_cast<Complex_Selector*>(s->tail()->perform(this));+      // new_tail->last_block(s->last_block());+      // new_tail->media_block(s->media_block());+      ss->tail(new_tail);+    }+    if ((new_head && new_head->has_placeholder()) || (new_tail && new_tail->has_placeholder())) {+      ss->has_placeholder(true);+    }+    else {+      ss->has_placeholder(false);+    }+    if (!ss->head() && ss->combinator() == Complex_Selector::ANCESTOR_OF) {+      return ss->tail();+    }+    else {+      return ss;+    }+  }++  Selector* Contextualize::operator()(Compound_Selector* s)+  {+    To_String to_string(&ctx);+    if (placeholder && extender && s->perform(&to_string) == placeholder->perform(&to_string)) {+      return extender;+    }+    Compound_Selector* ss = new (ctx.mem) Compound_Selector(s->pstate(), s->length());+    ss->last_block(s->last_block());+    ss->media_block(s->media_block());+    ss->has_line_break(s->has_line_break());+    for (size_t i = 0, L = s->length(); i < L; ++i) {+      Simple_Selector* simp = static_cast<Simple_Selector*>((*s)[i]->perform(this));+      if (simp) *ss << simp;+    }+    return ss->length() ? ss : 0;+  }++  Selector* Contextualize::operator()(Wrapped_Selector* s)+  {+    Selector* old_parent = parent;+    parent = 0;+    Wrapped_Selector* neg = new (ctx.mem) Wrapped_Selector(s->pstate(),+                                                           s->name(),+                                                           s->selector()->perform(this));+    parent = old_parent;+    return neg;+  }++  Selector* Contextualize::operator()(Pseudo_Selector* s)+  { return s; }++  Selector* Contextualize::operator()(Selector_Qualifier* s)+  { return s; }++  Selector* Contextualize::operator()(Type_Selector* s)+  { return s; }++  Selector* Contextualize::operator()(Selector_Placeholder* p)+  {+    To_String to_string(&ctx);+    if (placeholder && extender && p->perform(&to_string) == placeholder->perform(&to_string)) {+      return extender;+    }+    else {+      return p;+    }+  }++  Selector* Contextualize::operator()(Selector_Reference* s)+  {+    if (!parent) return 0;+    Selector_Reference* ss = new (ctx.mem) Selector_Reference(*s);+    ss->selector(parent);+    return ss;+  }+}
+ libsass/contextualize.hpp view
@@ -0,0 +1,46 @@+#ifndef SASS_CONTEXTUALIZE_H+#define SASS_CONTEXTUALIZE_H++#include "context.hpp"+#include "operation.hpp"+#include "environment.hpp"+#include "ast_fwd_decl.hpp"++namespace Sass {+  struct Backtrace;++  typedef Environment<AST_Node*> Env;++  class Contextualize : public Operation_CRTP<Selector*, Contextualize> {+++  public:+    Context&   ctx;+    Env*       env;+    Backtrace* backtrace;+    Selector*  parent;+    Selector* placeholder;+    Selector* extender;++    Selector* fallback_impl(AST_Node* n);+    Contextualize(Context&, Env*, Backtrace*, Selector* placeholder = 0, Selector* extender = 0);+    virtual ~Contextualize();+    Contextualize* with(Selector*, Env*, Backtrace*, Selector* placeholder = 0, Selector* extender = 0);+    using Operation<Selector*>::operator();++    Selector* operator()(Selector_List*);+    Selector* operator()(Complex_Selector*);+    Selector* operator()(Compound_Selector*);+    Selector* operator()(Wrapped_Selector*);+    Selector* operator()(Pseudo_Selector*);+    Selector* operator()(Selector_Qualifier*);+    Selector* operator()(Type_Selector*);+    Selector* operator()(Selector_Placeholder*);+    Selector* operator()(Selector_Reference*);++    template <typename U>+    Selector* fallback(U x) { return fallback_impl(x); }+  };+}++#endif
+ libsass/contextualize_eval.cpp view
@@ -0,0 +1,93 @@+#include "contextualize_eval.hpp"+#include "ast.hpp"+#include "eval.hpp"+#include "backtrace.hpp"+#include "to_string.hpp"+#include "parser.hpp"++namespace Sass {++  Contextualize_Eval::Contextualize_Eval(Context& ctx, Eval* eval, Env* env, Backtrace* bt)+  : Contextualize(ctx, env, bt), eval(eval)+  { }++  Contextualize_Eval::~Contextualize_Eval() { }++  Selector* Contextualize_Eval::fallback_impl(AST_Node* n)+  {+    return Contextualize::fallback_impl(n);+  }++  Contextualize_Eval* Contextualize_Eval::with(Selector* s, Env* e, Backtrace* bt, Selector* p, Selector* ex)+  {+    Contextualize::with(s, e, bt, p, ex);+    eval = eval->with(s, e, bt, p, ex);+    return this;+  }++  Selector* Contextualize_Eval::operator()(Selector_Schema* s)+  {+    To_String to_string;+    string result_str(s->contents()->perform(eval)->perform(&to_string));+    result_str += '{'; // the parser looks for a brace to end the selector+    Selector* result_sel = Parser::from_c_str(result_str.c_str(), ctx, s->pstate()).parse_selector_group();+    return result_sel->perform(this);+  }++  Selector* Contextualize_Eval::operator()(Selector_List* s)+  {+    return Contextualize::operator ()(s);+  }++  Selector* Contextualize_Eval::operator()(Complex_Selector* s)+  {+    return Contextualize::operator ()(s);+  }++  Selector* Contextualize_Eval::operator()(Compound_Selector* s)+  {+    return Contextualize::operator ()(s);+  }++  Selector* Contextualize_Eval::operator()(Wrapped_Selector* s)+  {+    return Contextualize::operator ()(s);+  }++  Selector* Contextualize_Eval::operator()(Pseudo_Selector* s)+  {+    return Contextualize::operator ()(s);+  }++  Selector* Contextualize_Eval::operator()(Attribute_Selector* s)+  {+    // the value might be interpolated; evaluate it+    String* v = s->value();+    if (v && eval) {+     Eval* eval_with = eval->with(env, backtrace);+     v = static_cast<String*>(v->perform(eval_with));+    }+    To_String toString;+    Attribute_Selector* ss = new (ctx.mem) Attribute_Selector(*s);+    ss->value(v);+    return ss;+  }++  Selector* Contextualize_Eval::operator()(Selector_Qualifier* s)+  {     return Contextualize::operator ()(s);+ }++  Selector* Contextualize_Eval::operator()(Type_Selector* s)+  {     return Contextualize::operator ()(s);+ }++  Selector* Contextualize_Eval::operator()(Selector_Placeholder* p)+  {+    return Contextualize::operator ()(p);+  }++  Selector* Contextualize_Eval::operator()(Selector_Reference* s)+  {+    return Contextualize::operator ()(s);+  }+}
+ libsass/contextualize_eval.hpp view
@@ -0,0 +1,44 @@+#ifndef SASS_CONTEXTUALIZE_EVAL_H+#define SASS_CONTEXTUALIZE_EVAL_H++#include "eval.hpp"+#include "context.hpp"+#include "operation.hpp"+#include "environment.hpp"+#include "ast_fwd_decl.hpp"++namespace Sass {+  struct Backtrace;++  typedef Environment<AST_Node*> Env;++  class Contextualize_Eval : public Contextualize {++    Eval*      eval;++    Selector* fallback_impl(AST_Node* n);++  public:+    Contextualize_Eval(Context&, Eval*, Env*, Backtrace*);+    virtual ~Contextualize_Eval();+    Contextualize_Eval* with(Selector*, Env*, Backtrace*, Selector* placeholder = 0, Selector* extender = 0);+    using Operation<Selector*>::operator();++    Selector* operator()(Selector_Schema*);+    Selector* operator()(Selector_List*);+    Selector* operator()(Complex_Selector*);+    Selector* operator()(Compound_Selector*);+    Selector* operator()(Wrapped_Selector*);+    Selector* operator()(Pseudo_Selector*);+    Selector* operator()(Attribute_Selector*);+    Selector* operator()(Selector_Qualifier*);+    Selector* operator()(Type_Selector*);+    Selector* operator()(Selector_Placeholder*);+    Selector* operator()(Selector_Reference*);++    template <typename U>+    Selector* fallback(U x) { return fallback_impl(x); }+  };+}++#endif
+ libsass/cssize.cpp view
@@ -0,0 +1,549 @@+#include <iostream>+#include <typeinfo>++#include "cssize.hpp"+#include "to_string.hpp"+#include "context.hpp"+#include "backtrace.hpp"++namespace Sass {++  Cssize::Cssize(Context& ctx, Env* env, Backtrace* bt)+  : ctx(ctx),+    env(env),+    block_stack(vector<Block*>()),+    p_stack(vector<Statement*>()),+    backtrace(bt)+  {  }++  Statement* Cssize::parent()+  {+    return p_stack.size() ? p_stack.back() : block_stack.front();+  }++  Statement* Cssize::operator()(Block* b)+  {+    Env new_env;+    new_env.link(*env);+    env = &new_env;+    Block* bb = new (ctx.mem) Block(b->pstate(), b->length(), b->is_root());+    // bb->tabs(b->tabs());+    block_stack.push_back(bb);+    append_block(b);+    block_stack.pop_back();+    env = env->parent();+    return bb;+  }++  Statement* Cssize::operator()(At_Rule* r)+  {+    if (!r->block() || !r->block()->length()) return r;++    if (parent()->statement_type() == Statement::RULESET)+    {+      return (r->is_keyframes()) ? new (ctx.mem) Bubble(r->pstate(), r) : bubble(r);+    }++    p_stack.push_back(r);+    At_Rule* rr = new (ctx.mem) At_Rule(r->pstate(),+                                        r->keyword(),+                                        r->selector(),+                                        r->block() ? r->block()->perform(this)->block() : 0);+    if (r->value()) rr->value(r->value());+    p_stack.pop_back();++    bool directive_exists = false;+    size_t L = rr->block() ? rr->block()->length() : 0;+    for (size_t i = 0; i < L && !directive_exists; ++i) {+      Statement* s = (*r->block())[i];+      if (s->statement_type() != Statement::BUBBLE) directive_exists = true;+      else {+        s = static_cast<Bubble*>(s)->node();+        if (s->statement_type() != Statement::DIRECTIVE) directive_exists = false;+        else directive_exists = (static_cast<At_Rule*>(s)->keyword() == rr->keyword());+      }++    }++    Block* result = new (ctx.mem) Block(rr->pstate());+    if (!(directive_exists || rr->is_keyframes()))+    {+      At_Rule* empty_node = static_cast<At_Rule*>(rr);+      empty_node->block(new (ctx.mem) Block(rr->block() ? rr->block()->pstate() : rr->pstate()));+      *result << empty_node;+    }++    Statement* ss = debubble(rr->block() ? rr->block() : new (ctx.mem) Block(rr->pstate()), rr);+    for (size_t i = 0, L = ss->block()->length(); i < L; ++i) {+      *result << (*ss->block())[i];+    }++    return result;+  }++  Statement* Cssize::operator()(Keyframe_Rule* r)+  {+    if (!r->block() || !r->block()->length()) return r;++    Keyframe_Rule* rr = new (ctx.mem) Keyframe_Rule(r->pstate(),+                                                    r->block()->perform(this)->block());+    if (r->selector()) rr->selector(r->selector());++    return debubble(rr->block(), rr)->block();+  }++  Statement* Cssize::operator()(Ruleset* r)+  {+    p_stack.push_back(r);+    Ruleset* rr = new (ctx.mem) Ruleset(r->pstate(),+                                        r->selector(),+                                        r->block()->perform(this)->block());+    // rr->tabs(r->block()->tabs());+    p_stack.pop_back();++    Block* props = new (ctx.mem) Block(rr->block()->pstate());+    Block* rules = new (ctx.mem) Block(rr->block()->pstate());+    for (size_t i = 0, L = rr->block()->length(); i < L; i++)+    {+      Statement* s = (*rr->block())[i];+      if (bubblable(s)) *rules << s;+      if (!bubblable(s)) *props << s;+    }++    if (props->length())+    {+      Block* bb = new (ctx.mem) Block(rr->block()->pstate());+      *bb += props;+      rr->block(bb);++      for (size_t i = 0, L = rules->length(); i < L; i++)+      {+        (*rules)[i]->tabs((*rules)[i]->tabs() + 1);+      }++      rules->unshift(rr);+    }++    rules = debubble(rules)->block();++    if (!(!rules->length() ||+          !bubblable(rules->last()) ||+          parent()->statement_type() == Statement::RULESET))+    {+      rules->last()->group_end(true);+    }++    return rules;+  }++  Statement* Cssize::operator()(Media_Block* m)+  {+    if (parent()->statement_type() == Statement::RULESET)+    { return bubble(m); }++    if (parent()->statement_type() == Statement::MEDIA)+    { return new (ctx.mem) Bubble(m->pstate(), m); }++    p_stack.push_back(m);++    Media_Block* mm = new (ctx.mem) Media_Block(m->pstate(),+                                                m->media_queries(),+                                                m->block()->perform(this)->block());+    mm->tabs(m->tabs());++    p_stack.pop_back();++    return debubble(mm->block(), mm)->block();+  }++  Statement* Cssize::operator()(Feature_Block* m)+  {+    if (!m->block()->length())+    { return m; }++    if (parent()->statement_type() == Statement::RULESET)+    { return bubble(m); }++    p_stack.push_back(m);++    Feature_Block* mm = new (ctx.mem) Feature_Block(m->pstate(),+                                                    m->feature_queries(),+                                                    m->block()->perform(this)->block());+    mm->tabs(m->tabs());++    p_stack.pop_back();++    return debubble(mm->block(), mm)->block();+  }++  Statement* Cssize::operator()(At_Root_Block* m)+  {+    bool tmp = false;+    for (size_t i = 0, L = p_stack.size(); i < L; ++i) {+      Statement* s = p_stack[i];+      tmp |= m->exclude_node(s);+    }++    if (!tmp)+    {+      Block* bb = m->block()->perform(this)->block();+      for (size_t i = 0, L = bb->length(); i < L; ++i) {+        // (bb->elements())[i]->tabs(m->tabs());+        if (bubblable((*bb)[i])) (*bb)[i]->tabs((*bb)[i]->tabs() + m->tabs());+      }+      if (bb->length() && bubblable(bb->last())) bb->last()->group_end(m->group_end());+      return bb;+    }++    if (m->exclude_node(parent()))+    {+      return new (ctx.mem) Bubble(m->pstate(), m);+    }++    return bubble(m);+  }++  Statement* Cssize::bubble(At_Rule* m)+  {+    Block* bb = new (ctx.mem) Block(this->parent()->pstate());+    Has_Block* new_rule = static_cast<Has_Block*>(shallow_copy(this->parent()));+    new_rule->block(bb);+    new_rule->tabs(this->parent()->tabs());++    size_t L = m->block() ? m->block()->length() : 0;+    for (size_t i = 0; i < L; ++i) {+      *new_rule->block() << (*m->block())[i];+    }++    Block* wrapper_block = new (ctx.mem) Block(m->block() ? m->block()->pstate() : m->pstate());+    *wrapper_block << new_rule;+    At_Rule* mm = new (ctx.mem) At_Rule(m->pstate(),+                                        m->keyword(),+                                        m->selector(),+                                        wrapper_block);+    if (m->value()) mm->value(m->value());++    Bubble* bubble = new (ctx.mem) Bubble(mm->pstate(), mm);+    return bubble;+  }++  Statement* Cssize::bubble(At_Root_Block* m)+  {+    Block* bb = new (ctx.mem) Block(this->parent()->pstate());+    Has_Block* new_rule = static_cast<Has_Block*>(shallow_copy(this->parent()));+    new_rule->block(bb);+    new_rule->tabs(this->parent()->tabs());++    for (size_t i = 0, L = m->block()->length(); i < L; ++i) {+      *new_rule->block() << (*m->block())[i];+    }++    Block* wrapper_block = new (ctx.mem) Block(m->block()->pstate());+    *wrapper_block << new_rule;+    At_Root_Block* mm = new (ctx.mem) At_Root_Block(m->pstate(),+                                                    wrapper_block,+                                                    m->expression());++    Bubble* bubble = new (ctx.mem) Bubble(mm->pstate(), mm);+    return bubble;+  }++  Statement* Cssize::bubble(Feature_Block* m)+  {+    Ruleset* parent = static_cast<Ruleset*>(shallow_copy(this->parent()));++    Block* bb = new (ctx.mem) Block(parent->block()->pstate());+    Ruleset* new_rule = new (ctx.mem) Ruleset(parent->pstate(),+                                              parent->selector(),+                                              bb);+    new_rule->tabs(parent->tabs());++    for (size_t i = 0, L = m->block()->length(); i < L; ++i) {+      *new_rule->block() << (*m->block())[i];+    }++    Block* wrapper_block = new (ctx.mem) Block(m->block()->pstate());+    *wrapper_block << new_rule;+    Feature_Block* mm = new (ctx.mem) Feature_Block(m->pstate(),+                                                    m->feature_queries(),+                                                    wrapper_block);++    mm->tabs(m->tabs());++    Bubble* bubble = new (ctx.mem) Bubble(mm->pstate(), mm);+    return bubble;+  }++  Statement* Cssize::bubble(Media_Block* m)+  {+    Ruleset* parent = static_cast<Ruleset*>(shallow_copy(this->parent()));++    Block* bb = new (ctx.mem) Block(parent->block()->pstate());+    Ruleset* new_rule = new (ctx.mem) Ruleset(parent->pstate(),+                                              parent->selector(),+                                              bb);+    new_rule->tabs(parent->tabs());++    for (size_t i = 0, L = m->block()->length(); i < L; ++i) {+      *new_rule->block() << (*m->block())[i];+    }++    Block* wrapper_block = new (ctx.mem) Block(m->block()->pstate());+    *wrapper_block << new_rule;+    Media_Block* mm = new (ctx.mem) Media_Block(m->pstate(),+                                                m->media_queries(),+                                                wrapper_block,+                                                m->selector());++    mm->tabs(m->tabs());++    Bubble* bubble = new (ctx.mem) Bubble(mm->pstate(), mm);++    return bubble;+  }++  bool Cssize::bubblable(Statement* s)+  {+    return s->statement_type() == Statement::RULESET || s->bubbles();+  }++  Statement* Cssize::flatten(Statement* s)+  {+    Block* bb = s->block();+    Block* result = new (ctx.mem) Block(bb->pstate(), 0, bb->is_root());+    for (size_t i = 0, L = bb->length(); i < L; ++i) {+      Statement* ss = (*bb)[i];+      if (ss->block()) {+        ss = flatten(ss);+        for (size_t j = 0, K = ss->block()->length(); j < K; ++j) {+          *result << (*ss->block())[j];+        }+      }+      else {+        *result << ss;+      }+    }+    return result;+  }++  vector<pair<bool, Block*>> Cssize::slice_by_bubble(Statement* b)+  {+    vector<pair<bool, Block*>> results;+    for (size_t i = 0, L = b->block()->length(); i < L; ++i) {+      Statement* value = (*b->block())[i];+      bool key = value->statement_type() == Statement::BUBBLE;++      if (!results.empty() && results.back().first == key)+      {+        Block* wrapper_block = results.back().second;+        *wrapper_block << value;+      }+      else+      {+        Block* wrapper_block = new (ctx.mem) Block(value->pstate());+        *wrapper_block << value;+        results.push_back(make_pair(key, wrapper_block));+      }+    }+    return results;+  }++  Statement* Cssize::shallow_copy(Statement* s)+  {+    switch (s->statement_type())+    {+      case Statement::RULESET:+        return new (ctx.mem) Ruleset(*static_cast<Ruleset*>(s));+      case Statement::MEDIA:+        return new (ctx.mem) Media_Block(*static_cast<Media_Block*>(s));+      case Statement::BUBBLE:+        return new (ctx.mem) Bubble(*static_cast<Bubble*>(s));+      case Statement::DIRECTIVE:+        return new (ctx.mem) At_Rule(*static_cast<At_Rule*>(s));+      case Statement::FEATURE:+        return new (ctx.mem) Feature_Block(*static_cast<Feature_Block*>(s));+      case Statement::ATROOT:+        return new (ctx.mem) At_Root_Block(*static_cast<At_Root_Block*>(s));+      case Statement::KEYFRAMERULE:+        return new (ctx.mem) Keyframe_Rule(*static_cast<Keyframe_Rule*>(s));+      case Statement::NONE:+      default:+        error("unknown internal error; please contact the LibSass maintainers", s->pstate(), backtrace);+        String_Constant* msg = new (ctx.mem) String_Constant(ParserState("[WARN]"), string("`CSSize` can't clone ") + typeid(*s).name());+        return new (ctx.mem) Warning(ParserState("[WARN]"), msg);+    }+  }++  Statement* Cssize::debubble(Block* children, Statement* parent)+  {+    Has_Block* previous_parent = 0;+    vector<pair<bool, Block*>> baz = slice_by_bubble(children);+    Block* result = new (ctx.mem) Block(children->pstate());++    for (size_t i = 0, L = baz.size(); i < L; ++i) {+      bool is_bubble = baz[i].first;+      Block* slice = baz[i].second;++      if (!is_bubble) {+        if (!parent) {+          *result << slice;+        }+        else if (previous_parent) {+          *previous_parent->block() += slice;+        }+        else {+          previous_parent = static_cast<Has_Block*>(shallow_copy(parent));+          previous_parent->tabs(parent->tabs());++          Has_Block* new_parent = static_cast<Has_Block*>(shallow_copy(parent));+          new_parent->block(slice);+          new_parent->tabs(parent->tabs());++          *result << new_parent;+        }+        continue;+      }++      Block* wrapper_block = new (ctx.mem) Block(children->block()->pstate(),+                                                 children->block()->length(),+                                                 children->block()->is_root());++      for (size_t j = 0, K = slice->length(); j < K; ++j)+      {+        Statement* ss = 0;+        Bubble* b = static_cast<Bubble*>((*slice)[j]);++        if (!parent ||+            parent->statement_type() != Statement::MEDIA ||+            b->node()->statement_type() != Statement::MEDIA ||+            static_cast<Media_Block*>(b->node())->media_queries() == static_cast<Media_Block*>(parent)->media_queries())+        {+          ss = b->node();+        }+        else+        {+          List* mq = merge_media_queries(static_cast<Media_Block*>(b->node()), static_cast<Media_Block*>(parent));+          static_cast<Media_Block*>(b->node())->media_queries(mq);+          ss = b->node();+        }++        if (!ss) continue;++        ss->tabs(ss->tabs() + b->tabs());+        ss->group_end(b->group_end());++        if (!ss) continue;++        Block* bb = new (ctx.mem) Block(children->block()->pstate(),+                                        children->block()->length(),+                                        children->block()->is_root());+        *bb << ss->perform(this);+        Statement* wrapper = flatten(bb);+        *wrapper_block << wrapper;++        if (wrapper->block()->length()) {+          previous_parent = 0;+        }+      }++      if (wrapper_block) {+        *result << flatten(wrapper_block);+      }+    }++    return flatten(result);+  }++  Statement* Cssize::fallback_impl(AST_Node* n)+  {+    return static_cast<Statement*>(n);+  }++  void Cssize::append_block(Block* b)+  {+    Block* current_block = block_stack.back();++    for (size_t i = 0, L = b->length(); i < L; ++i) {+      Statement* ith = (*b)[i]->perform(this);+      if (ith && ith->block()) {+        for (size_t j = 0, K = ith->block()->length(); j < K; ++j) {+          *current_block << (*ith->block())[j];+        }+      }+      else if (ith) {+        *current_block << ith;+      }+    }+  }++  List* Cssize::merge_media_queries(Media_Block* m1, Media_Block* m2)+  {+    List* qq = new (ctx.mem) List(m1->media_queries()->pstate(),+                                  m1->media_queries()->length(),+                                  List::COMMA);++    for (size_t i = 0, L = m1->media_queries()->length(); i < L; i++) {+      for (size_t j = 0, K = m2->media_queries()->length(); j < K; j++) {+        Media_Query* mq1 = static_cast<Media_Query*>((*m1->media_queries())[i]);+        Media_Query* mq2 = static_cast<Media_Query*>((*m2->media_queries())[j]);+        Media_Query* mq = merge_media_query(mq1, mq2);++        if (mq) *qq << mq;+      }+    }++    return qq;+  }+++  Media_Query* Cssize::merge_media_query(Media_Query* mq1, Media_Query* mq2)+  {+    To_String to_string(&ctx);++    string type;+    string mod;++    string m1 = string(mq1->is_restricted() ? "only" : mq1->is_negated() ? "not" : "");+    string t1 = mq1->media_type() ? mq1->media_type()->perform(&to_string) : "";+    string m2 = string(mq2->is_restricted() ? "only" : mq1->is_negated() ? "not" : "");+    string t2 = mq2->media_type() ? mq2->media_type()->perform(&to_string) : "";+++    if (t1.empty()) t1 = t2;+    if (t2.empty()) t2 = t1;++    if ((m1 == "not") ^ (m2 == "not")) {+      if (t1 == t2) {+        return 0;+      }+      type = m1 == "not" ? t2 : t1;+      mod = m1 == "not" ? m2 : m1;+    }+    else if (m1 == "not" && m2 == "not") {+      if (t1 != t2) {+        return 0;+      }+      type = t1;+      mod = "not";+    }+    else if (t1 != t2) {+      return 0;+    } else {+      type = t1;+      mod = m1.empty() ? m2 : m1;+    }++    Media_Query* mm = new (ctx.mem) Media_Query(+      mq1->pstate(), 0,+      mq1->length() + mq2->length(), mod == "not", mod == "only"+    );++    if (!type.empty()) {+      mm->media_type(new (ctx.mem) String_Constant(mq1->pstate(), type));+    }++    *mm += mq2;+    *mm += mq1;+    return mm;+  }+}
+ libsass/cssize.hpp view
@@ -0,0 +1,82 @@+#ifndef SASS_CSSIZE_H+#define SASS_CSSIZE_H++#include <vector>+#include <iostream>++#include "ast.hpp"+#include "context.hpp"+#include "operation.hpp"+#include "environment.hpp"++namespace Sass {+  using namespace std;++  typedef Environment<AST_Node*> Env;+  struct Backtrace;++  class Cssize : public Operation_CRTP<Statement*, Cssize> {++    Context&            ctx;+    Env*                env;+    vector<Block*>      block_stack;+    vector<Statement*>  p_stack;+    Backtrace*          backtrace;++    Statement* fallback_impl(AST_Node* n);++  public:+    Cssize(Context&, Env*, Backtrace*);+    virtual ~Cssize() { }++    using Operation<Statement*>::operator();++    Statement* operator()(Block*);+    Statement* operator()(Ruleset*);+    // Statement* operator()(Propset*);+    // Statement* operator()(Bubble*);+    Statement* operator()(Media_Block*);+    Statement* operator()(Feature_Block*);+    Statement* operator()(At_Root_Block*);+    Statement* operator()(At_Rule*);+    Statement* operator()(Keyframe_Rule*);+    // Statement* operator()(Declaration*);+    // Statement* operator()(Assignment*);+    // Statement* operator()(Import*);+    // Statement* operator()(Import_Stub*);+    // Statement* operator()(Warning*);+    // Statement* operator()(Error*);+    // Statement* operator()(Comment*);+    // Statement* operator()(If*);+    // Statement* operator()(For*);+    // Statement* operator()(Each*);+    // Statement* operator()(While*);+    // Statement* operator()(Return*);+    // Statement* operator()(Extension*);+    // Statement* operator()(Definition*);+    // Statement* operator()(Mixin_Call*);+    // Statement* operator()(Content*);++    Statement* parent();+    vector<pair<bool, Block*>> slice_by_bubble(Statement*);+    Statement* bubble(At_Rule*);+    Statement* bubble(At_Root_Block*);+    Statement* bubble(Media_Block*);+    Statement* bubble(Feature_Block*);+    Statement* shallow_copy(Statement*);+    Statement* debubble(Block* children, Statement* parent = 0);+    Statement* flatten(Statement*);+    bool bubblable(Statement*);++    List* merge_media_queries(Media_Block*, Media_Block*);+    Media_Query* merge_media_query(Media_Query*, Media_Query*);++    template <typename U>+    Statement* fallback(U x) { return fallback_impl(x); }++    void append_block(Block*);+  };++}++#endif
+ libsass/debug.hpp view
@@ -0,0 +1,39 @@+#ifndef SASS_DEBUG_H+#define SASS_DEBUG_H++#include <stdint.h>++enum dbg_lvl_t : uint32_t {+	NONE = 0,+	TRIM = 1,+	CHUNKS = 2,+	SUBWEAVE = 4,+	WEAVE = 8,+	EXTEND_COMPOUND = 16,+	EXTEND_COMPLEX = 32,+	LCS = 64,+  EXTEND_OBJECT = 128,+	ALL = UINT32_MAX+};++#ifdef DEBUG++#ifndef DEBUG_LVL+const uint32_t debug_lvl = UINT32_MAX;+#else+const uint32_t debug_lvl = (DEBUG_LVL);+#endif // DEBUG_LVL++#define DEBUG_PRINT(lvl, x) if((lvl) & debug_lvl) { std::cerr << x; }+#define DEBUG_PRINTLN(lvl, x) if((lvl) & debug_lvl) { std::cerr << x << std::endl; }+#define DEBUG_EXEC(lvl, x) if((lvl) & debug_lvl) { x; }++#else // DEBUG++#define DEBUG_PRINT(lvl, x)+#define DEBUG_PRINTLN(lvl, x)+#define DEBUG_EXEC(lvl, x)++#endif // DEBUG++#endif // SASS_DEBUG
+ libsass/debugger.hpp view
@@ -0,0 +1,391 @@+#ifndef SASS_DEBUGGER_H+#define SASS_DEBUGGER_H++#include <string>+#include <sstream>+#include "ast_fwd_decl.hpp"++using namespace std;+using namespace Sass;++inline string str_replace(std::string str, const std::string& oldStr, const std::string& newStr)+{+  size_t pos = 0;+  while((pos = str.find(oldStr, pos)) != std::string::npos)+  {+     str.replace(pos, oldStr.length(), newStr);+     pos += newStr.length();+  }+  return str;+}++inline string prettyprint(const string& str) {+  string clean = str_replace(str, "\n", "\\n");+  clean = str_replace(clean, "	", "\\t");+  clean = str_replace(clean, "\r", "\\r");+  return clean;+}++inline string longToHex(long long t) {+  std::stringstream is;+  is << std::hex << t;+  return is.str();+}++inline void debug_ast(AST_Node* node, string ind = "", Env* env = 0)+{+  if (node == 0) return;+  if (ind == "") cerr << "####################################################################\n";+  if (dynamic_cast<Bubble*>(node)) {+    Bubble* bubble = dynamic_cast<Bubble*>(node);+    cerr << ind << "Bubble " << bubble << " " << bubble->tabs() << endl;+  } else if (dynamic_cast<At_Root_Block*>(node)) {+    At_Root_Block* root_block = dynamic_cast<At_Root_Block*>(node);+    cerr << ind << "At_Root_Block " << root_block << " " << root_block->tabs() << endl;+    if (root_block->block()) for(auto i : root_block->block()->elements()) { debug_ast(i, ind + " ", env); }+  } else if (dynamic_cast<Selector_List*>(node)) {+    Selector_List* selector = dynamic_cast<Selector_List*>(node);++    cerr << ind << "Selector_List " << selector+      << " [block:" << selector->last_block() << "]"+      << (selector->last_block() && selector->last_block()->is_root() ? " [root]" : "")+      << " [@media:" << selector->media_block() << "]"+      << (selector->is_optional() ? " [is_optional]": " -")+      << (selector->has_line_break() ? " [line-break]": " -")+      << (selector->has_line_feed() ? " [line-feed]": " -")+    << endl;++    for(auto i : selector->elements()) { debug_ast(i, ind + " ", env); }++//  } else if (dynamic_cast<Expression*>(node)) {+//    Expression* expression = dynamic_cast<Expression*>(node);+//    cerr << ind << "Expression " << expression << " " << expression->concrete_type() << endl;++  } else if (dynamic_cast<Parent_Selector*>(node)) {+    Parent_Selector* selector = dynamic_cast<Parent_Selector*>(node);+    cerr << ind << "Parent_Selector " << selector;+    cerr << " <" << prettyprint(selector->pstate().token.ws_before()) << ">" << endl;+    debug_ast(selector->selector(), ind + "->", env);++  } else if (dynamic_cast<Complex_Selector*>(node)) {+    Complex_Selector* selector = dynamic_cast<Complex_Selector*>(node);+    cerr << ind << "Complex_Selector " << selector+      << " [block:" << selector->last_block() << "]"+      << " [weight:" << longToHex(selector->specificity()) << "]"+      << (selector->last_block() && selector->last_block()->is_root() ? " [root]" : "")+      << " [@media:" << selector->media_block() << "]"+      << (selector->is_optional() ? " [is_optional]": " -")+      << (selector->has_line_break() ? " [line-break]": " -")+      << (selector->has_line_feed() ? " [line-feed]": " -") << " -> ";+      switch (selector->combinator()) {+        case Complex_Selector::PARENT_OF:   cerr << "{>}"; break;+        case Complex_Selector::PRECEDES:    cerr << "{~}"; break;+        case Complex_Selector::ADJACENT_TO: cerr << "{+}"; break;+        case Complex_Selector::ANCESTOR_OF: cerr << "{ }"; break;+      }+    cerr << " <" << prettyprint(selector->pstate().token.ws_before()) << ">" << endl;+    debug_ast(selector->head(), ind + " ", env);+    debug_ast(selector->tail(), ind + "-", env);+  } else if (dynamic_cast<Compound_Selector*>(node)) {+    Compound_Selector* selector = dynamic_cast<Compound_Selector*>(node);+    cerr << ind << "Compound_Selector " << selector;+    cerr << " [block:" << selector->last_block() << "]";+    cerr << " [weight:" << longToHex(selector->specificity()) << "]";+    // cerr << (selector->last_block() && selector->last_block()->is_root() ? " [root]" : "");+    cerr << " [@media:" << selector->media_block() << "]";+    cerr << (selector->is_optional() ? " [is_optional]": " -");+    cerr << (selector->has_line_break() ? " [line-break]": " -");+    cerr << (selector->has_line_feed() ? " [line-feed]": " -");+    cerr << " <" << prettyprint(selector->pstate().token.ws_before()) << ">" << endl;+    for(auto i : selector->elements()) { debug_ast(i, ind + " ", env); }+  } else if (dynamic_cast<Propset*>(node)) {+    Propset* selector = dynamic_cast<Propset*>(node);+    cerr << ind << "Propset " << selector << " " << selector->tabs() << endl;+    if (selector->block()) for(auto i : selector->block()->elements()) { debug_ast(i, ind + " ", env); }+  } else if (dynamic_cast<Wrapped_Selector*>(node)) {+    Wrapped_Selector* selector = dynamic_cast<Wrapped_Selector*>(node);+    cerr << ind << "Wrapped_Selector " << selector << " <<" << selector->name() << ">>" << (selector->has_line_break() ? " [line-break]": " -") << (selector->has_line_feed() ? " [line-feed]": " -") << endl;+    debug_ast(selector->selector(), ind + " () ", env);+  } else if (dynamic_cast<Pseudo_Selector*>(node)) {+    Pseudo_Selector* selector = dynamic_cast<Pseudo_Selector*>(node);+    cerr << ind << "Pseudo_Selector " << selector << " <<" << selector->name() << ">>" << (selector->has_line_break() ? " [line-break]": " -") << (selector->has_line_feed() ? " [line-feed]": " -") << endl;+    debug_ast(selector->expression(), ind + " <= ", env);+  } else if (dynamic_cast<Attribute_Selector*>(node)) {+    Attribute_Selector* selector = dynamic_cast<Attribute_Selector*>(node);+    cerr << ind << "Attribute_Selector " << selector << " <<" << selector->name() << ">>" << (selector->has_line_break() ? " [line-break]": " -") << (selector->has_line_feed() ? " [line-feed]": " -") << endl;+    debug_ast(selector->value(), ind + "[" + selector->matcher() + "] ", env);+  } else if (dynamic_cast<Selector_Qualifier*>(node)) {+    Selector_Qualifier* selector = dynamic_cast<Selector_Qualifier*>(node);+    cerr << ind << "Selector_Qualifier " << selector << " <<" << selector->name() << ">>" << (selector->has_line_break() ? " [line-break]": " -") << (selector->has_line_feed() ? " [line-feed]": " -") << endl;+  } else if (dynamic_cast<Type_Selector*>(node)) {+    Type_Selector* selector = dynamic_cast<Type_Selector*>(node);+    cerr << ind << "Type_Selector " << selector << " <<" << selector->name() << ">>" << (selector->has_line_break() ? " [line-break]": " -") <<+      " <" << prettyprint(selector->pstate().token.ws_before()) << ">" << endl;+  } else if (dynamic_cast<Selector_Placeholder*>(node)) {++    Selector_Placeholder* selector = dynamic_cast<Selector_Placeholder*>(node);+    cerr << ind << "Selector_Placeholder [" << selector->name() << "] " << selector+      << " [block:" << selector->last_block() << "]"+      << " [@media:" << selector->media_block() << "]"+      << (selector->is_optional() ? " [is_optional]": " -")+      << (selector->has_line_break() ? " [line-break]": " -")+      << (selector->has_line_feed() ? " [line-feed]": " -")+    << endl;++  } else if (dynamic_cast<Selector_Reference*>(node)) {+    Selector_Reference* selector = dynamic_cast<Selector_Reference*>(node);+    cerr << ind << "Selector_Reference " << selector << " @ref " << selector->selector() << endl;+  } else if (dynamic_cast<Simple_Selector*>(node)) {+    Simple_Selector* selector = dynamic_cast<Simple_Selector*>(node);+    cerr << ind << "Simple_Selector " << selector << (selector->has_line_break() ? " [line-break]": " -") << (selector->has_line_feed() ? " [line-feed]": " -") << endl;++  } else if (dynamic_cast<Selector_Schema*>(node)) {+    Selector_Schema* selector = dynamic_cast<Selector_Schema*>(node);+    cerr << ind << "Selector_Schema " << selector+      << " [block:" << selector->last_block() << "]"+      << (selector->last_block() && selector->last_block()->is_root() ? " [root]" : "")+      << " [@media:" << selector->media_block() << "]"+      << (selector->has_line_break() ? " [line-break]": " -")+      << (selector->has_line_feed() ? " [line-feed]": " -")+    << endl;++    debug_ast(selector->contents(), ind + " ");+    // for(auto i : selector->elements()) { debug_ast(i, ind + " ", env); }++  } else if (dynamic_cast<Selector*>(node)) {+    Selector* selector = dynamic_cast<Selector*>(node);+    cerr << ind << "Selector " << selector+      << (selector->has_line_break() ? " [line-break]": " -")+      << (selector->has_line_feed() ? " [line-feed]": " -")+    << endl;++  } else if (dynamic_cast<Media_Query_Expression*>(node)) {+    Media_Query_Expression* block = dynamic_cast<Media_Query_Expression*>(node);+    cerr << ind << "Media_Query_Expression " << block+      << (block->is_interpolated() ? " [is_interpolated]": " -")+    << endl;+    debug_ast(block->feature(), ind + " f) ");+    debug_ast(block->value(), ind + " v) ");++  } else if (dynamic_cast<Media_Query*>(node)) {+    Media_Query* block = dynamic_cast<Media_Query*>(node);+    cerr << ind << "Media_Query " << block+      << (block->is_negated() ? " [is_negated]": " -")+      << (block->is_restricted() ? " [is_restricted]": " -")+    << endl;+    debug_ast(block->media_type(), ind + " ");+    for(auto i : block->elements()) { debug_ast(i, ind + " ", env); }++  } else if (dynamic_cast<Media_Block*>(node)) {+    Media_Block* block = dynamic_cast<Media_Block*>(node);+    cerr << ind << "Media_Block " << block << " " << block->tabs() << endl;+    debug_ast(block->media_queries(), ind + " =@ ");+    debug_ast(block->selector(), ind + " -@ ");+    if (block->block()) for(auto i : block->block()->elements()) { debug_ast(i, ind + " ", env); }+  } else if (dynamic_cast<Feature_Block*>(node)) {+    Feature_Block* block = dynamic_cast<Feature_Block*>(node);+    cerr << ind << "Feature_Block " << block << " " << block->tabs() << endl;+    if (block->block()) for(auto i : block->block()->elements()) { debug_ast(i, ind + " ", env); }+  } else if (dynamic_cast<Block*>(node)) {+    Block* root_block = dynamic_cast<Block*>(node);+    cerr << ind << "Block " << root_block << " " << root_block->tabs() << endl;+    if (root_block->block()) for(auto i : root_block->block()->elements()) { debug_ast(i, ind + " ", env); }+  } else if (dynamic_cast<Warning*>(node)) {+    Warning* block = dynamic_cast<Warning*>(node);+    cerr << ind << "Warning " << block << " " << block->tabs() << endl;+  } else if (dynamic_cast<Error*>(node)) {+    Error* block = dynamic_cast<Error*>(node);+    cerr << ind << "Error " << block << " " << block->tabs() << endl;+  } else if (dynamic_cast<Debug*>(node)) {+    Debug* block = dynamic_cast<Debug*>(node);+    cerr << ind << "Debug " << block << " " << block->tabs() << endl;+  } else if (dynamic_cast<Comment*>(node)) {+    Comment* block = dynamic_cast<Comment*>(node);+    cerr << ind << "Comment " << block << " " << block->tabs() <<+      " <" << prettyprint(block->pstate().token.ws_before()) << ">" << endl;+    debug_ast(block->text(), ind + "// ", env);+  } else if (dynamic_cast<If*>(node)) {+    If* block = dynamic_cast<If*>(node);+    cerr << ind << "If " << block << " " << block->tabs() << endl;+  } else if (dynamic_cast<Return*>(node)) {+    Return* block = dynamic_cast<Return*>(node);+    cerr << ind << "Return " << block << " " << block->tabs() << endl;+  } else if (dynamic_cast<Extension*>(node)) {+    Extension* block = dynamic_cast<Extension*>(node);+    cerr << ind << "Extension " << block << " " << block->tabs() << endl;+    debug_ast(block->selector(), ind + "-> ", env);+  } else if (dynamic_cast<Content*>(node)) {+    Content* block = dynamic_cast<Content*>(node);+    cerr << ind << "Content " << block << " " << block->tabs() << endl;+  } else if (dynamic_cast<Import_Stub*>(node)) {+    Import_Stub* block = dynamic_cast<Import_Stub*>(node);+    cerr << ind << "Import_Stub " << block << " " << block->tabs() << endl;+  } else if (dynamic_cast<Import*>(node)) {+    Import* block = dynamic_cast<Import*>(node);+    cerr << ind << "Import " << block << " " << block->tabs() << endl;+    // vector<string>         files_;+    for (auto imp : block->urls()) debug_ast(imp, "@ ", env);+  } else if (dynamic_cast<Assignment*>(node)) {+    Assignment* block = dynamic_cast<Assignment*>(node);+    cerr << ind << "Assignment " << block << " <<" << block->variable() << ">> " << block->tabs() << endl;+    debug_ast(block->value(), ind + "=", env);+  } else if (dynamic_cast<Declaration*>(node)) {+    Declaration* block = dynamic_cast<Declaration*>(node);+    cerr << ind << "Declaration " << block << " " << block->tabs() << endl;+    debug_ast(block->property(), ind + " prop: ", env);+    debug_ast(block->value(), ind + " value: ", env);+  } else if (dynamic_cast<Keyframe_Rule*>(node)) {+    Keyframe_Rule* has_block = dynamic_cast<Keyframe_Rule*>(node);+    cerr << ind << "Keyframe_Rule " << has_block << " " << has_block->tabs() << endl;+    if (has_block->selector()) debug_ast(has_block->selector(), ind + "@");+    if (has_block->block()) for(auto i : has_block->block()->elements()) { debug_ast(i, ind + " ", env); }+  } else if (dynamic_cast<At_Rule*>(node)) {+    At_Rule* block = dynamic_cast<At_Rule*>(node);+    cerr << ind << "At_Rule " << block << " [" << block->keyword() << "] " << block->tabs() << endl;+    debug_ast(block->value(), ind + "+", env);+    debug_ast(block->selector(), ind + "~", env);+    if (block->block()) for(auto i : block->block()->elements()) { debug_ast(i, ind + " ", env); }+  } else if (dynamic_cast<Each*>(node)) {+    Each* block = dynamic_cast<Each*>(node);+    cerr << ind << "Each " << block << " " << block->tabs() << endl;+    if (block->block()) for(auto i : block->block()->elements()) { debug_ast(i, ind + " ", env); }+  } else if (dynamic_cast<For*>(node)) {+    For* block = dynamic_cast<For*>(node);+    cerr << ind << "For " << block << " " << block->tabs() << endl;+    if (block->block()) for(auto i : block->block()->elements()) { debug_ast(i, ind + " ", env); }+  } else if (dynamic_cast<While*>(node)) {+    While* block = dynamic_cast<While*>(node);+    cerr << ind << "While " << block << " " << block->tabs() << endl;+    if (block->block()) for(auto i : block->block()->elements()) { debug_ast(i, ind + " ", env); }+  } else if (dynamic_cast<Definition*>(node)) {+    Definition* block = dynamic_cast<Definition*>(node);+    cerr << ind << "Definition " << block << " " << block->tabs() << endl;+    if (block->block()) for(auto i : block->block()->elements()) { debug_ast(i, ind + " ", env); }+  } else if (dynamic_cast<Mixin_Call*>(node)) {+    Mixin_Call* block = dynamic_cast<Mixin_Call*>(node);+    cerr << ind << "Mixin_Call " << block << " " << block->tabs() << endl;+    if (block->block()) for(auto i : block->block()->elements()) { debug_ast(i, ind + " ", env); }+  } else if (dynamic_cast<Ruleset*>(node)) {+    Ruleset* ruleset = dynamic_cast<Ruleset*>(node);+    cerr << ind << "Ruleset " << ruleset << " " << ruleset->tabs() << endl;+    debug_ast(ruleset->selector(), ind + " ");+    if (ruleset->block()) for(auto i : ruleset->block()->elements()) { debug_ast(i, ind + " ", env); }+  } else if (dynamic_cast<Block*>(node)) {+    Block* block = dynamic_cast<Block*>(node);+    cerr << ind << "Block " << block << " " << block->tabs() << endl;+    for(auto i : block->elements()) { debug_ast(i, ind + " ", env); }+  } else if (dynamic_cast<Textual*>(node)) {+    Textual* expression = dynamic_cast<Textual*>(node);+    cerr << ind << "Textual ";+    if (expression->type() == Textual::NUMBER) cerr << " [NUMBER]";+    else if (expression->type() == Textual::PERCENTAGE) cerr << " [PERCENTAGE]";+    else if (expression->type() == Textual::DIMENSION) cerr << " [DIMENSION]";+    else if (expression->type() == Textual::HEX) cerr << " [HEX]";+    cerr << expression << " [" << expression->value() << "]" << endl;+  } else if (dynamic_cast<Variable*>(node)) {+    Variable* expression = dynamic_cast<Variable*>(node);+    cerr << ind << "Variable " << expression << " [" << expression->name() << "]" << endl;+    string name(expression->name());+    if (env && env->has(name)) debug_ast(static_cast<Expression*>((*env)[name]), ind + " -> ", env);+  } else if (dynamic_cast<Function_Call_Schema*>(node)) {+    Function_Call_Schema* expression = dynamic_cast<Function_Call_Schema*>(node);+    cerr << ind << "Function_Call_Schema " << expression << "]" << endl;+    debug_ast(expression->name(), ind + "name: ", env);+    debug_ast(expression->arguments(), ind + " args: ", env);+  } else if (dynamic_cast<Function_Call*>(node)) {+    Function_Call* expression = dynamic_cast<Function_Call*>(node);+    cerr << ind << "Function_Call " << expression << " [" << expression->name() << "]" << endl;+    debug_ast(expression->arguments(), ind + " args: ", env);+  } else if (dynamic_cast<Arguments*>(node)) {+    Arguments* expression = dynamic_cast<Arguments*>(node);+    cerr << ind << "Arguments " << expression << "]" << endl;+    for(auto i : expression->elements()) { debug_ast(i, ind + " ", env); }+  } else if (dynamic_cast<Argument*>(node)) {+    Argument* expression = dynamic_cast<Argument*>(node);+    cerr << ind << "Argument " << expression << " [" << expression->value() << "]" << endl;+    debug_ast(expression->value(), ind + " value: ", env);+  } else if (dynamic_cast<Unary_Expression*>(node)) {+    Unary_Expression* expression = dynamic_cast<Unary_Expression*>(node);+    cerr << ind << "Unary_Expression " << expression << " [" << expression->type() << "]" << endl;+    debug_ast(expression->operand(), ind + " operand: ", env);+  } else if (dynamic_cast<Binary_Expression*>(node)) {+    Binary_Expression* expression = dynamic_cast<Binary_Expression*>(node);+    cerr << ind << "Binary_Expression " << expression << " [" << expression->type() << "]" << endl;+    debug_ast(expression->left(), ind + " left:  ", env);+    debug_ast(expression->right(), ind + " right: ", env);+  } else if (dynamic_cast<Map*>(node)) {+    Map* expression = dynamic_cast<Map*>(node);+    cerr << ind << "Map " << expression << " [Hashed]" << endl;+  } else if (dynamic_cast<List*>(node)) {+    List* expression = dynamic_cast<List*>(node);+    cerr << ind << "List " << expression << " (" << expression->length() << ") " <<+      (expression->separator() == Sass::List::Separator::COMMA ? "Comma " : "Space ") <<+      " [delayed: " << expression->is_delayed() << "] " <<+      " [interpolant: " << expression->is_interpolant() << "] " <<+      endl;+    for(auto i : expression->elements()) { debug_ast(i, ind + " ", env); }+  } else if (dynamic_cast<Content*>(node)) {+    Content* expression = dynamic_cast<Content*>(node);+    cerr << ind << "Content " << expression << " [Statement]" << endl;+  } else if (dynamic_cast<Boolean*>(node)) {+    Boolean* expression = dynamic_cast<Boolean*>(node);+    cerr << ind << "Boolean " << expression << " [" << expression->value() << "]" << endl;+  } else if (dynamic_cast<Color*>(node)) {+    Color* expression = dynamic_cast<Color*>(node);+    cerr << ind << "Color " << expression << " [" << expression->r() << ":"  << expression->g() << ":" << expression->b() << "@" << expression->a() << "]" << endl;+  } else if (dynamic_cast<Number*>(node)) {+    Number* expression = dynamic_cast<Number*>(node);+    cerr << ind << "Number " << expression << " [" << expression->value() << expression->unit() << "]" << endl;+  } else if (dynamic_cast<String_Quoted*>(node)) {+    String_Quoted* expression = dynamic_cast<String_Quoted*>(node);+    cerr << ind << "String_Quoted : " << expression << " [" << prettyprint(expression->value()) << "]" <<+      (expression->is_delayed() ? " {delayed}" : "") <<+      (expression->sass_fix_1291() ? " {sass_fix_1291}" : "") <<+      (expression->quote_mark() != 0 ? " {qm:" + string(1, expression->quote_mark()) + "}" : "") <<+      " <" << prettyprint(expression->pstate().token.ws_before()) << ">" << endl;+  } else if (dynamic_cast<String_Constant*>(node)) {+    String_Constant* expression = dynamic_cast<String_Constant*>(node);+    cerr << ind << "String_Constant : " << expression << " [" << prettyprint(expression->value()) << "]" <<+      (expression->is_delayed() ? " {delayed}" : "") <<+      (expression->sass_fix_1291() ? " {sass_fix_1291}" : "") <<+      " <" << prettyprint(expression->pstate().token.ws_before()) << ">" << endl;+  } else if (dynamic_cast<String_Schema*>(node)) {+    String_Schema* expression = dynamic_cast<String_Schema*>(node);+    cerr << ind << "String_Schema " << expression << " " << expression->concrete_type() <<+      (expression->has_interpolants() ? " {has_interpolants}" : "") <<+      endl;+    for(auto i : expression->elements()) { debug_ast(i, ind + " ", env); }+  } else if (dynamic_cast<String*>(node)) {+    String* expression = dynamic_cast<String*>(node);+    cerr << ind << "String " << expression << expression->concrete_type() <<+      " " << (expression->sass_fix_1291() ? "{sass_fix_1291}" : "") <<+      endl;+  } else if (dynamic_cast<Expression*>(node)) {+    Expression* expression = dynamic_cast<Expression*>(node);+    cerr << ind << "Expression " << expression;+    switch (expression->concrete_type()) {+      case Expression::Concrete_Type::NONE: cerr << " [NONE]"; break;+      case Expression::Concrete_Type::BOOLEAN: cerr << " [BOOLEAN]"; break;+      case Expression::Concrete_Type::NUMBER: cerr << " [NUMBER]"; break;+      case Expression::Concrete_Type::COLOR: cerr << " [COLOR]"; break;+      case Expression::Concrete_Type::STRING: cerr << " [STRING]"; break;+      case Expression::Concrete_Type::LIST: cerr << " [LIST]"; break;+      case Expression::Concrete_Type::MAP: cerr << " [MAP]"; break;+      case Expression::Concrete_Type::SELECTOR: cerr << " [SELECTOR]"; break;+      case Expression::Concrete_Type::NULL_VAL: cerr << " [NULL_VAL]"; break;+      case Expression::Concrete_Type::NUM_TYPES: cerr << " [NUM_TYPES]"; break;+    }+    cerr << endl;+  } else if (dynamic_cast<Has_Block*>(node)) {+    Has_Block* has_block = dynamic_cast<Has_Block*>(node);+    cerr << ind << "Has_Block " << has_block << " " << has_block->tabs() << endl;+    if (has_block->block()) for(auto i : has_block->block()->elements()) { debug_ast(i, ind + " ", env); }+  } else if (dynamic_cast<Statement*>(node)) {+    Statement* statement = dynamic_cast<Statement*>(node);+    cerr << ind << "Statement " << statement << " " << statement->tabs() << endl;+  }++  if (ind == "") cerr << "####################################################################\n";+}++#endif // SASS_DEBUGGER
+ libsass/emitter.cpp view
@@ -0,0 +1,255 @@+#include "util.hpp"+#include "context.hpp"+#include "output.hpp"+#include "emitter.hpp"+#include "utf8_string.hpp"++namespace Sass {+  using namespace std;++  Emitter::Emitter(Context* ctx)+  : wbuf(),+    ctx(ctx),+    indentation(0),+    scheduled_space(0),+    scheduled_linefeed(0),+    scheduled_delimiter(false),+    in_comment(false),+    in_wrapped(false),+    in_media_block(false),+    in_declaration(false),+    in_declaration_list(false)+  { }++  // return buffer as string+  string Emitter::get_buffer(void)+  {+    return wbuf.buffer;+  }++  Output_Style Emitter::output_style(void)+  {+    return ctx ? ctx->output_style : COMPRESSED;+  }++  // PROXY METHODS FOR SOURCE MAPS++  void Emitter::add_source_index(size_t idx)+  { wbuf.smap.source_index.push_back(idx); }++  string Emitter::generate_source_map(Context &ctx)+  { return wbuf.smap.generate_source_map(ctx); }++  void Emitter::set_filename(const string& str)+  { wbuf.smap.file = str; }++  void Emitter::add_open_mapping(AST_Node* node)+  { wbuf.smap.add_open_mapping(node); }+  void Emitter::add_close_mapping(AST_Node* node)+  { wbuf.smap.add_close_mapping(node); }+  ParserState Emitter::remap(const ParserState& pstate)+  { return wbuf.smap.remap(pstate); }++  // MAIN BUFFER MANIPULATION++  // add outstanding delimiter+  void Emitter::finalize(void)+  {+    scheduled_space = 0;+    if (scheduled_linefeed)+      scheduled_linefeed = 1;+    flush_schedules();+  }++  // flush scheduled space/linefeed+  void Emitter::flush_schedules(void)+  {+    // check the schedule+    if (scheduled_linefeed) {+      string linefeeds = "";++      for (size_t i = 0; i < scheduled_linefeed; i++)+        linefeeds += ctx ? ctx->linefeed : "\n";+      scheduled_space = 0;+      scheduled_linefeed = 0;+      append_string(linefeeds);++    } else if (scheduled_space) {+      string spaces(scheduled_space, ' ');+      scheduled_space = 0;+      append_string(spaces);+    }+    if (scheduled_delimiter) {+      scheduled_delimiter = false;+      append_string(";");+    }+  }++  // prepend some text or token to the buffer+  void Emitter::prepend_output(const OutputBuffer& output)+  {+    wbuf.smap.prepend(output);+    wbuf.buffer = output.buffer + wbuf.buffer;+  }++  // prepend some text or token to the buffer+  void Emitter::prepend_string(const string& text)+  {+    wbuf.smap.prepend(Offset(text));+    wbuf.buffer = text + wbuf.buffer;+  }++  // append some text or token to the buffer+  void Emitter::append_string(const string& text)+  {+    // write space/lf+    flush_schedules();++    if (in_comment && output_style() == COMPACT) {+      // unescape comment nodes+      string out = comment_to_string(text);+      // add to buffer+      wbuf.buffer += out;+      // account for data in source-maps+      wbuf.smap.append(Offset(out));+    } else {+      // add to buffer+      wbuf.buffer += text;+      // account for data in source-maps+      wbuf.smap.append(Offset(text));+    }+  }++  // append some white-space only text+  void Emitter::append_wspace(const string& text)+  {+    if (text.empty()) return;+    if (peek_linefeed(text.c_str())) {+      scheduled_space = 0;+      append_mandatory_linefeed();+    }+  }++  // append some text or token to the buffer+  // this adds source-mappings for node start and end+  void Emitter::append_token(const string& text, AST_Node* node)+  {+    flush_schedules();+    add_open_mapping(node);+    append_string(text);+    add_close_mapping(node);+  }++  // HELPER METHODS++  void Emitter::append_indentation()+  {+    if (output_style() == COMPRESSED) return;+    if (output_style() == COMPACT) return;+    if (scheduled_linefeed && indentation)+      scheduled_linefeed = 1;+    string indent = "";+    for (size_t i = 0; i < indentation; i++)+      indent += ctx ? ctx->indent : "  ";+    append_string(indent);+  }++  void Emitter::append_delimiter()+  {+    scheduled_delimiter = true;+    if (output_style() == COMPACT) {+      if (indentation == 0) {+        append_mandatory_linefeed();+      } else {+        append_mandatory_space();+      }+    } else if (output_style() != COMPRESSED) {+      append_optional_linefeed();+    }+  }++  void Emitter::append_comma_separator()+  {+    scheduled_space = 0;+    append_string(",");+    append_optional_space();+  }++  void Emitter::append_colon_separator()+  {+    scheduled_space = 0;+    append_string(":");+    append_optional_space();+  }++  void Emitter::append_mandatory_space()+  {+    scheduled_space = 1;+  }++  void Emitter::append_optional_space()+  {+    if (output_style() != COMPRESSED && buffer().size()) {+      char lst = buffer().at(buffer().length() - 1);+      if (!isspace(lst)) append_mandatory_space();+    }+  }++  void Emitter::append_special_linefeed()+  {+    if (output_style() == COMPACT) {+      append_mandatory_linefeed();+      for (size_t p = 0; p < indentation; p++)+        append_string(ctx ? ctx->indent : "  ");+    }+  }++  void Emitter::append_optional_linefeed()+  {+    if (output_style() == COMPACT) {+      append_mandatory_space();+    } else {+      append_mandatory_linefeed();+    }+  }++  void Emitter::append_mandatory_linefeed()+  {+    if (output_style() != COMPRESSED) {+      scheduled_linefeed = 1;+      scheduled_space = 0;+      // flush_schedules();+    }+  }++  void Emitter::append_scope_opener(AST_Node* node)+  {+    append_optional_space();+    flush_schedules();+    if (node) add_open_mapping(node);+    append_string("{");+    append_optional_linefeed();+    // append_optional_space();+    ++ indentation;+  }+  void Emitter::append_scope_closer(AST_Node* node)+  {+    -- indentation;+    scheduled_linefeed = 0;+    if (output_style() == COMPRESSED)+      scheduled_delimiter = false;+    if (output_style() == EXPANDED) {+      append_optional_linefeed();+      append_indentation();+    } else {+      append_optional_space();+    }+    append_string("}");+    if (node) add_close_mapping(node);+    append_optional_linefeed();+    if (indentation != 0) return;+    if (output_style() != COMPRESSED)+      scheduled_linefeed = 2;+  }++}
+ libsass/emitter.hpp view
@@ -0,0 +1,83 @@+#ifndef SASS_EMITTER_H+#define SASS_EMITTER_H++#include <string>+#include "source_map.hpp"+#include "ast_fwd_decl.hpp"++namespace Sass {+  class Context;+  using namespace std;++  class Emitter {++    public:+      Emitter(Context* ctx);+      virtual ~Emitter() { };++    protected:+      OutputBuffer wbuf;+    public:+      const string buffer(void) { return wbuf.buffer; }+      const SourceMap smap(void) { return wbuf.smap; }+      const OutputBuffer output(void) { return wbuf; }+      // proxy methods for source maps+      void add_source_index(size_t idx);+      void set_filename(const string& str);+      void add_open_mapping(AST_Node* node);+      void add_close_mapping(AST_Node* node);+      string generate_source_map(Context &ctx);+      ParserState remap(const ParserState& pstate);++    public:+      Context* ctx;+      size_t indentation;+      size_t scheduled_space;+      size_t scheduled_linefeed;+      bool scheduled_delimiter;++    public:+      bool in_comment;+      bool in_wrapped;+      bool in_media_block;+      bool in_declaration;+      bool in_declaration_list;++    public:+      // return buffer as string+      string get_buffer(void);+      // flush scheduled space/linefeed+      Output_Style output_style(void);+      // add outstanding linefeed+      void finalize(void);+      // flush scheduled space/linefeed+      void flush_schedules(void);+      // prepend some text or token to the buffer+      void prepend_string(const string& text);+      void prepend_output(const OutputBuffer& out);+      // append some text or token to the buffer+      void append_string(const string& text);+      // append some white-space only text+      void append_wspace(const string& text);+      // append some text or token to the buffer+      // this adds source-mappings for node start and end+      void append_token(const string& text, AST_Node* node);++    public: // syntax sugar+      void append_indentation();+      void append_optional_space(void);+      void append_mandatory_space(void);+      void append_special_linefeed(void);+      void append_optional_linefeed(void);+      void append_mandatory_linefeed(void);+      void append_scope_opener(AST_Node* node = 0);+      void append_scope_closer(AST_Node* node = 0);+      void append_comma_separator(void);+      void append_colon_separator(void);+      void append_delimiter(void);++  };++}++#endif
+ libsass/environment.hpp view
@@ -0,0 +1,167 @@+#ifndef SASS_ENVIRONMENT_H+#define SASS_ENVIRONMENT_H++#include <map>+#include <string>+#include <iostream>++#include "ast_fwd_decl.hpp"+#include "ast_def_macros.hpp"+#include "memory_manager.hpp"++namespace Sass {+  using std::string;+  using std::map;+  using std::cerr;+  using std::endl;++  template <typename T>+  class Environment {+    // TODO: test with unordered_map+    map<string, T> local_frame_;+    ADD_PROPERTY(Environment*, parent);++  public:+    Memory_Manager<AST_Node> mem;+    Environment() : local_frame_(map<string, T>()), parent_(0) { }++    // link parent to create a stack+    void link(Environment& env) { parent_ = &env; }+    void link(Environment* env) { parent_ = env; }++    // this is used to find the global frame+    // which is the second last on the stack+    bool is_lexical() const+    {+      return !! parent_ && parent_->parent_;+    }++    // only match the real root scope+    // there is still a parent around+    // not sure what it is actually use for+    // I guess we store functions etc. there+    bool is_root_scope() const+    {+      return parent_ && ! parent_->parent_;+    }++    // scope operates on the current frame++    map<string, T>& local_frame() {+      return local_frame_;+    }++    bool has_local(const string& key) const+    { return local_frame_.count(key); }++    T& get_local(const string& key)+    { return local_frame_[key]; }++    void set_local(const string& key, T val)+    { local_frame_[key] = val; }++    void del_local(const string& key)+    { local_frame_.erase(key); }+++    // global operates on the global frame+    // which is the second last on the stack++    Environment* global_env()+    {+      Environment* cur = this;+      while (cur->is_lexical()) {+        cur = cur->parent_;+      }+      return cur;+    }++    bool has_global(const string& key)+    { return global_env()->has(key); }++    T& get_global(const string& key)+    { return (*global_env())[key]; }++    void set_global(const string& key, T val)+    { global_env()->local_frame_[key] = val; }++    void del_global(const string& key)+    { global_env()->local_frame_.erase(key); }++    // see if we have a lexical variable+    // move down the stack but stop before we+    // reach the global frame (is not included)+    bool has_lexical(const string& key) const+    {+      auto cur = this;+      while (cur->is_lexical()) {+        if (cur->has_local(key)) return true;+        cur = cur->parent_;+      }+      return false;+    }++    // see if we have a lexical we could update+    // either update already existing lexical value+    // or if flag is set, we create one if no lexical found+    void set_lexical(const string& key, T val, bool global = false)+    {+      auto cur = this;+      while (cur->is_lexical()) {+        if (cur->has_local(key)) {+          cur->set_local(key, val);+          return;+        }+        cur = cur->parent_;+      }+      if (global) {+        set_global(key, val);+      } else {+        set_local(key, val);+      }+    }++    // look on the full stack for key+    // include all scopes available+    bool has(const string& key) const+    {+      auto cur = this;+      while (cur) {+        if (cur->has_local(key)) {+          return true;+        }+        cur = cur->parent_;+      }+      return false;+    }++    // use array access for getter and setter functions+    T& operator[](const string& key)+    {+      auto cur = this;+      while (cur) {+        if (cur->has_local(key)) {+          return cur->get_local(key);+        }+        cur = cur->parent_;+      }+      return get_local(key);+    }++    #ifdef DEBUG+    void print()+    {+      for (typename map<string, T>::iterator i = local_frame_.begin(); i != local_frame_.end(); ++i) {+        cerr << i->first << endl;+      }+      if (parent_) {+        cerr << "---" << endl;+        parent_->print();+      }+    }+    #endif++  };+}++#endif
+ libsass/error_handling.cpp view
@@ -0,0 +1,25 @@+#include "prelexer.hpp"+#include "backtrace.hpp"+#include "error_handling.hpp"++namespace Sass {++  Sass_Error::Sass_Error(Type type, ParserState pstate, string message)+  : type(type), pstate(pstate), message(message)+  { }++  void error(string msg, ParserState pstate)+  {+    throw Sass_Error(Sass_Error::syntax, pstate, msg);+  }++  void error(string msg, ParserState pstate, Backtrace* bt)+  {++    Backtrace top(bt, pstate, "");+    msg += top.to_string();++    throw Sass_Error(Sass_Error::syntax, pstate, msg);+  }++}
+ libsass/error_handling.hpp view
@@ -0,0 +1,29 @@+#ifndef SASS_ERROR_HANDLING_H+#define SASS_ERROR_HANDLING_H++#include <string>++#include "position.hpp"++namespace Sass {+  using namespace std;++  struct Backtrace;++  struct Sass_Error {+    enum Type { read, write, syntax, evaluation };++    Type type;+    ParserState pstate;+    string message;++    Sass_Error(Type type, ParserState pstate, string message);++  };++  void error(string msg, ParserState pstate);+  void error(string msg, ParserState pstate, Backtrace* bt);++}++#endif
+ libsass/eval.cpp view
@@ -0,0 +1,1288 @@+#include "file.hpp"+#include "eval.hpp"+#include "ast.hpp"+#include "bind.hpp"+#include "util.hpp"+#include "to_string.hpp"+#include "inspect.hpp"+#include "to_c.hpp"+#include "context.hpp"+#include "backtrace.hpp"+#include "prelexer.hpp"+#include "parser.hpp"++#include <cstdlib>+#include <cmath>+#include <iostream>+#include <iomanip>+#include <typeinfo>++namespace Sass {+  using namespace std;++  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+  typedef double (*bop)(double, double);+  bop ops[Binary_Expression::NUM_OPS] = {+    0, 0, // and, or+    0, 0, 0, 0, 0, 0, // eq, neq, gt, gte, lt, lte+    add, sub, mul, div, fmod+  };++  Eval::Eval(Context& ctx, Contextualize* contextualize, Listize* listize, Env* env, Backtrace* bt)+  : ctx(ctx), contextualize(contextualize), listize(listize), env(env), backtrace(bt) { }+  Eval::~Eval() { }++  Eval* Eval::with(Env* e, Backtrace* bt) // for setting the env before eval'ing an expression+  {+    contextualize = contextualize->with(0, e, bt);+    env = e;+    backtrace = bt;+    return this;+  }++  Eval* Eval::with(Selector* c, Env* e, Backtrace* bt, Selector* p, Selector* ex) // for setting the env before eval'ing an expression+  {+    contextualize = contextualize->with(c, e, bt, p, ex);+    env = e;+    backtrace = bt;+    return this;+  }++  Expression* Eval::operator()(Block* b)+  {+    Expression* val = 0;+    for (size_t i = 0, L = b->length(); i < L; ++i) {+      val = (*b)[i]->perform(this);+      if (val) return val;+    }+    return val;+  }++  Expression* Eval::operator()(Assignment* a)+  {+    string var(a->variable());+    if (a->is_global()) {+      env->set_global(var, a->value()->perform(this));+    }+    else if (a->is_default()) {+      if (env->has_lexical(var)) return 0;+      if (env->has_global(var)) {+        Expression* e = static_cast<Expression*>(env->get_global(var));+        if (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_lexical(var, a->value()->perform(this));+    }+    return 0;+  }++  Expression* Eval::operator()(If* i)+  {+    if (*i->predicate()->perform(this)) {+      return i->consequent()->perform(this);+    }+    else {+      Block* alt = i->alternative();+      if (alt) return alt->perform(this);+    }+    return 0;+  }++  // For does not create a new env scope+  // But iteration vars are reset afterwards+  Expression* Eval::operator()(For* f)+  {+    string variable(f->variable());+    Expression* low = f->lower_bound()->perform(this);+    if (low->concrete_type() != Expression::NUMBER) {+      error("lower bound of `@for` directive must be numeric", low->pstate());+    }+    Expression* high = f->upper_bound()->perform(this);+    if (high->concrete_type() != Expression::NUMBER) {+      error("upper bound of `@for` directive must be numeric", high->pstate());+    }+    Number* sass_start = static_cast<Number*>(low);+    Number* sass_end = static_cast<Number*>(high);+    // check if units are valid for sequence+    if (sass_start->unit() != sass_end->unit()) {+      stringstream msg; msg << "Incompatible units: '"+        << sass_start->unit() << "' and '"+        << sass_end->unit() << "'.";+      error(msg.str(), low->pstate(), backtrace);+    }+    double start = sass_start->value();+    double end = sass_end->value();+    // only create iterator once in this environment+    Number* it = new (env->mem) Number(low->pstate(), start, sass_end->unit());+    AST_Node* old_var = env->get_local(variable);+    env->set_local(variable, it);+    Block* body = f->block();+    Expression* 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;+      }+    }+    // restore original environment+    if (!old_var) env->del_local(variable);+    else env->set_local(variable, old_var);+    return val;+  }++  // Eval does not create a new env scope+  // But iteration vars are reset afterwards+  Expression* Eval::operator()(Each* e)+  {+    vector<string> variables(e->variables());+    Expression* expr = e->list()->perform(this);+    List* list = 0;+    Map* map = 0;+    if (expr->concrete_type() == Expression::MAP) {+      map = static_cast<Map*>(expr);+    }+    else if (expr->concrete_type() != Expression::LIST) {+      list = new (ctx.mem) List(expr->pstate(), 1, List::COMMA);+      *list << expr;+    }+    else {+      list = static_cast<List*>(expr);+    }+    // remember variables and then reset them+    vector<AST_Node*> old_vars(variables.size());+    for (size_t i = 0, L = variables.size(); i < L; ++i) {+      old_vars[i] = env->get_local(variables[i]);+      env->set_local(variables[i], 0);+    }+    Block* body = e->block();+    Expression* val = 0;++    if (map) {+      for (auto key : map->keys()) {+        Expression* value = map->at(key);++        if (variables.size() == 1) {+          List* variable = new (ctx.mem) List(map->pstate(), 2, List::SPACE);+          *variable << key;+          *variable << 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 {+      for (size_t i = 0, L = list->length(); i < L; ++i) {+        List* variable = 0;+        if ((*list)[i]->concrete_type() != Expression::LIST || variables.size() == 1) {+          variable = new (ctx.mem) List((*list)[i]->pstate(), 1, List::COMMA);+          *variable << (*list)[i];+        }+        else {+          variable = static_cast<List*>((*list)[i]);+        }+        for (size_t j = 0, K = variables.size(); j < K; ++j) {+          if (j < variable->length()) {+            env->set_local(variables[j], (*variable)[j]);+          }+          else {+            env->set_local(variables[j], new (ctx.mem) Null(expr->pstate()));+          }+          val = body->perform(this);+          if (val) break;+        }+        if (val) break;+      }+    }+    // restore original environment+    for (size_t j = 0, K = variables.size(); j < K; ++j) {+      if(!old_vars[j]) env->del_local(variables[j]);+      else env->set_local(variables[j], old_vars[j]);+    }+    return val;+  }++  Expression* Eval::operator()(While* w)+  {+    Expression* pred = w->predicate();+    Block* body = w->block();+    while (*pred->perform(this)) {+      Expression* val = body->perform(this);+      if (val) return val;+    }+    return 0;+  }++  Expression* Eval::operator()(Return* r)+  {+    return r->value()->perform(this);+  }++  Expression* Eval::operator()(Warning* w)+  {+    Expression* message = w->message()->perform(this);+    To_String to_string(&ctx);++    // try to use generic function+    if (env->has("@warn[f]")) {++      Definition* def = static_cast<Definition*>((*env)["@warn[f]"]);+      // Block*          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));+      Sass_Value* c_val = c_func(c_args, c_function, ctx.c_options);+      sass_delete_value(c_args);+      sass_delete_value(c_val);+      return 0;++    }++    string result(unquote(message->perform(&to_string)));+    Backtrace top(backtrace, w->pstate(), "");+    cerr << "WARNING: " << result;+    cerr << top.to_string(true);+    cerr << endl << endl;+    return 0;+  }++  Expression* Eval::operator()(Error* e)+  {+    Expression* message = e->message()->perform(this);+    To_String to_string(&ctx);++    // try to use generic function+    if (env->has("@error[f]")) {++      Definition* def = static_cast<Definition*>((*env)["@error[f]"]);+      // Block*          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));+      Sass_Value* c_val = c_func(c_args, c_function, ctx.c_options);+      sass_delete_value(c_args);+      sass_delete_value(c_val);+      return 0;++    }++    string result(unquote(message->perform(&to_string)));+    error(result, e->pstate());+    return 0;+  }++  Expression* Eval::operator()(Debug* d)+  {+    Expression* message = d->value()->perform(this);+    To_String to_string(&ctx);++    // try to use generic function+    if (env->has("@debug[f]")) {++      Definition* def = static_cast<Definition*>((*env)["@debug[f]"]);+      // Block*          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));+      Sass_Value* c_val = c_func(c_args, c_function, ctx.c_options);+      sass_delete_value(c_args);+      sass_delete_value(c_val);+      return 0;++    }++    string cwd(ctx.get_cwd());+    string result(unquote(message->perform(&to_string)));+    string rel_path(Sass::File::resolve_relative_path(d->pstate().path, cwd, cwd));+    cerr << rel_path << ":" << d->pstate().line << ":" << " DEBUG: " << result;+    cerr << endl;+    return 0;+  }++  Expression* Eval::operator()(List* l)+  {+    if (l->is_expanded()) return l;+    List* ll = new (ctx.mem) List(l->pstate(),+                                  l->length(),+                                  l->separator(),+                                  l->is_arglist());+    for (size_t i = 0, L = l->length(); i < L; ++i) {+      *ll << (*l)[i]->perform(this);+    }+    ll->is_expanded(true);+    return ll;+  }++  Expression* Eval::operator()(Map* m)+  {+    if (m->is_expanded()) return m;+    Map* mm = new (ctx.mem) Map(m->pstate(),+                                  m->length());+    for (auto key : m->keys()) {+      *mm << std::make_pair(key->perform(this), m->at(key)->perform(this));+    }+    mm->is_expanded(true);+    return mm;+  }++  // -- only need to define two comparisons, and the rest can be implemented in terms of them+  bool eq(Expression*, Expression*, Context&, Eval*);+  bool lt(Expression*, Expression*, Context&);+  // -- arithmetic on the combinations that matter+  Expression* op_numbers(Context&, Binary_Expression*, Expression*, Expression*);+  Expression* op_number_color(Context&, Binary_Expression::Type, Expression*, Expression*);+  Expression* op_color_number(Context&, Binary_Expression::Type, Expression*, Expression*);+  Expression* op_colors(Context&, Binary_Expression::Type, Expression*, Expression*);+  Expression* op_strings(Context&, Binary_Expression::Type, Expression*, Expression*);++  Expression* Eval::operator()(Binary_Expression* b)+  {+    Binary_Expression::Type op_type = b->type();+    // don't eval delayed expressions (the '/' when used as a separator)+    if (op_type == Binary_Expression::DIV && b->is_delayed()) return b;+    // if one of the operands is a '/' then make sure it's evaluated+    Expression* lhs = b->left()->perform(this);+    lhs->is_delayed(false);+    while (typeid(*lhs) == typeid(Binary_Expression)) lhs = lhs->perform(this);++    switch (op_type) {+      case Binary_Expression::AND:+        return *lhs ? b->right()->perform(this) : lhs;+        break;++      case Binary_Expression::OR:+        return *lhs ? lhs : b->right()->perform(this);+        break;++      default:+        break;+    }+    // not a logical connective, so go ahead and eval the rhs+    Expression* rhs = b->right()->perform(this);+    rhs->is_delayed(false);+    while (typeid(*rhs) == typeid(Binary_Expression)) rhs = rhs->perform(this);++    // see if it's a relational expression+    switch(op_type) {+      case Binary_Expression::EQ:  return new (ctx.mem) Boolean(b->pstate(), eq(lhs, rhs, ctx));+      case Binary_Expression::NEQ: return new (ctx.mem) Boolean(b->pstate(), !eq(lhs, rhs, ctx));+      case Binary_Expression::GT:  return new (ctx.mem) Boolean(b->pstate(), !lt(lhs, rhs, ctx) && !eq(lhs, rhs, ctx));+      case Binary_Expression::GTE: return new (ctx.mem) Boolean(b->pstate(), !lt(lhs, rhs, ctx));+      case Binary_Expression::LT:  return new (ctx.mem) Boolean(b->pstate(), lt(lhs, rhs, ctx));+      case Binary_Expression::LTE: return new (ctx.mem) Boolean(b->pstate(), lt(lhs, rhs, ctx) || eq(lhs, rhs, ctx));++      default:                     break;+    }++    Expression::Concrete_Type l_type = lhs->concrete_type();+    Expression::Concrete_Type r_type = rhs->concrete_type();++    if (l_type == Expression::NUMBER && r_type == Expression::NUMBER) {+      return op_numbers(ctx, b, lhs, rhs);+    }+    if (l_type == Expression::NUMBER && r_type == Expression::COLOR) {+      return op_number_color(ctx, op_type, lhs, rhs);+    }+    if (l_type == Expression::COLOR && r_type == Expression::NUMBER) {+      return op_color_number(ctx, op_type, lhs, rhs);+    }+    if (l_type == Expression::COLOR && r_type == Expression::COLOR) {+      return op_colors(ctx, op_type, lhs, rhs);+    }++    Expression* ex = op_strings(ctx, op_type, lhs, rhs);+    if (String_Constant* str = (String_Constant*) ex)+    {+      if (str->concrete_type() != Expression::STRING) return ex;+      String_Constant* lstr = dynamic_cast<String_Constant*>(lhs);+      String_Constant* rstr = dynamic_cast<String_Constant*>(rhs);+      if (String_Constant* org = lstr ? lstr : rstr)+      { str->quote_mark(org->quote_mark()); }+    }+    return ex;++  }++  Expression* Eval::operator()(Unary_Expression* u)+  {+    Expression* operand = u->operand()->perform(this);+    if (u->type() == Unary_Expression::NOT) {+      Boolean* result = new (ctx.mem) Boolean(u->pstate(), (bool)*operand);+      result->value(!result->value());+      return result;+    }+    else if (operand->concrete_type() == Expression::NUMBER) {+      Number* result = new (ctx.mem) Number(*static_cast<Number*>(operand));+      result->value(u->type() == Unary_Expression::MINUS+                    ? -result->value()+                    :  result->value());+      return result;+    }+    else {+      To_String to_string(&ctx);+      // Special cases: +/- variables which evaluate to null ouput just +/-,+      // but +/- null itself outputs the string+      if (operand->concrete_type() == Expression::NULL_VAL && typeid(*(u->operand())) == typeid(Variable)) {+        u->operand(new (ctx.mem) String_Constant(u->pstate(), ""));+      }+      else u->operand(operand);+      String_Constant* result = new (ctx.mem) String_Constant(u->pstate(),+                                                              u->perform(&to_string));+      return result;+    }+    // unreachable+    return u;+  }++  Expression* Eval::operator()(Function_Call* c)+  {+    string name(Util::normalize_underscores(c->name()));+    string full_name(name + "[f]");+    Arguments* args = c->arguments();+    if (full_name != "if[f]") {+      args = static_cast<Arguments*>(args->perform(this));+    }++    // try to use generic function+    if (!env->has(full_name)) {+      if (env->has("*[f]")) {+        full_name = "*[f]";+      }+    }++    // if it doesn't exist, just pass it through as a literal+    if (!env->has(full_name)) {+      Function_Call* lit = new (ctx.mem) Function_Call(c->pstate(),+                                                       c->name(),+                                                       args);+      To_String to_string(&ctx);+      return new (ctx.mem) String_Constant(c->pstate(),+                                           lit->perform(&to_string));+    }++    Expression*     result = c;+    Definition*     def    = static_cast<Definition*>((*env)[full_name]);+    Block*          body   = def->block();+    Native_Function func   = def->native_function();+    Sass_Function_Entry c_function = def->c_function();++    if (full_name != "if[f]") {+      for (size_t i = 0, L = args->length(); i < L; ++i) {+        (*args)[i]->value((*args)[i]->value()->perform(this));+      }+    }++    Parameters* params = def->parameters();+    Env new_env;+    new_env.link(def->environment());+    // bind("function " + c->name(), params, args, ctx, &new_env, this);+    // Env* old_env = env;+    // env = &new_env;++    // Backtrace here(backtrace, c->path(), c->line(), ", in function `" + c->name() + "`");+    // backtrace = &here;++    // if it's user-defined, eval the body+    if (body) {++      bind("function " + c->name(), params, args, ctx, &new_env, this);+      Env* old_env = env;+      env = &new_env;++      Backtrace here(backtrace, c->pstate(), ", in function `" + c->name() + "`");+      backtrace = &here;++      result = body->perform(this);+      if (!result) {+        error(string("function ") + c->name() + " did not return a value", c->pstate());+      }+      backtrace = here.parent;+      env = old_env;+    }+    // if it's native, invoke the underlying CPP function+    else if (func) {++      bind("function " + c->name(), params, args, ctx, &new_env, this);+      Env* old_env = env;+      env = &new_env;++      Backtrace here(backtrace, c->pstate(), ", in function `" + c->name() + "`");+      backtrace = &here;++      result = func(*env, *old_env, ctx, def->signature(), c->pstate(), backtrace);++      backtrace = here.parent;+      env = old_env;+    }+    // else if it's a user-defined c function+    else if (c_function) {++      Sass_Function_Fn c_func = sass_function_get_function(c_function);+      if (full_name == "*[f]") {+        String_Constant *str = new (ctx.mem) String_Constant(c->pstate(), c->name());+        Arguments* new_args = new (ctx.mem) Arguments(c->pstate());+        *new_args << new (ctx.mem) Argument(c->pstate(), str);+        *new_args += args;+        args = new_args;+      }++      // populates env with default values for params+      bind("function " + c->name(), params, args, ctx, &new_env, this);+      Env* old_env = env;+      env = &new_env;++      Backtrace here(backtrace, c->pstate(), ", in function `" + c->name() + "`");+      backtrace = &here;++      To_C to_c;+      union Sass_Value* c_args = sass_make_list(env->local_frame().size(), SASS_COMMA);+      for(size_t i = 0; i < params[0].length(); i++) {+        string key = params[0][i]->name();+        AST_Node* node = env->local_frame().at(key);+        Expression* arg = static_cast<Expression*>(node);+        sass_list_set_value(c_args, i, arg->perform(&to_c));+      }+      Sass_Value* c_val = c_func(c_args, c_function, ctx.c_options);+      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, ctx, backtrace, c->pstate());++      backtrace = here.parent;+      sass_delete_value(c_args);+      if (c_val != c_args)+        sass_delete_value(c_val);+      env = old_env;+    }+    // else it's an overloaded native function; resolve it+    else if (def->is_overload_stub()) {+      size_t arity = args->length();+      stringstream ss;+      ss << full_name << arity;+      string resolved_name(ss.str());+      if (!env->has(resolved_name)) error("overloaded function `" + string(c->name()) + "` given wrong number of arguments", c->pstate());+      Definition* resolved_def = static_cast<Definition*>((*env)[resolved_name]);+      params = resolved_def->parameters();+      Env newer_env;+      newer_env.link(resolved_def->environment());+      bind("function " + c->name(), params, args, ctx, &newer_env, this);+      Env* old_env = env;+      env = &newer_env;++      Backtrace here(backtrace, c->pstate(), ", in function `" + c->name() + "`");+      backtrace = &here;++      result = resolved_def->native_function()(*env, *old_env, ctx, resolved_def->signature(), c->pstate(), backtrace);++      backtrace = here.parent;+      env = old_env;+    }++    // backtrace = here.parent;+    // env = old_env;+++    // link back to function definition+    // only do this for custom functions+    if (result->pstate().file == string::npos)+      result->pstate(c->pstate());++    do {+      result->is_delayed(result->concrete_type() == Expression::STRING);+      result = result->perform(this);+    } while (result->concrete_type() == Expression::NONE);+    return result;+  }++  Expression* Eval::operator()(Function_Call_Schema* s)+  {+    Expression* evaluated_name = s->name()->perform(this);+    Expression* evaluated_args = s->arguments()->perform(this);+    String_Schema* ss = new (ctx.mem) String_Schema(s->pstate(), 2);+    (*ss) << evaluated_name << evaluated_args;+    return ss->perform(this);+  }++  Expression* Eval::operator()(Variable* v)+  {+    To_String to_string(&ctx);+    string name(v->name());+    Expression* value = 0;+    if (env->has(name)) value = static_cast<Expression*>((*env)[name]);+    else error("Undefined variable: \"" + v->name() + "\".", v->pstate());+    // cerr << "name: " << v->name() << "; type: " << typeid(*value).name() << "; value: " << value->perform(&to_string) << endl;+    if (typeid(*value) == typeid(Argument)) value = static_cast<Argument*>(value)->value();++    // behave according to as ruby sass (add leading zero)+    if (value->concrete_type() == Expression::NUMBER) {+      value = new (ctx.mem) Number(*static_cast<Number*>(value));+      static_cast<Number*>(value)->zero(true);+    }+    else if (value->concrete_type() == Expression::STRING) {+      if (auto str = dynamic_cast<String_Quoted*>(value)) {+        value = new (ctx.mem) String_Quoted(*str);+      } else if (auto str = dynamic_cast<String_Constant*>(value)) {+        value = new (ctx.mem) String_Constant(*str);+      }+    }+    else if (value->concrete_type() == Expression::LIST) {+      value = new (ctx.mem) List(*static_cast<List*>(value));+    }+    else if (value->concrete_type() == Expression::MAP) {+      value = new (ctx.mem) Map(*static_cast<Map*>(value));+    }+    else if (value->concrete_type() == Expression::BOOLEAN) {+      value = new (ctx.mem) Boolean(*static_cast<Boolean*>(value));+    }+    else if (value->concrete_type() == Expression::COLOR) {+      value = new (ctx.mem) Color(*static_cast<Color*>(value));+    }+    else if (value->concrete_type() == Expression::NULL_VAL) {+      value = new (ctx.mem) Null(value->pstate());+    }++    // cerr << "\ttype is now: " << typeid(*value).name() << endl << endl;+    return value;+  }++  Expression* Eval::operator()(Textual* t)+  {+    using Prelexer::number;+    Expression* result = 0;+    bool zero = !( t->value().substr(0, 1) == "." ||+                   t->value().substr(0, 2) == "-." );++    const string& text = t->value();+    size_t num_pos = text.find_first_not_of(" \n\r\t");+    if (num_pos == string::npos) num_pos = text.length();+    size_t unit_pos = text.find_first_not_of("-+0123456789.", num_pos);+    if (unit_pos == string::npos) unit_pos = text.length();+    const string& num = text.substr(num_pos, unit_pos - num_pos);++    switch (t->type())+    {+      case Textual::NUMBER:+        result = new (ctx.mem) Number(t->pstate(),+                                      sass_atof(num.c_str()),+                                      "",+                                      zero);+        break;+      case Textual::PERCENTAGE:+        result = new (ctx.mem) Number(t->pstate(),+                                      sass_atof(num.c_str()),+                                      "%",+                                      zero);+        break;+      case Textual::DIMENSION:+        result = new (ctx.mem) Number(t->pstate(),+                                      sass_atof(num.c_str()),+                                      Token(number(text.c_str())),+                                      zero);+        break;+      case Textual::HEX: {+        string hext(t->value().substr(1)); // chop off the '#'+        if (hext.length() == 6) {+          string r(hext.substr(0,2));+          string g(hext.substr(2,2));+          string b(hext.substr(4,2));+          result = new (ctx.mem) 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, true,+                                       t->value());+        }+        else {+          result = new (ctx.mem) Color(t->pstate(),+                                       static_cast<double>(strtol(string(2,hext[0]).c_str(), NULL, 16)),+                                       static_cast<double>(strtol(string(2,hext[1]).c_str(), NULL, 16)),+                                       static_cast<double>(strtol(string(2,hext[2]).c_str(), NULL, 16)),+                                       1, false,+                                       t->value());+        }+      } break;+    }+    return result;+  }++  Expression* Eval::operator()(Number* n)+  {+    // behave according to as ruby sass (add leading zero)+    return new (ctx.mem) Number(n->pstate(),+                                n->value(),+                                n->unit(),+                                true);+  }++  Expression* Eval::operator()(Boolean* b)+  {+    return b;+  }++  char is_quoted(string str)+  {+    size_t len = str.length();+    if (len < 2) return 0;+    if ((str[0] == '"' && str[len-1] == '"') || (str[0] == '\'' && str[len-1] == '\'')) {+      return str[0];+    }+    else {+      return 0;+    }+  }++  string Eval::interpolation(Expression* s) {+    if (String_Quoted* str_quoted = dynamic_cast<String_Quoted*>(s)) {+      if (str_quoted->quote_mark()) {+        return string_escape(str_quoted->value());+      } else {+        return evacuate_escapes(str_quoted->value());+      }+    } else if (String_Constant* str_constant = dynamic_cast<String_Constant*>(s)) {+      return evacuate_escapes(str_constant->value());+    } else if (String_Schema* str_schema = dynamic_cast<String_Schema*>(s)) {+      string res = "";+      for(auto i : str_schema->elements())+        res += (interpolation(i));+      //ToDo: do this in one step+      auto esc = evacuate_escapes(res);+      auto unq = unquote(esc);+      if (unq == esc) {+        return string_to_output(res);+      } else {+        return evacuate_quotes(unq);+      }+    } else if (List* list = dynamic_cast<List*>(s)) {+      string acc = ""; // ToDo: different output styles+      string sep = list->separator() == List::Separator::COMMA ? "," : " ";+      if (ctx.output_style != COMPRESSED && sep == ",") sep += " ";+      bool initial = false;+      for(auto item : list->elements()) {+        if (initial) acc += sep;+        acc += interpolation(item);+        initial = true;+      }+      return evacuate_quotes(acc);+    } else if (Variable* var = dynamic_cast<Variable*>(s)) {+      string name(var->name());+      if (!env->has(name)) error("Undefined variable: \"" + var->name() + "\".", var->pstate());+      Expression* value = static_cast<Expression*>((*env)[name]);+      return evacuate_quotes(interpolation(value));+    } else if (Binary_Expression* var = dynamic_cast<Binary_Expression*>(s)) {+      Expression* ex = var->perform(this);+      return evacuate_quotes(interpolation(ex));+    } else if (Function_Call* var = dynamic_cast<Function_Call*>(s)) {+      Expression* ex = var->perform(this);+      return evacuate_quotes(interpolation(ex));+    } else if (Parent_Selector* var = dynamic_cast<Parent_Selector*>(s)) {+      Expression* ex = var->perform(this);+      return evacuate_quotes(interpolation(ex));+    } else if (Unary_Expression* var = dynamic_cast<Unary_Expression*>(s)) {+      Expression* ex = var->perform(this);+      return evacuate_quotes(interpolation(ex));+    } else if (Selector* var = dynamic_cast<Selector*>(s)) {+      Expression* ex = var->perform(this);+      return evacuate_quotes(interpolation(ex));+    } else {+      To_String to_string(&ctx);+      // to_string.in_decl_list = true;+      return evacuate_quotes(s->perform(&to_string));+    }+  }++  Expression* Eval::operator()(String_Schema* s)+  {+    string acc;+    for (size_t i = 0, L = s->length(); i < L; ++i) {+      acc += interpolation((*s)[i]);+    }+    String_Quoted* str = new (ctx.mem) String_Quoted(s->pstate(), acc);+    if (!str->quote_mark()) {+      str->value(string_unescape(str->value()));+    } else if (str->quote_mark()) {+      str->quote_mark('*');+    }+    return str;+  }++  Expression* Eval::operator()(String_Constant* s)+  {+    if (!s->quote_mark() && !s->is_delayed() && ctx.names_to_colors.count(s->value())) {+      Color* c = new (ctx.mem) Color(*ctx.names_to_colors[s->value()]);+      c->pstate(s->pstate());+      c->disp(s->value());+      return c;+    }+    return s;+  }++  Expression* Eval::operator()(Feature_Query* q)+  {+    Feature_Query* qq = new (ctx.mem) Feature_Query(q->pstate(),+                                                    q->length());+    for (size_t i = 0, L = q->length(); i < L; ++i) {+      *qq << static_cast<Feature_Query_Condition*>((*q)[i]->perform(this));+    }+    return qq;+  }++  Expression* Eval::operator()(Feature_Query_Condition* c)+  {+    String* feature = c->feature();+    Expression* value = c->value();+    value = (value ? value->perform(this) : 0);+    Feature_Query_Condition* cc = new (ctx.mem) Feature_Query_Condition(c->pstate(),+                                                 c->length(),+                                                 feature,+                                                 value,+                                                 c->operand(),+                                                 c->is_root());+    for (size_t i = 0, L = c->length(); i < L; ++i) {+      *cc << static_cast<Feature_Query_Condition*>((*c)[i]->perform(this));+    }+    return cc;+  }++  Expression* Eval::operator()(At_Root_Expression* e)+  {+    Expression* feature = e->feature();+    feature = (feature ? feature->perform(this) : 0);+    Expression* value = e->value();+    value = (value ? value->perform(this) : 0);+    Expression* ee = new (ctx.mem) At_Root_Expression(e->pstate(),+                                                      static_cast<String*>(feature),+                                                      value,+                                                      e->is_interpolated());+    return ee;+  }++  Expression* Eval::operator()(Media_Query* q)+  {+    To_String to_string(&ctx);+    String* t = q->media_type();+    t = static_cast<String*>(t ? t->perform(this) : 0);+    Media_Query* qq = new (ctx.mem) 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 << static_cast<Media_Query_Expression*>((*q)[i]->perform(this));+    }+    return qq;+  }++  Expression* Eval::operator()(Media_Query_Expression* e)+  {+    Expression* feature = e->feature();+    feature = (feature ? feature->perform(this) : 0);+    Expression* value = e->value();+    value = (value ? value->perform(this) : 0);+    return new (ctx.mem) Media_Query_Expression(e->pstate(),+                                                feature,+                                                value,+                                                e->is_interpolated());+  }++  Expression* Eval::operator()(Null* n)+  {+    return n;+  }++  Expression* Eval::operator()(Argument* a)+  {+    Expression* val = a->value();+    val->is_delayed(false);+    val = val->perform(this);+    val->is_delayed(false);++    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* wrapper = new (ctx.mem) List(val->pstate(),+                                           0,+                                           List::COMMA,+                                           true);+        *wrapper << val;+        val = wrapper;+      }+    }+    return new (ctx.mem) Argument(a->pstate(),+                                  val,+                                  a->name(),+                                  is_rest_argument,+                                  is_keyword_argument);+  }++  Expression* Eval::operator()(Arguments* a)+  {+    Arguments* aa = new (ctx.mem) Arguments(a->pstate());+    for (size_t i = 0, L = a->length(); i < L; ++i) {+      *aa << static_cast<Argument*>((*a)[i]->perform(this));+    }+    return aa;+  }++  Expression* Eval::operator()(Comment* c)+  {+    return 0;+  }++  Expression* Eval::operator()(Parent_Selector* p)+  {+    Selector* s = p->perform(contextualize);+    // access to parent selector may return 0+    Selector_List* l = static_cast<Selector_List*>(s);+    if (!s) { l = new (ctx.mem) Selector_List(p->pstate()); }+    return l->perform(listize);+  }++  inline Expression* Eval::fallback_impl(AST_Node* n)+  {+    return static_cast<Expression*>(n);+  }++  // All the binary helpers.++  bool eq(Expression* lhs, Expression* rhs, Context& ctx)+  {+    Expression::Concrete_Type ltype = lhs->concrete_type();+    Expression::Concrete_Type rtype = rhs->concrete_type();+    if (ltype != rtype) return false;+    switch (ltype) {++      case Expression::BOOLEAN: {+        return static_cast<Boolean*>(lhs)->value() ==+               static_cast<Boolean*>(rhs)->value();+      } break;++      case Expression::NUMBER: {+        Number* l = static_cast<Number*>(lhs);+        Number* r = static_cast<Number*>(rhs);+        Number tmp_r(*r);+        tmp_r.normalize(l->find_convertible_unit());+        return l->unit() == tmp_r.unit() && l->value() == tmp_r.value()+               ? true+               : false;+      } break;++      case Expression::COLOR: {+        Color* l = static_cast<Color*>(lhs);+        Color* r = static_cast<Color*>(rhs);+        return l->r() == r->r() &&+               l->g() == r->g() &&+               l->b() == r->b() &&+               l->a() == r->a();+      } break;++      case Expression::STRING: {+        return unquote(static_cast<String_Constant*>(lhs)->value()) ==+               unquote(static_cast<String_Constant*>(rhs)->value());+      } break;++      case Expression::LIST: {+        List* l = static_cast<List*>(lhs);+        List* r = static_cast<List*>(rhs);+        if (l->length() != r->length()) return false;+        if (l->separator() != r->separator()) return false;+        for (size_t i = 0, L = l->length(); i < L; ++i) {+          if (!eq((*l)[i], (*r)[i], ctx)) return false;+        }+        return true;+      } break;++      case Expression::MAP: {+        Map* l = static_cast<Map*>(lhs);+        Map* r = static_cast<Map*>(rhs);+        if (l->length() != r->length()) return false;+        for (auto key : l->keys())+          if (!eq(l->at(key), r->at(key), ctx)) return false;+        return true;+      } break;+      case Expression::NULL_VAL: {+        return true;+      } break;++      default: break;+    }+    return false;+  }++  bool lt(Expression* lhs, Expression* rhs, Context& ctx)+  {+    if (lhs->concrete_type() != Expression::NUMBER ||+        rhs->concrete_type() != Expression::NUMBER)+      error("may only compare numbers", lhs->pstate());+    Number* l = static_cast<Number*>(lhs);+    Number* r = static_cast<Number*>(rhs);+    Number tmp_r(*r);+    tmp_r.normalize(l->find_convertible_unit());+    string l_unit(l->unit());+    string r_unit(tmp_r.unit());+    if (!l_unit.empty() && !r_unit.empty() && l->unit() != tmp_r.unit()) {+      error("cannot compare numbers with incompatible units", l->pstate());+    }+    return l->value() < tmp_r.value();+  }++  Expression* op_numbers(Context& ctx, Binary_Expression* b, Expression* lhs, Expression* rhs)+  {+    Number* l = static_cast<Number*>(lhs);+    Number* r = static_cast<Number*>(rhs);+    double lv = l->value();+    double rv = r->value();+    Binary_Expression::Type op = b->type();+    if (op == Binary_Expression::DIV && !rv) {+      return new (ctx.mem) String_Constant(l->pstate(), "Infinity");+    }+    if (op == Binary_Expression::MOD && !rv) {+      error("division by zero", r->pstate());+    }++    Number tmp(*r);+    tmp.normalize(l->find_convertible_unit());+    string l_unit(l->unit());+    string r_unit(tmp.unit());+    if (l_unit != r_unit && !l_unit.empty() && !r_unit.empty() &&+        (op == Binary_Expression::ADD || op == Binary_Expression::SUB)) {+      error("Incompatible units: '"+r_unit+"' and '"+l_unit+"'.", l->pstate());+    }+    Number* v = new (ctx.mem) Number(*l);+    v->pstate(b->pstate());+    if (l_unit.empty() && (op == Binary_Expression::ADD || op == Binary_Expression::SUB || op == Binary_Expression::MOD)) {+      v->numerator_units() = r->numerator_units();+      v->denominator_units() = r->denominator_units();+    }++    v->value(ops[op](lv, tmp.value()));+    if (op == Binary_Expression::MUL) {+      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 == Binary_Expression::DIV) {+      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]);+      }+    }+    v->normalize();+    return v;+  }++  Expression* op_number_color(Context& ctx, Binary_Expression::Type op, Expression* lhs, Expression* rhs)+  {+    Number* l = static_cast<Number*>(lhs);+    Color* r = static_cast<Color*>(rhs);+    // TODO: currently SASS converts colors to standard form when adding to strings;+    // when https://github.com/nex3/sass/issues/363 is added this can be removed to+    // preserve the original value+    r->disp("");+    double lv = l->value();+    switch (op) {+      case Binary_Expression::ADD:+      case Binary_Expression::MUL: {+        return new (ctx.mem) Color(l->pstate(),+                                   ops[op](lv, r->r()),+                                   ops[op](lv, r->g()),+                                   ops[op](lv, r->b()),+                                   r->a());+      } break;+      case Binary_Expression::SUB:+      case Binary_Expression::DIV: {+        string sep(op == Binary_Expression::SUB ? "-" : "/");+        To_String to_string(&ctx);+        string color(r->sixtuplet() && (ctx.output_style != COMPRESSED) ?+                     r->perform(&to_string) :+                     Util::normalize_sixtuplet(r->perform(&to_string)));+        return new (ctx.mem) String_Constant(l->pstate(),+                                             l->perform(&to_string)+                                             + sep+                                             + color);+      } break;+      case Binary_Expression::MOD: {+        error("cannot divide a number by a color", r->pstate());+      } break;+      default: break; // caller should ensure that we don't get here+    }+    // unreachable+    return l;+  }++  Expression* op_color_number(Context& ctx, Binary_Expression::Type op, Expression* lhs, Expression* rhs)+  {+    Color* l = static_cast<Color*>(lhs);+    Number* r = static_cast<Number*>(rhs);+    double rv = r->value();+    if (op == Binary_Expression::DIV && !rv) error("division by zero", r->pstate());+    return new (ctx.mem) Color(l->pstate(),+                               ops[op](l->r(), rv),+                               ops[op](l->g(), rv),+                               ops[op](l->b(), rv),+                               l->a());+  }++  Expression* op_colors(Context& ctx, Binary_Expression::Type op, Expression* lhs, Expression* rhs)+  {+    Color* l = static_cast<Color*>(lhs);+    Color* r = static_cast<Color*>(rhs);+    if (l->a() != r->a()) {+      error("alpha channels must be equal when combining colors", r->pstate());+    }+    if ((op == Binary_Expression::DIV || op == Binary_Expression::MOD) &&+        (!r->r() || !r->g() ||!r->b())) {+      error("division by zero", r->pstate());+    }+    return new (ctx.mem) Color(l->pstate(),+                               ops[op](l->r(), r->r()),+                               ops[op](l->g(), r->g()),+                               ops[op](l->b(), r->b()),+                               l->a());+  }++  Expression* op_strings(Context& ctx, Binary_Expression::Type op, Expression* lhs, Expression*rhs)+  {+    To_String to_string(&ctx);+    Expression::Concrete_Type ltype = lhs->concrete_type();+    Expression::Concrete_Type rtype = rhs->concrete_type();++    string lstr(lhs->perform(&to_string));+    string rstr(rhs->perform(&to_string));++    bool l_str_quoted = ((Sass::String*)lhs) && ((Sass::String*)lhs)->sass_fix_1291();+    bool r_str_quoted = ((Sass::String*)rhs) && ((Sass::String*)rhs)->sass_fix_1291();+    bool l_str_color = ltype == Expression::STRING && ctx.names_to_colors.count(lstr) && !l_str_quoted;+    bool r_str_color = rtype == Expression::STRING && ctx.names_to_colors.count(rstr) && !r_str_quoted;++    if (l_str_color && r_str_color) {+      return op_colors(ctx, op, ctx.names_to_colors[lstr], ctx.names_to_colors[rstr]);+    }+    else if (l_str_color && rtype == Expression::COLOR) {+      return op_colors(ctx, op, ctx.names_to_colors[lstr], rhs);+    }+    else if (l_str_color && rtype == Expression::NUMBER) {+      return op_color_number(ctx, op, ctx.names_to_colors[lstr], rhs);+    }+    else if (ltype == Expression::COLOR && r_str_color) {+      return op_number_color(ctx, op, lhs, ctx.names_to_colors[rstr]);+    }+    else if (ltype == Expression::NUMBER && r_str_color) {+      return op_number_color(ctx, op, lhs, ctx.names_to_colors[rstr]);+    }+    if (op == Binary_Expression::MUL) error("invalid operands for multiplication", lhs->pstate());+    if (op == Binary_Expression::MOD) error("invalid operands for modulo", lhs->pstate());+    string sep;+    switch (op) {+      case Binary_Expression::SUB: sep = "-"; break;+      case Binary_Expression::DIV: sep = "/"; break;+      default:                         break;+    }+    if (ltype == Expression::NULL_VAL) error("invalid null operation: \"null plus "+quote(unquote(rstr), '"')+"\".", lhs->pstate());+    if (rtype == Expression::NULL_VAL) error("invalid null operation: \""+quote(unquote(lstr), '"')+" plus null\".", lhs->pstate());+    string result((lstr) + sep + (rstr));+    String_Quoted* str = new (ctx.mem) String_Quoted(lhs->pstate(), result);+    str->quote_mark(0);+    return str;+  }++  Expression* cval_to_astnode(Sass_Value* v, Context& ctx, Backtrace* backtrace, ParserState pstate)+  {+    using std::strlen;+    using std::strcpy;+    Expression* e = 0;+    switch (sass_value_get_tag(v)) {+      case SASS_BOOLEAN: {+        e = new (ctx.mem) Boolean(pstate, !!sass_boolean_get_value(v));+      } break;+      case SASS_NUMBER: {+        e = new (ctx.mem) Number(pstate, sass_number_get_value(v), sass_number_get_unit(v));+      } break;+      case SASS_COLOR: {+        e = new (ctx.mem) 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: {+        e = new (ctx.mem) String_Constant(pstate, sass_string_get_value(v));+      } break;+      case SASS_LIST: {+        List* l = new (ctx.mem) List(pstate, sass_list_get_length(v), sass_list_get_separator(v) == SASS_COMMA ? List::COMMA : List::SPACE);+        for (size_t i = 0, L = sass_list_get_length(v); i < L; ++i) {+          *l << cval_to_astnode(sass_list_get_value(v, i), ctx, backtrace, pstate);+        }+        e = l;+      } break;+      case SASS_MAP: {+        Map* m = new (ctx.mem) 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), ctx, backtrace, pstate),+            cval_to_astnode(sass_map_get_value(v, i), ctx, backtrace, pstate));+        }+        e = m;+      } break;+      case SASS_NULL: {+        e = new (ctx.mem) Null(pstate);+      } break;+      case SASS_ERROR: {+        error("Error in C function: " + string(sass_error_get_message(v)), pstate, backtrace);+      } break;+      case SASS_WARNING: {+        error("Warning in C function: " + string(sass_warning_get_message(v)), pstate, backtrace);+      } break;+    }+    return e;+  }++}
+ libsass/eval.hpp view
@@ -0,0 +1,88 @@+#ifndef SASS_EVAL_H+#define SASS_EVAL_H++#include <iostream>++#include "context.hpp"+#include "position.hpp"+#include "operation.hpp"+#include "environment.hpp"+#include "contextualize.hpp"+#include "listize.hpp"+#include "sass_values.h"++namespace Sass {+  using namespace std;++  typedef Environment<AST_Node*> Env;+  struct Backtrace;+  class Contextualize;+  class Listize;++  class Eval : public Operation_CRTP<Expression*, Eval> {++    Context&   ctx;++    Expression* fallback_impl(AST_Node* n);++  public:+    Contextualize* contextualize;+    Listize*   listize;+    Env*       env;+    Backtrace* backtrace;+    Eval(Context&, Contextualize*, Listize*, Env*, Backtrace*);+    virtual ~Eval();+    Eval* with(Env* e, Backtrace* bt); // for setting the env before eval'ing an expression+    Eval* with(Selector* c, Env* e, Backtrace* bt, Selector* placeholder = 0, Selector* extender = 0); // for setting the env before eval'ing an expression+    using Operation<Expression*>::operator();++    // for evaluating function bodies+    Expression* operator()(Block*);+    Expression* operator()(Assignment*);+    Expression* operator()(If*);+    Expression* operator()(For*);+    Expression* operator()(Each*);+    Expression* operator()(While*);+    Expression* operator()(Return*);+    Expression* operator()(Warning*);+    Expression* operator()(Error*);+    Expression* operator()(Debug*);++    Expression* operator()(List*);+    Expression* operator()(Map*);+    Expression* operator()(Binary_Expression*);+    Expression* operator()(Unary_Expression*);+    Expression* operator()(Function_Call*);+    Expression* operator()(Function_Call_Schema*);+    Expression* operator()(Variable*);+    Expression* operator()(Textual*);+    Expression* operator()(Number*);+    Expression* operator()(Boolean*);+    Expression* operator()(String_Schema*);+    Expression* operator()(String_Constant*);+    Expression* operator()(Media_Query*);+    Expression* operator()(Media_Query_Expression*);+    Expression* operator()(At_Root_Expression*);+    Expression* operator()(Feature_Query*);+    Expression* operator()(Feature_Query_Condition*);+    Expression* operator()(Null*);+    Expression* operator()(Argument*);+    Expression* operator()(Arguments*);+    Expression* operator()(Comment*);+    Expression* operator()(Parent_Selector* p);++    template <typename U>+    Expression* fallback(U x) { return fallback_impl(x); }++  private:+    string interpolation(Expression* s);++  };++  Expression* cval_to_astnode(Sass_Value* v, Context& ctx, Backtrace* backtrace, ParserState pstate = ParserState("[AST]"));++  bool eq(Expression*, Expression*, Context&);+  bool lt(Expression*, Expression*, Context&);+}++#endif
+ libsass/expand.cpp view
@@ -0,0 +1,538 @@+#include <iostream>+#include <typeinfo>++#include "expand.hpp"+#include "bind.hpp"+#include "eval.hpp"+#include "contextualize_eval.hpp"+#include "to_string.hpp"+#include "backtrace.hpp"+#include "context.hpp"+#include "parser.hpp"++namespace Sass {++  Expand::Expand(Context& ctx, Eval* eval, Contextualize_Eval* contextualize_eval, Env* env, Backtrace* bt)+  : ctx(ctx),+    eval(eval),+    contextualize_eval(contextualize_eval),+    env(env),+    block_stack(vector<Block*>()),+    property_stack(vector<String*>()),+    selector_stack(vector<Selector*>()),+    at_root_selector_stack(vector<Selector*>()),+    in_at_root(false),+    in_keyframes(false),+    backtrace(bt)+  { selector_stack.push_back(0); }++  Statement* Expand::operator()(Block* b)+  {+    Env new_env;+    new_env.link(*env);+    env = &new_env;+    Block* bb = new (ctx.mem) Block(b->pstate(), b->length(), b->is_root());+    block_stack.push_back(bb);+    append_block(b);+    block_stack.pop_back();+    env = env->parent();+    return bb;+  }++  Statement* Expand::operator()(Ruleset* r)+  {+    bool old_in_at_root = in_at_root;+    in_at_root = false;++    if (in_keyframes) {+      To_String to_string;+      Keyframe_Rule* k = new (ctx.mem) Keyframe_Rule(r->pstate(), r->block()->perform(this)->block());+      if (r->selector()) k->selector(r->selector()->perform(contextualize_eval->with(0, env, backtrace)));+      in_at_root = old_in_at_root;+      old_in_at_root = false;+      return k;+    }++    Contextualize_Eval* contextual = contextualize_eval->with(selector_stack.back(), env, backtrace);+    if (old_in_at_root && !r->selector()->has_reference())+      contextual = contextualize_eval->with(at_root_selector_stack.back(), env, backtrace);++    Selector* sel_ctx = r->selector()->perform(contextual);+    if (sel_ctx == 0) throw "Cannot expand null selector";++    Emitter emitter(&ctx);+    Inspect isp(emitter);+    sel_ctx->perform(&isp);+    string str = isp.get_buffer();+    str += ";";++    Parser p(ctx, r->pstate());+    p.block_stack.push_back(r->selector() ? r->selector()->last_block() : 0);+    p.last_media_block = r->selector() ? r->selector()->media_block() : 0;+    p.source   = str.c_str();+    p.position = str.c_str();+    p.end      = str.c_str() + strlen(str.c_str());+    Selector_List* sel_lst = p.parse_selector_group();+    // sel_lst->pstate(isp.remap(sel_lst->pstate()));++    for(size_t i = 0; i < sel_lst->length(); i++) {++      Complex_Selector* pIter = (*sel_lst)[i];+      while (pIter) {+        Compound_Selector* pHead = pIter->head();+        // pIter->pstate(isp.remap(pIter->pstate()));+        if (pHead) {+          // pHead->pstate(isp.remap(pHead->pstate()));+          // (*pHead)[0]->pstate(isp.remap((*pHead)[0]->pstate()));+        }+        pIter = pIter->tail();+      }+    }+    sel_ctx = sel_lst;++    selector_stack.push_back(sel_ctx);+    Block* blk = r->block()->perform(this)->block();+    Ruleset* rr = new (ctx.mem) Ruleset(r->pstate(),+                                        sel_ctx,+                                        blk);+    rr->tabs(r->tabs());+    selector_stack.pop_back();+    in_at_root = old_in_at_root;+    old_in_at_root = false;+    return rr;+  }++  Statement* Expand::operator()(Propset* p)+  {+    property_stack.push_back(p->property_fragment());+    Block* expanded_block = p->block()->perform(this)->block();++    Block* current_block = block_stack.back();+    for (size_t i = 0, L = expanded_block->length(); i < L; ++i) {+      Statement* stm = (*expanded_block)[i];+      if (typeid(*stm) == typeid(Declaration)) {+        Declaration* dec = static_cast<Declaration*>(stm);+        String_Schema* combined_prop = new (ctx.mem) String_Schema(p->pstate());+        if (!property_stack.empty()) {+          *combined_prop << property_stack.back()+                         << new (ctx.mem) String_Constant(p->pstate(), "-")+                         << dec->property(); // TODO: eval the prop into a string constant+        }+        else {+          *combined_prop << dec->property();+        }+        dec->property(combined_prop);+        *current_block << dec;+      }+      else if (typeid(*stm) == typeid(Comment)) {+        // drop comments in propsets+      }+      else {+        error("contents of namespaced properties must result in style declarations only", stm->pstate(), backtrace);+      }+    }++    property_stack.pop_back();++    return 0;+  }++  Statement* Expand::operator()(Feature_Block* f)+  {+    Expression* feature_queries = f->feature_queries()->perform(eval->with(env, backtrace));+    Feature_Block* ff = new (ctx.mem) Feature_Block(f->pstate(),+                                                    static_cast<Feature_Query*>(feature_queries),+                                                    f->block()->perform(this)->block());+    ff->selector(selector_stack.back());+    return ff;+  }++  Statement* Expand::operator()(Media_Block* m)+  {+    To_String to_string(&ctx);+    Expression* mq = m->media_queries()->perform(eval->with(env, backtrace));+    mq = Parser::from_c_str(mq->perform(&to_string).c_str(), ctx, mq->pstate()).parse_media_queries();+    Media_Block* mm = new (ctx.mem) Media_Block(m->pstate(),+                                                static_cast<List*>(mq),+                                                m->block()->perform(this)->block(),+                                                selector_stack.back());+    mm->tabs(m->tabs());+    return mm;+  }++  Statement* Expand::operator()(At_Root_Block* a)+  {+    in_at_root = true;+    at_root_selector_stack.push_back(0);+    Block* ab = a->block();+    Expression* ae = a->expression();+    if (ae) ae = ae->perform(eval->with(env, backtrace));+    else ae = new (ctx.mem) At_Root_Expression(a->pstate());+    Block* bb = ab ? ab->perform(this)->block() : 0;+    At_Root_Block* aa = new (ctx.mem) At_Root_Block(a->pstate(),+                                                    bb,+                                                    static_cast<At_Root_Expression*>(ae));+    at_root_selector_stack.pop_back();+    in_at_root = false;+    return aa;+  }++  Statement* Expand::operator()(At_Rule* a)+  {+    bool old_in_keyframes = in_keyframes;+    in_keyframes = a->is_keyframes();+    Block* ab = a->block();+    Selector* as = a->selector();+    Expression* av = a->value();+    if (as) as = as->perform(contextualize_eval->with(0, env, backtrace));+    else if (av) av = av->perform(eval->with(env, backtrace));+    Block* bb = ab ? ab->perform(this)->block() : 0;+    At_Rule* aa = new (ctx.mem) At_Rule(a->pstate(),+                                        a->keyword(),+                                        as,+                                        bb);+    if (av) aa->value(av);+    in_keyframes = old_in_keyframes;+    return aa;+  }++  Statement* Expand::operator()(Declaration* d)+  {+    String* old_p = d->property();+    String* new_p = static_cast<String*>(old_p->perform(eval->with(env, backtrace)));+    Selector* p = selector_stack.size() <= 1 ? 0 : selector_stack.back();+    Expression* value = d->value()->perform(eval->with(p, env, backtrace));+    if (value->is_invisible() && !d->is_important()) return 0;+    Declaration* decl = new (ctx.mem) Declaration(d->pstate(),+                                                  new_p,+                                                  value,+                                                  d->is_important());+    decl->tabs(d->tabs());+    return decl;+  }++  Statement* Expand::operator()(Assignment* a)+  {+    string var(a->variable());+    Selector* p = selector_stack.size() <= 1 ? 0 : selector_stack.back();+    if (a->is_global()) {+      env->set_global(var, a->value()->perform(eval->with(p, env, backtrace)));+    }+    else if (a->is_default()) {+      if (env->has_lexical(var)) return 0;+      if (env->has_global(var)) {+        Expression* e = static_cast<Expression*>(env->get_global(var));+        if (e->concrete_type() == Expression::NULL_VAL) {+          env->set_global(var, a->value()->perform(eval->with(p, env, backtrace)));+        }+      } else {+        env->set_global(var, a->value()->perform(eval->with(p, env, backtrace)));+      }+    }+    else {+      env->set_lexical(var, a->value()->perform(eval->with(p, env, backtrace)));+    }+    return 0;+  }++  Statement* Expand::operator()(Import* imp)+  {+    Import* result = new (ctx.mem) Import(imp->pstate());+    for ( size_t i = 0, S = imp->urls().size(); i < S; ++i) {+      result->urls().push_back(imp->urls()[i]->perform(eval->with(env, backtrace)));+    }+    return result;+  }++  Statement* Expand::operator()(Import_Stub* i)+  {+    append_block(ctx.style_sheets[i->file_name()]);+    return 0;+  }++  Statement* Expand::operator()(Warning* w)+  {+    // eval handles this too, because warnings may occur in functions+    w->perform(eval->with(env, backtrace));+    return 0;+  }++  Statement* Expand::operator()(Error* e)+  {+    // eval handles this too, because errors may occur in functions+    e->perform(eval->with(env, backtrace));+    return 0;+  }++  Statement* Expand::operator()(Debug* d)+  {+    // eval handles this too, because warnings may occur in functions+    d->perform(eval->with(env, backtrace));+    return 0;+  }++  Statement* Expand::operator()(Comment* c)+  {+    // TODO: eval the text, once we're parsing/storing it as a String_Schema+    return new (ctx.mem) Comment(c->pstate(), static_cast<String*>(c->text()->perform(eval->with(env, backtrace))), c->is_important());+  }++  Statement* Expand::operator()(If* i)+  {+    if (*i->predicate()->perform(eval->with(env, backtrace))) {+      append_block(i->consequent());+    }+    else {+      Block* alt = i->alternative();+      if (alt) append_block(alt);+    }+    return 0;+  }++  // For does not create a new env scope+  // But iteration vars are reset afterwards+  Statement* Expand::operator()(For* f)+  {+    string variable(f->variable());+    Expression* low = f->lower_bound()->perform(eval->with(env, backtrace));+    if (low->concrete_type() != Expression::NUMBER) {+      error("lower bound of `@for` directive must be numeric", low->pstate(), backtrace);+    }+    Expression* high = f->upper_bound()->perform(eval->with(env, backtrace));+    if (high->concrete_type() != Expression::NUMBER) {+      error("upper bound of `@for` directive must be numeric", high->pstate(), backtrace);+    }+    Number* sass_start = static_cast<Number*>(low);+    Number* sass_end = static_cast<Number*>(high);+    // check if units are valid for sequence+    if (sass_start->unit() != sass_end->unit()) {+      stringstream msg; msg << "Incompatible units: '"+        << sass_start->unit() << "' and '"+        << sass_end->unit() << "'.";+      error(msg.str(), low->pstate(), backtrace);+    }+    double start = sass_start->value();+    double end = sass_end->value();+    // only create iterator once in this environment+    Number* it = new (env->mem) Number(low->pstate(), start, sass_end->unit());+    AST_Node* old_var = env->get_local(variable);+    env->set_local(variable, it);+    Block* body = f->block();+    if (start < end) {+      if (f->is_inclusive()) ++end;+      for (double i = start;+           i < end;+           ++i) {+        it->value(i);+        env->set_local(variable, it);+        append_block(body);+      }+    } else {+      if (f->is_inclusive()) --end;+      for (double i = start;+           i > end;+           --i) {+        it->value(i);+        env->set_local(variable, it);+        append_block(body);+      }+    }+    // restore original environment+    if (!old_var) env->del_local(variable);+    else env->set_local(variable, old_var);+    return 0;+  }++  // Eval does not create a new env scope+  // But iteration vars are reset afterwards+  Statement* Expand::operator()(Each* e)+  {+    vector<string> variables(e->variables());+    Expression* expr = e->list()->perform(eval->with(env, backtrace));+    List* list = 0;+    Map* map = 0;+    if (expr->concrete_type() == Expression::MAP) {+      map = static_cast<Map*>(expr);+    }+    else if (expr->concrete_type() != Expression::LIST) {+      list = new (ctx.mem) List(expr->pstate(), 1, List::COMMA);+      *list << expr;+    }+    else {+      list = static_cast<List*>(expr);+    }+    // remember variables and then reset them+    vector<AST_Node*> old_vars(variables.size());+    for (size_t i = 0, L = variables.size(); i < L; ++i) {+      old_vars[i] = env->get_local(variables[i]);+      env->set_local(variables[i], 0);+    }+    Block* body = e->block();++    if (map) {+      for (auto key : map->keys()) {+        Expression* k = key->perform(eval->with(env, backtrace));+        Expression* v = map->at(key)->perform(eval->with(env, backtrace));++        if (variables.size() == 1) {+          List* variable = new (ctx.mem) List(map->pstate(), 2, List::SPACE);+          *variable << k;+          *variable << v;+          env->set_local(variables[0], variable);+        } else {+          env->set_local(variables[0], k);+          env->set_local(variables[1], v);+        }+        append_block(body);+      }+    }+    else {+      for (size_t i = 0, L = list->length(); i < L; ++i) {+        List* variable = 0;+        if ((*list)[i]->concrete_type() != Expression::LIST  || variables.size() == 1) {+          variable = new (ctx.mem) List((*list)[i]->pstate(), 1, List::COMMA);+          *variable << (*list)[i];+        }+        else {+          variable = static_cast<List*>((*list)[i]);+        }+        for (size_t j = 0, K = variables.size(); j < K; ++j) {+          if (j < variable->length()) {+            env->set_local(variables[j], (*variable)[j]->perform(eval->with(env, backtrace)));+          }+          else {+            env->set_local(variables[j], new (ctx.mem) Null(expr->pstate()));+          }+        }+        append_block(body);+      }+    }+    // restore original environment+    for (size_t j = 0, K = variables.size(); j < K; ++j) {+      if(!old_vars[j]) env->del_local(variables[j]);+      else env->set_local(variables[j], old_vars[j]);+    }+    return 0;+  }++  Statement* Expand::operator()(While* w)+  {+    Expression* pred = w->predicate();+    Block* body = w->block();+    while (*pred->perform(eval->with(env, backtrace))) {+      append_block(body);+    }+    return 0;+  }++  Statement* Expand::operator()(Return* r)+  {+    error("@return may only be used within a function", r->pstate(), backtrace);+    return 0;+  }++  Statement* Expand::operator()(Extension* e)+  {+    To_String to_string(&ctx);+    Selector_List* extender = static_cast<Selector_List*>(selector_stack.back());+    if (!extender) return 0;+    Contextualize_Eval* eval = contextualize_eval->with(0, env, backtrace);+    Selector_List* selector_list = static_cast<Selector_List*>(e->selector());+    Selector_List* contextualized = static_cast<Selector_List*>(selector_list->perform(eval));+    // ToDo: remove once feature proves stable!+    // if (contextualized->length() != 1) {+    //   error("selector groups may not be extended", extendee->pstate(), backtrace);+    // }+    for (auto complex_sel : contextualized->elements()) {+      Complex_Selector* c = complex_sel;+      if (!c->head() || c->tail()) {+        error("nested selectors may not be extended", c->pstate(), backtrace);+      }+      Compound_Selector* compound_sel = c->head();+      compound_sel->is_optional(selector_list->is_optional());+      // // need to convert the compound selector into a by-value data structure+      // vector<string> target_vec;+      // for (size_t i = 0, L = compound_sel->length(); i < L; ++i)+      // { target_vec.push_back((*compound_sel)[i]->perform(&to_string)); }+      for (size_t i = 0, L = extender->length(); i < L; ++i) {+        // let's test this out+        // cerr << "REGISTERING EXTENSION REQUEST: " << (*extender)[i]->perform(&to_string) << " <- " << compound_sel->perform(&to_string) << endl;+        ctx.subset_map.put(compound_sel->to_str_vec(), make_pair((*extender)[i], compound_sel));+      }+    }+    return 0;+  }++  Statement* Expand::operator()(Definition* d)+  {+    Definition* dd = new (ctx.mem) Definition(*d);+    env->local_frame()[d->name() ++                        (d->type() == Definition::MIXIN ? "[m]" : "[f]")] = dd;+    // set the static link so we can have lexical scoping+    dd->environment(env);+    return 0;+  }++  Statement* Expand::operator()(Mixin_Call* c)+  {+    string full_name(c->name() + "[m]");+    if (!env->has(full_name)) {+      error("no mixin named " + c->name(), c->pstate(), backtrace);+    }+    Definition* def = static_cast<Definition*>((*env)[full_name]);+    Block* body = def->block();+    Parameters* params = def->parameters();+    Selector* p = selector_stack.size() <= 1 ? 0 : selector_stack.back();++    Arguments* args = static_cast<Arguments*>(c->arguments()+                                               ->perform(eval->with(p, env, backtrace)));+    Backtrace here(backtrace, c->pstate(), ", in mixin `" + c->name() + "`");+    backtrace = &here;+    Env new_env;+    new_env.link(def->environment());+    if (c->block()) {+      // represent mixin content blocks as thunks/closures+      Definition* thunk = new (ctx.mem) Definition(c->pstate(),+                                                   "@content",+                                                   new (ctx.mem) Parameters(c->pstate()),+                                                   c->block(),+                                                   &ctx,+                                                   Definition::MIXIN);+      thunk->environment(env);+      new_env.local_frame()["@content[m]"] = thunk;+    }+    bind("mixin " + c->name(), params, args, ctx, &new_env, eval);+    Env* old_env = env;+    env = &new_env;+    append_block(body);+    env = old_env;+    backtrace = here.parent;+    return 0;+  }++  Statement* Expand::operator()(Content* c)+  {+    // convert @content directives into mixin calls to the underlying thunk+    if (!env->has("@content[m]")) return 0;+    Mixin_Call* call = new (ctx.mem) Mixin_Call(c->pstate(),+                                                "@content",+                                                new (ctx.mem) Arguments(c->pstate()));+    return call->perform(this);+  }++  inline Statement* Expand::fallback_impl(AST_Node* n)+  {+    error("unknown internal error; please contact the LibSass maintainers", n->pstate(), backtrace);+    String_Constant* msg = new (ctx.mem) String_Constant(ParserState("[WARN]"), string("`Expand` doesn't handle ") + typeid(*n).name());+    return new (ctx.mem) Warning(ParserState("[WARN]"), msg);+  }++  inline void Expand::append_block(Block* b)+  {+    Block* current_block = block_stack.back();+    for (size_t i = 0, L = b->length(); i < L; ++i) {+      Statement* ith = (*b)[i]->perform(this);+      if (ith) *current_block << ith;+    }+  }+}
+ libsass/expand.hpp view
@@ -0,0 +1,78 @@+#ifndef SASS_EXPAND_H+#define SASS_EXPAND_H++#include <map>+#include <vector>+#include <iostream>++#include "ast.hpp"+#include "eval.hpp"+#include "operation.hpp"+#include "environment.hpp"+#include "contextualize.hpp"++namespace Sass {+  using namespace std;++  class Context;+  class Eval;+  class Contextualize_Eval;+  typedef Environment<AST_Node*> Env;+  struct Backtrace;++  class Expand : public Operation_CRTP<Statement*, Expand> {++    Context&          ctx;+    Eval*             eval;+    Contextualize_Eval*    contextualize_eval;+    Env*              env;+    vector<Block*>    block_stack;+    vector<String*>   property_stack;+    vector<Selector*> selector_stack;+    vector<Selector*> at_root_selector_stack;+    bool              in_at_root;+    bool              in_keyframes;+    Backtrace*        backtrace;++    Statement* fallback_impl(AST_Node* n);++  public:+    Expand(Context&, Eval*, Contextualize_Eval*, Env*, Backtrace*);+    virtual ~Expand() { }++    using Operation<Statement*>::operator();++    Statement* operator()(Block*);+    Statement* operator()(Ruleset*);+    Statement* operator()(Propset*);+    Statement* operator()(Media_Block*);+    Statement* operator()(Feature_Block*);+    Statement* operator()(At_Root_Block*);+    Statement* operator()(At_Rule*);+    Statement* operator()(Declaration*);+    Statement* operator()(Assignment*);+    Statement* operator()(Import*);+    Statement* operator()(Import_Stub*);+    Statement* operator()(Warning*);+    Statement* operator()(Error*);+    Statement* operator()(Debug*);+    Statement* operator()(Comment*);+    Statement* operator()(If*);+    Statement* operator()(For*);+    Statement* operator()(Each*);+    Statement* operator()(While*);+    Statement* operator()(Return*);+    Statement* operator()(Extension*);+    Statement* operator()(Definition*);+    Statement* operator()(Mixin_Call*);+    Statement* operator()(Content*);++    template <typename U>+    Statement* fallback(U x) { return fallback_impl(x); }++    void append_block(Block*);+  };++}++#endif
+ libsass/extend.cpp view
@@ -0,0 +1,2010 @@+#include "extend.hpp"+#include "context.hpp"+#include "contextualize.hpp"+#include "to_string.hpp"+#include "backtrace.hpp"+#include "paths.hpp"+#include "parser.hpp"+#include "node.hpp"+#include "sass_util.hpp"+#include "debug.hpp"+#include <iostream>+#include <deque>++/*+ NOTES:++ - The print* functions print to cerr. This allows our testing frameworks (like sass-spec) to ignore the output, which+   is very helpful when debugging. The format of the output is mainly to wrap things in square brackets to match what+   ruby already outputs (to make comparisons easier).++ - For the direct porting effort, we're trying to port method-for-method until we get all the tests passing.+   Where applicable, I've tried to include the ruby code above the function for reference until all our tests pass.+   The ruby code isn't always directly portable, so I've tried to include any modified ruby code that was actually+   used for the porting.++ - DO NOT try to optimize yet. We get a tremendous benefit out of comparing the output of each stage of the extend to the ruby+   output at the same stage. This makes it much easier to determine where problems are. Try to keep as close to+   the ruby code as you can until we have all the sass-spec tests passing. Then, we should optimize. However, if you see+   something that could probably be optimized, let's not forget it. Add a // TODO: or // IMPROVEMENT: comment.++ - Coding conventions in this file (these may need to be changed before merging back into master)+   - Very basic hungarian notation:+     p prefix for pointers (pSelector)+     no prefix for value types and references (selector)+   - Use STL iterators where possible+   - prefer verbose naming over terse naming+   - use typedefs for STL container types for make maintenance easier++ - You may see a lot of comments that say "// TODO: is this the correct combinator?". See the comment referring to combinators+   in extendCompoundSelector for a more extensive explanation of my confusion. I think our divergence in data model from ruby+   sass causes this to be necessary.+++ GLOBAL TODOS:++ - wrap the contents of the print functions in DEBUG preprocesser conditionals so they will be optimized away in non-debug mode.++ - consider making the extend* functions member functions to avoid passing around ctx and subsetMap map around. This has the+   drawback that the implementation details of the operator are then exposed to the outside world, which is not ideal and+   can cause additional compile time dependencies.++ - mark the helper methods in this file static to given them compilation unit linkage.++ - implement parent directive matching++ - fix compilation warnings for unused Extend members if we really don't need those references anymore.+ */+++namespace Sass {+++  typedef pair<Complex_Selector*, Compound_Selector*> ExtensionPair;+  typedef vector<ExtensionPair> SubsetMapEntries;++++#ifdef DEBUG++  // TODO: move the ast specific ostream operators into ast.hpp/ast.cpp+  ostream& operator<<(ostream& os, const Complex_Selector::Combinator combinator) {+    switch (combinator) {+      case Complex_Selector::ANCESTOR_OF: os << "\" \""; break;+      case Complex_Selector::PARENT_OF:   os << "\">\""; break;+      case Complex_Selector::PRECEDES:    os << "\"~\""; break;+      case Complex_Selector::ADJACENT_TO: os << "\"+\""; break;+    }++    return os;+  }+++  ostream& operator<<(ostream& os, Compound_Selector& compoundSelector) {+    To_String to_string;+    os << compoundSelector.perform(&to_string);+    return os;+  }+++  // Print a string representation of a Compound_Selector+  static void printCompoundSelector(Compound_Selector* pCompoundSelector, const char* message=NULL, bool newline=true) {+    To_String to_string;++    if (message) {+      cerr << message;+    }++    if (pCompoundSelector) {+      cerr << *pCompoundSelector;+    } else {+      cerr << "NULL";+    }++    if (newline) {+      cerr << endl;+    }+  }+++  ostream& operator<<(ostream& os, Complex_Selector& complexSelector) {+    To_String to_string;++    os << "[";+    Complex_Selector* pIter = &complexSelector;+    bool first = true;+    while (pIter) {+      if (pIter->combinator() != Complex_Selector::ANCESTOR_OF) {+        if (!first) {+          os << ", ";+        }+        first = false;+        os << pIter->combinator();+      }++      if (!first) {+        os << ", ";+      }+      first = false;++      if (pIter->head()) {+        os << pIter->head()->perform(&to_string);+      } else {+        os << "NULL_HEAD";+      }++      pIter = pIter->tail();+    }+    os << "]";++    return os;+  }+++  // Print a string representation of a Complex_Selector+  static void printComplexSelector(Complex_Selector* pComplexSelector, const char* message=NULL, bool newline=true) {+    To_String to_string;++    if (message) {+      cerr << message;+    }++    if (pComplexSelector) {+      cerr << *pComplexSelector;+    } else {+      cerr << "NULL";+    }++    if (newline) {+      cerr << endl;+    }+  }+++  // Print a string representation of a SourcesSet+  static void printSourcesSet(SourcesSet& sources, Context& ctx, const char* message=NULL, bool newline=true) {+    To_String to_string;++    if (message) {+      cerr << message;+    }++    // Convert to a deque of strings so we can sort since order doesn't matter in a set. This should cut down on+    // the differences we see when debug printing.+    typedef deque<string> SourceStrings;+    SourceStrings sourceStrings;+    for (SourcesSet::iterator iterator = sources.begin(), iteratorEnd = sources.end(); iterator != iteratorEnd; ++iterator) {+      Complex_Selector* pSource = *iterator;+      stringstream sstream;+      sstream << complexSelectorToNode(pSource, ctx);+      sourceStrings.push_back(sstream.str());+    }++    // Sort to get consistent output+    std::sort(sourceStrings.begin(), sourceStrings.end());++    cerr << "SourcesSet[";+    for (SourceStrings::iterator iterator = sourceStrings.begin(), iteratorEnd = sourceStrings.end(); iterator != iteratorEnd; ++iterator) {+      string source = *iterator;+      if (iterator != sourceStrings.begin()) {+        cerr << ", ";+      }+      cerr << source;+    }+    cerr << "]";++    if (newline) {+      cerr << endl;+    }+  }+++  ostream& operator<<(ostream& os, SubsetMapEntries& entries) {+    os << "SUBSET_MAP_ENTRIES[";++    for (SubsetMapEntries::iterator iterator = entries.begin(), endIterator = entries.end(); iterator != endIterator; ++iterator) {+      Complex_Selector* pExtComplexSelector = iterator->first;    // The selector up to where the @extend is (ie, the thing to merge)+      Compound_Selector* pExtCompoundSelector = iterator->second; // The stuff after the @extend++      if (iterator != entries.begin()) {+        os << ", ";+      }++      os << "(";++      if (pExtComplexSelector) {+        cerr << *pExtComplexSelector;+      } else {+        cerr << "NULL";+      }++      os << " -> ";++      if (pExtCompoundSelector) {+        cerr << *pExtCompoundSelector;+      } else {+        cerr << "NULL";+      }++      os << ")";++    }++    os << "]";++    return os;+  }+#endif++  static bool parentSuperselector(Complex_Selector* pOne, Complex_Selector* pTwo, Context& ctx) {+    // TODO: figure out a better way to create a Complex_Selector from scratch+    // TODO: There's got to be a better way. This got ugly quick...+    Position noPosition(-1, -1, -1);+    Type_Selector fakeParent(ParserState("[FAKE]"), "temp");+    Compound_Selector fakeHead(ParserState("[FAKE]"), 1 /*size*/);+    fakeHead.elements().push_back(&fakeParent);+    Complex_Selector fakeParentContainer(ParserState("[FAKE]"), Complex_Selector::ANCESTOR_OF, &fakeHead /*head*/, NULL /*tail*/);++    pOne->set_innermost(&fakeParentContainer, Complex_Selector::ANCESTOR_OF);+    pTwo->set_innermost(&fakeParentContainer, Complex_Selector::ANCESTOR_OF);++    bool isSuperselector = pOne->is_superselector_of(pTwo);++    pOne->clear_innermost();+    pTwo->clear_innermost();++    return isSuperselector;+  }++  void nodeToComplexSelectorDeque(const Node& node, ComplexSelectorDeque& out, Context& ctx) {+    for (NodeDeque::iterator iter = node.collection()->begin(), iterEnd = node.collection()->end(); iter != iterEnd; iter++) {+      Node& child = *iter;+      out.push_back(nodeToComplexSelector(child, ctx));+    }+  }++  Node complexSelectorDequeToNode(const ComplexSelectorDeque& deque, Context& ctx) {+    Node result = Node::createCollection();++    for (ComplexSelectorDeque::const_iterator iter = deque.begin(), iterEnd = deque.end(); iter != iterEnd; iter++) {+      Complex_Selector* pChild = *iter;+      result.collection()->push_back(complexSelectorToNode(pChild, ctx));+    }++    return result;+  }++  class LcsCollectionComparator {+  public:+    LcsCollectionComparator(Context& ctx) : mCtx(ctx) {}++    Context& mCtx;++    bool operator()(Complex_Selector* pOne, Complex_Selector* pTwo, Complex_Selector*& pOut) const {+      /*+      This code is based on the following block from ruby sass' subweave+        do |s1, s2|+          next s1 if s1 == s2+          next unless s1.first.is_a?(SimpleSequence) && s2.first.is_a?(SimpleSequence)+          next s2 if parent_superselector?(s1, s2)+          next s1 if parent_superselector?(s2, s1)+        end+      */++      if (selectors_equal(*pOne, *pTwo, true /*simpleSelectorOrderDependent*/)) {+        pOut = pOne;+        return true;+      }++      if (pOne->combinator() != Complex_Selector::ANCESTOR_OF || pTwo->combinator() != Complex_Selector::ANCESTOR_OF) {+        return false;+      }++      if (parentSuperselector(pOne, pTwo, mCtx)) {+        pOut = pTwo;+        return true;+      }++      if (parentSuperselector(pTwo, pOne, mCtx)) {+        pOut = pOne;+        return true;+      }++      return false;+    }+  };+++  /*+  This is the equivalent of ruby's Sass::Util.lcs_backtrace.++  # Computes a single longest common subsequence for arrays x and y.+  # Algorithm from http://en.wikipedia.org/wiki/Longest_common_subsequence_problem#Reading_out_an_LCS+  */+  void lcs_backtrace(const LCSTable& c, ComplexSelectorDeque& x, ComplexSelectorDeque& y, int i, int j, const LcsCollectionComparator& comparator, ComplexSelectorDeque& out) {+    //DEBUG_PRINTLN(LCS, "LCSBACK: X=" << x << " Y=" << y << " I=" << i << " J=" << j)+    // TODO: make printComplexSelectorDeque and use DEBUG_EXEC AND DEBUG_PRINTLN HERE to get equivalent output++    if (i == 0 || j == 0) {+      DEBUG_PRINTLN(LCS, "RETURNING EMPTY")+      return;+    }+++    Complex_Selector* pCompareOut = NULL;+    if (comparator(x[i], y[j], pCompareOut)) {+      DEBUG_PRINTLN(LCS, "RETURNING AFTER ELEM COMPARE")+      lcs_backtrace(c, x, y, i - 1, j - 1, comparator, out);+      out.push_back(pCompareOut);+      return;+    }++    if (c[i][j - 1] > c[i - 1][j]) {+      DEBUG_PRINTLN(LCS, "RETURNING AFTER TABLE COMPARE")+      lcs_backtrace(c, x, y, i, j - 1, comparator, out);+      return;+    }++    DEBUG_PRINTLN(LCS, "FINAL RETURN")+    lcs_backtrace(c, x, y, i - 1, j, comparator, out);+    return;+  }++  /*+  This is the equivalent of ruby's Sass::Util.lcs_table.++  # Calculates the memoization table for the Least Common Subsequence algorithm.+  # Algorithm from http://en.wikipedia.org/wiki/Longest_common_subsequence_problem#Computing_the_length_of_the_LCS+  */+  void lcs_table(const ComplexSelectorDeque& x, const ComplexSelectorDeque& y, const LcsCollectionComparator& comparator, LCSTable& out) {+    //DEBUG_PRINTLN(LCS, "LCSTABLE: X=" << x << " Y=" << y)+    // TODO: make printComplexSelectorDeque and use DEBUG_EXEC AND DEBUG_PRINTLN HERE to get equivalent output++    LCSTable c(x.size(), vector<int>(y.size()));++    // These shouldn't be necessary since the vector will be initialized to 0 already.+    // x.size.times {|i| c[i][0] = 0}+    // y.size.times {|j| c[0][j] = 0}++    for (size_t i = 1; i < x.size(); i++) {+      for (size_t j = 1; j < y.size(); j++) {+        Complex_Selector* pCompareOut = NULL;++        if (comparator(x[i], y[j], pCompareOut)) {+          c[i][j] = c[i - 1][j - 1] + 1;+        } else {+          c[i][j] = max(c[i][j - 1], c[i - 1][j]);+        }+      }+    }++    out = c;+  }++  /*+  This is the equivalent of ruby's Sass::Util.lcs.++  # Computes a single longest common subsequence for `x` and `y`.+  # If there are more than one longest common subsequences,+  # the one returned is that which starts first in `x`.++  # @param x [NodeCollection]+  # @param y [NodeCollection]+  # @comparator An equality check between elements of `x` and `y`.+  # @return [NodeCollection] The LCS++  http://en.wikipedia.org/wiki/Longest_common_subsequence_problem+  */+  void lcs(ComplexSelectorDeque& x, ComplexSelectorDeque& y, const LcsCollectionComparator& comparator, Context& ctx, ComplexSelectorDeque& out) {+    //DEBUG_PRINTLN(LCS, "LCS: X=" << x << " Y=" << y)+    // TODO: make printComplexSelectorDeque and use DEBUG_EXEC AND DEBUG_PRINTLN HERE to get equivalent output++    x.push_front(NULL);+    y.push_front(NULL);++    LCSTable table;+    lcs_table(x, y, comparator, table);++    return lcs_backtrace(table, x, y, static_cast<int>(x.size()) - 1, static_cast<int>(y.size()) - 1, comparator, out);+  }+++  /*+   This is the equivalent of ruby's Sequence.trim.++   The following is the modified version of the ruby code that was more portable to C++. You+   should be able to drop it into ruby 3.2.19 and get the same results from ruby sass.++        # Avoid truly horrific quadratic behavior. TODO: I think there+        # may be a way to get perfect trimming without going quadratic.+        return seqses if seqses.size > 100++        # Keep the results in a separate array so we can be sure we aren't+        # comparing against an already-trimmed selector. This ensures that two+        # identical selectors don't mutually trim one another.+        result = seqses.dup++        # This is n^2 on the sequences, but only comparing between+        # separate sequences should limit the quadratic behavior.+        seqses.each_with_index do |seqs1, i|+          tempResult = []++          for seq1 in seqs1 do+            max_spec = 0+            for seq in _sources(seq1) do+              max_spec = [max_spec, seq.specificity].max+            end+++            isMoreSpecificOuter = false+            for seqs2 in result do+              if seqs1.equal?(seqs2) then+                next+              end++              # Second Law of Extend: the specificity of a generated selector+              # should never be less than the specificity of the extending+              # selector.+              #+              # See https://github.com/nex3/sass/issues/324.+              isMoreSpecificInner = false+              for seq2 in seqs2 do+                isMoreSpecificInner = _specificity(seq2) >= max_spec && _superselector?(seq2, seq1)+                if isMoreSpecificInner then+                  break+                end+              end++              if isMoreSpecificInner then+                isMoreSpecificOuter = true+                break+              end+            end++            if !isMoreSpecificOuter then+              tempResult.push(seq1)+            end+          end++          result[i] = tempResult++        end++        result+   */+  /*+   - IMPROVEMENT: We could probably work directly in the output trimmed deque.+   */+  static Node trim(Node& seqses, Context& ctx) {+    // See the comments in the above ruby code before embarking on understanding this function.++    // Avoid poor performance in extreme cases.+    if (seqses.collection()->size() > 100) {+      return seqses;+    }+++    DEBUG_PRINTLN(TRIM, "TRIM: " << seqses)+++    Node result = Node::createCollection();+    result.plus(seqses);++    DEBUG_PRINTLN(TRIM, "RESULT INITIAL: " << result)++    // Normally we use the standard STL iterators, but in this case, we need to access the result collection by index since we're+    // iterating the input collection, computing a value, and then setting the result in the output collection. We have to keep track+    // of the index manually.+    int toTrimIndex = 0;++    for (NodeDeque::iterator seqsesIter = seqses.collection()->begin(), seqsesIterEnd = seqses.collection()->end(); seqsesIter != seqsesIterEnd; ++seqsesIter) {+      Node& seqs1 = *seqsesIter;++      DEBUG_PRINTLN(TRIM, "SEQS1: " << seqs1 << " " << toTrimIndex)++      Node tempResult = Node::createCollection();++      for (NodeDeque::iterator seqs1Iter = seqs1.collection()->begin(), seqs1EndIter = seqs1.collection()->end(); seqs1Iter != seqs1EndIter; ++seqs1Iter) {+        Node& seq1 = *seqs1Iter;++        Complex_Selector* pSeq1 = nodeToComplexSelector(seq1, ctx);++        // Compute the maximum specificity. This requires looking at the "sources" of the sequence. See SimpleSequence.sources in the ruby code+        // for a good description of sources.+        //+        // TODO: I'm pretty sure there's a bug in the sources code. It was implemented for sass-spec's 182_test_nested_extend_loop test.+        // While the test passes, I compared the state of each trim call to verify correctness. The last trim call had incorrect sources. We+        // had an extra source that the ruby version did not have. Without a failing test case, this is going to be extra hard to find. My+        // best guess at this point is that we're cloning an object somewhere and maintaining the sources when we shouldn't be. This is purely+        // a guess though.+        unsigned long maxSpecificity = 0;+        SourcesSet sources = pSeq1->sources();++        DEBUG_PRINTLN(TRIM, "TRIMASDF SEQ1: " << seq1)+        DEBUG_EXEC(TRIM, printSourcesSet(sources, ctx, "TRIMASDF SOURCES: "))++        for (SourcesSet::iterator sourcesSetIterator = sources.begin(), sourcesSetIteratorEnd = sources.end(); sourcesSetIterator != sourcesSetIteratorEnd; ++sourcesSetIterator) {+          const Complex_Selector* const pCurrentSelector = *sourcesSetIterator;+          maxSpecificity = max(maxSpecificity, pCurrentSelector->specificity());+        }++        DEBUG_PRINTLN(TRIM, "MAX SPECIFICITY: " << maxSpecificity)++        bool isMoreSpecificOuter = false;++        int resultIndex = 0;++        for (NodeDeque::iterator resultIter = result.collection()->begin(), resultIterEnd = result.collection()->end(); resultIter != resultIterEnd; ++resultIter) {+          Node& seqs2 = *resultIter;++          DEBUG_PRINTLN(TRIM, "SEQS1: " << seqs1)+          DEBUG_PRINTLN(TRIM, "SEQS2: " << seqs2)++          // Do not compare the same sequence to itself. The ruby call we're trying to+          // emulate is: seqs1.equal?(seqs2). equal? is an object comparison, not an equivalency comparision.+          // Since we have the same pointers in seqes and results, we can do a pointer comparision. seqs1 is+          // derived from seqses and seqs2 is derived from result.+          if (seqs1.collection() == seqs2.collection()) {+            DEBUG_PRINTLN(TRIM, "CONTINUE")+            continue;+          }++          bool isMoreSpecificInner = false;++          for (NodeDeque::iterator seqs2Iter = seqs2.collection()->begin(), seqs2IterEnd = seqs2.collection()->end(); seqs2Iter != seqs2IterEnd; ++seqs2Iter) {+            Node& seq2 = *seqs2Iter;++            Complex_Selector* pSeq2 = nodeToComplexSelector(seq2, ctx);++            DEBUG_PRINTLN(TRIM, "SEQ2 SPEC: " << pSeq2->specificity())+            DEBUG_PRINTLN(TRIM, "IS SPEC: " << pSeq2->specificity() << " >= " << maxSpecificity << " " << (pSeq2->specificity() >= maxSpecificity ? "true" : "false"))+            DEBUG_PRINTLN(TRIM, "IS SUPER: " << (pSeq2->is_superselector_of(pSeq1) ? "true" : "false"))++            isMoreSpecificInner = pSeq2->specificity() >= maxSpecificity && pSeq2->is_superselector_of(pSeq1);++            if (isMoreSpecificInner) {+              DEBUG_PRINTLN(TRIM, "FOUND MORE SPECIFIC")+              break;+            }+          }++          // If we found something more specific, we're done. Let the outer loop know and stop iterating.+          if (isMoreSpecificInner) {+            isMoreSpecificOuter = true;+            break;+          }++          resultIndex++;+        }++        if (!isMoreSpecificOuter) {+          DEBUG_PRINTLN(TRIM, "PUSHING: " << seq1)+          tempResult.collection()->push_back(seq1);+        }++      }++      DEBUG_PRINTLN(TRIM, "RESULT BEFORE ASSIGN: " << result)+      DEBUG_PRINTLN(TRIM, "TEMP RESULT: " << toTrimIndex << " " << tempResult)+      (*result.collection())[toTrimIndex] = tempResult;++      toTrimIndex++;++      DEBUG_PRINTLN(TRIM, "RESULT: " << result)+    }++    return result;+  }++++  static bool parentSuperselector(const Node& one, const Node& two, Context& ctx) {+    // TODO: figure out a better way to create a Complex_Selector from scratch+    // TODO: There's got to be a better way. This got ugly quick...+    Position noPosition(-1, -1, -1);+    Type_Selector fakeParent(ParserState("[FAKE]"), "temp");+    Compound_Selector fakeHead(ParserState("[FAKE]"), 1 /*size*/);+    fakeHead.elements().push_back(&fakeParent);+    Complex_Selector fakeParentContainer(ParserState("[FAKE]"), Complex_Selector::ANCESTOR_OF, &fakeHead /*head*/, NULL /*tail*/);++    Complex_Selector* pOneWithFakeParent = nodeToComplexSelector(one, ctx);+    pOneWithFakeParent->set_innermost(&fakeParentContainer, Complex_Selector::ANCESTOR_OF);+    Complex_Selector* pTwoWithFakeParent = nodeToComplexSelector(two, ctx);+    pTwoWithFakeParent->set_innermost(&fakeParentContainer, Complex_Selector::ANCESTOR_OF);++    return pOneWithFakeParent->is_superselector_of(pTwoWithFakeParent);+  }+++  class ParentSuperselectorChunker {+  public:+    ParentSuperselectorChunker(Node& lcs, Context& ctx) : mLcs(lcs), mCtx(ctx) {}+    Node& mLcs;+    Context& mCtx;++    bool operator()(const Node& seq) const {+      // {|s| parent_superselector?(s.first, lcs.first)}+      return parentSuperselector(seq.collection()->front(), mLcs.collection()->front(), mCtx);+    }+  };++  class SubweaveEmptyChunker {+  public:+    bool operator()(const Node& seq) const {+      // {|s| s.empty?}++      return seq.collection()->empty();+    }+  };++  /*+  # Takes initial subsequences of `seq1` and `seq2` and returns all+  # orderings of those subsequences. The initial subsequences are determined+  # by a block.+  #+  # Destructively removes the initial subsequences of `seq1` and `seq2`.+  #+  # For example, given `(A B C | D E)` and `(1 2 | 3 4 5)` (with `|`+  # denoting the boundary of the initial subsequence), this would return+  # `[(A B C 1 2), (1 2 A B C)]`. The sequences would then be `(D E)` and+  # `(3 4 5)`.+  #+  # @param seq1 [Array]+  # @param seq2 [Array]+  # @yield [a] Used to determine when to cut off the initial subsequences.+  #   Called repeatedly for each sequence until it returns true.+  # @yieldparam a [Array] A final subsequence of one input sequence after+  #   cutting off some initial subsequence.+  # @yieldreturn [Boolean] Whether or not to cut off the initial subsequence+  #   here.+  # @return [Array<Array>] All possible orderings of the initial subsequences.+  def chunks(seq1, seq2)+    chunk1 = []+    chunk1 << seq1.shift until yield seq1+    chunk2 = []+    chunk2 << seq2.shift until yield seq2+    return [] if chunk1.empty? && chunk2.empty?+    return [chunk2] if chunk1.empty?+    return [chunk1] if chunk2.empty?+    [chunk1 + chunk2, chunk2 + chunk1]+  end+  */+  template<typename ChunkerType>+  static Node chunks(Node& seq1, Node& seq2, const ChunkerType& chunker) {+    Node chunk1 = Node::createCollection();+    while (!chunker(seq1)) {+      chunk1.collection()->push_back(seq1.collection()->front());+      seq1.collection()->pop_front();+    }++    Node chunk2 = Node::createCollection();+    while (!chunker(seq2)) {+      chunk2.collection()->push_back(seq2.collection()->front());+      seq2.collection()->pop_front();+    }++    if (chunk1.collection()->empty() && chunk2.collection()->empty()) {+      DEBUG_PRINTLN(CHUNKS, "RETURNING BOTH EMPTY")+      return Node::createCollection();+    }++    if (chunk1.collection()->empty()) {+      Node chunk2Wrapper = Node::createCollection();+      chunk2Wrapper.collection()->push_back(chunk2);+      DEBUG_PRINTLN(CHUNKS, "RETURNING ONE EMPTY")+      return chunk2Wrapper;+    }++    if (chunk2.collection()->empty()) {+      Node chunk1Wrapper = Node::createCollection();+      chunk1Wrapper.collection()->push_back(chunk1);+      DEBUG_PRINTLN(CHUNKS, "RETURNING TWO EMPTY")+      return chunk1Wrapper;+    }++    Node perms = Node::createCollection();++    Node firstPermutation = Node::createCollection();+    firstPermutation.collection()->insert(firstPermutation.collection()->end(), chunk1.collection()->begin(), chunk1.collection()->end());+    firstPermutation.collection()->insert(firstPermutation.collection()->end(), chunk2.collection()->begin(), chunk2.collection()->end());+    perms.collection()->push_back(firstPermutation);++    Node secondPermutation = Node::createCollection();+    secondPermutation.collection()->insert(secondPermutation.collection()->end(), chunk2.collection()->begin(), chunk2.collection()->end());+    secondPermutation.collection()->insert(secondPermutation.collection()->end(), chunk1.collection()->begin(), chunk1.collection()->end());+    perms.collection()->push_back(secondPermutation);++    DEBUG_PRINTLN(CHUNKS, "RETURNING PERM")++    return perms;+  }+++  static Node groupSelectors(Node& seq, Context& ctx) {+    Node newSeq = Node::createCollection();++    Node tail = Node::createCollection();+    tail.plus(seq);++    while (!tail.collection()->empty()) {+      Node head = Node::createCollection();++      do {+        head.collection()->push_back(tail.collection()->front());+        tail.collection()->pop_front();+      } while (!tail.collection()->empty() && (head.collection()->back().isCombinator() || tail.collection()->front().isCombinator()));++      newSeq.collection()->push_back(head);+    }++    return newSeq;+  }+++  static void getAndRemoveInitialOps(Node& seq, Node& ops) {+    NodeDeque& seqCollection = *(seq.collection());+    NodeDeque& opsCollection = *(ops.collection());++    while (seqCollection.size() > 0 && seqCollection.front().isCombinator()) {+      opsCollection.push_back(seqCollection.front());+      seqCollection.pop_front();+    }+  }+++  static void getAndRemoveFinalOps(Node& seq, Node& ops) {+    NodeDeque& seqCollection = *(seq.collection());+    NodeDeque& opsCollection = *(ops.collection());++    while (seqCollection.size() > 0 && seqCollection.back().isCombinator()) {+      opsCollection.push_back(seqCollection.back()); // Purposefully reversed to match ruby code+      seqCollection.pop_back();+    }+  }+++  /*+      def merge_initial_ops(seq1, seq2)+        ops1, ops2 = [], []+        ops1 << seq1.shift while seq1.first.is_a?(String)+        ops2 << seq2.shift while seq2.first.is_a?(String)++        newline = false+        newline ||= !!ops1.shift if ops1.first == "\n"+        newline ||= !!ops2.shift if ops2.first == "\n"++        # If neither sequence is a subsequence of the other, they cannot be+        # merged successfully+        lcs = Sass::Util.lcs(ops1, ops2)+        return unless lcs == ops1 || lcs == ops2+        return (newline ? ["\n"] : []) + (ops1.size > ops2.size ? ops1 : ops2)+      end+  */+  static Node mergeInitialOps(Node& seq1, Node& seq2, Context& ctx) {+    Node ops1 = Node::createCollection();+    Node ops2 = Node::createCollection();++    getAndRemoveInitialOps(seq1, ops1);+    getAndRemoveInitialOps(seq2, ops2);++    // TODO: Do we have this information available to us?+    // newline = false+    // newline ||= !!ops1.shift if ops1.first == "\n"+    // newline ||= !!ops2.shift if ops2.first == "\n"++    // If neither sequence is a subsequence of the other, they cannot be merged successfully+    DefaultLcsComparator lcsDefaultComparator;+    Node opsLcs = lcs(ops1, ops2, lcsDefaultComparator, ctx);++    if (!(opsLcs == ops1 || opsLcs == ops2)) {+      return Node::createNil();+    }++    // TODO: more newline logic+    // return (newline ? ["\n"] : []) + (ops1.size > ops2.size ? ops1 : ops2)++    return (ops1.collection()->size() > ops2.collection()->size() ? ops1 : ops2);+  }+++  /*+      def merge_final_ops(seq1, seq2, res = [])+++        # This code looks complicated, but it's actually just a bunch of special+        # cases for interactions between different combinators.+        op1, op2 = ops1.first, ops2.first+        if op1 && op2+          sel1 = seq1.pop+          sel2 = seq2.pop+          if op1 == '~' && op2 == '~'+            if sel1.superselector?(sel2)+              res.unshift sel2, '~'+            elsif sel2.superselector?(sel1)+              res.unshift sel1, '~'+            else+              merged = sel1.unify(sel2.members, sel2.subject?)+              res.unshift [+                [sel1, '~', sel2, '~'],+                [sel2, '~', sel1, '~'],+                ([merged, '~'] if merged)+              ].compact+            end+          elsif (op1 == '~' && op2 == '+') || (op1 == '+' && op2 == '~')+            if op1 == '~'+              tilde_sel, plus_sel = sel1, sel2+            else+              tilde_sel, plus_sel = sel2, sel1+            end++            if tilde_sel.superselector?(plus_sel)+              res.unshift plus_sel, '+'+            else+              merged = plus_sel.unify(tilde_sel.members, tilde_sel.subject?)+              res.unshift [+                [tilde_sel, '~', plus_sel, '+'],+                ([merged, '+'] if merged)+              ].compact+            end+          elsif op1 == '>' && %w[~ +].include?(op2)+            res.unshift sel2, op2+            seq1.push sel1, op1+          elsif op2 == '>' && %w[~ +].include?(op1)+            res.unshift sel1, op1+            seq2.push sel2, op2+          elsif op1 == op2+            return unless merged = sel1.unify(sel2.members, sel2.subject?)+            res.unshift merged, op1+          else+            # Unknown selector combinators can't be unified+            return+          end+          return merge_final_ops(seq1, seq2, res)+        elsif op1+          seq2.pop if op1 == '>' && seq2.last && seq2.last.superselector?(seq1.last)+          res.unshift seq1.pop, op1+          return merge_final_ops(seq1, seq2, res)+        else # op2+          seq1.pop if op2 == '>' && seq1.last && seq1.last.superselector?(seq2.last)+          res.unshift seq2.pop, op2+          return merge_final_ops(seq1, seq2, res)+        end+      end+  */+  static Node mergeFinalOps(Node& seq1, Node& seq2, Context& ctx, Node& res) {++    Node ops1 = Node::createCollection();+    Node ops2 = Node::createCollection();++    getAndRemoveFinalOps(seq1, ops1);+    getAndRemoveFinalOps(seq2, ops2);++    // TODO: do we have newlines to remove?+    // ops1.reject! {|o| o == "\n"}+    // ops2.reject! {|o| o == "\n"}++    if (ops1.collection()->empty() && ops2.collection()->empty()) {+      return res;+    }++    if (ops1.collection()->size() > 1 || ops2.collection()->size() > 1) {+      DefaultLcsComparator lcsDefaultComparator;+      Node opsLcs = lcs(ops1, ops2, lcsDefaultComparator, ctx);++      // If there are multiple operators, something hacky's going on. If one is a supersequence of the other, use that, otherwise give up.++      if (!(opsLcs == ops1 || opsLcs == ops2)) {+        return Node::createNil();+      }++      if (ops1.collection()->size() > ops2.collection()->size()) {+        res.collection()->insert(res.collection()->begin(), ops1.collection()->rbegin(), ops1.collection()->rend());+      } else {+        res.collection()->insert(res.collection()->begin(), ops2.collection()->rbegin(), ops2.collection()->rend());+      }++      return res;+    }++    if (!ops1.collection()->empty() && !ops2.collection()->empty()) {++      Node op1 = ops1.collection()->front();+      Node op2 = ops2.collection()->front();++      Node sel1 = seq1.collection()->back();+      seq1.collection()->pop_back();++      Node sel2 = seq2.collection()->back();+      seq2.collection()->pop_back();++      if (op1.combinator() == Complex_Selector::PRECEDES && op2.combinator() == Complex_Selector::PRECEDES) {++        if (sel1.selector()->is_superselector_of(sel2.selector())) {++          res.collection()->push_front(op1 /*PRECEDES - could have been op2 as well*/);+          res.collection()->push_front(sel2);++        } else if (sel2.selector()->is_superselector_of(sel1.selector())) {++          res.collection()->push_front(op1 /*PRECEDES - could have been op2 as well*/);+          res.collection()->push_front(sel1);++        } else {++          DEBUG_PRINTLN(ALL, "sel1: " << sel1)+          DEBUG_PRINTLN(ALL, "sel2: " << sel2)++          Complex_Selector* pMergedWrapper = sel1.selector()->clone(ctx); // 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* pMerged = sel1.selector()->head()->unify_with(sel2.selector()->head(), ctx);+          pMergedWrapper->head(pMerged);++          DEBUG_EXEC(ALL, printCompoundSelector(pMerged, "MERGED: "))++          Node newRes = Node::createCollection();++          Node firstPerm = Node::createCollection();+          firstPerm.collection()->push_back(sel1);+          firstPerm.collection()->push_back(Node::createCombinator(Complex_Selector::PRECEDES));+          firstPerm.collection()->push_back(sel2);+          firstPerm.collection()->push_back(Node::createCombinator(Complex_Selector::PRECEDES));+          newRes.collection()->push_back(firstPerm);++          Node secondPerm = Node::createCollection();+          secondPerm.collection()->push_back(sel2);+          secondPerm.collection()->push_back(Node::createCombinator(Complex_Selector::PRECEDES));+          secondPerm.collection()->push_back(sel1);+          secondPerm.collection()->push_back(Node::createCombinator(Complex_Selector::PRECEDES));+          newRes.collection()->push_back(secondPerm);++          if (pMerged) {+            Node mergedPerm = Node::createCollection();+            mergedPerm.collection()->push_back(Node::createSelector(pMergedWrapper, ctx));+            mergedPerm.collection()->push_back(Node::createCombinator(Complex_Selector::PRECEDES));+            newRes.collection()->push_back(mergedPerm);+          }++          res.collection()->push_front(newRes);++          DEBUG_PRINTLN(ALL, "RESULT: " << res)++        }++      } else if (((op1.combinator() == Complex_Selector::PRECEDES && op2.combinator() == Complex_Selector::ADJACENT_TO)) || ((op1.combinator() == Complex_Selector::ADJACENT_TO && op2.combinator() == Complex_Selector::PRECEDES))) {++          Node tildeSel = sel1;+          Node tildeOp = op1;+          Node plusSel = sel2;+          Node plusOp = op2;+          if (op1.combinator() != Complex_Selector::PRECEDES) {+            tildeSel = sel2;+            tildeOp = op2;+            plusSel = sel1;+            plusOp = op1;+          }++          if (tildeSel.selector()->is_superselector_of(plusSel.selector())) {++            res.collection()->push_front(plusOp);+            res.collection()->push_front(plusSel);++          } else {++            DEBUG_PRINTLN(ALL, "PLUS SEL: " << plusSel)+            DEBUG_PRINTLN(ALL, "TILDE SEL: " << tildeSel)++            Complex_Selector* pMergedWrapper = plusSel.selector()->clone(ctx); // 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* pMerged = plusSel.selector()->head()->unify_with(tildeSel.selector()->head(), ctx);+            pMergedWrapper->head(pMerged);++            DEBUG_EXEC(ALL, printCompoundSelector(pMerged, "MERGED: "))++            Node newRes = Node::createCollection();++            Node firstPerm = Node::createCollection();+            firstPerm.collection()->push_back(tildeSel);+            firstPerm.collection()->push_back(Node::createCombinator(Complex_Selector::PRECEDES));+            firstPerm.collection()->push_back(plusSel);+            firstPerm.collection()->push_back(Node::createCombinator(Complex_Selector::ADJACENT_TO));+            newRes.collection()->push_back(firstPerm);++            if (pMerged) {+              Node mergedPerm = Node::createCollection();+              mergedPerm.collection()->push_back(Node::createSelector(pMergedWrapper, ctx));+              mergedPerm.collection()->push_back(Node::createCombinator(Complex_Selector::ADJACENT_TO));+              newRes.collection()->push_back(mergedPerm);+            }++            res.collection()->push_front(newRes);++            DEBUG_PRINTLN(ALL, "RESULT: " << res)++          }+      } else if (op1.combinator() == Complex_Selector::PARENT_OF && (op2.combinator() == Complex_Selector::PRECEDES || op2.combinator() == Complex_Selector::ADJACENT_TO)) {++        res.collection()->push_front(op2);+        res.collection()->push_front(sel2);++        seq2.collection()->push_back(sel1);+        seq2.collection()->push_back(op1);++      } else if (op2.combinator() == Complex_Selector::PARENT_OF && (op1.combinator() == Complex_Selector::PRECEDES || op1.combinator() == Complex_Selector::ADJACENT_TO)) {++        res.collection()->push_front(op1);+        res.collection()->push_front(sel1);++        seq2.collection()->push_back(sel2);+        seq2.collection()->push_back(op2);++      } else if (op1.combinator() == op2.combinator()) {++        DEBUG_PRINTLN(ALL, "sel1: " << sel1)+        DEBUG_PRINTLN(ALL, "sel2: " << sel2)++        Complex_Selector* pMergedWrapper = sel1.selector()->clone(ctx); // 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* pMerged = sel1.selector()->head()->unify_with(sel2.selector()->head(), ctx);+        pMergedWrapper->head(pMerged);++        DEBUG_EXEC(ALL, printCompoundSelector(pMerged, "MERGED: "))++        if (!pMerged) {+          return Node::createNil();+        }++        res.collection()->push_front(op1);+        res.collection()->push_front(Node::createSelector(pMergedWrapper, ctx));++        DEBUG_PRINTLN(ALL, "RESULT: " << res)++      } else {+        return Node::createNil();+      }++      return mergeFinalOps(seq1, seq2, ctx, res);++    } else if (!ops1.collection()->empty()) {++      Node op1 = ops1.collection()->front();++      if (op1.combinator() == Complex_Selector::PARENT_OF && !seq2.collection()->empty() && seq2.collection()->back().selector()->is_superselector_of(seq1.collection()->back().selector())) {+        seq2.collection()->pop_back();+      }++      // TODO: consider unshift(NodeCollection, Node)+      res.collection()->push_front(op1);+      res.collection()->push_front(seq1.collection()->back());+      seq1.collection()->pop_back();++      return mergeFinalOps(seq1, seq2, ctx, res);++    } else { // !ops2.collection()->empty()++      Node op2 = ops2.collection()->front();++      if (op2.combinator() == Complex_Selector::PARENT_OF && !seq1.collection()->empty() && seq1.collection()->back().selector()->is_superselector_of(seq2.collection()->back().selector())) {+        seq1.collection()->pop_back();+      }++      res.collection()->push_front(op2);+      res.collection()->push_front(seq2.collection()->back());+      seq2.collection()->pop_back();++      return mergeFinalOps(seq1, seq2, ctx, res);++    }++  }+++  /*+    This is the equivalent of ruby's Sequence.subweave.++    Here is the original subweave code for reference during porting.++      def subweave(seq1, seq2)+        return [seq2] if seq1.empty?+        return [seq1] if seq2.empty?++        seq1, seq2 = seq1.dup, seq2.dup+        return unless init = merge_initial_ops(seq1, seq2)+        return unless fin = merge_final_ops(seq1, seq2)+        seq1 = group_selectors(seq1)+        seq2 = group_selectors(seq2)+        lcs = Sass::Util.lcs(seq2, seq1) do |s1, s2|+          next s1 if s1 == s2+          next unless s1.first.is_a?(SimpleSequence) && s2.first.is_a?(SimpleSequence)+          next s2 if parent_superselector?(s1, s2)+          next s1 if parent_superselector?(s2, s1)+        end++        diff = [[init]]+        until lcs.empty?+          diff << chunks(seq1, seq2) {|s| parent_superselector?(s.first, lcs.first)} << [lcs.shift]+          seq1.shift+          seq2.shift+        end+        diff << chunks(seq1, seq2) {|s| s.empty?}+        diff += fin.map {|sel| sel.is_a?(Array) ? sel : [sel]}+        diff.reject! {|c| c.empty?}++        result = Sass::Util.paths(diff).map {|p| p.flatten}.reject {|p| path_has_two_subjects?(p)}++        result+      end+  */+  static Node subweave(Node& one, Node& two, Context& ctx) {+    // Check for the simple cases+    if (one.collection()->size() == 0) {+      Node out = Node::createCollection();+      out.collection()->push_back(two);+      return out;+    }+    if (two.collection()->size() == 0) {+      Node out = Node::createCollection();+      out.collection()->push_back(one);+      return out;+    }++++    Node seq1 = Node::createCollection();+    seq1.plus(one);+    Node seq2 = Node::createCollection();+    seq2.plus(two);++    DEBUG_PRINTLN(SUBWEAVE, "SUBWEAVE ONE: " << seq1)+    DEBUG_PRINTLN(SUBWEAVE, "SUBWEAVE TWO: " << seq2)++    Node init = mergeInitialOps(seq1, seq2, ctx);+    if (init.isNil()) {+      return Node::createNil();+    }++    DEBUG_PRINTLN(SUBWEAVE, "INIT: " << init)++    Node res = Node::createCollection();+    Node fin = mergeFinalOps(seq1, seq2, ctx, res);+    if (fin.isNil()) {+      return Node::createNil();+    }++    DEBUG_PRINTLN(SUBWEAVE, "FIN: " << fin)+++    // Moving this line up since fin isn't modified between now and when it happened before+    // fin.map {|sel| sel.is_a?(Array) ? sel : [sel]}++    for (NodeDeque::iterator finIter = fin.collection()->begin(), finEndIter = fin.collection()->end();+           finIter != finEndIter; ++finIter) {++      Node& childNode = *finIter;++      if (!childNode.isCollection()) {+        Node wrapper = Node::createCollection();+        wrapper.collection()->push_back(childNode);+        childNode = wrapper;+      }++    }++    DEBUG_PRINTLN(SUBWEAVE, "FIN MAPPED: " << fin)++++    Node groupSeq1 = groupSelectors(seq1, ctx);+    DEBUG_PRINTLN(SUBWEAVE, "SEQ1: " << groupSeq1)++    Node groupSeq2 = groupSelectors(seq2, ctx);+    DEBUG_PRINTLN(SUBWEAVE, "SEQ2: " << groupSeq2)+++    ComplexSelectorDeque groupSeq1Converted;+    nodeToComplexSelectorDeque(groupSeq1, groupSeq1Converted, ctx);++    ComplexSelectorDeque groupSeq2Converted;+    nodeToComplexSelectorDeque(groupSeq2, groupSeq2Converted, ctx);++    ComplexSelectorDeque out;+    LcsCollectionComparator collectionComparator(ctx);+    lcs(groupSeq2Converted, groupSeq1Converted, collectionComparator, ctx, out);+    Node seqLcs = complexSelectorDequeToNode(out, ctx);++    DEBUG_PRINTLN(SUBWEAVE, "SEQLCS: " << seqLcs)+++    Node initWrapper = Node::createCollection();+    initWrapper.collection()->push_back(init);+    Node diff = Node::createCollection();+    diff.collection()->push_back(initWrapper);++    DEBUG_PRINTLN(SUBWEAVE, "DIFF INIT: " << diff)+++    while (!seqLcs.collection()->empty()) {+      ParentSuperselectorChunker superselectorChunker(seqLcs, ctx);+      Node chunksResult = chunks(groupSeq1, groupSeq2, superselectorChunker);+      diff.collection()->push_back(chunksResult);++      Node lcsWrapper = Node::createCollection();+      lcsWrapper.collection()->push_back(seqLcs.collection()->front());+      seqLcs.collection()->pop_front();+      diff.collection()->push_back(lcsWrapper);++      groupSeq1.collection()->pop_front();+      groupSeq2.collection()->pop_front();+    }++    DEBUG_PRINTLN(SUBWEAVE, "DIFF POST LCS: " << diff)+++    DEBUG_PRINTLN(SUBWEAVE, "CHUNKS: ONE=" << groupSeq1 << " TWO=" << groupSeq2)+++    SubweaveEmptyChunker emptyChunker;+    Node chunksResult = chunks(groupSeq1, groupSeq2, emptyChunker);+    diff.collection()->push_back(chunksResult);+++    DEBUG_PRINTLN(SUBWEAVE, "DIFF POST CHUNKS: " << diff)+++    diff.collection()->insert(diff.collection()->end(), fin.collection()->begin(), fin.collection()->end());++    DEBUG_PRINTLN(SUBWEAVE, "DIFF POST FIN MAPPED: " << diff)++    // JMA - filter out the empty nodes (use a new collection, since iterator erase() invalidates the old collection)+    Node diffFiltered = Node::createCollection();+    for (NodeDeque::iterator diffIter = diff.collection()->begin(), diffEndIter = diff.collection()->end();+           diffIter != diffEndIter; ++diffIter) {+      Node& node = *diffIter;+      if (node.collection() && !node.collection()->empty()) {+        diffFiltered.collection()->push_back(node);+      }+    }+    diff = diffFiltered;++    DEBUG_PRINTLN(SUBWEAVE, "DIFF POST REJECT: " << diff)+++    Node pathsResult = paths(diff, ctx);++    DEBUG_PRINTLN(SUBWEAVE, "PATHS: " << pathsResult)+++    // We're flattening in place+    for (NodeDeque::iterator pathsIter = pathsResult.collection()->begin(), pathsEndIter = pathsResult.collection()->end();+      pathsIter != pathsEndIter; ++pathsIter) {++      Node& child = *pathsIter;+      child = flatten(child, ctx);+    }++    DEBUG_PRINTLN(SUBWEAVE, "FLATTENED: " << pathsResult)+++    /*+      TODO: implement+      rejected = mapped.reject {|p| path_has_two_subjects?(p)}+      $stderr.puts "REJECTED: #{rejected}"+     */+++    return pathsResult;++  }+  /*+  // disabled to avoid clang warning [-Wunused-function]+  static Node subweaveNaive(const Node& one, const Node& two, Context& ctx) {+    Node out = Node::createCollection();++    // Check for the simple cases+    if (one.isNil()) {+      out.collection()->push_back(two.clone(ctx));+    } else if (two.isNil()) {+      out.collection()->push_back(one.clone(ctx));+    } 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.clone(ctx);+      Node twoCloned = two.clone(ctx);+      firstPerm.plus(twoCloned);+      out.collection()->push_back(firstPerm);++      Node secondPerm = two.clone(ctx);+      Node oneCloned = one.clone(ctx);+      secondPerm.plus(oneCloned );+      out.collection()->push_back(secondPerm);+    }++    return out;+  }+  */+++  /*+   This is the equivalent of ruby's Sequence.weave.++   The following is the modified version of the ruby code that was more portable to C++. You+   should be able to drop it into ruby 3.2.19 and get the same results from ruby sass.++      def weave(path)+        # This function works by moving through the selector path left-to-right,+        # building all possible prefixes simultaneously. These prefixes are+        # `befores`, while the remaining parenthesized suffixes is `afters`.+        befores = [[]]+        afters = path.dup++        until afters.empty?+          current = afters.shift.dup+          last_current = [current.pop]++          tempResult = []++          for before in befores do+            sub = subweave(before, current)+            if sub.nil?+              next+            end++            for seqs in sub do+              tempResult.push(seqs + last_current)+            end+          end++          befores = tempResult++        end++        return befores+      end+   */+  /*+      def weave(path)+        befores = [[]]+        afters = path.dup++        until afters.empty?+          current = afters.shift.dup++          last_current = [current.pop]+++          tempResult = []++          for before in befores do+            sub = subweave(before, current)++            if sub.nil?+              next []+            end+++            for seqs in sub do+              toPush = seqs + last_current++              tempResult.push(seqs + last_current)+            end++          end++          befores = tempResult++        end++        return befores+      end+  */+  static Node weave(Node& path, Context& ctx) {++    DEBUG_PRINTLN(WEAVE, "WEAVE: " << path)++    Node befores = Node::createCollection();+    befores.collection()->push_back(Node::createCollection());++    Node afters = Node::createCollection();+    afters.plus(path);++    while (!afters.collection()->empty()) {+      Node current = afters.collection()->front().clone(ctx);+      afters.collection()->pop_front();+      DEBUG_PRINTLN(WEAVE, "CURRENT: " << current)++      Node last_current = Node::createCollection();+      last_current.collection()->push_back(current.collection()->back());+      current.collection()->pop_back();+      DEBUG_PRINTLN(WEAVE, "CURRENT POST POP: " << current)+      DEBUG_PRINTLN(WEAVE, "LAST CURRENT: " << last_current)++      Node tempResult = Node::createCollection();++      for (NodeDeque::iterator beforesIter = befores.collection()->begin(), beforesEndIter = befores.collection()->end(); beforesIter != beforesEndIter; beforesIter++) {+        Node& before = *beforesIter;++        Node sub = subweave(before, current, ctx);++        DEBUG_PRINTLN(WEAVE, "SUB: " << sub)++        if (sub.isNil()) {+          return Node::createCollection();+        }++        for (NodeDeque::iterator subIter = sub.collection()->begin(), subEndIter = sub.collection()->end(); subIter != subEndIter; subIter++) {+          Node& seqs = *subIter;++          Node toPush = Node::createCollection();+          toPush.plus(seqs);+          toPush.plus(last_current);++          tempResult.collection()->push_back(toPush);++        }+      }++      befores = tempResult;++    }++    return befores;+  }++++  // This forward declaration is needed since extendComplexSelector calls extendCompoundSelector, which may recursively+  // call extendComplexSelector again.+  static Node extendComplexSelector(+    Complex_Selector* pComplexSelector,+    Context& ctx,+    ExtensionSubsetMap& subsetMap,+    set<Compound_Selector> seen);++++  /*+   This is the equivalent of ruby's SimpleSequence.do_extend.++    // TODO: I think I have some modified ruby code to put here. Check.+  */+  /*+   ISSUES:+   - Previous TODO: Do we need to group the results by extender?+   - What does subject do in?: next unless unified = seq.members.last.unify(self_without_sel, subject?)+   - IMPROVEMENT: The search for uniqueness at the end is not ideal since it's has to loop over everything...+   - IMPROVEMENT: Check if the final search for uniqueness is doing anything that extendComplexSelector isn't already doing...+   */+  template<typename KeyType>+  class GroupByToAFunctor {+  public:+    KeyType operator()(ExtensionPair& extPair) const {+      Complex_Selector* pSelector = extPair.first;+      return *pSelector;+    }+  };+  static Node extendCompoundSelector(+    Compound_Selector* pSelector,+    Context& ctx,+    ExtensionSubsetMap& subsetMap,+    set<Compound_Selector> seen) {++    DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pSelector, "EXTEND COMPOUND: "))++    Node extendedSelectors = Node::createCollection();++    To_String to_string;++    SubsetMapEntries entries = subsetMap.get_v(pSelector->to_str_vec());+++    typedef vector<pair<Complex_Selector, vector<ExtensionPair> > > GroupedByToAResult;++    GroupByToAFunctor<Complex_Selector> extPairKeyFunctor;+    GroupedByToAResult arr;+    group_by_to_a(entries, extPairKeyFunctor, arr);+++    typedef pair<Compound_Selector*, Complex_Selector*> SelsNewSeqPair;+    typedef vector<SelsNewSeqPair> SelsNewSeqPairCollection;+++    SelsNewSeqPairCollection holder;+++    for (GroupedByToAResult::iterator groupedIter = arr.begin(), groupedIterEnd = arr.end(); groupedIter != groupedIterEnd; groupedIter++) {+      pair<Complex_Selector, vector<ExtensionPair> >& groupedPair = *groupedIter;++      Complex_Selector& seq = groupedPair.first;+      vector<ExtensionPair>& group = groupedPair.second;++//      DEBUG_EXEC(EXTEND_COMPOUND, printComplexSelector(&seq, "SEQ: "))+++      Compound_Selector* pSels = new (ctx.mem) Compound_Selector(pSelector->pstate());+      for (vector<ExtensionPair>::iterator groupIter = group.begin(), groupIterEnd = group.end(); groupIter != groupIterEnd; groupIter++) {+        ExtensionPair& pair = *groupIter;+        Compound_Selector* pCompound = pair.second;+        for (size_t index = 0; index < pCompound->length(); index++) {+          Simple_Selector* pSimpleSelector = (*pCompound)[index];+          (*pSels) << pSimpleSelector;+        }+      }++++//      DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pSels, "SELS: "))+++      Complex_Selector* pExtComplexSelector = &seq;    // The selector up to where the @extend is (ie, the thing to merge)+      Compound_Selector* pExtCompoundSelector = pSels; // All the simple selectors to be replaced from the current compound selector from all extensions++++      // TODO: This can return a Compound_Selector with no elements. Should that just be returning NULL?+      Compound_Selector* pSelectorWithoutExtendSelectors = pSelector->minus(pExtCompoundSelector, ctx);+++//      DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pSelector, "MEMBERS: "))+//      DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pSelectorWithoutExtendSelectors, "SELF_WO_SEL: "))+++      Compound_Selector* pInnermostCompoundSelector = pExtComplexSelector->base();+      Compound_Selector* pUnifiedSelector = NULL;++      if (!pInnermostCompoundSelector) {+        pInnermostCompoundSelector = new (ctx.mem) Compound_Selector(pSelector->pstate());+      }++      pUnifiedSelector = pInnermostCompoundSelector->unify_with(pSelectorWithoutExtendSelectors, ctx);++//      DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pInnermostCompoundSelector, "LHS: "))+//      DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pSelectorWithoutExtendSelectors, "RHS: "))+//      DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pUnifiedSelector, "UNIFIED: "))++      if (!pUnifiedSelector || pUnifiedSelector->length() == 0) {+        continue;+      }++++      // TODO: implement the parent directive match (if necessary based on test failures)+      // next if group.map {|e, _| check_directives_match!(e, parent_directives)}.none?+++++      // TODO: This seems a little fishy to me. See if it causes any problems. From the ruby, we should be able to just+      // get rid of the last Compound_Selector and replace it with this one. I think the reason this code is more+      // complex is that Complex_Selector contains a combinator, but in ruby combinators have already been filtered+      // out and aren't operated on.+      Complex_Selector* pNewSelector = pExtComplexSelector->cloneFully(ctx);+      Complex_Selector* pNewInnerMost = new (ctx.mem) Complex_Selector(pSelector->pstate(), Complex_Selector::ANCESTOR_OF, pUnifiedSelector, NULL);+      Complex_Selector::Combinator combinator = pNewSelector->clear_innermost();+      pNewSelector->set_innermost(pNewInnerMost, combinator);++#ifdef DEBUG+      SourcesSet debugSet;+      debugSet = pNewSelector->sources();+      if (debugSet.size() > 0) {+        throw "The new selector should start with no sources. Something needs to be cloned to fix this.";+      }+      debugSet = pExtComplexSelector->sources();+      if (debugSet.size() > 0) {+        throw "The extension selector from our subset map should not have sources. These will bleed to the new selector. Something needs to be cloned to fix this.";+      }+#endif+++      if (pSelector && pSelector->has_line_feed()) pNewSelector->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_EXEC(EXTEND_COMPOUND, SourcesSet oldSet = pNewSelector->sources(); printSourcesSet(oldSet, ctx, "SOURCES NEW SEQ BEGIN: "))++      SourcesSet newSourcesSet = pSelector->sources();+      DEBUG_EXEC(EXTEND_COMPOUND, printSourcesSet(newSourcesSet, ctx, "SOURCES THIS EXTEND: "))++      newSourcesSet.insert(pExtComplexSelector);+      DEBUG_EXEC(EXTEND_COMPOUND, printSourcesSet(newSourcesSet, ctx, "SOURCES WITH NEW SOURCE: "))++      pNewSelector->addSources(newSourcesSet, ctx);++      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: "))++++      holder.push_back(make_pair(pSels, pNewSelector));+    }+++    for (SelsNewSeqPairCollection::iterator holderIter = holder.begin(), holderIterEnd = holder.end(); holderIter != holderIterEnd; holderIter++) {+      SelsNewSeqPair& pair = *holderIter;++      Compound_Selector* pSels = pair.first;+      Complex_Selector* pNewSelector = pair.second;+++      if (seen.find(*pSels) != seen.end()) {+        continue;+      }+++      set<Compound_Selector> recurseSeen(seen);+      recurseSeen.insert(*pSels);+++      DEBUG_PRINTLN(EXTEND_COMPOUND, "RECURSING DO EXTEND: " << complexSelectorToNode(pNewSelector, ctx))++      Node recurseExtendedSelectors = extendComplexSelector(pNewSelector, ctx, subsetMap, recurseSeen);++      DEBUG_PRINTLN(EXTEND_COMPOUND, "RECURSING DO EXTEND RETURN: " << recurseExtendedSelectors)++      for (NodeDeque::iterator iterator = recurseExtendedSelectors.collection()->begin(), endIterator = recurseExtendedSelectors.collection()->end();+           iterator != endIterator; ++iterator) {+        Node& newSelector = *iterator;++//        DEBUG_PRINTLN(EXTEND_COMPOUND, "EXTENDED AT THIS POINT: " << extendedSelectors)+//        DEBUG_PRINTLN(EXTEND_COMPOUND, "SELECTOR EXISTS ALREADY: " << newSelector << " " << extendedSelectors.contains(newSelector, false /*simpleSelectorOrderDependent*/));++        if (!extendedSelectors.contains(newSelector, false /*simpleSelectorOrderDependent*/)) {+//          DEBUG_PRINTLN(EXTEND_COMPOUND, "ADDING NEW SELECTOR")+          extendedSelectors.collection()->push_back(newSelector);+        }+      }+    }++    DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pSelector, "EXTEND COMPOUND END: "))++    return extendedSelectors;+  }+++  static bool complexSelectorHasExtension(+    Complex_Selector* pComplexSelector,+    Context& ctx,+    ExtensionSubsetMap& subsetMap) {++    bool hasExtension = false;++    Complex_Selector* pIter = pComplexSelector;++    while (!hasExtension && pIter) {+      Compound_Selector* pHead = pIter->head();++      if (pHead) {+        SubsetMapEntries entries = subsetMap.get_v(pHead->to_str_vec());+        for (ExtensionPair ext : entries) {+          // check if both selectors have the same media block parent+          if (ext.first->media_block() == pComplexSelector->media_block()) continue;+          To_String to_string(&ctx);+          if (ext.second->media_block() == 0) continue;+          if (pComplexSelector->media_block() &&+              ext.second->media_block()->media_queries() &&+              pComplexSelector->media_block()->media_queries()+          ) {+            string query_left(ext.second->media_block()->media_queries()->perform(&to_string));+            string query_right(pComplexSelector->media_block()->media_queries()->perform(&to_string));+            if (query_left == query_right) continue;+          }++          // fail if one goes across media block boundaries+          stringstream err;+          string cwd(Sass::File::get_cwd());+          ParserState pstate(ext.second->pstate());+          string rel_path(Sass::File::resolve_relative_path(pstate.path, cwd, cwd));+          err << "You may not @extend an outer selector from within @media.\n";+          err << "You may only @extend selectors within the same directive.\n";+          err << "From \"@extend " << ext.second->perform(&to_string) << "\"";+          err << " on line " << pstate.line+1 << " of " << rel_path << "\n";+          error(err.str(), pComplexSelector->pstate());+        }+        if (entries.size() > 0) hasExtension = true;+      }++      pIter = pIter->tail();+    }++    if (!hasExtension) {+      /* ToDo: don't break stuff+      stringstream err;+      To_String to_string(&ctx);+      string cwd(Sass::File::get_cwd());+      string sel1(pComplexSelector->perform(&to_string));+      Compound_Selector* pExtendSelector = 0;+      for (auto i : subsetMap.values()) {+        if (i.first == pComplexSelector) {+          pExtendSelector = i.second;+          break;+        }+      }+      if (!pExtendSelector || !pExtendSelector->is_optional()) {+        string sel2(pExtendSelector ? pExtendSelector->perform(&to_string) : "[unknown]");+        err << "\"" << sel1 << "\" failed to @extend \"" << sel2 << "\"\n";+        err << "The selector \"" << sel2 << "\" was not found.\n";+        err << "Use \"@extend " << sel2 << " !optional\" if the extend should be able to fail.";+        error(err.str(), pExtendSelector ? pExtendSelector->pstate() : pComplexSelector->pstate());+      }+      */+    }++    return hasExtension;+  }+++  /*+   This is the equivalent of ruby's Sequence.do_extend.++   // TODO: I think I have some modified ruby code to put here. Check.+   */+  /*+   ISSUES:+   - check to automatically include combinators doesn't transfer over to libsass' data model where+     the combinator and compound selector are one unit+     next [[sseq_or_op]] unless sseq_or_op.is_a?(SimpleSequence)+   */+  static Node extendComplexSelector(+    Complex_Selector* pComplexSelector,+    Context& ctx,+    ExtensionSubsetMap& subsetMap,+    set<Compound_Selector> seen) {++    pComplexSelector->tail()->has_line_feed(pComplexSelector->has_line_feed());++    Node complexSelector = complexSelectorToNode(pComplexSelector, ctx);+    DEBUG_PRINTLN(EXTEND_COMPLEX, "EXTEND COMPLEX: " << complexSelector)++    Node extendedNotExpanded = Node::createCollection();++    for (NodeDeque::iterator complexSelIter = complexSelector.collection()->begin(), complexSelIterEnd = complexSelector.collection()->end(); complexSelIter != complexSelIterEnd; ++complexSelIter) {+      Node& sseqOrOp = *complexSelIter;++      DEBUG_PRINTLN(EXTEND_COMPLEX, "LOOP: " << sseqOrOp)++      // If it's not a selector (meaning it's a combinator), just include it automatically+      if (!sseqOrOp.isSelector()) {+        // Wrap our Combinator in two collections to match ruby. This is essentially making a collection Node+        // with one collection child. The collection child represents a Complex_Selector that is only a combinator.+        Node outer = Node::createCollection();+        Node inner = Node::createCollection();+        outer.collection()->push_back(inner);+        inner.collection()->push_back(sseqOrOp);+        extendedNotExpanded.collection()->push_back(outer);+        continue;+      }++      Compound_Selector* pCompoundSelector = sseqOrOp.selector()->head();++      Node extended = extendCompoundSelector(pCompoundSelector, ctx, subsetMap, seen);++      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* pJustCurrentCompoundSelector = sseqOrOp.selector();++      bool isSuperselector = false;+      for (NodeDeque::iterator iterator = extended.collection()->begin(), endIterator = extended.collection()->end();+           iterator != endIterator; ++iterator) {+        Node& childNode = *iterator;+        Complex_Selector* pExtensionSelector = nodeToComplexSelector(childNode, ctx);+        if (pExtensionSelector->is_superselector_of(pJustCurrentCompoundSelector)) {+          isSuperselector = true;+          break;+        }+      }++      if (!isSuperselector) {+        extended.collection()->push_front(complexSelectorToNode(pJustCurrentCompoundSelector, ctx));+      }++      DEBUG_PRINTLN(EXTEND_COMPLEX, "CHOICES UNSHIFTED: " << extended)++      // Aggregate our current extensions+      extendedNotExpanded.collection()->push_back(extended);+    }+++    DEBUG_PRINTLN(EXTEND_COMPLEX, "EXTENDED NOT EXPANDED: " << extendedNotExpanded)++++    // Ruby Equivalent: paths+    Node paths = Sass::paths(extendedNotExpanded, ctx);++    DEBUG_PRINTLN(EXTEND_COMPLEX, "PATHS: " << paths)++++    // Ruby Equivalent: weave+    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);+      weaves.collection()->push_back(weaved);+    }++    DEBUG_PRINTLN(EXTEND_COMPLEX, "WEAVES: " << weaves)++++    // Ruby Equivalent: trim+    Node trimmed = trim(weaves, ctx);++    DEBUG_PRINTLN(EXTEND_COMPLEX, "TRIMMED: " << trimmed)+++    // Ruby Equivalent: flatten+    Node extendedSelectors = flatten(trimmed, ctx, 1);++    DEBUG_PRINTLN(EXTEND_COMPLEX, ">>>>> EXTENDED: " << extendedSelectors)+++    DEBUG_PRINTLN(EXTEND_COMPLEX, "EXTEND COMPLEX END: " << complexSelector)+++    return extendedSelectors;+  }++++  /*+   This is the equivalent of ruby's CommaSequence.do_extend.+  */+  static Selector_List* extendSelectorList(Selector_List* pSelectorList, Context& ctx, ExtensionSubsetMap& subsetMap, bool& extendedSomething) {++    To_String to_string(&ctx);++    Selector_List* pNewSelectors = new (ctx.mem) Selector_List(pSelectorList->pstate(), pSelectorList->length());++    extendedSomething = false;++    for (size_t index = 0, length = pSelectorList->length(); index < length; index++) {+      Complex_Selector* pSelector = (*pSelectorList)[index];++      // ruby sass seems to keep a list of things that have extensions and then only extend those. We don't currently do that.+      // Since it's not that expensive to check if an extension exists in the subset map and since it can be relatively expensive to+      // run through the extend code (which does a data model transformation), check if there is anything to extend before doing+      // the extend. We might be able to optimize extendComplexSelector, but this approach keeps us closer to ruby sass (which helps+      // when debugging).+      if (!complexSelectorHasExtension(pSelector, ctx, subsetMap)) {+        *pNewSelectors << pSelector;+        continue;+      }++      extendedSomething = true;++      set<Compound_Selector> seen;+      Node extendedSelectors = extendComplexSelector(pSelector, ctx, subsetMap, seen);++      if (!pSelector->has_placeholder()) {+        if (!extendedSelectors.contains(complexSelectorToNode(pSelector, ctx), true /*simpleSelectorOrderDependent*/)) {+          *pNewSelectors << pSelector;+        }+      }++      for (NodeDeque::iterator iterator = extendedSelectors.collection()->begin(), iteratorEnd = extendedSelectors.collection()->end(); iterator != iteratorEnd; ++iterator) {+        Node& childNode = *iterator;+        *pNewSelectors << nodeToComplexSelector(childNode, ctx);+      }+    }++    return pNewSelectors;++  }+++  bool shouldExtendBlock(Block* b) {++    // If a block is empty, there's no reason to extend it since any rules placed on this block+    // won't have any output. The main benefit of this is for structures like:+    //+    //    .a {+    //      .b {+    //        x: y;+    //      }+    //    }+    //+    // We end up visiting two rulesets (one with the selector .a and the other with the selector .a .b).+    // In this case, we don't want to try to pull rules onto .a since they won't get output anyway since+    // there are no child statements. However .a .b should have extensions applied.++    for (size_t i = 0, L = b->length(); i < L; ++i) {+      Statement* stm = (*b)[i];++      if (typeid(*stm) == typeid(Ruleset)) {+        // Do nothing. This doesn't count as a statement that causes extension since we'll iterate over this rule set in a future visit and try to extend it.+      }+      else {+        return true;+      }+    }++    return false;++  }+++  // Extend a ruleset by extending the selectors and updating them on the ruleset. The block's rules don't need to change.+  template <typename ObjectType>+  static void extendObjectWithSelectorAndBlock(ObjectType* pObject, Context& ctx, ExtensionSubsetMap& subsetMap) {+    To_String to_string(&ctx);++    DEBUG_PRINTLN(EXTEND_OBJECT, "FOUND SELECTOR: " << static_cast<Selector_List*>(pObject->selector())->perform(&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.+    if (!shouldExtendBlock(pObject->block())) {+      DEBUG_PRINTLN(EXTEND_OBJECT, "RETURNING WITHOUT EXTEND ATTEMPT")+      return;+    }++    bool extendedSomething = false;+    Selector_List* pNewSelectorList = extendSelectorList(static_cast<Selector_List*>(pObject->selector()), ctx, subsetMap, extendedSomething);++    if (extendedSomething && pNewSelectorList) {+      DEBUG_PRINTLN(EXTEND_OBJECT, "EXTEND ORIGINAL SELECTORS: " << static_cast<Selector_List*>(pObject->selector())->perform(&to_string))+      DEBUG_PRINTLN(EXTEND_OBJECT, "EXTEND SETTING NEW SELECTORS: " << pNewSelectorList->perform(&to_string))++      // re-parse in order to restructure expanded placeholder nodes correctly.+      //+      // TODO: I don't know if this is needed, but it was in the original C++ implementation, so I kept it. Try running the tests without re-parsing.+      // this probably messes up source-maps+      pObject->selector(+        Parser::from_c_str(+          (pNewSelectorList->perform(&to_string) + ";").c_str(),+          ctx,+          pNewSelectorList->pstate()+        ).parse_selector_group()+      );+    } else {+      DEBUG_PRINTLN(EXTEND_OBJECT, "EXTEND DID NOT TRY TO EXTEND ANYTHING")+    }+  }++++  Extend::Extend(Context& ctx, ExtensionSubsetMap& ssm)+  : ctx(ctx), subset_map(ssm)+  { }++  void Extend::operator()(Block* b)+  {+    for (size_t i = 0, L = b->length(); i < L; ++i) {+      (*b)[i]->perform(this);+    }+  }++  void Extend::operator()(Ruleset* pRuleset)+  {+    extendObjectWithSelectorAndBlock(pRuleset, ctx, subset_map);++    pRuleset->block()->perform(this);+  }++  void Extend::operator()(Feature_Block* pFeatureBlock)+  {+    if (pFeatureBlock->selector()) {+      extendObjectWithSelectorAndBlock(pFeatureBlock, ctx, subset_map);+    }++    pFeatureBlock->block()->perform(this);+  }++  void Extend::operator()(Media_Block* pMediaBlock)+  {+    if (pMediaBlock->selector()) {+      extendObjectWithSelectorAndBlock(pMediaBlock, ctx, subset_map);+    }++    pMediaBlock->block()->perform(this);+  }++  void Extend::operator()(At_Rule* a)+  {+    if (a->block()) a->block()->perform(this);+  }+}
+ libsass/extend.hpp view
@@ -0,0 +1,45 @@+#ifndef SASS_EXTEND_H+#define SASS_EXTEND_H++#include <map>+#include <set>+#include <vector>+#include <iostream>++#include "ast.hpp"+#include "operation.hpp"+#include "subset_map.hpp"++namespace Sass {+  using namespace std;++  class Context;++  typedef Subset_Map<string, pair<Complex_Selector*, Compound_Selector*> > ExtensionSubsetMap;++  class Extend : public Operation_CRTP<void, Extend> {++    Context&            ctx;+    ExtensionSubsetMap& subset_map;++    void fallback_impl(AST_Node* n) { };++  public:+    Extend(Context&, ExtensionSubsetMap&);+    virtual ~Extend() { }++    using Operation<void>::operator();++    void operator()(Block*);+    void operator()(Ruleset*);+    void operator()(Feature_Block*);+    void operator()(Media_Block*);+    void operator()(At_Rule*);++    template <typename U>+    void fallback(U x) { return fallback_impl(x); }+  };++}++#endif
+ libsass/file.cpp view
@@ -0,0 +1,349 @@+#ifdef _WIN32+#include <direct.h>+#define getcwd _getcwd+#define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR)+#else+#include <unistd.h>+#endif++#include <iostream>+#include <fstream>+#include <cctype>+#include <vector>+#include <algorithm>+#include <sys/stat.h>+#include "file.hpp"+#include "context.hpp"+#include "utf8_string.hpp"+#include "sass2scss.h"++#ifdef _WIN32+#include <windows.h>+#endif++#ifndef FS_CASE_SENSITIVE+#ifdef _WIN32+#define FS_CASE_SENSITIVE 0+#else+#define FS_CASE_SENSITIVE 1+#endif+#endif++namespace Sass {+  namespace File {+    using namespace std;++    // return the current directory+    // always with forward slashes+    string get_cwd()+    {+      const size_t wd_len = 1024;+      char wd[wd_len];+      string cwd = getcwd(wd, wd_len);+      #ifdef _WIN32+        //convert backslashes to forward slashes+        replace(cwd.begin(), cwd.end(), '\\', '/');+      #endif+      if (cwd[cwd.length() - 1] != '/') cwd += '/';+      return cwd;+    }++    // test if path exists and is a file+    bool file_exists(const string& path)+    {+      #ifdef _WIN32+        wstring wpath = UTF_8::convert_to_utf16(path);+        DWORD dwAttrib = GetFileAttributesW(wpath.c_str());+        return (dwAttrib != INVALID_FILE_ATTRIBUTES &&+               (!(dwAttrib & FILE_ATTRIBUTE_DIRECTORY)));+      #else+        struct stat st_buf;+        return (stat (path.c_str(), &st_buf) == 0) &&+               (!S_ISDIR (st_buf.st_mode));+      #endif+    }++    // return if given path is absolute+    // works with *nix and windows paths+    bool is_absolute_path(const string& path)+    {+      #ifdef _WIN32+        if (path.length() >= 2 && isalpha(path[0]) && path[1] == ':') return true;+      #endif+      return path[0] == '/';+    }++    // helper function to find the last directory seperator+    inline size_t find_last_folder_separator(const string& path, size_t limit = string::npos)+    {+      size_t pos = string::npos;+      size_t pos_p = path.find_last_of('/', limit);+      #ifdef _WIN32+        size_t pos_w = path.find_last_of('\\', limit);+      #else+        size_t pos_w = string::npos;+      #endif+      if (pos_p != string::npos && pos_w != string::npos) {+        pos = max(pos_p, pos_w);+      }+      else if (pos_p != string::npos) {+        pos = pos_p;+      }+      else {+        pos = pos_w;+      }+      return pos;+    }++    // return only the directory part of path+    string dir_name(const string& path)+    {+      size_t pos = find_last_folder_separator(path);+      if (pos == string::npos) return "";+      else return path.substr(0, pos+1);+    }++    // return only the filename part of path+    string base_name(const string& path)+    {+      size_t pos = find_last_folder_separator(path);+      if (pos == string::npos) return path;+      else return path.substr(pos+1);+    }++    // do a locigal clean up of the path+    // no physical check on the filesystem+    string make_canonical_path (string path)+    {++      // declarations+      size_t pos;++      #ifdef _WIN32+        //convert backslashes to forward slashes+        replace(path.begin(), path.end(), '\\', '/');+      #endif++      pos = 0; // remove all self references inside the path string+      while((pos = path.find("/./", pos)) != string::npos) path.erase(pos, 2);++      pos = 0; // 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);++      pos = 0; // collapse multiple delimiters into a single one+      while((pos = path.find("//", pos)) != string::npos) path.erase(pos, 1);++      return path;++    }++    // join two path segments cleanly together+    // but only if right side is not absolute yet+    string join_paths(string l, string r)+    {++      #ifdef _WIN32+        // convert Windows backslashes to URL forward slashes+        replace(l.begin(), l.end(), '\\', '/');+        replace(r.begin(), r.end(), '\\', '/');+      #endif++      if (l.empty()) return r;+      if (r.empty()) return l;++      if (is_absolute_path(r)) return r;+      if (l[l.length()-1] != '/') l += '/';++      while ((r.length() > 3) && ((r.substr(0, 3) == "../") || (r.substr(0, 3)) == "..\\")) {+        r = r.substr(3);+        size_t pos = find_last_folder_separator(l, l.length() - 2);+        l = l.substr(0, pos == string::npos ? pos : pos + 1);+      }++      return l + r;+    }++    // create an absolute path by resolving relative paths with cwd+    string make_absolute_path(const string& path, const string& cwd)+    {+      return make_canonical_path((is_absolute_path(path) ? path : join_paths(cwd, path)));+    }++    // create a path that is relative to the given base directory+    // path and base will first be resolved against cwd to make them absolute+    string resolve_relative_path(const string& uri, const string& base, const string& cwd)+    {++      string absolute_uri = make_absolute_path(uri, cwd);+      string absolute_base = make_absolute_path(base, cwd);++      #ifdef _WIN32+        // absolute link must have a drive letter, and we know that we+        // can only create relative links if both are on the same drive+        if (absolute_base[0] != absolute_uri[0]) return absolute_uri;+      #endif++      string stripped_uri = "";+      string stripped_base = "";++      size_t index = 0;+      size_t minSize = min(absolute_uri.size(), absolute_base.size());+      for (size_t i = 0; i < minSize; ++i) {+        #ifdef FS_CASE_SENSITIVE+          if (absolute_uri[i] != absolute_base[i]) break;+        #else+          // compare the charactes in a case insensitive manner+          // windows fs is only case insensitive in ascii ranges+          if (tolower(absolute_uri[i]) != tolower(absolute_base[i])) break;+        #endif+        if (absolute_uri[i] == '/') index = i + 1;+      }+      for (size_t i = index; i < absolute_uri.size(); ++i) {+        stripped_uri += absolute_uri[i];+      }+      for (size_t i = index; i < absolute_base.size(); ++i) {+        stripped_base += absolute_base[i];+      }++      size_t left = 0;+      size_t directories = 0;+      for (size_t right = 0; right < stripped_base.size(); ++right) {+        if (stripped_base[right] == '/') {+          if (stripped_base.substr(left, 2) != "..") {+            ++directories;+          }+          else if (directories > 1) {+            --directories;+          }+          else {+            directories = 0;+          }+          left = right + 1;+        }+      }++      string result = "";+      for (size_t i = 0; i < directories; ++i) {+        result += "../";+      }+      result += stripped_uri;++      return result;+    }++    // Resolution order for ambiguous imports:+    // (1) filename as given+    // (2) underscore + given+    // (3) underscore + given + extension+    // (4) given + extension+    string resolve_file(const string& filename)+    {+      // supported extensions+      const vector<string> exts = {+        ".scss", ".sass", ".css"+      };+      // split the filename+      string base(dir_name(filename));+      string name(base_name(filename));+      // create full path (maybe relative)+      string path(join_paths(base, name));+      if (file_exists(path)) return path;+      // next test variation with underscore+      path = join_paths(base, "_" + name);+      if (file_exists(path)) return path;+      // next test exts plus underscore+      for(auto ext : exts) {+        path = join_paths(base, "_" + name + ext);+        if (file_exists(path)) return path;+      }+      // next test plain name with exts+      for(auto ext : exts) {+        path = join_paths(base, name + ext);+        if (file_exists(path)) return path;+      }+      // nothing found+      return string("");+    }++    // helper function to resolve a filename+    string find_file(const string& file, const vector<string> paths)+    {+      // search in every include path for a match+      for (size_t i = 0, S = paths.size(); i < S; ++i)+      {+        string path(join_paths(paths[i], file));+        string resolved(resolve_file(path));+        if (resolved != "") return resolved;+      }+      // nothing found+      return string("");+    }++    // inc paths can be directly passed from C code+    string find_file(const string& file, const char* paths[])+    {+      if (paths == 0) return string("");+      vector<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+    char* read_file(const string& path)+    {+      #ifdef _WIN32+        BYTE* pBuffer;+        DWORD dwBytes;+        // windows unicode filepaths are encoded in utf16+        wstring wpath = UTF_8::convert_to_utf16(path);+        HANDLE hFile = CreateFileW(wpath.c_str(), 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));+        ReadFile(hFile, pBuffer, dwFileLength, &dwBytes, NULL);+        pBuffer[dwFileLength] = '\0';+        CloseHandle(hFile);+        // just convert from unsigned char*+        char* contents = (char*) pBuffer;+      #else+        struct stat st;+        if (stat(path.c_str(), &st) == -1 || S_ISDIR(st.st_mode)) return 0;+        ifstream file(path.c_str(), ios::in | ios::binary | ios::ate);+        char* contents = 0;+        if (file.is_open()) {+          size_t size = file.tellg();+          // allocate an extra byte for the null char+          contents = (char*) malloc((size+1)*sizeof(char));+          file.seekg(0, ios::beg);+          file.read(contents, size);+          contents[size] = '\0';+          file.close();+        }+      #endif+      string extension;+      if (path.length() > 5) {+        extension = path.substr(path.length() - 5, 5);+      }+      for(size_t i=0; i<extension.size();++i)+        extension[i] = tolower(extension[i]);+      if (extension == ".sass" && contents != 0) {+        char * converted = sass2scss(contents, SASS2SCSS_PRETTIFY_1 | SASS2SCSS_KEEP_COMMENT);+        free(contents); // free the indented contents+        return converted; // should be freed by caller+      } else {+        return contents;+      }+    }++  }+}
+ libsass/file.hpp view
@@ -0,0 +1,60 @@+#ifndef SASS_FILE_H+#define SASS_FILE_H++#include <string>+#include <vector>++namespace Sass {+  using namespace std;+  class Context;+  namespace File {++    // return the current directory+    // always with forward slashes+    string get_cwd();++    // test if path exists and is a file+    bool file_exists(const string& file);++    // return if given path is absolute+    // works with *nix and windows paths+    bool is_absolute_path(const string& path);++    // return only the directory part of path+    string dir_name(const string& path);++    // return only the filename part of path+    string base_name(const string&);++    // do a locigal clean up of the path+    // no physical check on the filesystem+    string make_canonical_path (string path);++    // join two path segments cleanly together+    // but only if right side is not absolute yet+    string join_paths(string root, string name);++    // create an absolute path by resolving relative paths with cwd+    string make_absolute_path(const string& path, const string& cwd = ".");++    // create a path that is relative to the given base directory+    // path and base will first be resolved against cwd to make them absolute+    string resolve_relative_path(const string& path, const string& base, const string& cwd = ".");++    // try to find/resolve the filename+    string resolve_file(const string& file);++    // helper function to resolve a filename+    string find_file(const string& file, const vector<string> paths);+    // inc paths can be directly passed from C code+    string find_file(const string& file, const char** paths);++    // try to load the given filename+    // returned memory must be freed+    // will auto convert .sass files+    char* read_file(const string& file);++  }+}++#endif
+ libsass/functions.cpp view
@@ -0,0 +1,1567 @@+#include "functions.hpp"+#include "ast.hpp"+#include "context.hpp"+#include "backtrace.hpp"+#include "parser.hpp"+#include "constants.hpp"+#include "to_string.hpp"+#include "inspect.hpp"+#include "eval.hpp"+#include "util.hpp"+#include "utf8_string.hpp"+#include "utf8.h"++#include <atomic>+#include <cstdlib>+#include <cmath>+#include <cctype>+#include <sstream>+#include <string>+#include <iomanip>+#include <iostream>+#include <random>+#include <set>++#ifdef __MINGW32__+#include "windows.h"+#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)++namespace Sass {+  using std::stringstream;+  using std::endl;++  Definition* make_native_function(Signature sig, Native_Function func, Context& ctx)+  {+    Parser sig_parser = Parser::from_c_str(sig, ctx, ParserState("[built-in function]"));+    sig_parser.lex<Prelexer::identifier>();+    string name(Util::normalize_underscores(sig_parser.lexed));+    Parameters* params = sig_parser.parse_parameters();+    return new (ctx.mem) Definition(ParserState("[built-in function]"),+                                    sig,+                                    name,+                                    params,+                                    func,+                                    &ctx,+                                    false);+  }++  Definition* make_c_function(Sass_Function_Entry c_func, Context& ctx)+  {+    const char* sig = sass_function_get_signature(c_func);+    Parser sig_parser = Parser::from_c_str(sig, ctx, ParserState("[c function]"));+    // allow to overload generic callback plus @warn, @error and @debug with custom functions+    sig_parser.lex < alternatives < identifier, exactly <'*'>,+                                    exactly < Constants::warn_kwd >,+                                    exactly < Constants::error_kwd >,+                                    exactly < Constants::debug_kwd >+                   >              >();+    string name(Util::normalize_underscores(sig_parser.lexed));+    Parameters* params = sig_parser.parse_parameters();+    return new (ctx.mem) Definition(ParserState("[c function]"),+                                    sig,+                                    name,+                                    params,+                                    c_func,+                                    &ctx,+                                    false, true);+  }++  namespace Functions {++    template <typename T>+    T* get_arg(const string& argname, Env& env, Signature sig, ParserState pstate, Backtrace* backtrace)+    {+      // Minimal error handling -- the expectation is that built-ins will be written correctly!+      T* val = dynamic_cast<T*>(env[argname]);+      if (!val) {+        string msg("argument `");+        msg += argname;+        msg += "` of `";+        msg += sig;+        msg += "` must be a ";+        msg += T::type_name();+        error(msg, pstate, backtrace);+      }+      return val;+    }++    Map* get_arg_m(const string& argname, Env& env, Signature sig, ParserState pstate, Backtrace* backtrace, Context& ctx)+    {+      // Minimal error handling -- the expectation is that built-ins will be written correctly!+      Map* val = dynamic_cast<Map*>(env[argname]);+      if (val) return val;++      List* lval = dynamic_cast<List*>(env[argname]);+      if (lval && lval->length() == 0) return new (ctx.mem) Map(pstate, 0);++      // fallback on get_arg for error handling+      val = get_arg<Map>(argname, env, sig, pstate, backtrace);+      return val;+    }++    Number* get_arg_r(const string& argname, Env& env, Signature sig, ParserState pstate, double lo, double hi, Backtrace* backtrace)+    {+      // Minimal error handling -- the expectation is that built-ins will be written correctly!+      Number* val = get_arg<Number>(argname, env, sig, pstate, backtrace);+      double v = val->value();+      if (!(lo <= v && v <= hi)) {+        stringstream msg;+        msg << "argument `" << argname << "` of `" << sig << "` must be between ";+        msg << lo << " and " << hi;+        error(msg.str(), pstate, backtrace);+      }+      return val;+    }++#ifdef __MINGW32__+    uint64_t GetSeed()+    {+      HCRYPTPROV hp = 0;+      BYTE rb[8];+      CryptAcquireContext(&hp, 0, 0, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT);+      CryptGenRandom(hp, sizeof(rb), rb);+      CryptReleaseContext(hp, 0);++      uint64_t seed;+      memcpy(&seed, &rb[0], sizeof(seed));++      return seed;+    }+#else+    static random_device rd;+    uint64_t GetSeed()+    {+	  return rd();+	}+#endif++    // note: the performance of many  implementations of+    // random_device degrades sharply once the entropy pool+    // is exhausted. For practical use, random_device is+    // generally only used to seed a PRNG such as mt19937.+    static mt19937 rand(static_cast<unsigned int>(GetSeed()));++    // features+    static set<string> features {+      "global-variable-shadowing",+      "at-error",+      "units-level-3"+    };++    ////////////////+    // RGB FUNCTIONS+    ////////////////++    inline double color_num(Number* n) {+      if (n->unit() == "%") {+        return std::min(std::max(n->value(), 0.0), 1.0) * 255;+      } else {+        return std::min(std::max(n->value(), 0.0), 255.0);+      }+    }++    Signature rgb_sig = "rgb($red, $green, $blue)";+    BUILT_IN(rgb)+    {+      return new (ctx.mem) Color(pstate,+                                 color_num(ARGR("$red",   Number, 0, 255)),+                                 color_num(ARGR("$green", Number, 0, 255)),+                                 color_num(ARGR("$blue",  Number, 0, 255)));+    }++    Signature rgba_4_sig = "rgba($red, $green, $blue, $alpha)";+    BUILT_IN(rgba_4)+    {+      return new (ctx.mem) Color(pstate,+                                 color_num(ARGR("$red",   Number, 0, 255)),+                                 color_num(ARGR("$green", Number, 0, 255)),+                                 color_num(ARGR("$blue",  Number, 0, 255)),+                                 ARGR("$alpha", Number, 0, 1)->value());+    }++    Signature rgba_2_sig = "rgba($color, $alpha)";+    BUILT_IN(rgba_2)+    {+      Color* c_arg = ARG("$color", Color);+      Color* new_c = new (ctx.mem) Color(*c_arg);+      new_c->a(ARGR("$alpha", Number, 0, 1)->value());+      new_c->disp("");+      return new_c;+    }++    Signature red_sig = "red($color)";+    BUILT_IN(red)+    { return new (ctx.mem) Number(pstate, ARG("$color", Color)->r()); }++    Signature green_sig = "green($color)";+    BUILT_IN(green)+    { return new (ctx.mem) Number(pstate, ARG("$color", Color)->g()); }++    Signature blue_sig = "blue($color)";+    BUILT_IN(blue)+    { return new (ctx.mem) Number(pstate, ARG("$color", Color)->b()); }++    Signature mix_sig = "mix($color-1, $color-2, $weight: 50%)";+    BUILT_IN(mix)+    {+      Color*  color1 = ARG("$color-1", Color);+      Color*  color2 = ARG("$color-2", Color);+      Number* weight = ARGR("$weight", Number, 0, 100);++      double p = weight->value()/100;+      double w = 2*p - 1;+      double a = color1->a() - color2->a();++      double w1 = (((w * a == -1) ? w : (w + a)/(1 + w*a)) + 1)/2.0;+      double w2 = 1 - w1;++      return new (ctx.mem) Color(pstate,+                                 std::floor(w1*color1->r() + w2*color2->r()),+                                 std::floor(w1*color1->g() + w2*color2->g()),+                                 std::floor(w1*color1->b() + w2*color2->b()),+                                 color1->a()*p + color2->a()*(1-p));+    }++    ////////////////+    // HSL FUNCTIONS+    ////////////////++    // RGB to HSL helper function+    struct HSL { double h; double s; double l; };+    HSL rgb_to_hsl(double r, double g, double b)+    {++      // Algorithm from http://en.wikipedia.org/wiki/wHSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV+      r /= 255.0; g /= 255.0; b /= 255.0;++      double max = std::max(r, std::max(g, b));+      double min = std::min(r, std::min(g, b));+      double del = max - min;++      double h = 0, s = 0, l = (max + min) / 2.0;++      if (max == min) {+        h = s = 0; // achromatic+      }+      else {+        if (l < 0.5) s = del / (2.0 * l);+        else         s = del / (2.0 - 2.0 * l);++        if      (r == max) h = 60 * (g - b) / del;+        else if (g == max) h = 60 * (b - r) / del + 120;+        else if (b == max) h = 60 * (r - g) / del + 240;+      }++      HSL hsl_struct;+      hsl_struct.h = h;+      hsl_struct.s = s * 100;+      hsl_struct.l = l * 100;++      return hsl_struct;+    }++    // hue to RGB helper function+    double h_to_rgb(double m1, double m2, double h) {+      if (h < 0) h += 1;+      if (h > 1) h -= 1;+      if (h*6.0 < 1) return m1 + (m2 - m1)*h*6;+      if (h*2.0 < 1) return m2;+      if (h*3.0 < 2) return m1 + (m2 - m1) * (2.0/3.0 - h)*6;+      return m1;+    }++    Color* hsla_impl(double h, double s, double l, double a, Context& ctx, ParserState pstate)+    {+      h /= 360.0;+      s /= 100.0;+      l /= 100.0;++      // Algorithm from the CSS3 spec: http://www.w3.org/TR/css3-color/#hsl-color.+      double m2;+      if (l <= 0.5) m2 = l*(s+1.0);+      else m2 = (l+s)-(l*s);+      double m1 = (l*2)-m2;+      // round the results -- consider moving this into the Color constructor+      double r = (h_to_rgb(m1, m2, h+1.0/3.0) * 255.0);+      double g = (h_to_rgb(m1, m2, h) * 255.0);+      double b = (h_to_rgb(m1, m2, h-1.0/3.0) * 255.0);++      return new (ctx.mem) Color(pstate, r, g, b, a);+    }++    Signature hsl_sig = "hsl($hue, $saturation, $lightness)";+    BUILT_IN(hsl)+    {+      return hsla_impl(ARG("$hue", Number)->value(),+                       ARGR("$saturation", Number, 0, 100)->value(),+                       ARGR("$lightness", Number, 0, 100)->value(),+                       1.0,+                       ctx,+                       pstate);+    }++    Signature hsla_sig = "hsla($hue, $saturation, $lightness, $alpha)";+    BUILT_IN(hsla)+    {+      return hsla_impl(ARG("$hue", Number)->value(),+                       ARGR("$saturation", Number, 0, 100)->value(),+                       ARGR("$lightness", Number, 0, 100)->value(),+                       ARGR("$alpha", Number, 0, 1)->value(),+                       ctx,+                       pstate);+    }++    Signature hue_sig = "hue($color)";+    BUILT_IN(hue)+    {+      Color* rgb_color = ARG("$color", Color);+      HSL hsl_color = rgb_to_hsl(rgb_color->r(),+                                 rgb_color->g(),+                                 rgb_color->b());+      return new (ctx.mem) Number(pstate, hsl_color.h, "deg");+    }++    Signature saturation_sig = "saturation($color)";+    BUILT_IN(saturation)+    {+      Color* rgb_color = ARG("$color", Color);+      HSL hsl_color = rgb_to_hsl(rgb_color->r(),+                                 rgb_color->g(),+                                 rgb_color->b());+      return new (ctx.mem) Number(pstate, hsl_color.s, "%");+    }++    Signature lightness_sig = "lightness($color)";+    BUILT_IN(lightness)+    {+      Color* rgb_color = ARG("$color", Color);+      HSL hsl_color = rgb_to_hsl(rgb_color->r(),+                                 rgb_color->g(),+                                 rgb_color->b());+      return new (ctx.mem) Number(pstate, hsl_color.l, "%");+    }++    Signature adjust_hue_sig = "adjust-hue($color, $degrees)";+    BUILT_IN(adjust_hue)+    {+      Color* rgb_color = ARG("$color", Color);+      Number* degrees = ARG("$degrees", Number);+      HSL hsl_color = rgb_to_hsl(rgb_color->r(),+                                 rgb_color->g(),+                                 rgb_color->b());+      return hsla_impl(hsl_color.h + degrees->value(),+                       hsl_color.s,+                       hsl_color.l,+                       rgb_color->a(),+                       ctx,+                       pstate);+    }++    Signature lighten_sig = "lighten($color, $amount)";+    BUILT_IN(lighten)+    {+      Color* rgb_color = ARG("$color", Color);+      Number* amount = ARGR("$amount", Number, 0, 100);+      HSL hsl_color = rgb_to_hsl(rgb_color->r(),+                                 rgb_color->g(),+                                 rgb_color->b());+      //Check lightness is not negative before lighten it+      double hslcolorL = hsl_color.l;+      if (hslcolorL < 0) {+        hslcolorL = 0;+      }++      return hsla_impl(hsl_color.h,+                       hsl_color.s,+                       hslcolorL + amount->value(),+                       rgb_color->a(),+                       ctx,+                       pstate);+    }++    Signature darken_sig = "darken($color, $amount)";+    BUILT_IN(darken)+    {+      Color* rgb_color = ARG("$color", Color);+      Number* amount = ARGR("$amount", Number, 0, 100);+      HSL hsl_color = rgb_to_hsl(rgb_color->r(),+                                 rgb_color->g(),+                                 rgb_color->b());++      //Check lightness if not over 100, before darken it+      double hslcolorL = hsl_color.l;+      if (hslcolorL > 100) {+        hslcolorL = 100;+      }++      return hsla_impl(hsl_color.h,+                       hsl_color.s,+                       hslcolorL - amount->value(),+                       rgb_color->a(),+                       ctx,+                       pstate);+    }++    Signature saturate_sig = "saturate($color, $amount: false)";+    BUILT_IN(saturate)+    {+      // CSS3 filter function overload: pass literal through directly+      Number* amount = dynamic_cast<Number*>(env["$amount"]);+      if (!amount) {+        To_String to_string(&ctx);+        return new (ctx.mem) String_Constant(pstate, "saturate(" + env["$color"]->perform(&to_string) + ")");+      }++      ARGR("$amount", Number, 0, 100);+      Color* 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();++      // Saturation cannot be below 0 or above 100+      if (hslcolorS < 0) {+        hslcolorS = 0;+      }+      if (hslcolorS > 100) {+        hslcolorS = 100;+      }++      return hsla_impl(hsl_color.h,+                       hslcolorS,+                       hsl_color.l,+                       rgb_color->a(),+                       ctx,+                       pstate);+    }++    Signature desaturate_sig = "desaturate($color, $amount)";+    BUILT_IN(desaturate)+    {+      Color* rgb_color = ARG("$color", Color);+      Number* amount = ARGR("$amount", Number, 0, 100);+      HSL hsl_color = rgb_to_hsl(rgb_color->r(),+                                 rgb_color->g(),+                                 rgb_color->b());++      double hslcolorS = hsl_color.s - amount->value();++      // Saturation cannot be below 0 or above 100+      if (hslcolorS <= 0) {+        hslcolorS = 0;+      }+      if (hslcolorS > 100) {+        hslcolorS = 100;+      }++      return hsla_impl(hsl_color.h,+                       hslcolorS,+                       hsl_color.l,+                       rgb_color->a(),+                       ctx,+                       pstate);+    }++    Signature grayscale_sig = "grayscale($color)";+    BUILT_IN(grayscale)+    {+      // CSS3 filter function overload: pass literal through directly+      Number* amount = dynamic_cast<Number*>(env["$color"]);+      if (amount) {+        To_String to_string(&ctx);+        return new (ctx.mem) String_Constant(pstate, "grayscale(" + amount->perform(&to_string) + ")");+      }++      Color* rgb_color = ARG("$color", Color);+      HSL hsl_color = rgb_to_hsl(rgb_color->r(),+                                 rgb_color->g(),+                                 rgb_color->b());+      return hsla_impl(hsl_color.h,+                       0.0,+                       hsl_color.l,+                       rgb_color->a(),+                       ctx,+                       pstate);+    }++    Signature complement_sig = "complement($color)";+    BUILT_IN(complement)+    {+      Color* rgb_color = ARG("$color", Color);+      HSL hsl_color = rgb_to_hsl(rgb_color->r(),+                                 rgb_color->g(),+                                 rgb_color->b());+      return hsla_impl(hsl_color.h - 180.0,+                       hsl_color.s,+                       hsl_color.l,+                       rgb_color->a(),+                       ctx,+                       pstate);+    }++    Signature invert_sig = "invert($color)";+    BUILT_IN(invert)+    {+      // CSS3 filter function overload: pass literal through directly+      Number* amount = dynamic_cast<Number*>(env["$color"]);+      if (amount) {+        To_String to_string(&ctx);+        return new (ctx.mem) String_Constant(pstate, "invert(" + amount->perform(&to_string) + ")");+      }++      Color* rgb_color = ARG("$color", Color);+      return new (ctx.mem) Color(pstate,+                                 255 - rgb_color->r(),+                                 255 - rgb_color->g(),+                                 255 - rgb_color->b(),+                                 rgb_color->a());+    }++    ////////////////////+    // OPACITY FUNCTIONS+    ////////////////////+    Signature alpha_sig = "alpha($color)";+    Signature opacity_sig = "opacity($color)";+    BUILT_IN(alpha)+    {+      String_Constant* ie_kwd = dynamic_cast<String_Constant*>(env["$color"]);+      if (ie_kwd) {+        return new (ctx.mem) String_Constant(pstate, "alpha(" + ie_kwd->value() + ")");+      }++      // CSS3 filter function overload: pass literal through directly+      Number* amount = dynamic_cast<Number*>(env["$color"]);+      if (amount) {+        To_String to_string(&ctx);+        return new (ctx.mem) String_Constant(pstate, "opacity(" + amount->perform(&to_string) + ")");+      }++      return new (ctx.mem) Number(pstate, ARG("$color", Color)->a());+    }++    Signature opacify_sig = "opacify($color, $amount)";+    Signature fade_in_sig = "fade-in($color, $amount)";+    BUILT_IN(opacify)+    {+      Color* color = ARG("$color", Color);+      double amount = ARGR("$amount", Number, 0, 1)->value();+      double alpha = std::min(color->a() + amount, 1.0);+      return new (ctx.mem) Color(pstate,+                                 color->r(),+                                 color->g(),+                                 color->b(),+                                 alpha);+    }++    Signature transparentize_sig = "transparentize($color, $amount)";+    Signature fade_out_sig = "fade-out($color, $amount)";+    BUILT_IN(transparentize)+    {+      Color* color = ARG("$color", Color);+      double amount = ARGR("$amount", Number, 0, 1)->value();+      double alpha = std::max(color->a() - amount, 0.0);+      return new (ctx.mem) Color(pstate,+                                 color->r(),+                                 color->g(),+                                 color->b(),+                                 alpha);+    }++    ////////////////////////+    // OTHER COLOR FUNCTIONS+    ////////////////////////++    Signature adjust_color_sig = "adjust-color($color, $red: false, $green: false, $blue: false, $hue: false, $saturation: false, $lightness: false, $alpha: false)";+    BUILT_IN(adjust_color)+    {+      Color* color = ARG("$color", Color);+      Number* r = dynamic_cast<Number*>(env["$red"]);+      Number* g = dynamic_cast<Number*>(env["$green"]);+      Number* b = dynamic_cast<Number*>(env["$blue"]);+      Number* h = dynamic_cast<Number*>(env["$hue"]);+      Number* s = dynamic_cast<Number*>(env["$saturation"]);+      Number* l = dynamic_cast<Number*>(env["$lightness"]);+      Number* a = dynamic_cast<Number*>(env["$alpha"]);++      bool rgb = r || g || b;+      bool hsl = h || s || l;++      if (rgb && hsl) {+        error("cannot specify both RGB and HSL values for `adjust-color`", pstate);+      }+      if (rgb) {+        return new (ctx.mem) Color(pstate,+                                   color->r() + (r ? r->value() : 0),+                                   color->g() + (g ? g->value() : 0),+                                   color->b() + (b ? b->value() : 0),+                                   color->a() + (a ? a->value() : 0));+      }+      if (hsl) {+        HSL hsl_struct = rgb_to_hsl(color->r(), color->g(), color->b());+        return hsla_impl(hsl_struct.h + (h ? h->value() : 0),+                         hsl_struct.s + (s ? s->value() : 0),+                         hsl_struct.l + (l ? l->value() : 0),+                         color->a() + (a ? a->value() : 0),+                         ctx,+                         pstate);+      }+      if (a) {+        return new (ctx.mem) Color(pstate,+                                   color->r(),+                                   color->g(),+                                   color->b(),+                                   color->a() + (a ? a->value() : 0));+      }+      error("not enough arguments for `adjust-color`", pstate);+      // unreachable+      return color;+    }++    Signature scale_color_sig = "scale-color($color, $red: false, $green: false, $blue: false, $hue: false, $saturation: false, $lightness: false, $alpha: false)";+    BUILT_IN(scale_color)+    {+      Color* color = ARG("$color", Color);+      Number* r = dynamic_cast<Number*>(env["$red"]);+      Number* g = dynamic_cast<Number*>(env["$green"]);+      Number* b = dynamic_cast<Number*>(env["$blue"]);+      Number* h = dynamic_cast<Number*>(env["$hue"]);+      Number* s = dynamic_cast<Number*>(env["$saturation"]);+      Number* l = dynamic_cast<Number*>(env["$lightness"]);+      Number* a = dynamic_cast<Number*>(env["$alpha"]);++      bool rgb = r || g || b;+      bool hsl = h || s || l;++      if (rgb && hsl) {+        error("cannot specify both RGB and HSL values for `scale-color`", pstate);+      }+      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;+        return new (ctx.mem) Color(pstate,+                                   color->r() + rscale * (rscale > 0.0 ? 255 - color->r() : color->r()),+                                   color->g() + gscale * (gscale > 0.0 ? 255 - color->g() : color->g()),+                                   color->b() + bscale * (bscale > 0.0 ? 255 - color->b() : color->b()),+                                   color->a() + ascale * (ascale > 0.0 ? 1.0 - color->a() : color->a()));+      }+      if (hsl) {+        double hscale = (h ? 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;+        HSL hsl_struct = rgb_to_hsl(color->r(), color->g(), color->b());+        hsl_struct.h += hscale * (hscale > 0.0 ? 360.0 - hsl_struct.h : hsl_struct.h);+        hsl_struct.s += sscale * (sscale > 0.0 ? 100.0 - hsl_struct.s : hsl_struct.s);+        hsl_struct.l += lscale * (lscale > 0.0 ? 100.0 - hsl_struct.l : hsl_struct.l);+        double alpha = color->a() + ascale * (ascale > 0.0 ? 1.0 - color->a() : color->a());+        return hsla_impl(hsl_struct.h, hsl_struct.s, hsl_struct.l, alpha, ctx, pstate);+      }+      if (a) {+        double ascale = (a ? ARGR("$alpha", Number, -100.0, 100.0)->value() : 0.0) / 100.0;+        return new (ctx.mem) Color(pstate,+                                   color->r(),+                                   color->g(),+                                   color->b(),+                                   color->a() + ascale * (ascale > 0.0 ? 1.0 - color->a() : color->a()));+      }+      error("not enough arguments for `scale-color`", pstate);+      // unreachable+      return color;+    }++    Signature change_color_sig = "change-color($color, $red: false, $green: false, $blue: false, $hue: false, $saturation: false, $lightness: false, $alpha: false)";+    BUILT_IN(change_color)+    {+      Color* color = ARG("$color", Color);+      Number* r = dynamic_cast<Number*>(env["$red"]);+      Number* g = dynamic_cast<Number*>(env["$green"]);+      Number* b = dynamic_cast<Number*>(env["$blue"]);+      Number* h = dynamic_cast<Number*>(env["$hue"]);+      Number* s = dynamic_cast<Number*>(env["$saturation"]);+      Number* l = dynamic_cast<Number*>(env["$lightness"]);+      Number* a = dynamic_cast<Number*>(env["$alpha"]);++      bool rgb = r || g || b;+      bool hsl = h || s || l;++      if (rgb && hsl) {+        error("cannot specify both RGB and HSL values for `change-color`", pstate);+      }+      if (rgb) {+        return new (ctx.mem) 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());+      }+      if (hsl) {+        HSL hsl_struct = rgb_to_hsl(color->r(), color->g(), color->b());+        if (h) hsl_struct.h = static_cast<double>(((static_cast<int>(h->value()) % 360) + 360) % 360) / 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();+        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();+        return new (ctx.mem) Color(pstate,+                                   color->r(),+                                   color->g(),+                                   color->b(),+                                   alpha);+      }+      error("not enough arguments for `change-color`", pstate);+      // unreachable+      return color;+    }++    template <size_t range>+    static double cap_channel(double c) {+      if      (c > range) return range;+      else if (c < 0)     return 0;+      else                return c;+    }++    Signature ie_hex_str_sig = "ie-hex-str($color)";+    BUILT_IN(ie_hex_str)+    {+      Color* c = ARG("$color", Color);+      double r = cap_channel<0xff>(c->r());+      double g = cap_channel<0xff>(c->g());+      double b = cap_channel<0xff>(c->b());+      double a = cap_channel<1>   (c->a()) * 255;++      stringstream ss;+      ss << '#' << std::setw(2) << std::setfill('0');+      ss << std::hex << std::setw(2) << static_cast<unsigned long>(std::floor(a+0.5));+      ss << std::hex << std::setw(2) << static_cast<unsigned long>(std::floor(r+0.5));+      ss << std::hex << std::setw(2) << static_cast<unsigned long>(std::floor(g+0.5));+      ss << std::hex << std::setw(2) << static_cast<unsigned long>(std::floor(b+0.5));++      string result(ss.str());+      for (size_t i = 0, L = result.length(); i < L; ++i) {+        result[i] = std::toupper(result[i]);+      }+      return new (ctx.mem) String_Constant(pstate, result);+    }++    ///////////////////+    // STRING FUNCTIONS+    ///////////////////++    Signature unquote_sig = "unquote($string)";+    BUILT_IN(sass_unquote)+    {+      To_String to_string(&ctx);+      AST_Node* arg = env["$string"];+      if (String_Quoted* string_quoted = dynamic_cast<String_Quoted*>(arg)) {+        String_Constant* result = new (ctx.mem) String_Constant(pstate, string_quoted->value());+        // remember if the string was quoted (color tokens)+        result->sass_fix_1291(string_quoted->quote_mark() != 0);+        return result;+      }+      return new (ctx.mem) String_Constant(pstate, string(arg->perform(&to_string)));+    }++    Signature quote_sig = "quote($string)";+    BUILT_IN(sass_quote)+    {+      To_String to_string(&ctx);+      AST_Node* arg = env["$string"];+      string str(quote(arg->perform(&to_string), String_Constant::double_quote()));+      String_Constant* result = new (ctx.mem) String_Constant(pstate, str);+      result->is_delayed(true);+      return result;+    }+++    Signature str_length_sig = "str-length($string)";+    BUILT_IN(str_length)+    {+      size_t len = string::npos;+      try {+        String_Constant* s = ARG("$string", String_Constant);+        len = UTF_8::code_point_count(s->value(), 0, s->value().size());++      }+      catch (utf8::invalid_code_point) {+        string msg("utf8::invalid_code_point");+        error(msg, pstate, backtrace);+      }+      catch (utf8::not_enough_room) {+        string msg("utf8::not_enough_room");+        error(msg, pstate, backtrace);+      }+      catch (utf8::invalid_utf8) {+        string msg("utf8::invalid_utf8");+        error(msg, pstate, backtrace);+      }+      catch (...) { throw; }+      // return something even if we had an error (-1)+      return new (ctx.mem) Number(pstate, len);+    }++    Signature str_insert_sig = "str-insert($string, $insert, $index)";+    BUILT_IN(str_insert)+    {+      string str;+      try {+        String_Constant* s = ARG("$string", String_Constant);+        str = s->value();+        str = unquote(str);+        String_Constant* i = ARG("$insert", String_Constant);+        string ins = i->value();+        ins = unquote(ins);+        Number* ind = ARG("$index", Number);+        double index = ind->value();+        size_t len = UTF_8::code_point_count(str, 0, str.size());++        if (index > 0 && index <= len) {+          // positive and within string length+          str.insert(UTF_8::offset_at_position(str, static_cast<size_t>(index) - 1), ins);+        }+        else if (index > len) {+          // positive and past string length+          str += ins;+        }+        else if (index == 0) {+          str = ins + str;+        }+        else if (std::abs(index) <= len) {+          // negative and within string length+          index += len + 1;+          str.insert(UTF_8::offset_at_position(str, static_cast<size_t>(index)), ins);+        }+        else {+          // negative and past string length+          str = ins + str;+        }++        if (String_Quoted* ss = dynamic_cast<String_Quoted*>(s)) {+          if (ss->quote_mark()) str = quote(str);+        }+      }+      catch (utf8::invalid_code_point) {+        string msg("utf8::invalid_code_point");+        error(msg, pstate, backtrace);+      }+      catch (utf8::not_enough_room) {+        string msg("utf8::not_enough_room");+        error(msg, pstate, backtrace);+      }+      catch (utf8::invalid_utf8) {+        string msg("utf8::invalid_utf8");+        error(msg, pstate, backtrace);+      }+      catch (...) { throw; }+      return new (ctx.mem) String_Constant(pstate, str);+    }++    Signature str_index_sig = "str-index($string, $substring)";+    BUILT_IN(str_index)+    {+      size_t index = string::npos;+      try {+        String_Constant* s = ARG("$string", String_Constant);+        String_Constant* t = ARG("$substring", String_Constant);+        string str = s->value();+        str = unquote(str);+        string substr = t->value();+        substr = unquote(substr);++        size_t c_index = str.find(substr);+        if(c_index == string::npos) {+          return new (ctx.mem) Null(pstate);+        }+        index = UTF_8::code_point_count(str, 0, c_index) + 1;+      }+      catch (utf8::invalid_code_point) {+        string msg("utf8::invalid_code_point");+        error(msg, pstate, backtrace);+      }+      catch (utf8::not_enough_room) {+        string msg("utf8::not_enough_room");+        error(msg, pstate, backtrace);+      }+      catch (utf8::invalid_utf8) {+        string msg("utf8::invalid_utf8");+        error(msg, pstate, backtrace);+      }+      catch (...) { throw; }+      // return something even if we had an error (-1)+      return new (ctx.mem) Number(pstate, index);+    }++    Signature str_slice_sig = "str-slice($string, $start-at, $end-at:-1)";+    BUILT_IN(str_slice)+    {+      string newstr;+      try {+        String_Constant* s = ARG("$string", String_Constant);+        Number* n = ARG("$start-at", Number);+        Number* m = ARG("$end-at", Number);++        string str = unquote(s->value());++        // normalize into 0-based indices+        size_t start = UTF_8::offset_at_position(str, UTF_8::normalize_index(static_cast<int>(n->value()), UTF_8::code_point_count(str)));+        size_t end = UTF_8::offset_at_position(str, UTF_8::normalize_index(static_cast<int>(m->value()), UTF_8::code_point_count(str)));++        // `str-slice` should always return an empty string when $end-at == 0+        // `normalize_index` normalizes 1 -> 0 so we need to check the original value+        if(m->value() == 0) {+          if (String_Quoted* ss = dynamic_cast<String_Quoted*>(s)) {+            if(!ss->quote_mark()) return new (ctx.mem) Null(pstate);+          } else {+            return new (ctx.mem) Null(pstate);+          }+          newstr = "";+        } else if(start == end && m->value() != 0) {+          newstr = str.substr(start, 1);+        } else if(end > start) {+          newstr = str.substr(start, end - start + UTF_8::code_point_size_at_offset(str, end));+        }+        if (String_Quoted* ss = dynamic_cast<String_Quoted*>(s)) {+          if(ss->quote_mark()) newstr = quote(newstr);+        }+      }+      catch (utf8::invalid_code_point) {+        string msg("utf8::invalid_code_point");+        error(msg, pstate, backtrace);+      }+      catch (utf8::not_enough_room) {+        string msg("utf8::not_enough_room");+        error(msg, pstate, backtrace);+      }+      catch (utf8::invalid_utf8) {+        string msg("utf8::invalid_utf8");+        error(msg, pstate, backtrace);+      }+      catch (...) { throw; }+      return new (ctx.mem) String_Quoted(pstate, newstr);+    }++    Signature to_upper_case_sig = "to-upper-case($string)";+    BUILT_IN(to_upper_case)+    {+      String_Constant* s = ARG("$string", String_Constant);+      string str = s->value();++      for (size_t i = 0, L = str.length(); i < L; ++i) {+        if (Sass::Util::isAscii(str[i])) {+          str[i] = std::toupper(str[i]);+        }+      }++      if (String_Quoted* ss = dynamic_cast<String_Quoted*>(s)) {+        str = ss->quote_mark() ? quote(str) : str;+      }+      return new (ctx.mem) String_Constant(pstate, str);+    }++    Signature to_lower_case_sig = "to-lower-case($string)";+    BUILT_IN(to_lower_case)+    {+      String_Constant* s = ARG("$string", String_Constant);+      string str = s->value();++      for (size_t i = 0, L = str.length(); i < L; ++i) {+        if (Sass::Util::isAscii(str[i])) {+          str[i] = std::tolower(str[i]);+        }+      }++      if (String_Quoted* ss = dynamic_cast<String_Quoted*>(s)) {+        str = ss->quote_mark() ? quote(str, '"') : str;+      }+      return new (ctx.mem) String_Constant(pstate, str);+    }++    ///////////////////+    // NUMBER FUNCTIONS+    ///////////////////++    Signature percentage_sig = "percentage($number)";+    BUILT_IN(percentage)+    {+      Number* n = ARG("$number", Number);+      if (!n->is_unitless()) error("argument $number of `" + string(sig) + "` must be unitless", pstate);+      return new (ctx.mem) Number(pstate, n->value() * 100, "%");+    }++    Signature round_sig = "round($number)";+    BUILT_IN(round)+    {+      Number* n = ARG("$number", Number);+      Number* r = new (ctx.mem) Number(*n);+      r->pstate(pstate);+      r->value(std::floor(r->value() + 0.5));+      return r;+    }++    Signature ceil_sig = "ceil($number)";+    BUILT_IN(ceil)+    {+      Number* n = ARG("$number", Number);+      Number* r = new (ctx.mem) Number(*n);+      r->pstate(pstate);+      r->value(std::ceil(r->value()));+      return r;+    }++    Signature floor_sig = "floor($number)";+    BUILT_IN(floor)+    {+      Number* n = ARG("$number", Number);+      Number* r = new (ctx.mem) Number(*n);+      r->pstate(pstate);+      r->value(std::floor(r->value()));+      return r;+    }++    Signature abs_sig = "abs($number)";+    BUILT_IN(abs)+    {+      Number* n = ARG("$number", Number);+      Number* r = new (ctx.mem) Number(*n);+      r->pstate(pstate);+      r->value(std::abs(r->value()));+      return r;+    }++    Signature min_sig = "min($numbers...)";+    BUILT_IN(min)+    {+      List* arglist = ARG("$numbers", List);+      Number* least = 0;+      for (size_t i = 0, L = arglist->length(); i < L; ++i) {+        Number* xi = dynamic_cast<Number*>(arglist->value_at_index(i));+        if (least) {+          if (!xi) error("`" + string(sig) + "` only takes numeric arguments", pstate);+          if (lt(xi, least, ctx)) least = xi;+        } else least = xi;+      }+      return least;+    }++    Signature max_sig = "max($numbers...)";+    BUILT_IN(max)+    {+      List* arglist = ARG("$numbers", List);+      Number* greatest = 0;+      for (size_t i = 0, L = arglist->length(); i < L; ++i) {+        Number* xi = dynamic_cast<Number*>(arglist->value_at_index(i));+        if (greatest) {+          if (!xi) error("`" + string(sig) + "` only takes numeric arguments", pstate);+          if (lt(greatest, xi, ctx)) greatest = xi;+        } else greatest = xi;+      }+      return greatest;+    }++    Signature random_sig = "random($limit:false)";+    BUILT_IN(random)+    {+      Number* l = dynamic_cast<Number*>(env["$limit"]);+      if (l && trunc(l->value()) != l->value()) error("argument $limit of `" + string(sig) + "` must be an integer", pstate);+      if (l) {+        uniform_real_distribution<> distributor(1, l->value() + 1);+        uint_fast32_t distributed = static_cast<uint_fast32_t>(distributor(rand));+        return new (ctx.mem) Number(pstate, (double)distributed);+      }+      else {+        uniform_real_distribution<> distributor(0, 1);+        uint_fast32_t distributed = static_cast<uint_fast32_t>(distributor(rand));+        return new (ctx.mem) Number(pstate, trunc(distributed));+     }+    }++    /////////////////+    // LIST FUNCTIONS+    /////////////////++    Signature length_sig = "length($list)";+    BUILT_IN(length)+    {+      Expression* v = ARG("$list", Expression);+      if (v->concrete_type() == Expression::MAP) {+        Map* map = dynamic_cast<Map*>(env["$list"]);+        return new (ctx.mem) Number(pstate,+                                    map ? map->length() : 1);+      }++      List* list = dynamic_cast<List*>(env["$list"]);+      return new (ctx.mem) Number(pstate,+                                  list ? list->length() : 1);+    }++    Signature nth_sig = "nth($list, $n)";+    BUILT_IN(nth)+    {+      Map* m = dynamic_cast<Map*>(env["$list"]);+      List* l = dynamic_cast<List*>(env["$list"]);+      Number* n = ARG("$n", Number);+      if (n->value() == 0) error("argument `$n` of `" + string(sig) + "` must be non-zero", pstate);+      // if the argument isn't a list, then wrap it in a singleton list+      if (!m && !l) {+        l = new (ctx.mem) List(pstate, 1);+        *l << ARG("$list", Expression);+      }+      size_t len = m ? m->length() : l->length();+      bool empty = m ? m->empty() : l->empty();+      if (empty) error("argument `$list` of `" + 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 `" + string(sig) + "`", pstate);++      if (m) {+        l = new (ctx.mem) List(pstate, 1);+        *l << m->keys()[static_cast<unsigned int>(index)];+        *l << m->at(m->keys()[static_cast<unsigned int>(index)]);+        return l;+      }+      else {+        return l->value_at_index(static_cast<int>(index));+      }+    }++    Signature set_nth_sig = "set-nth($list, $n, $value)";+    BUILT_IN(set_nth)+    {+      List* l = dynamic_cast<List*>(env["$list"]);+      Number* n = ARG("$n", Number);+      Expression* v = ARG("$value", Expression);+      if (!l) {+        l = new (ctx.mem) List(pstate, 1);+        *l << ARG("$list", Expression);+      }+      if (l->empty()) error("argument `$list` of `" + string(sig) + "` must not be empty", pstate);+      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 `" + string(sig) + "`", pstate);+      List* result = new (ctx.mem) List(pstate, l->length(), l->separator());+      for (size_t i = 0, L = l->length(); i < L; ++i) {+        *result << ((i == index) ? v : (*l)[i]);+      }+      return result;+    }++    Signature index_sig = "index($list, $value)";+    BUILT_IN(index)+    {+      List* l = dynamic_cast<List*>(env["$list"]);+      Expression* v = ARG("$value", Expression);+      if (!l) {+        l = new (ctx.mem) List(pstate, 1);+        *l << ARG("$list", Expression);+      }+      for (size_t i = 0, L = l->length(); i < L; ++i) {+        if (eq(l->value_at_index(i), v, ctx)) return new (ctx.mem) Number(pstate, i+1);+      }+      return new (ctx.mem) Null(pstate);+    }++    Signature join_sig = "join($list1, $list2, $separator: auto)";+    BUILT_IN(join)+    {+      List* l1 = dynamic_cast<List*>(env["$list1"]);+      List* l2 = dynamic_cast<List*>(env["$list2"]);+      String_Constant* sep = ARG("$separator", String_Constant);+      List::Separator sep_val = (l1 ? l1->separator() : List::SPACE);+      if (!l1) {+        l1 = new (ctx.mem) List(pstate, 1);+        *l1 << ARG("$list1", Expression);+        sep_val = (l2 ? l2->separator() : List::SPACE);+      }+      if (!l2) {+        l2 = new (ctx.mem) List(pstate, 1);+        *l2 << ARG("$list2", Expression);+      }+      size_t len = l1->length() + l2->length();+      string sep_str = unquote(sep->value());+      if (sep_str == "space") sep_val = List::SPACE;+      else if (sep_str == "comma") sep_val = List::COMMA;+      else if (sep_str != "auto") error("argument `$separator` of `" + string(sig) + "` must be `space`, `comma`, or `auto`", pstate);+      List* result = new (ctx.mem) List(pstate, len, sep_val);+      *result += l1;+      *result += l2;+      return result;+    }++    Signature append_sig = "append($list, $val, $separator: auto)";+    BUILT_IN(append)+    {+      List* l = dynamic_cast<List*>(env["$list"]);+      Expression* v = ARG("$val", Expression);+      String_Constant* sep = ARG("$separator", String_Constant);+      if (!l) {+        l = new (ctx.mem) List(pstate, 1);+        *l << ARG("$list", Expression);+      }+      List* result = new (ctx.mem) List(pstate, l->length() + 1, l->separator());+      string sep_str(unquote(sep->value()));+      if (sep_str == "space") result->separator(List::SPACE);+      else if (sep_str == "comma") result->separator(List::COMMA);+      else if (sep_str != "auto") error("argument `$separator` of `" + string(sig) + "` must be `space`, `comma`, or `auto`", pstate);+      *result += l;+      *result << v;+      return result;+    }++    Signature zip_sig = "zip($lists...)";+    BUILT_IN(zip)+    {+      List* arglist = new (ctx.mem) List(*ARG("$lists", List));+      size_t shortest = 0;+      for (size_t i = 0, L = arglist->length(); i < L; ++i) {+        List* ith = dynamic_cast<List*>(arglist->value_at_index(i));+        if (!ith) {+          ith = new (ctx.mem) List(pstate, 1);+          *ith << arglist->value_at_index(i);+          if (arglist->is_arglist()) {+            ((Argument*)(*arglist)[i])->value(ith);+          } else {+            (*arglist)[i] = ith;+          }+        }+        shortest = (i ? std::min(shortest, ith->length()) : ith->length());+      }+      List* zippers = new (ctx.mem) List(pstate, shortest, List::COMMA);+      size_t L = arglist->length();+      for (size_t i = 0; i < shortest; ++i) {+        List* zipper = new (ctx.mem) List(pstate, L);+        for (size_t j = 0; j < L; ++j) {+          *zipper << (*static_cast<List*>(arglist->value_at_index(j)))[i];+        }+        *zippers << zipper;+      }+      return zippers;+    }++    Signature compact_sig = "compact($values...)";+    BUILT_IN(compact)+    {+      error("`compact` has been removed from libsass because it's not part of the Sass spec", pstate);+      return 0; // suppress warning, error will exit anyway+    }++    Signature list_separator_sig = "list_separator($list)";+    BUILT_IN(list_separator)+    {+      List* l = dynamic_cast<List*>(env["$list"]);+      if (!l) {+        l = new (ctx.mem) List(pstate, 1);+        *l << ARG("$list", Expression);+      }+      return new (ctx.mem) String_Constant(pstate,+                                           l->separator() == List::COMMA ? "comma" : "space");+    }++    /////////////////+    // MAP FUNCTIONS+    /////////////////++    Signature map_get_sig = "map-get($map, $key)";+    BUILT_IN(map_get)+    {+      Map* m = ARGM("$map", Map, ctx);+      Expression* v = ARG("$key", Expression);+      try {+        return m->at(v);+      } catch (const std::out_of_range&) {+        return new (ctx.mem) Null(pstate);+      }+      catch (...) { throw; }+    }++    Signature map_has_key_sig = "map-has-key($map, $key)";+    BUILT_IN(map_has_key)+    {+      Map* m = ARGM("$map", Map, ctx);+      Expression* v = ARG("$key", Expression);+      return new (ctx.mem) Boolean(pstate, m->has(v));+    }++    Signature map_keys_sig = "map-keys($map)";+    BUILT_IN(map_keys)+    {+      Map* m = ARGM("$map", Map, ctx);+      List* result = new (ctx.mem) List(pstate, m->length(), List::COMMA);+      for ( auto key : m->keys()) {+        *result << key;+      }+      return result;+    }++    Signature map_values_sig = "map-values($map)";+    BUILT_IN(map_values)+    {+      Map* m = ARGM("$map", Map, ctx);+      List* result = new (ctx.mem) List(pstate, m->length(), List::COMMA);+      for ( auto key : m->keys()) {+        *result << m->at(key);+      }+      return result;+    }++    Signature map_merge_sig = "map-merge($map1, $map2)";+    BUILT_IN(map_merge)+    {+      Map* m1 = ARGM("$map1", Map, ctx);+      Map* m2 = ARGM("$map2", Map, ctx);++      size_t len = m1->length() + m2->length();+      Map* result = new (ctx.mem) Map(pstate, len);+      *result += m1;+      *result += m2;+      return result;+    }++    Signature map_remove_sig = "map-remove($map, $keys...)";+    BUILT_IN(map_remove)+    {+      bool remove;+      Map* m = ARGM("$map", Map, ctx);+      List* arglist = ARG("$keys", List);+      Map* result = new (ctx.mem) Map(pstate, 1);+      for (auto key : m->keys()) {+        remove = false;+        for (size_t j = 0, K = arglist->length(); j < K && !remove; ++j) {+          remove = eq(key, arglist->value_at_index(j), ctx);+        }+        if (!remove) *result << make_pair(key, m->at(key));+      }+      return result;+    }++    Signature keywords_sig = "keywords($args)";+    BUILT_IN(keywords)+    {+      List* arglist = new (ctx.mem) List(*ARG("$args", List));+      Map* result = new (ctx.mem) Map(pstate, 1);+      // The parser ensures the ordering of arguments so we can assert this+      // isn't keyword argument list the first argument isn't a keyword argument+      if (!(arglist->empty() || ((Argument*)(*arglist)[0])->is_keyword_argument())) return result;+      for (size_t i = 0, L = arglist->length(); i < L; ++i) {+        string name = string(((Argument*)(*arglist)[i])->name());+        string sanitized_name = string(name, 1);+        *result << make_pair(new (ctx.mem) String_Constant(pstate, sanitized_name),+                             ((Argument*)(*arglist)[i])->value());+      }+      return result;+    }++    //////////////////////////+    // INTROSPECTION FUNCTIONS+    //////////////////////////++    Signature type_of_sig = "type-of($value)";+    BUILT_IN(type_of)+    {+      Expression* v = ARG("$value", Expression);+      if (v->concrete_type() == Expression::STRING) {+        To_String to_string(&ctx);+        string str(v->perform(&to_string));+        if (ctx.names_to_colors.count(str)) {+          return new (ctx.mem) String_Constant(pstate, "color");+        }+      }+      return new (ctx.mem) String_Constant(pstate, ARG("$value", Expression)->type());+    }++    Signature unit_sig = "unit($number)";+    BUILT_IN(unit)+    { return new (ctx.mem) String_Quoted(pstate, quote(ARG("$number", Number)->unit(), '"')); }++    Signature unitless_sig = "unitless($number)";+    BUILT_IN(unitless)+    { return new (ctx.mem) Boolean(pstate, ARG("$number", Number)->is_unitless()); }++    Signature comparable_sig = "comparable($number-1, $number-2)";+    BUILT_IN(comparable)+    {+      Number* n1 = ARG("$number-1", Number);+      Number* n2 = ARG("$number-2", Number);+      if (n1->is_unitless() || n2->is_unitless()) {+        return new (ctx.mem) Boolean(pstate, true);+      }+      Number tmp_n2(*n2);+      tmp_n2.normalize(n1->find_convertible_unit());+      return new (ctx.mem) Boolean(pstate, n1->unit() == tmp_n2.unit());+    }++    Signature variable_exists_sig = "variable-exists($name)";+    BUILT_IN(variable_exists)+    {+      string s = Util::normalize_underscores(unquote(ARG("$name", String_Constant)->value()));++      if(d_env.has("$"+s)) {+        return new (ctx.mem) Boolean(pstate, true);+      }+      else {+        return new (ctx.mem) Boolean(pstate, false);+      }+    }++    Signature global_variable_exists_sig = "global-variable-exists($name)";+    BUILT_IN(global_variable_exists)+    {+      string s = Util::normalize_underscores(unquote(ARG("$name", String_Constant)->value()));++      if(d_env.has_global("$"+s)) {+        return new (ctx.mem) Boolean(pstate, true);+      }+      else {+        return new (ctx.mem) Boolean(pstate, false);+      }+    }++    Signature function_exists_sig = "function-exists($name)";+    BUILT_IN(function_exists)+    {+      string s = Util::normalize_underscores(unquote(ARG("$name", String_Constant)->value()));++      if(d_env.has_global(s+"[f]")) {+        return new (ctx.mem) Boolean(pstate, true);+      }+      else {+        return new (ctx.mem) Boolean(pstate, false);+      }+    }++    Signature mixin_exists_sig = "mixin-exists($name)";+    BUILT_IN(mixin_exists)+    {+      string s = Util::normalize_underscores(unquote(ARG("$name", String_Constant)->value()));++      if(d_env.has_global(s+"[m]")) {+        return new (ctx.mem) Boolean(pstate, true);+      }+      else {+        return new (ctx.mem) Boolean(pstate, false);+      }+    }++    Signature feature_exists_sig = "feature-exists($name)";+    BUILT_IN(feature_exists)+    {+      string s = unquote(ARG("$name", String_Constant)->value());++      if(features.find(s) == features.end()) {+        return new (ctx.mem) Boolean(pstate, false);+      }+      else {+        return new (ctx.mem) Boolean(pstate, true);+      }+    }++    Signature call_sig = "call($name, $args...)";+    BUILT_IN(call)+    {+      string name = Util::normalize_underscores(unquote(ARG("$name", String_Constant)->value()));+      List* arglist = new (ctx.mem) List(*ARG("$args", List));++      Arguments* args = new (ctx.mem) Arguments(pstate);+      for (size_t i = 0, L = arglist->length(); i < L; ++i) {+        Expression* expr = arglist->value_at_index(i);+        if (arglist->is_arglist()) {+           Argument* arg = static_cast<Argument*>(expr);+          *args << new (ctx.mem) Argument(pstate,+                                          expr,+                                          "",+                                          arg->is_rest_argument(),+                                          arg->is_keyword_argument());+        } else {+          *args << new (ctx.mem) Argument(pstate, expr);+        }+      }+      Function_Call* func = new (ctx.mem) Function_Call(pstate, name, args);+      Contextualize contextualize(ctx, &d_env, backtrace);+      Listize listize(ctx);+      Eval eval(ctx, &contextualize, &listize, &d_env, backtrace);+      return func->perform(&eval);++    }++    ////////////////////+    // BOOLEAN FUNCTIONS+    ////////////////////++    Signature not_sig = "not($value)";+    BUILT_IN(sass_not)+    { return new (ctx.mem) Boolean(pstate, ARG("$value", Expression)->is_false()); }++    Signature if_sig = "if($condition, $if-true, $if-false)";+    // BUILT_IN(sass_if)+    // { return ARG("$condition", Expression)->is_false() ? ARG("$if-false", Expression) : ARG("$if-true", Expression); }+    BUILT_IN(sass_if)+    {+      Contextualize contextualize(ctx, &d_env, backtrace);+      Listize listize(ctx);+      Eval eval(ctx, &contextualize, &listize, &d_env, backtrace);+      bool is_true = !ARG("$condition", Expression)->perform(&eval)->is_false();+      if (is_true) {+        return ARG("$if-true", Expression)->perform(&eval);+      }+      else {+        return ARG("$if-false", Expression)->perform(&eval);+      }+    }++    ////////////////+    // 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+    }++    //////////////////////////+    // MISCELLANEOUS FUNCTIONS+    //////////////////////////++    Signature inspect_sig = "inspect($value)";+    BUILT_IN(inspect)+    {+      Expression* v = ARG("$value", Expression);+      if (v->concrete_type() == Expression::NULL_VAL) {+        return new (ctx.mem) String_Constant(pstate, "null");+      } else if (v->concrete_type() == Expression::BOOLEAN && *v == 0) {+        return new (ctx.mem) String_Constant(pstate, "false");+      } else if (v->concrete_type() == Expression::STRING) {+        return v;+      } else {+        Output_Style old_style;+        old_style = ctx.output_style;+        ctx.output_style = NESTED;+        To_String to_string(&ctx);+        string inspect = v->perform(&to_string);+        ctx.output_style = old_style;+        return new (ctx.mem) String_Constant(pstate, inspect);+++      }+      // return v;+    }++    Signature unique_id_sig = "unique-id()";+    BUILT_IN(unique_id)+    {+      std::stringstream ss;+      uniform_real_distribution<> distributor(0, 4294967296); // 16^8+      uint_fast32_t distributed = static_cast<uint_fast32_t>(distributor(rand));+      ss << "u" << setfill('0') << setw(8) << std::hex << distributed;+      return new (ctx.mem) String_Constant(pstate, ss.str());+    }++  }+}
+ libsass/functions.hpp view
@@ -0,0 +1,184 @@+#ifndef SASS_FUNCTIONS_H+#define SASS_FUNCTIONS_H++#include <string>++#include "position.hpp"+#include "environment.hpp"+#include "sass_functions.h"++#define BUILT_IN(name) Expression*\+name(Env& env, Env& d_env, Context& ctx, Signature sig, ParserState pstate, Backtrace* backtrace)++namespace Sass {+  class Context;+  struct Backtrace;+  class AST_Node;+  class Expression;+  class Definition;+  typedef Environment<AST_Node*> Env;+  typedef const char* Signature;+  typedef Expression* (*Native_Function)(Env&, Env&, Context&, Signature, ParserState, Backtrace*);++  Definition* make_native_function(Signature, Native_Function, Context& ctx);+  Definition* make_c_function(Sass_Function_Entry c_func, Context& ctx);++  namespace Functions {++    extern Signature rgb_sig;+    extern Signature rgba_4_sig;+    extern Signature rgba_2_sig;+    extern Signature red_sig;+    extern Signature green_sig;+    extern Signature blue_sig;+    extern Signature mix_sig;+    extern Signature hsl_sig;+    extern Signature hsla_sig;+    extern Signature hue_sig;+    extern Signature saturation_sig;+    extern Signature lightness_sig;+    extern Signature adjust_hue_sig;+    extern Signature lighten_sig;+    extern Signature darken_sig;+    extern Signature saturate_sig;+    extern Signature desaturate_sig;+    extern Signature grayscale_sig;+    extern Signature complement_sig;+    extern Signature invert_sig;+    extern Signature alpha_sig;+    extern Signature opacity_sig;+    extern Signature opacify_sig;+    extern Signature fade_in_sig;+    extern Signature transparentize_sig;+    extern Signature fade_out_sig;+    extern Signature adjust_color_sig;+    extern Signature scale_color_sig;+    extern Signature change_color_sig;+    extern Signature ie_hex_str_sig;+    extern Signature unquote_sig;+    extern Signature quote_sig;+    extern Signature str_length_sig;+    extern Signature str_insert_sig;+    extern Signature str_index_sig;+    extern Signature str_slice_sig;+    extern Signature to_upper_case_sig;+    extern Signature to_lower_case_sig;+    extern Signature percentage_sig;+    extern Signature round_sig;+    extern Signature ceil_sig;+    extern Signature floor_sig;+    extern Signature abs_sig;+    extern Signature min_sig;+    extern Signature max_sig;+    extern Signature inspect_sig;+    extern Signature random_sig;+    extern Signature length_sig;+    extern Signature nth_sig;+    extern Signature index_sig;+    extern Signature join_sig;+    extern Signature append_sig;+    extern Signature zip_sig;+    extern Signature compact_sig;+    extern Signature list_separator_sig;+    extern Signature type_of_sig;+    extern Signature unit_sig;+    extern Signature unitless_sig;+    extern Signature comparable_sig;+    extern Signature variable_exists_sig;+    extern Signature global_variable_exists_sig;+    extern Signature function_exists_sig;+    extern Signature mixin_exists_sig;+    extern Signature feature_exists_sig;+    extern Signature call_sig;+    extern Signature not_sig;+    extern Signature if_sig;+    extern Signature image_url_sig;+    extern Signature map_get_sig;+    extern Signature map_merge_sig;+    extern Signature map_remove_sig;+    extern Signature map_keys_sig;+    extern Signature map_values_sig;+    extern Signature map_has_key_sig;+    extern Signature keywords_sig;+    extern Signature set_nth_sig;+    extern Signature unique_id_sig;++    BUILT_IN(rgb);+    BUILT_IN(rgba_4);+    BUILT_IN(rgba_2);+    BUILT_IN(red);+    BUILT_IN(green);+    BUILT_IN(blue);+    BUILT_IN(mix);+    BUILT_IN(hsl);+    BUILT_IN(hsla);+    BUILT_IN(hue);+    BUILT_IN(saturation);+    BUILT_IN(lightness);+    BUILT_IN(adjust_hue);+    BUILT_IN(lighten);+    BUILT_IN(darken);+    BUILT_IN(saturate);+    BUILT_IN(desaturate);+    BUILT_IN(grayscale);+    BUILT_IN(complement);+    BUILT_IN(invert);+    BUILT_IN(alpha);+    BUILT_IN(opacify);+    BUILT_IN(transparentize);+    BUILT_IN(adjust_color);+    BUILT_IN(scale_color);+    BUILT_IN(change_color);+    BUILT_IN(ie_hex_str);+    BUILT_IN(sass_unquote);+    BUILT_IN(sass_quote);+    BUILT_IN(str_length);+    BUILT_IN(str_insert);+    BUILT_IN(str_index);+    BUILT_IN(str_slice);+    BUILT_IN(to_upper_case);+    BUILT_IN(to_lower_case);+    BUILT_IN(percentage);+    BUILT_IN(round);+    BUILT_IN(ceil);+    BUILT_IN(floor);+    BUILT_IN(abs);+    BUILT_IN(min);+    BUILT_IN(max);+    BUILT_IN(inspect);+    BUILT_IN(random);+    BUILT_IN(length);+    BUILT_IN(nth);+    BUILT_IN(index);+    BUILT_IN(join);+    BUILT_IN(append);+    BUILT_IN(zip);+    BUILT_IN(compact);+    BUILT_IN(list_separator);+    BUILT_IN(type_of);+    BUILT_IN(unit);+    BUILT_IN(unitless);+    BUILT_IN(comparable);+    BUILT_IN(variable_exists);+    BUILT_IN(global_variable_exists);+    BUILT_IN(function_exists);+    BUILT_IN(mixin_exists);+    BUILT_IN(feature_exists);+    BUILT_IN(call);+    BUILT_IN(sass_not);+    BUILT_IN(sass_if);+    BUILT_IN(image_url);+    BUILT_IN(map_get);+    BUILT_IN(map_merge);+    BUILT_IN(map_remove);+    BUILT_IN(map_keys);+    BUILT_IN(map_values);+    BUILT_IN(map_has_key);+    BUILT_IN(keywords);+    BUILT_IN(set_nth);+    BUILT_IN(unique_id);++  }+}++#endif
+ libsass/inspect.cpp view
@@ -0,0 +1,866 @@+#include "inspect.hpp"+#include "ast.hpp"+#include "context.hpp"+#include "utf8/checked.h"+#include <cmath>+#include <string>+#include <iostream>+#include <iomanip>+#include <stdint.h>+#include <stdint.h>++namespace Sass {+  using namespace std;++  Inspect::Inspect(Emitter emi)+  : Emitter(emi)+  { }+  Inspect::~Inspect() { }++  // statements+  void Inspect::operator()(Block* block)+  {+    if (!block->is_root()) {+      add_open_mapping(block);+      append_scope_opener();+    }+    if (output_style() == NESTED) indentation += block->tabs();+    for (size_t i = 0, L = block->length(); i < L; ++i) {+      (*block)[i]->perform(this);+    }+    if (output_style() == NESTED) indentation -= block->tabs();+    if (!block->is_root()) {+      append_scope_closer();+      add_close_mapping(block);+    }++  }++  void Inspect::operator()(Ruleset* ruleset)+  {+    ruleset->selector()->perform(this);+    ruleset->block()->perform(this);+  }++  void Inspect::operator()(Keyframe_Rule* rule)+  {+    if (rule->selector()) rule->selector()->perform(this);+    if (rule->block()) rule->block()->perform(this);+  }++  void Inspect::operator()(Propset* propset)+  {+    propset->property_fragment()->perform(this);+    append_colon_separator();+    propset->block()->perform(this);+  }++  void Inspect::operator()(Bubble* bubble)+  {+    append_indentation();+    append_token("::BUBBLE", bubble);+    append_scope_opener();+    bubble->node()->perform(this);+    append_scope_closer();+  }++  void Inspect::operator()(Media_Block* media_block)+  {+    append_indentation();+    append_token("@media", media_block);+    append_mandatory_space();+    in_media_block = true;+    media_block->media_queries()->perform(this);+    in_media_block = false;+    media_block->block()->perform(this);+  }++  void Inspect::operator()(Feature_Block* feature_block)+  {+    append_indentation();+    append_token("@supports", feature_block);+    append_mandatory_space();+    feature_block->feature_queries()->perform(this);+    feature_block->block()->perform(this);+  }++  void Inspect::operator()(At_Root_Block* at_root_block)+  {+    append_indentation();+    append_token("@at-root ", at_root_block);+    append_mandatory_space();+    if(at_root_block->expression()) at_root_block->expression()->perform(this);+    at_root_block->block()->perform(this);+  }++  void Inspect::operator()(At_Rule* at_rule)+  {+    append_indentation();+    append_token(at_rule->keyword(), at_rule);+    if (at_rule->selector()) {+      append_mandatory_space();+      in_wrapped = true;+      at_rule->selector()->perform(this);+      in_wrapped = false;+    }+    if (at_rule->block()) {+      at_rule->block()->perform(this);+    }+    else {+      append_delimiter();+    }+  }++  void Inspect::operator()(Declaration* dec)+  {+    if (dec->value()->concrete_type() == Expression::NULL_VAL) return;+    in_declaration = true;+    if (output_style() == NESTED)+      indentation += dec->tabs();+    append_indentation();+    dec->property()->perform(this);+    append_colon_separator();+    dec->value()->perform(this);+    if (dec->is_important()) {+      append_optional_space();+      append_string("!important");+    }+    append_delimiter();+    if (output_style() == NESTED)+      indentation -= dec->tabs();+    in_declaration = false;+  }++  void Inspect::operator()(Assignment* assn)+  {+    append_token(assn->variable(), assn);+    append_colon_separator();+    assn->value()->perform(this);+    if (assn->is_default()) {+      append_optional_space();+      append_string("!default");+    }+    append_delimiter();+  }++  void Inspect::operator()(Import* import)+  {+    if (!import->urls().empty()) {+      append_token("@import", import);+      append_mandatory_space();++      if (String_Quoted* strq = dynamic_cast<String_Quoted*>(import->urls().front())) {+        strq->is_delayed(false);+      }++      import->urls().front()->perform(this);+      append_delimiter();+      for (size_t i = 1, S = import->urls().size(); i < S; ++i) {+        append_mandatory_linefeed();+        append_token("@import", import);+        append_mandatory_space();++        if (String_Quoted* strq = dynamic_cast<String_Quoted*>(import->urls()[i])) {+          strq->is_delayed(false);+        }++        import->urls()[i]->perform(this);+        append_delimiter();+      }+    }+  }++  void Inspect::operator()(Import_Stub* import)+  {+    append_indentation();+    append_token("@import", import);+    append_mandatory_space();+    append_string(import->file_name());+    append_delimiter();+  }++  void Inspect::operator()(Warning* warning)+  {+    append_indentation();+    append_token("@warn", warning);+    append_mandatory_space();+    warning->message()->perform(this);+    append_delimiter();+  }++  void Inspect::operator()(Error* error)+  {+    append_indentation();+    append_token("@error", error);+    append_mandatory_space();+    error->message()->perform(this);+    append_delimiter();+  }++  void Inspect::operator()(Debug* debug)+  {+    append_indentation();+    append_token("@debug", debug);+    append_mandatory_space();+    debug->value()->perform(this);+    append_delimiter();+  }++  void Inspect::operator()(Comment* comment)+  {+    in_comment = true;+    comment->text()->perform(this);+    in_comment = false;+  }++  void Inspect::operator()(If* cond)+  {+    append_indentation();+    append_token("@if", cond);+    append_mandatory_space();+    cond->predicate()->perform(this);+    cond->consequent()->perform(this);+    if (cond->alternative()) {+      append_optional_linefeed();+      append_indentation();+      append_string("else");+      cond->alternative()->perform(this);+    }+  }++  void Inspect::operator()(For* loop)+  {+    append_indentation();+    append_token("@for", loop);+    append_mandatory_space();+    append_string(loop->variable());+    append_string(" from ");+    loop->lower_bound()->perform(this);+    append_string(loop->is_inclusive() ? " through " : " to ");+    loop->upper_bound()->perform(this);+    loop->block()->perform(this);+  }++  void Inspect::operator()(Each* loop)+  {+    append_indentation();+    append_token("@each", loop);+    append_mandatory_space();+    append_string(loop->variables()[0]);+    for (size_t i = 1, L = loop->variables().size(); i < L; ++i) {+      append_comma_separator();+      append_string(loop->variables()[i]);+    }+    append_string(" in ");+    loop->list()->perform(this);+    loop->block()->perform(this);+  }++  void Inspect::operator()(While* loop)+  {+    append_indentation();+    append_token("@while", loop);+    append_mandatory_space();+    loop->predicate()->perform(this);+    loop->block()->perform(this);+  }++  void Inspect::operator()(Return* ret)+  {+    append_indentation();+    append_token("@return", ret);+    append_mandatory_space();+    ret->value()->perform(this);+    append_delimiter();+  }++  void Inspect::operator()(Extension* extend)+  {+    append_indentation();+    append_token("@extend", extend);+    append_mandatory_space();+    extend->selector()->perform(this);+    append_delimiter();+  }++  void Inspect::operator()(Definition* def)+  {+    append_indentation();+    if (def->type() == Definition::MIXIN) {+      append_token("@mixin", def);+      append_mandatory_space();+    } else {+      append_token("@function", def);+      append_mandatory_space();+    }+    append_string(def->name());+    def->parameters()->perform(this);+    def->block()->perform(this);+  }++  void Inspect::operator()(Mixin_Call* call)+  {+    append_indentation();+    append_token("@include", call);+    append_mandatory_space();+    append_string(call->name());+    if (call->arguments()) {+      call->arguments()->perform(this);+    }+    if (call->block()) {+      append_optional_space();+      call->block()->perform(this);+    }+    if (!call->block()) append_delimiter();+  }++  void Inspect::operator()(Content* content)+  {+    append_indentation();+    append_token("@content", content);+    append_delimiter();+  }++  void Inspect::operator()(Map* map)+  {+    if (map->empty()) return;+    if (map->is_invisible()) return;+    bool items_output = false;+    append_string("(");+    for (auto key : map->keys()) {+      if (key->is_invisible()) continue;+      if (map->at(key)->is_invisible()) continue;+      if (items_output) append_comma_separator();+      key->perform(this);+      append_colon_separator();+      map->at(key)->perform(this);+      items_output = true;+    }+    append_string(")");+  }++  void Inspect::operator()(List* list)+  {+    string sep(list->separator() == List::SPACE ? " " : ",");+    if (output_style() != COMPRESSED && sep == ",") sep += " ";+    else if (in_media_block && sep != " ") sep += " "; // verified+    if (list->empty()) return;+    bool items_output = false;+    in_declaration_list = in_declaration;+    for (size_t i = 0, L = list->length(); i < L; ++i) {+      Expression* list_item = (*list)[i];+      if (list_item->is_invisible()) {+        continue;+      }+      if (items_output) {+        append_string(sep);+      }+      if (items_output && sep != " ")+        append_optional_space();+      list_item->perform(this);+      items_output = true;+    }+    in_declaration_list = false;+  }++  void Inspect::operator()(Binary_Expression* expr)+  {+    expr->left()->perform(this);+    switch (expr->type()) {+      case Binary_Expression::AND: append_string(" and "); break;+      case Binary_Expression::OR:  append_string(" or ");  break;+      case Binary_Expression::EQ:  append_string(" == ");  break;+      case Binary_Expression::NEQ: append_string(" != ");  break;+      case Binary_Expression::GT:  append_string(" > ");   break;+      case Binary_Expression::GTE: append_string(" >= ");  break;+      case Binary_Expression::LT:  append_string(" < ");   break;+      case Binary_Expression::LTE: append_string(" <= ");  break;+      case Binary_Expression::ADD: append_string(" + ");   break;+      case Binary_Expression::SUB: append_string(" - ");   break;+      case Binary_Expression::MUL: append_string(" * ");   break;+      case Binary_Expression::DIV: append_string("/");     break;+      case Binary_Expression::MOD: append_string(" % ");   break;+      default: break; // shouldn't get here+    }+    expr->right()->perform(this);+  }++  void Inspect::operator()(Unary_Expression* expr)+  {+    if (expr->type() == Unary_Expression::PLUS) append_string("+");+    else                                        append_string("-");+    expr->operand()->perform(this);+  }++  void Inspect::operator()(Function_Call* call)+  {+    append_token(call->name(), call);+    call->arguments()->perform(this);+  }++  void Inspect::operator()(Function_Call_Schema* call)+  {+    call->name()->perform(this);+    call->arguments()->perform(this);+  }++  void Inspect::operator()(Variable* var)+  {+    append_token(var->name(), var);+  }++  void Inspect::operator()(Textual* txt)+  {+    append_token(txt->value(), txt);+  }++  void Inspect::operator()(Number* n)+  {+    stringstream ss;+    ss.precision(ctx ? ctx->precision : 5);+    ss << fixed << n->value();+    string d(ss.str());+    // store if the value did not equal zero+    // if after applying precsision, the value gets+    // truncated to zero, sass emits 0.0 instead of 0+    bool nonzero = n->value() != 0;+    for (size_t i = d.length()-1; d[i] == '0'; --i) {+      d.resize(d.length()-1);+    }+    if (d[d.length()-1] == '.') d.resize(d.length()-1);+    if (n->numerator_units().size() > 1 ||+        n->denominator_units().size() > 0 ||+        (n->numerator_units().size() && n->numerator_units()[0].find_first_of('/') != string::npos) ||+        (n->numerator_units().size() && n->numerator_units()[0].find_first_of('*') != string::npos)+    ) {+      error(d + n->unit() + " isn't a valid CSS value.", n->pstate());+    }+    if (!n->zero() && !in_declaration_list) {+      if (d.substr(0, 3) == "-0.") d.erase(1, 1);+      if (d.substr(0, 2) == "0.") d.erase(0, 1);+    }+    // remove the leading minus+    if (d == "-0") d.erase(0, 1);+    // use fractional output if we had+    // a value before it got truncated+    if (d == "0" && nonzero) d = "0.0";+    // append number and unit+    append_token(d + n->unit(), n);+  }++  // helper function for serializing colors+  template <size_t range>+  static double cap_channel(double c) {+    if      (c > range) return range;+    else if (c < 0)     return 0;+    else                return c;+  }++  void Inspect::operator()(Color* c)+  {+    stringstream ss;++    // check if we prefer short hex colors+    bool want_short = output_style() == COMPRESSED;++    // original color name+    // maybe an unknown token+    string name = c->disp();++    // resolved color+    string res_name = name;++    double r = round(cap_channel<0xff>(c->r()));+    double g = round(cap_channel<0xff>(c->g()));+    double b = round(cap_channel<0xff>(c->b()));+    double a = cap_channel<1>   (c->a());++    // get color from given name (if one was given at all)+    if (name != "" && ctx && ctx->names_to_colors.count(name)) {+      Color* n = ctx->names_to_colors[name];+      r = round(cap_channel<0xff>(n->r()));+      g = round(cap_channel<0xff>(n->g()));+      b = round(cap_channel<0xff>(n->b()));+      a = cap_channel<1>   (n->a());+    }+    // otherwise get the possible resolved color name+    else {+      int numval = static_cast<int>(r) * 0x10000 + static_cast<int>(g) * 0x100 + static_cast<int>(b);+      if (ctx && ctx->colors_to_names.count(numval))+        res_name = ctx->colors_to_names[numval];+    }++    stringstream hexlet;+    hexlet << '#' << setw(1) << setfill('0');+    // create a short color hexlet if there is any need for it+    if (want_short && is_color_doublet(r, g, b) && a == 1) {+      hexlet << hex << setw(1) << (static_cast<unsigned long>(r) >> 4);+      hexlet << hex << setw(1) << (static_cast<unsigned long>(g) >> 4);+      hexlet << hex << setw(1) << (static_cast<unsigned long>(b) >> 4);+    } else {+      hexlet << hex << setw(2) << static_cast<unsigned long>(r);+      hexlet << hex << setw(2) << static_cast<unsigned long>(g);+      hexlet << hex << setw(2) << static_cast<unsigned long>(b);+    }++    // retain the originally specified color definition if unchanged+    if (name != "") {+      ss << name;+    }+    else if (r == 0 && g == 0 && b == 0 && a == 0) {+        ss << "transparent";+    }+    else if (a >= 1) {+      if (res_name != "") {+        if (want_short && hexlet.str().size() < res_name.size()) {+          ss << hexlet.str();+        } else {+          ss << res_name;+        }+      }+      else {+        ss << hexlet.str();+      }+    }+    else {+      ss << "rgba(";+      ss << static_cast<unsigned long>(r) << ",";+      if (output_style() != COMPRESSED) ss << " ";+      ss << static_cast<unsigned long>(g) << ",";+      if (output_style() != COMPRESSED) ss << " ";+      ss << static_cast<unsigned long>(b) << ",";+      if (output_style() != COMPRESSED) ss << " ";+      ss << a << ')';+    }+    append_token(ss.str(), c);+  }++  void Inspect::operator()(Boolean* b)+  {+    append_token(b->value() ? "true" : "false", b);+  }++  void Inspect::operator()(String_Schema* ss)+  {+    // Evaluation should turn these into String_Constants, so this method is+    // only for inspection purposes.+    for (size_t i = 0, L = ss->length(); i < L; ++i) {+      if ((*ss)[i]->is_interpolant()) append_string("#{");+      (*ss)[i]->perform(this);+      if ((*ss)[i]->is_interpolant()) append_string("}");+    }+  }++  void Inspect::operator()(String_Constant* s)+  {+    if (String_Quoted* quoted = dynamic_cast<String_Quoted*>(s)) {+      return Inspect::operator()(quoted);+    }+    append_token(s->value(), s);+  }++  void Inspect::operator()(String_Quoted* s)+  {+    if (s->quote_mark()) {+      append_token(quote(s->value(), s->quote_mark()), s);+    } else {+      append_token(s->value(), s);+    }+  }++  void Inspect::operator()(Feature_Query* fq)+  {+    size_t i = 0;+    (*fq)[i++]->perform(this);+    for (size_t L = fq->length(); i < L; ++i) {+      (*fq)[i]->perform(this);+    }+  }++  void Inspect::operator()(Feature_Query_Condition* fqc)+  {+    if (fqc->operand() == Feature_Query_Condition::AND) {+      append_mandatory_space();+      append_token("and", fqc);+      append_mandatory_space();+    } else if (fqc->operand() == Feature_Query_Condition::OR) {+      append_mandatory_space();+      append_token("or", fqc);+      append_mandatory_space();+    } else if (fqc->operand() == Feature_Query_Condition::NOT) {+      append_mandatory_space();+      append_token("not", fqc);+      append_mandatory_space();+    }++    if (!fqc->is_root()) append_string("(");++    if (!fqc->length()) {+      fqc->feature()->perform(this);+      append_string(": "); // verified+      fqc->value()->perform(this);+    }+    for (size_t i = 0, L = fqc->length(); i < L; ++i)+      (*fqc)[i]->perform(this);++    if (!fqc->is_root()) append_string(")");+  }++  void Inspect::operator()(Media_Query* mq)+  {+    size_t i = 0;+    if (mq->media_type()) {+      if      (mq->is_negated())    append_string("not ");+      else if (mq->is_restricted()) append_string("only ");+      mq->media_type()->perform(this);+    }+    else {+      (*mq)[i++]->perform(this);+    }+    for (size_t L = mq->length(); i < L; ++i) {+      append_string(" and ");+      (*mq)[i]->perform(this);+    }+  }++  void Inspect::operator()(Media_Query_Expression* mqe)+  {+    if (mqe->is_interpolated()) {+      mqe->feature()->perform(this);+    }+    else {+      append_string("(");+      mqe->feature()->perform(this);+      if (mqe->value()) {+        append_string(": "); // verified+        mqe->value()->perform(this);+      }+      append_string(")");+    }+  }++  void Inspect::operator()(At_Root_Expression* ae)+  {+    if (ae->is_interpolated()) {+      ae->feature()->perform(this);+    }+    else {+      append_string("(");+      ae->feature()->perform(this);+      if (ae->value()) {+        append_colon_separator();+        ae->value()->perform(this);+      }+      append_string(")");+    }+  }++  void Inspect::operator()(Null* n)+  {+    append_token("null", n);+  }++  void Inspect::operator()(Parent_Selector* p)+  {+    if (p->selector()) {+      p->selector()->perform(this);+      append_delimiter();+    }+    else {+      append_string("&");+    }+  }++  // parameters and arguments+  void Inspect::operator()(Parameter* p)+  {+    append_token(p->name(), p);+    if (p->default_value()) {+      append_colon_separator();+      p->default_value()->perform(this);+    }+    else if (p->is_rest_parameter()) {+      append_string("...");+    }+  }++  void Inspect::operator()(Parameters* p)+  {+    append_string("(");+    if (!p->empty()) {+      (*p)[0]->perform(this);+      for (size_t i = 1, L = p->length(); i < L; ++i) {+        append_comma_separator();+        (*p)[i]->perform(this);+      }+    }+    append_string(")");+  }++  void Inspect::operator()(Argument* a)+  {+    if (!a->name().empty()) {+      append_token(a->name(), a);+      append_colon_separator();+    }+    // Special case: argument nulls can be ignored+    if (a->value()->concrete_type() == Expression::NULL_VAL) {+      return;+    }+    if (a->value()->concrete_type() == Expression::STRING) {+      String_Constant* s = static_cast<String_Constant*>(a->value());+      s->perform(this);+    } else a->value()->perform(this);+    if (a->is_rest_argument()) {+      append_string("...");+    }+  }++  void Inspect::operator()(Arguments* a)+  {+    append_string("(");+    if (!a->empty()) {+      (*a)[0]->perform(this);+      for (size_t i = 1, L = a->length(); i < L; ++i) {+        append_string(", "); // verified+        // Sass Bug? append_comma_separator();+        (*a)[i]->perform(this);+      }+    }+    append_string(")");+  }++  void Inspect::operator()(Selector_Schema* s)+  {+    s->contents()->perform(this);+  }++  void Inspect::operator()(Selector_Reference* ref)+  {+    if (ref->selector()) ref->selector()->perform(this);+    else                 append_string("&");+  }++  void Inspect::operator()(Selector_Placeholder* s)+  {+    append_token(s->name(), s);+    if (s->has_line_break()) append_optional_linefeed();+    if (s->has_line_break()) append_indentation();++  }++  void Inspect::operator()(Type_Selector* s)+  {+    append_token(s->name(), s);+  }++  void Inspect::operator()(Selector_Qualifier* s)+  {+    append_token(s->name(), s);+    if (s->has_line_break()) append_optional_linefeed();+    if (s->has_line_break()) append_indentation();+  }++  void Inspect::operator()(Attribute_Selector* s)+  {+    append_string("[");+    add_open_mapping(s);+    append_token(s->name(), s);+    if (!s->matcher().empty()) {+      append_string(s->matcher());+      if (s->value()) {+        s->value()->perform(this);+      }+    }+    add_close_mapping(s);+    append_string("]");+  }++  void Inspect::operator()(Pseudo_Selector* s)+  {+    append_token(s->name(), s);+    if (s->expression()) {+      s->expression()->perform(this);+      append_string(")");+    }+  }++  void Inspect::operator()(Wrapped_Selector* s)+  {+    bool was = in_wrapped;+    in_wrapped = true;+    append_token(s->name(), s);+    s->selector()->perform(this);+    append_string(")");+    in_wrapped = was;+  }++  void Inspect::operator()(Compound_Selector* s)+  {+    for (size_t i = 0, L = s->length(); i < L; ++i) {+      (*s)[i]->perform(this);+    }+    if (s->has_line_break()) {+      append_optional_linefeed();+    }+  }++  void Inspect::operator()(Complex_Selector* c)+  {+    Compound_Selector*           head = c->head();+    Complex_Selector*            tail = c->tail();+    Complex_Selector::Combinator comb = c->combinator();+    if (head && !head->is_empty_reference()) head->perform(this);+    bool is_empty = head && head->is_empty_reference();+    bool is_tail = head && !head->is_empty_reference() && tail;+    if (output_style() == COMPRESSED && comb != Complex_Selector::ANCESTOR_OF) scheduled_space = 0;++    switch (comb) {+      case Complex_Selector::ANCESTOR_OF:+        if (is_tail) append_mandatory_space();+      break;+      case Complex_Selector::PARENT_OF:+        append_optional_space();+        append_string(">");+        append_optional_space();+      break;+      case Complex_Selector::ADJACENT_TO:+        append_optional_space();+        append_string("+");+        append_optional_space();+      break;+      case Complex_Selector::PRECEDES:+        if (is_empty) append_optional_space();+        else append_mandatory_space();+        append_string("~");+        if (tail) append_mandatory_space();+        else append_optional_space();+      break;+    }+    if (tail && comb != Complex_Selector::ANCESTOR_OF) {+      if (c->has_line_break()) append_optional_linefeed();+    }+    if (tail) tail->perform(this);+  }++  void Inspect::operator()(Selector_List* g)+  {+    if (g->empty()) return;+    for (size_t i = 0, L = g->length(); i < L; ++i) {+      if (!in_wrapped && i == 0) append_indentation();+      (*g)[i]->perform(this);+      if (i < L - 1) {+        append_comma_separator();+        if ((*g)[i]->has_line_feed()) {+          append_optional_linefeed();+          append_indentation();+        }+      }+    }+  }++  void Inspect::fallback_impl(AST_Node* n)+  {+  }++}
+ libsass/inspect.hpp view
@@ -0,0 +1,98 @@+#ifndef SASS_INSPECT_H+#define SASS_INSPECT_H++#include <string>++#include "position.hpp"+#include "operation.hpp"+#include "emitter.hpp"++namespace Sass {+  class Context;+  using namespace std;++  class Inspect : public Operation_CRTP<void, Inspect>, public Emitter {+  protected:+    // import all the class-specific methods and override as desired+    using Operation_CRTP<void, Inspect>::operator();++    void fallback_impl(AST_Node* n);++  public:++    Inspect(Emitter emi);+    virtual ~Inspect();++    // statements+    virtual void operator()(Block*);+    virtual void operator()(Ruleset*);+    virtual void operator()(Propset*);+    virtual void operator()(Bubble*);+    virtual void operator()(Feature_Block*);+    virtual void operator()(Media_Block*);+    virtual void operator()(At_Root_Block*);+    virtual void operator()(At_Rule*);+    virtual void operator()(Keyframe_Rule*);+    virtual void operator()(Declaration*);+    virtual void operator()(Assignment*);+    virtual void operator()(Import*);+    virtual void operator()(Import_Stub*);+    virtual void operator()(Warning*);+    virtual void operator()(Error*);+    virtual void operator()(Debug*);+    virtual void operator()(Comment*);+    virtual void operator()(If*);+    virtual void operator()(For*);+    virtual void operator()(Each*);+    virtual void operator()(While*);+    virtual void operator()(Return*);+    virtual void operator()(Extension*);+    virtual void operator()(Definition*);+    virtual void operator()(Mixin_Call*);+    virtual void operator()(Content*);+    // expressions+    virtual void operator()(Map*);+    virtual void operator()(List*);+    virtual void operator()(Binary_Expression*);+    virtual void operator()(Unary_Expression*);+    virtual void operator()(Function_Call*);+    virtual void operator()(Function_Call_Schema*);+    virtual void operator()(Variable*);+    virtual void operator()(Textual*);+    virtual void operator()(Number*);+    virtual void operator()(Color*);+    virtual void operator()(Boolean*);+    virtual void operator()(String_Schema*);+    virtual void operator()(String_Constant*);+    virtual void operator()(String_Quoted*);+    virtual void operator()(Feature_Query*);+    virtual void operator()(Feature_Query_Condition*);+    virtual void operator()(Media_Query*);+    virtual void operator()(Media_Query_Expression*);+    virtual void operator()(At_Root_Expression*);+    virtual void operator()(Null*);+    virtual void operator()(Parent_Selector* p);+    // parameters and arguments+    virtual void operator()(Parameter*);+    virtual void operator()(Parameters*);+    virtual void operator()(Argument*);+    virtual void operator()(Arguments*);+    // selectors+    virtual void operator()(Selector_Schema*);+    virtual void operator()(Selector_Reference*);+    virtual void operator()(Selector_Placeholder*);+    virtual void operator()(Type_Selector*);+    virtual void operator()(Selector_Qualifier*);+    virtual void operator()(Attribute_Selector*);+    virtual void operator()(Pseudo_Selector*);+    virtual void operator()(Wrapped_Selector*);+    virtual void operator()(Compound_Selector*);+    virtual void operator()(Complex_Selector*);+    virtual void operator()(Selector_List*);++    // template <typename U>+    // void fallback(U x) { fallback_impl(reinterpret_cast<AST_Node*>(x)); }+  };++}+#endif
+ libsass/json.cpp view
@@ -0,0 +1,1411 @@+/*+  Copyright (C) 2011 Joseph A. Adams (joeyadams3.14159@gmail.com)+  All rights reserved.++  Permission is hereby granted, free of charge, to any person obtaining a copy+  of this software and associated documentation files (the "Software"), to deal+  in the Software without restriction, including without limitation the rights+  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+  copies of the Software, and to permit persons to whom the Software is+  furnished to do so, subject to the following conditions:++  The above copyright notice and this permission notice shall be included in+  all copies or substantial portions of the Software.++  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN+  THE SOFTWARE.+*/++#include "json.hpp"++#include <assert.h>+#include <stdint.h>+#include <stdio.h>+#include <stdlib.h>+#include <string.h>++#ifdef _MSC_VER++#include <stdarg.h>+#define snprintf c99_snprintf++inline int c99_vsnprintf(char* str, size_t size, const char* format, va_list ap)+{+    int count = -1;++    if (size != 0)+        count = _vsnprintf_s(str, size, _TRUNCATE, format, ap);+    if (count == -1)+        count = _vscprintf(format, ap);++    return count;+}++inline int c99_snprintf(char* str, size_t size, const char* format, ...)+{+    int count;+    va_list ap;++    va_start(ap, format);+    count = c99_vsnprintf(str, size, format, ap);+    va_end(ap);++    return count;+}+#endif // _MSC_VER++#define out_of_memory() do {                    \+    fprintf(stderr, "Out of memory.\n");    \+    exit(EXIT_FAILURE);                     \+  } while (0)++/* Sadly, strdup is not portable. */+static char *json_strdup(const char *str)+{+  char *ret = (char*) malloc(strlen(str) + 1);+  if (ret == NULL)+    out_of_memory();+  strcpy(ret, str);+  return ret;+}++/* String buffer */++typedef struct+{+  char *cur;+  char *end;+  char *start;+} SB;++static void sb_init(SB *sb)+{+  sb->start = (char*) malloc(17);+  if (sb->start == NULL)+    out_of_memory();+  sb->cur = sb->start;+  sb->end = sb->start + 16;+}++/* sb and need may be evaluated multiple times. */+#define sb_need(sb, need) do {                  \+    if ((sb)->end - (sb)->cur < (need))     \+      sb_grow(sb, need);                  \+  } while (0)++static void sb_grow(SB *sb, int need)+{+  size_t length = sb->cur - sb->start;+  size_t alloc = sb->end - sb->start;+  +  do {+    alloc *= 2;+  } while (alloc < length + need);+  +  sb->start = (char*) realloc(sb->start, alloc + 1);+  if (sb->start == NULL)+    out_of_memory();+  sb->cur = sb->start + length;+  sb->end = sb->start + alloc;+}++static void sb_put(SB *sb, const char *bytes, int count)+{+  sb_need(sb, count);+  memcpy(sb->cur, bytes, count);+  sb->cur += count;+}++#define sb_putc(sb, c) do {         \+    if ((sb)->cur >= (sb)->end) \+      sb_grow(sb, 1);         \+    *(sb)->cur++ = (c);         \+  } while (0)++static void sb_puts(SB *sb, const char *str)+{+  sb_put(sb, str, strlen(str));+}++static char *sb_finish(SB *sb)+{+  *sb->cur = 0;+  assert(sb->start <= sb->cur && strlen(sb->start) == (size_t)(sb->cur - sb->start));+  return sb->start;+}++static void sb_free(SB *sb)+{+  free(sb->start);+}++/*+ * Unicode helper functions+ *+ * These are taken from the ccan/charset module and customized a bit.+ * Putting them here means the compiler can (choose to) inline them,+ * and it keeps ccan/json from having a dependency.+ */++/*+ * Type for Unicode codepoints.+ * We need our own because wchar_t might be 16 bits.+ */+typedef uint32_t uchar_t;++/*+ * Validate a single UTF-8 character starting at @s.+ * The string must be null-terminated.+ *+ * If it's valid, return its length (1 thru 4).+ * If it's invalid or clipped, return 0.+ *+ * This function implements the syntax given in RFC3629, which is+ * the same as that given in The Unicode Standard, Version 6.0.+ *+ * It has the following properties:+ *+ *  * All codepoints U+0000..U+10FFFF may be encoded,+ *    except for U+D800..U+DFFF, which are reserved+ *    for UTF-16 surrogate pair encoding.+ *  * UTF-8 byte sequences longer than 4 bytes are not permitted,+ *    as they exceed the range of Unicode.+ *  * The sixty-six Unicode "non-characters" are permitted+ *    (namely, U+FDD0..U+FDEF, U+xxFFFE, and U+xxFFFF).+ */+static int utf8_validate_cz(const char *s)+{+  unsigned char c = *s++;+  +  if (c <= 0x7F) {        /* 00..7F */+    return 1;+  } else if (c <= 0xC1) { /* 80..C1 */+    /* Disallow overlong 2-byte sequence. */+    return 0;+  } else if (c <= 0xDF) { /* C2..DF */+    /* Make sure subsequent byte is in the range 0x80..0xBF. */+    if (((unsigned char)*s++ & 0xC0) != 0x80)+      return 0;+    +    return 2;+  } else if (c <= 0xEF) { /* E0..EF */+    /* Disallow overlong 3-byte sequence. */+    if (c == 0xE0 && (unsigned char)*s < 0xA0)+      return 0;+    +    /* Disallow U+D800..U+DFFF. */+    if (c == 0xED && (unsigned char)*s > 0x9F)+      return 0;+    +    /* Make sure subsequent bytes are in the range 0x80..0xBF. */+    if (((unsigned char)*s++ & 0xC0) != 0x80)+      return 0;+    if (((unsigned char)*s++ & 0xC0) != 0x80)+      return 0;+    +    return 3;+  } else if (c <= 0xF4) { /* F0..F4 */+    /* Disallow overlong 4-byte sequence. */+    if (c == 0xF0 && (unsigned char)*s < 0x90)+      return 0;+    +    /* Disallow codepoints beyond U+10FFFF. */+    if (c == 0xF4 && (unsigned char)*s > 0x8F)+      return 0;+    +    /* Make sure subsequent bytes are in the range 0x80..0xBF. */+    if (((unsigned char)*s++ & 0xC0) != 0x80)+      return 0;+    if (((unsigned char)*s++ & 0xC0) != 0x80)+      return 0;+    if (((unsigned char)*s++ & 0xC0) != 0x80)+      return 0;+    +    return 4;+  } else {                /* F5..FF */+    return 0;+  }+}++/* Validate a null-terminated UTF-8 string. */+static bool utf8_validate(const char *s)+{+  int len;+  +  for (; *s != 0; s += len) {+    len = utf8_validate_cz(s);+    if (len == 0)+      return false;+  }+  +  return true;+}++/*+ * Read a single UTF-8 character starting at @s,+ * returning the length, in bytes, of the character read.+ *+ * This function assumes input is valid UTF-8,+ * and that there are enough characters in front of @s.+ */+static int utf8_read_char(const char *s, uchar_t *out)+{+  const unsigned char *c = (const unsigned char*) s;+  +  assert(utf8_validate_cz(s));++  if (c[0] <= 0x7F) {+    /* 00..7F */+    *out = c[0];+    return 1;+  } else if (c[0] <= 0xDF) {+    /* C2..DF (unless input is invalid) */+    *out = ((uchar_t)c[0] & 0x1F) << 6 |+           ((uchar_t)c[1] & 0x3F);+    return 2;+  } else if (c[0] <= 0xEF) {+    /* E0..EF */+    *out = ((uchar_t)c[0] &  0xF) << 12 |+           ((uchar_t)c[1] & 0x3F) << 6  |+           ((uchar_t)c[2] & 0x3F);+    return 3;+  } else {+    /* F0..F4 (unless input is invalid) */+    *out = ((uchar_t)c[0] &  0x7) << 18 |+           ((uchar_t)c[1] & 0x3F) << 12 |+           ((uchar_t)c[2] & 0x3F) << 6  |+           ((uchar_t)c[3] & 0x3F);+    return 4;+  }+}++/*+ * Write a single UTF-8 character to @s,+ * returning the length, in bytes, of the character written.+ *+ * @unicode must be U+0000..U+10FFFF, but not U+D800..U+DFFF.+ *+ * This function will write up to 4 bytes to @out.+ */+static int utf8_write_char(uchar_t unicode, char *out)+{+  unsigned char *o = (unsigned char*) out;+  +  assert(unicode <= 0x10FFFF && !(unicode >= 0xD800 && unicode <= 0xDFFF));++  if (unicode <= 0x7F) {+    /* U+0000..U+007F */+    *o++ = unicode;+    return 1;+  } else if (unicode <= 0x7FF) {+    /* U+0080..U+07FF */+    *o++ = 0xC0 | unicode >> 6;+    *o++ = 0x80 | (unicode & 0x3F);+    return 2;+  } else if (unicode <= 0xFFFF) {+    /* U+0800..U+FFFF */+    *o++ = 0xE0 | unicode >> 12;+    *o++ = 0x80 | (unicode >> 6 & 0x3F);+    *o++ = 0x80 | (unicode & 0x3F);+    return 3;+  } else {+    /* U+10000..U+10FFFF */+    *o++ = 0xF0 | unicode >> 18;+    *o++ = 0x80 | (unicode >> 12 & 0x3F);+    *o++ = 0x80 | (unicode >> 6 & 0x3F);+    *o++ = 0x80 | (unicode & 0x3F);+    return 4;+  }+}++/*+ * Compute the Unicode codepoint of a UTF-16 surrogate pair.+ *+ * @uc should be 0xD800..0xDBFF, and @lc should be 0xDC00..0xDFFF.+ * If they aren't, this function returns false.+ */+static bool from_surrogate_pair(uint16_t uc, uint16_t lc, uchar_t *unicode)+{+  if (uc >= 0xD800 && uc <= 0xDBFF && lc >= 0xDC00 && lc <= 0xDFFF) {+    *unicode = 0x10000 + ((((uchar_t)uc & 0x3FF) << 10) | (lc & 0x3FF));+    return true;+  } else {+    return false;+  }+}++/*+ * Construct a UTF-16 surrogate pair given a Unicode codepoint.+ *+ * @unicode must be U+10000..U+10FFFF.+ */+static void to_surrogate_pair(uchar_t unicode, uint16_t *uc, uint16_t *lc)+{+  uchar_t n;+  +  assert(unicode >= 0x10000 && unicode <= 0x10FFFF);+  +  n = unicode - 0x10000;+  *uc = ((n >> 10) & 0x3FF) | 0xD800;+  *lc = (n & 0x3FF) | 0xDC00;+}++#define is_space(c) ((c) == '\t' || (c) == '\n' || (c) == '\r' || (c) == ' ')+#define is_digit(c) ((c) >= '0' && (c) <= '9')++static bool parse_value     (const char **sp, JsonNode        **out);+static bool parse_string    (const char **sp, char            **out);+static bool parse_number    (const char **sp, double           *out);+static bool parse_array     (const char **sp, JsonNode        **out);+static bool parse_object    (const char **sp, JsonNode        **out);+static bool parse_hex16     (const char **sp, uint16_t         *out);++static bool expect_literal  (const char **sp, const char *str);+static void skip_space      (const char **sp);++static void emit_value              (SB *out, const JsonNode *node);+static void emit_value_indented     (SB *out, const JsonNode *node, const char *space, int indent_level);+static void emit_string             (SB *out, const char *str);+static void emit_number             (SB *out, double num);+static void emit_array              (SB *out, const JsonNode *array);+static void emit_array_indented     (SB *out, const JsonNode *array, const char *space, int indent_level);+static void emit_object             (SB *out, const JsonNode *object);+static void emit_object_indented    (SB *out, const JsonNode *object, const char *space, int indent_level);++static int write_hex16(char *out, uint16_t val);++static JsonNode *mknode(JsonTag tag);+static void append_node(JsonNode *parent, JsonNode *child);+static void prepend_node(JsonNode *parent, JsonNode *child);+static void append_member(JsonNode *object, char *key, JsonNode *value);++/* Assertion-friendly validity checks */+static bool tag_is_valid(unsigned int tag);+static bool number_is_valid(const char *num);++JsonNode *json_decode(const char *json)+{+  const char *s = json;+  JsonNode *ret;+  +  skip_space(&s);+  if (!parse_value(&s, &ret))+    return NULL;+  +  skip_space(&s);+  if (*s != 0) {+    json_delete(ret);+    return NULL;+  }+  +  return ret;+}++char *json_encode(const JsonNode *node)+{+  return json_stringify(node, NULL);+}++char *json_encode_string(const char *str)+{+  SB sb;+  sb_init(&sb);+  +  emit_string(&sb, str);+  +  return sb_finish(&sb);+}++char *json_stringify(const JsonNode *node, const char *space)+{+  SB sb;+  sb_init(&sb);+  +  if (space != NULL)+    emit_value_indented(&sb, node, space, 0);+  else+    emit_value(&sb, node);+  +  return sb_finish(&sb);+}++void json_delete(JsonNode *node)+{+  if (node != NULL) {+    json_remove_from_parent(node);+    +    switch (node->tag) {+      case JSON_STRING:+        free(node->string_);+        break;+      case JSON_ARRAY:+      case JSON_OBJECT:+      {+        JsonNode *child, *next;+        for (child = node->children.head; child != NULL; child = next) {+          next = child->next;+          json_delete(child);+        }+        break;+      }+      default:;+    }+    +    free(node);+  }+}++bool json_validate(const char *json)+{+  const char *s = json;+  +  skip_space(&s);+  if (!parse_value(&s, NULL))+    return false;+  +  skip_space(&s);+  if (*s != 0)+    return false;+  +  return true;+}++JsonNode *json_find_element(JsonNode *array, int index)+{+  JsonNode *element;+  int i = 0;+  +  if (array == NULL || array->tag != JSON_ARRAY)+    return NULL;+  +  json_foreach(element, array) {+    if (i == index)+      return element;+    i++;+  }+  +  return NULL;+}++JsonNode *json_find_member(JsonNode *object, const char *name)+{+  JsonNode *member;+  +  if (object == NULL || object->tag != JSON_OBJECT)+    return NULL;+  +  json_foreach(member, object)+    if (strcmp(member->key, name) == 0)+      return member;+  +  return NULL;+}++JsonNode *json_first_child(const JsonNode *node)+{+  if (node != NULL && (node->tag == JSON_ARRAY || node->tag == JSON_OBJECT))+    return node->children.head;+  return NULL;+}++static JsonNode *mknode(JsonTag tag)+{+  JsonNode *ret = (JsonNode*) calloc(1, sizeof(JsonNode));+  if (ret == NULL)+    out_of_memory();+  ret->tag = tag;+  return ret;+}++JsonNode *json_mknull(void)+{+  return mknode(JSON_NULL);+}++JsonNode *json_mkbool(bool b)+{+  JsonNode *ret = mknode(JSON_BOOL);+  ret->bool_ = b;+  return ret;+}++static JsonNode *mkstring(char *s)+{+  JsonNode *ret = mknode(JSON_STRING);+  ret->string_ = s;+  return ret;+}++JsonNode *json_mkstring(const char *s)+{+  return mkstring(json_strdup(s));+}++JsonNode *json_mknumber(double n)+{+  JsonNode *node = mknode(JSON_NUMBER);+  node->number_ = n;+  return node;+}++JsonNode *json_mkarray(void)+{+  return mknode(JSON_ARRAY);+}++JsonNode *json_mkobject(void)+{+  return mknode(JSON_OBJECT);+}++static void append_node(JsonNode *parent, JsonNode *child)+{+  child->parent = parent;+  child->prev = parent->children.tail;+  child->next = NULL;+  +  if (parent->children.tail != NULL)+    parent->children.tail->next = child;+  else+    parent->children.head = child;+  parent->children.tail = child;+}++static void prepend_node(JsonNode *parent, JsonNode *child)+{+  child->parent = parent;+  child->prev = NULL;+  child->next = parent->children.head;+  +  if (parent->children.head != NULL)+    parent->children.head->prev = child;+  else+    parent->children.tail = child;+  parent->children.head = child;+}++static void append_member(JsonNode *object, char *key, JsonNode *value)+{+  value->key = key;+  append_node(object, value);+}++void json_append_element(JsonNode *array, JsonNode *element)+{+  assert(array->tag == JSON_ARRAY);+  assert(element->parent == NULL);+  +  append_node(array, element);+}++void json_prepend_element(JsonNode *array, JsonNode *element)+{+  assert(array->tag == JSON_ARRAY);+  assert(element->parent == NULL);+  +  prepend_node(array, element);+}++void json_append_member(JsonNode *object, const char *key, JsonNode *value)+{+  assert(object->tag == JSON_OBJECT);+  assert(value->parent == NULL);+  +  append_member(object, json_strdup(key), value);+}++void json_prepend_member(JsonNode *object, const char *key, JsonNode *value)+{+  assert(object->tag == JSON_OBJECT);+  assert(value->parent == NULL);+  +  value->key = json_strdup(key);+  prepend_node(object, value);+}++void json_remove_from_parent(JsonNode *node)+{+  JsonNode *parent = node->parent;+  +  if (parent != NULL) {+    if (node->prev != NULL)+      node->prev->next = node->next;+    else+      parent->children.head = node->next;+    if (node->next != NULL)+      node->next->prev = node->prev;+    else+      parent->children.tail = node->prev;+    +    free(node->key);+    +    node->parent = NULL;+    node->prev = node->next = NULL;+    node->key = NULL;+  }+}++static bool parse_value(const char **sp, JsonNode **out)+{+  const char *s = *sp;+  +  switch (*s) {+    case 'n':+      if (expect_literal(&s, "null")) {+        if (out)+          *out = json_mknull();+        *sp = s;+        return true;+      }+      return false;+    +    case 'f':+      if (expect_literal(&s, "false")) {+        if (out)+          *out = json_mkbool(false);+        *sp = s;+        return true;+      }+      return false;+    +    case 't':+      if (expect_literal(&s, "true")) {+        if (out)+          *out = json_mkbool(true);+        *sp = s;+        return true;+      }+      return false;+    +    case '"': {+      char *str;+      if (parse_string(&s, out ? &str : NULL)) {+        if (out)+          *out = mkstring(str);+        *sp = s;+        return true;+      }+      return false;+    }+    +    case '[':+      if (parse_array(&s, out)) {+        *sp = s;+        return true;+      }+      return false;+    +    case '{':+      if (parse_object(&s, out)) {+        *sp = s;+        return true;+      }+      return false;+    +    default: {+      double num;+      if (parse_number(&s, out ? &num : NULL)) {+        if (out)+          *out = json_mknumber(num);+        *sp = s;+        return true;+      }+      return false;+    }+  }+}++static bool parse_array(const char **sp, JsonNode **out)+{+  const char *s = *sp;+  JsonNode *ret = out ? json_mkarray() : NULL;+  JsonNode *element;+  +  if (*s++ != '[')+    goto failure;+  skip_space(&s);+  +  if (*s == ']') {+    s++;+    goto success;+  }+  +  for (;;) {+    if (!parse_value(&s, out ? &element : NULL))+      goto failure;+    skip_space(&s);+    +    if (out)+      json_append_element(ret, element);+    +    if (*s == ']') {+      s++;+      goto success;+    }+    +    if (*s++ != ',')+      goto failure;+    skip_space(&s);+  }+  +success:+  *sp = s;+  if (out)+    *out = ret;+  return true;++failure:+  json_delete(ret);+  return false;+}++static bool parse_object(const char **sp, JsonNode **out)+{+  const char *s = *sp;+  JsonNode *ret = out ? json_mkobject() : NULL;+  char *key;+  JsonNode *value;+  +  if (*s++ != '{')+    goto failure;+  skip_space(&s);+  +  if (*s == '}') {+    s++;+    goto success;+  }+  +  for (;;) {+    if (!parse_string(&s, out ? &key : NULL))+      goto failure;+    skip_space(&s);+    +    if (*s++ != ':')+      goto failure_free_key;+    skip_space(&s);+    +    if (!parse_value(&s, out ? &value : NULL))+      goto failure_free_key;+    skip_space(&s);+    +    if (out)+      append_member(ret, key, value);+    +    if (*s == '}') {+      s++;+      goto success;+    }+    +    if (*s++ != ',')+      goto failure;+    skip_space(&s);+  }+  +success:+  *sp = s;+  if (out)+    *out = ret;+  return true;++failure_free_key:+  if (out)+    free(key);+failure:+  json_delete(ret);+  return false;+}++bool parse_string(const char **sp, char **out)+{+  const char *s = *sp;+  SB sb;+  char throwaway_buffer[4];+    /* enough space for a UTF-8 character */+  char *b;+  +  if (*s++ != '"')+    return false;+  +  if (out) {+    sb_init(&sb);+    sb_need(&sb, 4);+    b = sb.cur;+  } else {+    b = throwaway_buffer;+  }+  +  while (*s != '"') {+    unsigned char c = *s++;+    +    /* Parse next character, and write it to b. */+    if (c == '\\') {+      c = *s++;+      switch (c) {+        case '"':+        case '\\':+        case '/':+          *b++ = c;+          break;+        case 'b':+          *b++ = '\b';+          break;+        case 'f':+          *b++ = '\f';+          break;+        case 'n':+          *b++ = '\n';+          break;+        case 'r':+          *b++ = '\r';+          break;+        case 't':+          *b++ = '\t';+          break;+        case 'u':+        {+          uint16_t uc, lc;+          uchar_t unicode;+          +          if (!parse_hex16(&s, &uc))+            goto failed;+          +          if (uc >= 0xD800 && uc <= 0xDFFF) {+            /* Handle UTF-16 surrogate pair. */+            if (*s++ != '\\' || *s++ != 'u' || !parse_hex16(&s, &lc))+              goto failed; /* Incomplete surrogate pair. */+            if (!from_surrogate_pair(uc, lc, &unicode))+              goto failed; /* Invalid surrogate pair. */+          } else if (uc == 0) {+            /* Disallow "\u0000". */+            goto failed;+          } else {+            unicode = uc;+          }+          +          b += utf8_write_char(unicode, b);+          break;+        }+        default:+          /* Invalid escape */+          goto failed;+      }+    } else if (c <= 0x1F) {+      /* Control characters are not allowed in string literals. */+      goto failed;+    } else {+      /* Validate and echo a UTF-8 character. */+      int len;+      +      s--;+      len = utf8_validate_cz(s);+      if (len == 0)+        goto failed; /* Invalid UTF-8 character. */+      +      while (len--)+        *b++ = *s++;+    }+    +    /*+     * Update sb to know about the new bytes,+     * and set up b to write another character.+     */+    if (out) {+      sb.cur = b;+      sb_need(&sb, 4);+      b = sb.cur;+    } else {+      b = throwaway_buffer;+    }+  }+  s++;+  +  if (out)+    *out = sb_finish(&sb);+  *sp = s;+  return true;++failed:+  if (out)+    sb_free(&sb);+  return false;+}++/*+ * The JSON spec says that a number shall follow this precise pattern+ * (spaces and quotes added for readability):+ *   '-'? (0 | [1-9][0-9]*) ('.' [0-9]+)? ([Ee] [+-]? [0-9]+)?+ *+ * However, some JSON parsers are more liberal.  For instance, PHP accepts+ * '.5' and '1.'.  JSON.parse accepts '+3'.+ *+ * This function takes the strict approach.+ */+bool parse_number(const char **sp, double *out)+{+  const char *s = *sp;++  /* '-'? */+  if (*s == '-')+    s++;++  /* (0 | [1-9][0-9]*) */+  if (*s == '0') {+    s++;+  } else {+    if (!is_digit(*s))+      return false;+    do {+      s++;+    } while (is_digit(*s));+  }++  /* ('.' [0-9]+)? */+  if (*s == '.') {+    s++;+    if (!is_digit(*s))+      return false;+    do {+      s++;+    } while (is_digit(*s));+  }++  /* ([Ee] [+-]? [0-9]+)? */+  if (*s == 'E' || *s == 'e') {+    s++;+    if (*s == '+' || *s == '-')+      s++;+    if (!is_digit(*s))+      return false;+    do {+      s++;+    } while (is_digit(*s));+  }++  if (out)+    *out = strtod(*sp, NULL);++  *sp = s;+  return true;+}++static void skip_space(const char **sp)+{+  const char *s = *sp;+  while (is_space(*s))+    s++;+  *sp = s;+}++static void emit_value(SB *out, const JsonNode *node)+{+  assert(tag_is_valid(node->tag));+  switch (node->tag) {+    case JSON_NULL:+      sb_puts(out, "null");+      break;+    case JSON_BOOL:+      sb_puts(out, node->bool_ ? "true" : "false");+      break;+    case JSON_STRING:+      emit_string(out, node->string_);+      break;+    case JSON_NUMBER:+      emit_number(out, node->number_);+      break;+    case JSON_ARRAY:+      emit_array(out, node);+      break;+    case JSON_OBJECT:+      emit_object(out, node);+      break;+    default:+      assert(false);+  }+}++void emit_value_indented(SB *out, const JsonNode *node, const char *space, int indent_level)+{+  assert(tag_is_valid(node->tag));+  switch (node->tag) {+    case JSON_NULL:+      sb_puts(out, "null");+      break;+    case JSON_BOOL:+      sb_puts(out, node->bool_ ? "true" : "false");+      break;+    case JSON_STRING:+      emit_string(out, node->string_);+      break;+    case JSON_NUMBER:+      emit_number(out, node->number_);+      break;+    case JSON_ARRAY:+      emit_array_indented(out, node, space, indent_level);+      break;+    case JSON_OBJECT:+      emit_object_indented(out, node, space, indent_level);+      break;+    default:+      assert(false);+  }+}++static void emit_array(SB *out, const JsonNode *array)+{+  const JsonNode *element;+  +  sb_putc(out, '[');+  json_foreach(element, array) {+    emit_value(out, element);+    if (element->next != NULL)+      sb_putc(out, ',');+  }+  sb_putc(out, ']');+}++static void emit_array_indented(SB *out, const JsonNode *array, const char *space, int indent_level)+{+  const JsonNode *element = array->children.head;+  int i;+  +  if (element == NULL) {+    sb_puts(out, "[]");+    return;+  }+  +  sb_puts(out, "[\n");+  while (element != NULL) {+    for (i = 0; i < indent_level + 1; i++)+      sb_puts(out, space);+    emit_value_indented(out, element, space, indent_level + 1);+    +    element = element->next;+    sb_puts(out, element != NULL ? ",\n" : "\n");+  }+  for (i = 0; i < indent_level; i++)+    sb_puts(out, space);+  sb_putc(out, ']');+}++static void emit_object(SB *out, const JsonNode *object)+{+  const JsonNode *member;+  +  sb_putc(out, '{');+  json_foreach(member, object) {+    emit_string(out, member->key);+    sb_putc(out, ':');+    emit_value(out, member);+    if (member->next != NULL)+      sb_putc(out, ',');+  }+  sb_putc(out, '}');+}++static void emit_object_indented(SB *out, const JsonNode *object, const char *space, int indent_level)+{+  const JsonNode *member = object->children.head;+  int i;+  +  if (member == NULL) {+    sb_puts(out, "{}");+    return;+  }+  +  sb_puts(out, "{\n");+  while (member != NULL) {+    for (i = 0; i < indent_level + 1; i++)+      sb_puts(out, space);+    emit_string(out, member->key);+    sb_puts(out, ": ");+    emit_value_indented(out, member, space, indent_level + 1);+    +    member = member->next;+    sb_puts(out, member != NULL ? ",\n" : "\n");+  }+  for (i = 0; i < indent_level; i++)+    sb_puts(out, space);+  sb_putc(out, '}');+}++void emit_string(SB *out, const char *str)+{+  bool escape_unicode = false;+  const char *s = str;+  char *b;+  +  assert(utf8_validate(str));+  +  /*+   * 14 bytes is enough space to write up to two+   * \uXXXX escapes and two quotation marks.+   */+  sb_need(out, 14);+  b = out->cur;+  +  *b++ = '"';+  while (*s != 0) {+    unsigned char c = *s++;+    +    /* Encode the next character, and write it to b. */+    switch (c) {+      case '"':+        *b++ = '\\';+        *b++ = '"';+        break;+      case '\\':+        *b++ = '\\';+        *b++ = '\\';+        break;+      case '\b':+        *b++ = '\\';+        *b++ = 'b';+        break;+      case '\f':+        *b++ = '\\';+        *b++ = 'f';+        break;+      case '\n':+        *b++ = '\\';+        *b++ = 'n';+        break;+      case '\r':+        *b++ = '\\';+        *b++ = 'r';+        break;+      case '\t':+        *b++ = '\\';+        *b++ = 't';+        break;+      default: {+        int len;+        +        s--;+        len = utf8_validate_cz(s);+        +        if (len == 0) {+          /*+           * Handle invalid UTF-8 character gracefully in production+           * by writing a replacement character (U+FFFD)+           * and skipping a single byte.+           *+           * This should never happen when assertions are enabled+           * due to the assertion at the beginning of this function.+           */+          assert(false);+          if (escape_unicode) {+            strcpy(b, "\\uFFFD");+            b += 6;+          } else {+            *b++ = 0xEFu;+            *b++ = 0xBFu;+            *b++ = 0xBDu;+          }+          s++;+        } else if (c < 0x1F || (c >= 0x80 && escape_unicode)) {+          /* Encode using \u.... */+          uint32_t unicode;+          +          s += utf8_read_char(s, &unicode);+          +          if (unicode <= 0xFFFF) {+            *b++ = '\\';+            *b++ = 'u';+            b += write_hex16(b, unicode);+          } else {+            /* Produce a surrogate pair. */+            uint16_t uc, lc;+            assert(unicode <= 0x10FFFF);+            to_surrogate_pair(unicode, &uc, &lc);+            *b++ = '\\';+            *b++ = 'u';+            b += write_hex16(b, uc);+            *b++ = '\\';+            *b++ = 'u';+            b += write_hex16(b, lc);+          }+        } else {+          /* Write the character directly. */+          while (len--)+            *b++ = *s++;+        }+        +        break;+      }+    }+  +    /*+     * Update *out to know about the new bytes,+     * and set up b to write another encoded character.+     */+    out->cur = b;+    sb_need(out, 14);+    b = out->cur;+  }+  *b++ = '"';+  +  out->cur = b;+}++static void emit_number(SB *out, double num)+{+  /*+   * This isn't exactly how JavaScript renders numbers,+   * but it should produce valid JSON for reasonable numbers+   * preserve precision well enough, and avoid some oddities+   * like 0.3 -> 0.299999999999999988898 .+   */+  char buf[64];+  sprintf(buf, "%.16g", num);+  +  if (number_is_valid(buf))+    sb_puts(out, buf);+  else+    sb_puts(out, "null");+}++static bool tag_is_valid(unsigned int tag)+{+  return (/* tag >= JSON_NULL && */ tag <= JSON_OBJECT);+}++static bool number_is_valid(const char *num)+{+  return (parse_number(&num, NULL) && *num == '\0');+}++static bool expect_literal(const char **sp, const char *str)+{+  const char *s = *sp;+  +  while (*str != '\0')+    if (*s++ != *str++)+      return false;+  +  *sp = s;+  return true;+}++/*+ * Parses exactly 4 hex characters (capital or lowercase).+ * Fails if any input chars are not [0-9A-Fa-f].+ */+static bool parse_hex16(const char **sp, uint16_t *out)+{+  const char *s = *sp;+  uint16_t ret = 0;+  uint16_t i;+  uint16_t tmp;+  char c;++  for (i = 0; i < 4; i++) {+    c = *s++;+    if (c >= '0' && c <= '9')+      tmp = c - '0';+    else if (c >= 'A' && c <= 'F')+      tmp = c - 'A' + 10;+    else if (c >= 'a' && c <= 'f')+      tmp = c - 'a' + 10;+    else+      return false;++    ret <<= 4;+    ret += tmp;+  }+  +  if (out)+    *out = ret;+  *sp = s;+  return true;+}++/*+ * Encodes a 16-bit number into hexadecimal,+ * writing exactly 4 hex chars.+ */+static int write_hex16(char *out, uint16_t val)+{+  const char *hex = "0123456789ABCDEF";+  +  *out++ = hex[(val >> 12) & 0xF];+  *out++ = hex[(val >> 8)  & 0xF];+  *out++ = hex[(val >> 4)  & 0xF];+  *out++ = hex[ val        & 0xF];+  +  return 4;+}++bool json_check(const JsonNode *node, char errmsg[256])+{+  #define problem(...) do { \+      if (errmsg != NULL) \+        snprintf(errmsg, 256, __VA_ARGS__); \+      return false; \+    } while (0)+  +  if (node->key != NULL && !utf8_validate(node->key))+    problem("key contains invalid UTF-8");+  +  if (!tag_is_valid(node->tag))+    problem("tag is invalid (%u)", node->tag);+  +  if (node->tag == JSON_BOOL) {+    if (node->bool_ != false && node->bool_ != true)+      problem("bool_ is neither false (%d) nor true (%d)", (int)false, (int)true);+  } else if (node->tag == JSON_STRING) {+    if (node->string_ == NULL)+      problem("string_ is NULL");+    if (!utf8_validate(node->string_))+      problem("string_ contains invalid UTF-8");+  } else if (node->tag == JSON_ARRAY || node->tag == JSON_OBJECT) {+    JsonNode *head = node->children.head;+    JsonNode *tail = node->children.tail;+    +    if (head == NULL || tail == NULL) {+      if (head != NULL)+        problem("tail is NULL, but head is not");+      if (tail != NULL)+        problem("head is NULL, but tail is not");+    } else {+      JsonNode *child;+      JsonNode *last = NULL;+      +      if (head->prev != NULL)+        problem("First child's prev pointer is not NULL");+      +      for (child = head; child != NULL; last = child, child = child->next) {+        if (child == node)+          problem("node is its own child");+        if (child->next == child)+          problem("child->next == child (cycle)");+        if (child->next == head)+          problem("child->next == head (cycle)");+        +        if (child->parent != node)+          problem("child does not point back to parent");+        if (child->next != NULL && child->next->prev != child)+          problem("child->next does not point back to child");+        +        if (node->tag == JSON_ARRAY && child->key != NULL)+          problem("Array element's key is not NULL");+        if (node->tag == JSON_OBJECT && child->key == NULL)+          problem("Object member's key is NULL");+        +        if (!json_check(child, errmsg))+          return false;+      }+      +      if (last != tail)+        problem("tail does not match pointer found by starting at head and following next links");+    }+  }+  +  return true;+  +  #undef problem+}
+ libsass/json.hpp view
@@ -0,0 +1,117 @@+/*+  Copyright (C) 2011 Joseph A. Adams (joeyadams3.14159@gmail.com)+  All rights reserved.++  Permission is hereby granted, free of charge, to any person obtaining a copy+  of this software and associated documentation files (the "Software"), to deal+  in the Software without restriction, including without limitation the rights+  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+  copies of the Software, and to permit persons to whom the Software is+  furnished to do so, subject to the following conditions:++  The above copyright notice and this permission notice shall be included in+  all copies or substantial portions of the Software.++  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN+  THE SOFTWARE.+*/++#ifndef CCAN_JSON_H+#define CCAN_JSON_H++#include <stdbool.h>+#include <stddef.h>++typedef enum {+  JSON_NULL,+  JSON_BOOL,+  JSON_STRING,+  JSON_NUMBER,+  JSON_ARRAY,+  JSON_OBJECT,+} JsonTag;++typedef struct JsonNode JsonNode;++struct JsonNode+{+  /* only if parent is an object or array (NULL otherwise) */+  JsonNode *parent;+  JsonNode *prev, *next;+  +  /* only if parent is an object (NULL otherwise) */+  char *key; /* Must be valid UTF-8. */+  +  JsonTag tag;+  union {+    /* JSON_BOOL */+    bool bool_;+    +    /* JSON_STRING */+    char *string_; /* Must be valid UTF-8. */+    +    /* JSON_NUMBER */+    double number_;+    +    /* JSON_ARRAY */+    /* JSON_OBJECT */+    struct {+      JsonNode *head, *tail;+    } children;+  };+};++/*** Encoding, decoding, and validation ***/++JsonNode   *json_decode         (const char *json);+char       *json_encode         (const JsonNode *node);+char       *json_encode_string  (const char *str);+char       *json_stringify      (const JsonNode *node, const char *space);+void        json_delete         (JsonNode *node);++bool        json_validate       (const char *json);++/*** Lookup and traversal ***/++JsonNode   *json_find_element   (JsonNode *array, int index);+JsonNode   *json_find_member    (JsonNode *object, const char *key);++JsonNode   *json_first_child    (const JsonNode *node);++#define json_foreach(i, object_or_array)            \+  for ((i) = json_first_child(object_or_array);   \+     (i) != NULL;                               \+     (i) = (i)->next)++/*** Construction and manipulation ***/++JsonNode *json_mknull(void);+JsonNode *json_mkbool(bool b);+JsonNode *json_mkstring(const char *s);+JsonNode *json_mknumber(double n);+JsonNode *json_mkarray(void);+JsonNode *json_mkobject(void);++void json_append_element(JsonNode *array, JsonNode *element);+void json_prepend_element(JsonNode *array, JsonNode *element);+void json_append_member(JsonNode *object, const char *key, JsonNode *value);+void json_prepend_member(JsonNode *object, const char *key, JsonNode *value);++void json_remove_from_parent(JsonNode *node);++/*** Debugging ***/++/*+ * Look for structure and encoding problems in a JsonNode or its descendents.+ *+ * If a problem is detected, return false, writing a description of the problem+ * to errmsg (unless errmsg is NULL).+ */+bool json_check(const JsonNode *node, char errmsg[256]);++#endif
+ libsass/kwd_arg_macros.hpp view
@@ -0,0 +1,28 @@+#ifndef SASS_KWD_ARG_MACROS_H+#define SASS_KWD_ARG_MACROS_H++// Example usage:+// KWD_ARG_SET(Args) {+//   KWD_ARG(Args, string, foo);+//   KWD_ARG(Args, int, bar);+//   ...+// };+//+// ... and later ...+//+// something(Args().foo("hey").bar(3));++#define KWD_ARG_SET(set_name) class set_name++#define KWD_ARG(set_name, type, name) \+private: \+  type name##_; \+public: \+  set_name& name(type name##__) { \+    name##_ = name##__; \+    return *this; \+  } \+  type name() { return name##_; } \+private:++#endif
+ libsass/lexer.cpp view
@@ -0,0 +1,133 @@+#include <cctype>+#include <cstddef>+#include <iostream>+#include <iomanip>+#include "lexer.hpp"+#include "constants.hpp"+++namespace Sass {+  using namespace Constants;++  namespace Prelexer {++    //####################################+    // BASIC CHARACTER MATCHERS+    //####################################++    // Match standard control chars+    const char* kwd_at(const char* src) { return exactly<'@'>(src); }+    const char* kwd_dot(const char* src) { return exactly<'.'>(src); }+    const char* kwd_comma(const char* src) { return exactly<','>(src); };+    const char* kwd_colon(const char* src) { return exactly<':'>(src); };+    const char* kwd_star(const char* src) { return exactly<'*'>(src); };+    const char* kwd_plus(const char* src) { return exactly<'+'>(src); };+    const char* kwd_minus(const char* src) { return exactly<'-'>(src); };+    const char* kwd_slash(const char* src) { return exactly<'/'>(src); };++    //####################################+    // implement some function that do exist in the standard+    // but those are locale aware which brought some trouble+    // this even seems to improve performance by quite a bit+    //####################################++    const bool is_alpha(const char& chr)+    {+      return unsigned(chr - 'A') <= 'Z' - 'A' ||+             unsigned(chr - 'a') <= 'z' - 'a';+    }++    const bool is_space(const char& chr)+    {+      // adapted the technique from is_alpha+      return chr == ' ' || unsigned(chr - '\t') <= '\r' - '\t';+    }++    const bool is_digit(const char& chr)+    {+      // adapted the technique from is_alpha+      return unsigned(chr - '0') <= '9' - '0';+    }++    const bool is_xdigit(const char& chr)+    {+      // adapted the technique from is_alpha+      return unsigned(chr - '0') <= '9' - '0' ||+             unsigned(chr - 'a') <= 'f' - 'a' ||+             unsigned(chr - 'A') <= 'F' - 'A';+    }++    const bool is_punct(const char& chr)+    {+      // locale independent+      return chr == '.';+    }++    const bool is_alnum(const char& chr)+    {+      return is_alpha(chr) || is_digit(chr);+    }++    // check if char is outside ascii range+    const bool is_unicode(const char& chr)+    {+      // check for unicode range+      return unsigned(chr) > 127;+    }++    // Match word character (look ahead)+    const bool is_character(const char& chr)+    {+      // valid alpha, numeric or unicode char (plus hyphen)+      return is_alnum(chr) || is_unicode(chr) || chr == '-';+    }++    //####################################+    // BASIC CLASS MATCHERS+    //####################################++    // create matchers that advance the position+    const char* space(const char* src) { return is_space(*src) ? src + 1 : 0; }+    const char* alpha(const char* src) { return is_alpha(*src) ? src + 1 : 0; }+    const char* unicode(const char* src) { return is_unicode(*src) ? src + 1 : 0; }+    const char* digit(const char* src) { return is_digit(*src) ? src + 1 : 0; }+    const char* xdigit(const char* src) { return is_xdigit(*src) ? src + 1 : 0; }+    const char* alnum(const char* src) { return is_alnum(*src) ? src + 1 : 0; }+    const char* punct(const char* src) { return is_punct(*src) ? src + 1 : 0; }+    const char* character(const char* src) { return is_character(*src) ? src + 1 : 0; }++    // Match multiple ctype characters.+    const char* spaces(const char* src) { return one_plus<space>(src); }+    const char* digits(const char* src) { return one_plus<digit>(src); }++    // Whitespace handling.+    const char* no_spaces(const char* src) { return negate< space >(src); }+    const char* optional_spaces(const char* src) { return zero_plus< space >(src); }++    // Match any single character.+    const char* any_char(const char* src) { return *src ? src + 1 : src; }++    // Match word boundary (zero-width lookahead).+    const char* word_boundary(const char* src) { return is_character(*src) ? 0 : src; }++    // Match linefeed /(?:\n|\r\n?)/+    const char* re_linebreak(const char* src)+    {+      // end of file or unix linefeed return here+      if (*src == 0 || *src == '\n') return src + 1;+      // a carriage return may optionally be followed by a linefeed+      if (*src == '\r') return *(src + 1) == '\n' ? src + 2 : src + 1;+      // no linefeed+      return 0;+    }++    // Assert string boundaries (/\Z|\z|\A/)+    // This is a zero-width positive lookahead+    const char* end_of_line(const char* src)+    {+      // end of file or unix linefeed return here+      return *src == 0 || *src == '\n' || *src == '\r' ? src : 0;+    }++  }+}
+ libsass/lexer.hpp view
@@ -0,0 +1,239 @@+#ifndef SASS_LEXER_H+#define SASS_LEXER_H++#include <cstring>++namespace Sass {+  namespace Prelexer {++    //####################################+    // BASIC CHARACTER MATCHERS+    //####################################++    // Match standard control chars+    const char* kwd_at(const char* src);+    const char* kwd_dot(const char* src);+    const char* kwd_comma(const char* src);+    const char* kwd_colon(const char* src);+    const char* kwd_star(const char* src);+    const char* kwd_plus(const char* src);+    const char* kwd_minus(const char* src);+    const char* kwd_slash(const char* src);++    //####################################+    // BASIC CLASS MATCHERS+    //####################################++    // These are locale independant+    const bool is_space(const char& src);+    const bool is_alpha(const char& src);+    const bool is_punct(const char& src);+    const bool is_digit(const char& src);+    const bool is_alnum(const char& src);+    const bool is_xdigit(const char& src);+    const bool is_unicode(const char& src);+    const bool is_character(const char& src);++    // Match a single ctype predicate.+    const char* space(const char* src);+    const char* alpha(const char* src);+    const char* digit(const char* src);+    const char* xdigit(const char* src);+    const char* alnum(const char* src);+    const char* punct(const char* src);+    const char* unicode(const char* src);+    const char* character(const char* src);++    // Match multiple ctype characters.+    const char* spaces(const char* src);+    const char* digits(const char* src);++    // Whitespace handling.+    const char* no_spaces(const char* src);+    const char* optional_spaces(const char* src);++    // Match any single character (/./).+    const char* any_char(const char* src);++    // Assert word boundary (/\b/)+    // Is a zero-width positive lookaheads+    const char* word_boundary(const char* src);++    // Match a single linebreak (/(?:\n|\r\n?)/).+    const char* re_linebreak(const char* src);++    // Assert string boundaries (/\Z|\z|\A/)+    // There are zero-width positive lookaheads+    const char* end_of_line(const char* src);+    // const char* end_of_string(const char* src);+    // const char* start_of_string(const char* src);++    // Type definition for prelexer functions+    typedef const char* (*prelexer)(const char*);++    //####################################+    // BASIC "REGEX" CONSTRUCTORS+    //####################################++    // Match a single character literal.+    // Regex equivalent: /(?:literal)/+    template <char chr>+    const char* exactly(const char* src) {+      return *src == chr ? src + 1 : 0;+    }++    // Match a string constant.+    // Regex equivalent: /[axy]/+    template <const char* str>+    const char* exactly(const char* src) {+      if (str == 0) return 0;+      const char* pre = str;+      if (src == 0) return 0;+      // there is a small chance that the search string+      // is longer than the rest of the string to look at+      while (*pre && *src == *pre) {+        ++src, ++pre;+      }+      return *pre ? 0 : src;+    }++    // Match for members of char class.+    // Regex equivalent: /[axy]/+    template <const char* char_class>+    const char* class_char(const char* src) {+      const char* cc = char_class;+      while (*cc && *src != *cc) ++cc;+      return *cc ? src + 1 : 0;+    }++    // Match for members of char class.+    // Regex equivalent: /[axy]/+    template <const char* char_class>+    const char* class_chars(const char* src) {+      const char* p = src;+      while (class_char<char_class>(p)) ++p;+      return p == src ? 0 : p;+    }++    // Match all except the supplied one.+    // Regex equivalent: /[^x]/+    template <const char chr>+    const char* any_char_but(const char* src) {+      return (*src && *src != chr) ? src + 1 : 0;+    }++    // Succeeds if the matcher fails.+    // Aka. zero-width negative lookahead.+    // Regex equivalent: /(?!literal)/+    template <prelexer mx>+    const char* negate(const char* src) {+      return mx(src) ? 0 : src;+    }++    // Succeeds if the matcher succeeds.+    // Aka. zero-width positive lookahead.+    // Regex equivalent: /(?=literal)/+    // just hangs around until we need it+    template <prelexer mx>+    const char* lookahead(const char* src) {+      return mx(src) ? src : 0;+    }++    // Tries supplied matchers in order.+    // Succeeds if one of them succeeds.+    // Regex equivalent: /(?:FOO|BAR)/+    template <prelexer... mxs>+    const char* alternatives(const char* src) {+      const char* rslt;+      for (prelexer mx : { mxs... }) {+        if ((rslt = mx(src))) return rslt;+      }+      return 0;+    }++    // Tries supplied matchers in order.+    // Succeeds if all of them succeeds.+    // Regex equivalent: /(?:FOO)(?:BAR)/+    template <prelexer... mxs>+    const char* sequence(const char* src) {+      const char* rslt = src;+      for (prelexer mx : { mxs... }) {+        if (!(rslt = mx(rslt))) return 0;+      }+      return rslt;+    }++    // Match a pattern or not. Always succeeds.+    // Regex equivalent: /(?:literal)?/+    template <prelexer mx>+    const char* optional(const char* src) {+      const char* p = mx(src);+      return p ? p : src;+    }++    // Match zero or more of the patterns.+    // Regex equivalent: /(?:literal)*/+    template <prelexer mx>+    const char* zero_plus(const char* src) {+      const char* p = mx(src);+      while (p) src = p, p = mx(src);+      return src;+    }++    // Match one or more of the patterns.+    // Regex equivalent: /(?:literal)+/+    template <prelexer mx>+    const char* one_plus(const char* src) {+      const char* p = mx(src);+      if (!p) return 0;+      while (p) src = p, p = mx(src);+      return src;+    }++    // Match mx non-greedy until delimiter.+    // Other prelexers are greedy by default.+    // Regex equivalent: /(?:$mx)*?(?=$delim)\b/+    template <prelexer mx, prelexer delim>+    const char* non_greedy(const char* src) {+      while (!delim(src)) {+        const char* p = mx(src);+        if (p == src) return 0;+        if (p == 0) return 0;+        src = p;+      }+      return src;+    }++    //####################################+    // ADVANCED "REGEX" CONSTRUCTORS+    //####################################++    // Match with word boundary rule.+    // Regex equivalent: /(?:$mx)\b/+    template <const char* str>+    const char* word(const char* src) {+      return sequence <+               exactly < str >,+               word_boundary+             >(src);+    }++    template <char chr>+    const char* loosely(const char* src) {+      return sequence <+               optional_spaces,+               exactly < chr >+             >(src);+    }+    template <const char* str>+    const char* loosely(const char* src) {+      return sequence <+               optional_spaces,+               exactly < str >+             >(src);+    }++  }+}++#endif
+ libsass/listize.cpp view
@@ -0,0 +1,83 @@+#include <iostream>+#include <typeinfo>++#include "listize.hpp"+#include "to_string.hpp"+#include "context.hpp"+#include "backtrace.hpp"+#include "error_handling.hpp"++namespace Sass {++  Listize::Listize(Context& ctx)+  : ctx(ctx)+  {  }++  Expression* Listize::operator()(Selector_List* sel)+  {+    List* l = new (ctx.mem) List(sel->pstate(), sel->length(), List::COMMA);+    for (size_t i = 0, L = sel->length(); i < L; ++i) {+      *l << (*sel)[i]->perform(this);+    }+    return l;+  }++  Expression* Listize::operator()(Compound_Selector* sel)+  {+    To_String to_string;+    string str;+    for (size_t i = 0, L = sel->length(); i < L; ++i) {+      Expression* e = (*sel)[i]->perform(this);+      if (e) str += e->perform(&to_string);+    }+    return new (ctx.mem) String_Constant(sel->pstate(), str);+  }++  Expression* Listize::operator()(Complex_Selector* sel)+  {+    List* l = new (ctx.mem) List(sel->pstate(), 2);++    Compound_Selector* head = sel->head();+    if (head && !head->is_empty_reference())+    {+      Expression* hh = head->perform(this);+      if (hh) *l << hh;+    }++    switch(sel->combinator())+    {+      case Complex_Selector::PARENT_OF:+        *l << new (ctx.mem) String_Constant(sel->pstate(), ">");+      break;+      case Complex_Selector::ADJACENT_TO:+        *l << new (ctx.mem) String_Constant(sel->pstate(), "+");+      break;+      case Complex_Selector::PRECEDES:+        *l << new (ctx.mem) String_Constant(sel->pstate(), "~");+      break;+      case Complex_Selector::ANCESTOR_OF:+      break;+    }++    Complex_Selector* tail = sel->tail();+    if (tail)+    {+      Expression* tt = tail->perform(this);+      if (tt && tt->concrete_type() == Expression::LIST)+      { *l += static_cast<List*>(tt); }+      else if (tt) *l << static_cast<List*>(tt);+    }+    if (l->length() == 0) return 0;+    return l;+  }++  Expression* Listize::operator()(Selector_Reference* sel)+  {+    return 0;+  }++  Expression* Listize::fallback_impl(AST_Node* n)+  {+    return static_cast<Expression*>(n);+  }+}
+ libsass/listize.hpp view
@@ -0,0 +1,41 @@+#ifndef SASS_LISTIZE_H+#define SASS_LISTIZE_H++#include <vector>+#include <iostream>++#include "ast.hpp"+#include "context.hpp"+#include "operation.hpp"+#include "environment.hpp"++namespace Sass {+  using namespace std;++  typedef Environment<AST_Node*> Env;+  struct Backtrace;++  class Listize : public Operation_CRTP<Expression*, Listize> {++    Context&            ctx;++    Expression* fallback_impl(AST_Node* n);++  public:+    Listize(Context&);+    virtual ~Listize() { }++    using Operation<Expression*>::operator();++    Expression* operator()(Selector_List*);+    Expression* operator()(Complex_Selector*);+    Expression* operator()(Compound_Selector*);+    Expression* operator()(Selector_Reference*);++    template <typename U>+    Expression* fallback(U x) { return fallback_impl(x); }+  };++}++#endif
+ libsass/mapping.hpp view
@@ -0,0 +1,18 @@+#ifndef SASS_MAPPING_H+#define SASS_MAPPING_H++#include "position.hpp"++namespace Sass {++  struct Mapping {+    Position original_position;+    Position generated_position;++    Mapping(const Position& original_position, const Position& generated_position)+    : original_position(original_position), generated_position(generated_position) { }+  };++}++#endif
+ libsass/memory_manager.hpp view
@@ -0,0 +1,57 @@+#ifndef SASS_MEMORY_MANAGER_H+#define SASS_MEMORY_MANAGER_H++#include <vector>+#include <iostream>++namespace Sass {+  using namespace std;+  /////////////////////////////////////////////////////////////////////////////+  // A class for tracking allocations of AST_Node objects. The intended usage+  // is something like: Some_Node* n = new (mem_mgr) Some_Node(...);+  // Then, at the end of the program, the memory manager will delete all of the+  // allocated nodes that have been passed to it.+  // In the future, this class may implement a custom allocator.+  /////////////////////////////////////////////////////////////////////////////+  template <typename T>+  class Memory_Manager {+    vector<T*> nodes;++  public:+    Memory_Manager(size_t size = 0) : nodes(vector<T*>())+    { nodes.reserve(size); }++    ~Memory_Manager()+    {+      for (size_t i = 0, S = nodes.size(); i < S; ++i) {+        // cout << "deleting " << typeid(*nodes[i]).name() << endl;+        delete nodes[i];+      }+    }++    T* operator()(T* np)+    {+      nodes.push_back(np);+      // cout << "registering " << typeid(*np).name() << endl;+      return np;+    }++    void remove(T* np)+    {+      nodes.erase(find(nodes.begin(), nodes.end(), np));+    }+  };+}++template <typename T>+inline void* operator new(size_t size, Sass::Memory_Manager<T>& mem_mgr)+{ return mem_mgr(static_cast<T*>(operator new(size))); }++template <typename T>+inline void operator delete(void *np, Sass::Memory_Manager<T>& mem_mgr)+{+  mem_mgr.remove(reinterpret_cast<T*>(np));+  operator delete(np);+}++#endif
+ libsass/node.cpp view
@@ -0,0 +1,251 @@+#include "node.hpp"+#include "to_string.hpp"+#include "context.hpp"+#include "parser.hpp"++namespace Sass {+++  Node Node::createCombinator(const Complex_Selector::Combinator& combinator) {+    NodeDequePtr null;+    return Node(COMBINATOR, combinator, NULL /*pSelector*/, null /*pCollection*/);+  }+++  Node Node::createSelector(Complex_Selector* pSelector, Context& ctx) {+    NodeDequePtr null;++    Complex_Selector* pStripped = pSelector->clone(ctx);+    pStripped->tail(NULL);+    pStripped->combinator(Complex_Selector::ANCESTOR_OF);++    return Node(SELECTOR, Complex_Selector::ANCESTOR_OF, pStripped, null /*pCollection*/);+  }+++  Node Node::createCollection() {+    NodeDequePtr pEmptyCollection = make_shared<NodeDeque>();+    return Node(COLLECTION, Complex_Selector::ANCESTOR_OF, NULL /*pSelector*/, pEmptyCollection);+  }+++  Node Node::createCollection(const NodeDeque& values) {+    NodeDequePtr pShallowCopiedCollection = make_shared<NodeDeque>(values);+    return Node(COLLECTION, Complex_Selector::ANCESTOR_OF, NULL /*pSelector*/, pShallowCopiedCollection);+  }+++  Node Node::createNil() {+    NodeDequePtr null;+    return Node(NIL, Complex_Selector::ANCESTOR_OF, NULL /*pSelector*/, null /*pCollection*/);+  }+++  Node::Node(const TYPE& type, Complex_Selector::Combinator combinator, Complex_Selector* pSelector, NodeDequePtr& pCollection)+  : got_line_feed(false), mType(type), mCombinator(combinator), mpSelector(pSelector), mpCollection(pCollection)+  { /* if (pSelector) got_line_feed = pSelector->has_line_feed(); */ }+++  Node Node::clone(Context& ctx) const {+    NodeDequePtr pNewCollection = make_shared<NodeDeque>();+    if (mpCollection) {+      for (NodeDeque::iterator iter = mpCollection->begin(), iterEnd = mpCollection->end(); iter != iterEnd; iter++) {+        Node& toClone = *iter;+        pNewCollection->push_back(toClone.clone(ctx));+      }+    }++    return Node(mType, mCombinator, mpSelector ? mpSelector->clone(ctx) : NULL, pNewCollection);+  }+++  bool Node::contains(const Node& potentialChild, bool simpleSelectorOrderDependent) const {+  	bool found = false;++    for (NodeDeque::iterator iter = mpCollection->begin(), iterEnd = mpCollection->end(); iter != iterEnd; iter++) {+      Node& toTest = *iter;++      if (nodesEqual(toTest, potentialChild, simpleSelectorOrderDependent)) {+        found = true;+        break;+      }+    }++    return found;+  }+++  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()) {+      return false;+    }++    if (lhs.isCombinator()) {++    	return lhs.combinator() == rhs.combinator();++    } else if (lhs.isNil()) {++      return true; // no state to check++    } else if (lhs.isSelector()){++      return selectors_equal(*lhs.selector(), *rhs.selector(), simpleSelectorOrderDependent);++    } else if (lhs.isCollection()) {++      if (lhs.collection()->size() != rhs.collection()->size()) {+        return false;+      }++      for (NodeDeque::iterator lhsIter = lhs.collection()->begin(), lhsIterEnd = lhs.collection()->end(),+           rhsIter = rhs.collection()->begin(); lhsIter != lhsIterEnd; lhsIter++, rhsIter++) {++        if (!nodesEqual(*lhsIter, *rhsIter, simpleSelectorOrderDependent)) {+          return false;+        }++      }++      return true;++    }++    // We shouldn't get here.+    throw "Comparing unknown node types. A new type was probably added and this method wasn't implemented for it.";+  }+++  void Node::plus(Node& rhs) {+  	if (!this->isCollection() || !rhs.isCollection()) {+    	throw "Both the current node and rhs must be collections.";+    }+  	this->collection()->insert(this->collection()->end(), rhs.collection()->begin(), rhs.collection()->end());+  }++#ifdef DEBUG+  ostream& operator<<(ostream& os, const Node& node) {++    if (node.isCombinator()) {++      switch (node.combinator()) {+        case Complex_Selector::ANCESTOR_OF: os << "\" \""; break;+        case Complex_Selector::PARENT_OF:   os << "\">\""; break;+        case Complex_Selector::PRECEDES:    os << "\"~\""; break;+        case Complex_Selector::ADJACENT_TO: os << "\"+\""; break;+      }++    } else if (node.isNil()) {++      os << "nil";++    } else if (node.isSelector()){++      To_String to_string;+      os << node.selector()->head()->perform(&to_string);++    } else if (node.isCollection()) {++			os << "[";++      for (NodeDeque::iterator iter = node.collection()->begin(), iterBegin = node.collection()->begin(), iterEnd = node.collection()->end(); iter != iterEnd; iter++) {+        if (iter != iterBegin) {+          os << ", ";+        }++				os << (*iter);+      }++      os << "]";++    }++    return os;++  }+#endif+++  Node complexSelectorToNode(Complex_Selector* pToConvert, Context& ctx) {+    if (pToConvert == NULL) {+      return Node::createNil();+    }++    Node node = Node::createCollection();++    while (pToConvert) {++      // the first Complex_Selector may contain a dummy head pointer, skip it.+      if (pToConvert->head() != NULL && !pToConvert->head()->is_empty_reference()) {+        node.collection()->push_back(Node::createSelector(pToConvert, ctx));+      }++      if (pToConvert->combinator() != Complex_Selector::ANCESTOR_OF) {+        node.collection()->push_back(Node::createCombinator(pToConvert->combinator()));+      }++      pToConvert = pToConvert->tail();+    }++    return node;+  }+++  Complex_Selector* nodeToComplexSelector(const Node& toConvert, Context& ctx) {+    if (toConvert.isNil()) {+      return NULL;+    }+++    if (!toConvert.isCollection()) {+      throw "The node to convert to a Complex_Selector* must be a collection type or nil.";+    }+++    NodeDeque& childNodes = *toConvert.collection();++    string noPath("");+    Position noPosition(-1, -1, -1);+    Complex_Selector* pFirst = new (ctx.mem) Complex_Selector(ParserState("[NODE]"), Complex_Selector::ANCESTOR_OF, NULL, NULL);+    Complex_Selector* pCurrent = pFirst;++    for (NodeDeque::iterator childIter = childNodes.begin(), childIterEnd = childNodes.end(); childIter != childIterEnd; childIter++) {++      Node& child = *childIter;++      if (child.isSelector()) {+        pCurrent->tail(child.selector()->clone(ctx));   // JMA - need to clone the selector, because they can end up getting shared across Node collections, and can result in an infinite loop during the call to parentSuperselector()+        pCurrent = pCurrent->tail();+      } else if (child.isCombinator()) {+        pCurrent->combinator(child.combinator());++        // if the next node is also a combinator, create another Complex_Selector to hold it so it doesn't replace the current combinator+        if (childIter+1 != childIterEnd) {+          Node& nextNode = *(childIter+1);+          if (nextNode.isCombinator()) {+            pCurrent->tail(new (ctx.mem) Complex_Selector(ParserState("[NODE]"), Complex_Selector::ANCESTOR_OF, NULL, NULL));+            pCurrent = pCurrent->tail();+          }+        }+      } else {+        throw "The node to convert's children must be only combinators or selectors.";+      }+    }++    // Put the dummy Compound_Selector in the first position, for consistency with the rest of libsass+    Compound_Selector* fakeHead = new (ctx.mem) Compound_Selector(ParserState("[NODE]"), 1);+    Selector_Reference* selectorRef = new (ctx.mem) Selector_Reference(ParserState("[NODE]"), NULL);+    fakeHead->elements().push_back(selectorRef);+    pFirst->head(fakeHead);+    pFirst->has_line_feed(pFirst->has_line_feed() || pFirst->tail()->has_line_feed() || toConvert.got_line_feed);++    return pFirst;+  }+++}
+ libsass/node.hpp view
@@ -0,0 +1,121 @@+#ifndef SASS_NODE_H+#define SASS_NODE_H++#include <deque>+#include <iostream>+#include <memory>++#include "ast.hpp"+++namespace Sass {+++  using namespace std;+++  class Context;++  /*+   There are a lot of stumbling blocks when trying to port the ruby extend code to C++. The biggest is the choice of+   data type. The ruby code will pretty seamlessly switch types between an Array<SimpleSequence or Op> (libsass'+   equivalent is the Complex_Selector) to a Sequence, which contains more metadata about the sequence than just the+   selector info. They also have the ability to have arbitrary nestings of arrays like [1, [2]], which is hard to+   implement using Array equivalents in C++ (like the deque or vector). They also have the ability to include nil+   in the arrays, like [1, nil, 3], which has potential semantic differences than an empty array [1, [], 3]. To be+   able to represent all of these as unique cases, we need to create a tree of variant objects. The tree nature allows+   the inconsistent nesting levels. The variant nature (while making some of the C++ code uglier) allows the code to+   more closely match the ruby code, which is a huge benefit when attempting to implement an complex algorithm like+   the Extend operator.++   Note that the current libsass data model also pairs the combinator with the Complex_Selector that follows it, but+   ruby sass has no such restriction, so we attempt to create a data structure that can handle them split apart.+   */++  class Node;+  typedef deque<Node> NodeDeque;+  typedef shared_ptr<NodeDeque> NodeDequePtr;++  class Node {+  public:+    enum TYPE {+      SELECTOR,+      COMBINATOR,+      COLLECTION,+      NIL+    };++		TYPE type() const { return mType; }+    bool isCombinator() const { return mType == COMBINATOR; }+    bool isSelector() const { return mType == SELECTOR; }+    bool isCollection() const { return mType == COLLECTION; }+    bool isNil() const { return mType == NIL; }+    bool got_line_feed;++    Complex_Selector::Combinator combinator() const { return mCombinator; }++    Complex_Selector* selector() { return mpSelector; }+    const Complex_Selector* selector() const { return mpSelector; }++    NodeDequePtr collection() { return mpCollection; }+    const NodeDequePtr collection() const { return mpCollection; }++    static Node createCombinator(const Complex_Selector::Combinator& combinator);++    // This method will clone the selector, stripping off the tail and combinator+    static Node createSelector(Complex_Selector* pSelector, Context& ctx);++    static Node createCollection();+    static Node createCollection(const NodeDeque& values);++    static Node createNil();++    Node clone(Context& ctx) const;++    bool operator==(const Node& rhs) const;+    inline bool operator!=(const Node& rhs) const { return !(*this == rhs); }+++    /*+    COLLECTION FUNCTIONS++    Most types don't need any helper methods (nil and combinator due to their simplicity and+    selector due to the fact that we leverage the non-node selector code on the Complex_Selector+    whereever possible). The following methods are intended to be called on Node objects whose+    type is COLLECTION only.+    */++    // rhs and this must be node collections. Shallow copy the nodes from rhs to the end of this.+    // This function DOES NOT remove the nodes from rhs.+    void plus(Node& rhs);++    // potentialChild must be a node collection of selectors/combinators. this must be a collection+    // of collections of nodes/combinators. This method checks if potentialChild is a child of this+    // Node.+    bool contains(const Node& potentialChild, bool simpleSelectorOrderDependent) const;++  private:+    // Private constructor; Use the static methods (like createCombinator and createSelector)+    // to instantiate this object. This is more expressive, and it allows us to break apart each+    // case into separate functions.+    Node(const TYPE& type, Complex_Selector::Combinator combinator, Complex_Selector* pSelector, NodeDequePtr& pCollection);++    TYPE mType;++    // TODO: can we union these to save on memory?+    Complex_Selector::Combinator mCombinator;+    Complex_Selector* mpSelector; // this is an AST_Node, so it will be handled by the Memory_Manager+    NodeDequePtr mpCollection;+  };++#ifdef DEBUG+  ostream& operator<<(ostream& os, const Node& node);+#endif+  Node complexSelectorToNode(Complex_Selector* pToConvert, Context& ctx);+  Complex_Selector* nodeToComplexSelector(const Node& toConvert, Context& ctx);++  bool nodesEqual(const Node& one, const Node& two, bool simpleSelectorOrderDependent);++}++#endif
+ libsass/operation.hpp view
@@ -0,0 +1,166 @@+#ifndef SASS_OPERATION_H+#define SASS_OPERATION_H++#include <iostream>+#include <typeinfo>++#include "ast_fwd_decl.hpp"++namespace Sass {+  using namespace std;++  template<typename T>+  class Operation {+  public:+    virtual T operator()(AST_Node* x)               = 0;+    virtual ~Operation()                            { }+    // statements+    virtual T operator()(Block* x)                  = 0;+    virtual T operator()(Ruleset* x)                = 0;+    virtual T operator()(Propset* x)                = 0;+    virtual T operator()(Bubble* x)                 = 0;+    virtual T operator()(Feature_Block* x)          = 0;+    virtual T operator()(Media_Block* x)            = 0;+    virtual T operator()(At_Root_Block* x)          = 0;+    virtual T operator()(At_Rule* x)                = 0;+    virtual T operator()(Keyframe_Rule* x)          = 0;+    virtual T operator()(Declaration* x)            = 0;+    virtual T operator()(Assignment* x)             = 0;+    virtual T operator()(Import* x)                 = 0;+    virtual T operator()(Import_Stub* x)            = 0;+    virtual T operator()(Warning* x)                = 0;+    virtual T operator()(Error* x)                  = 0;+    virtual T operator()(Debug* x)                  = 0;+    virtual T operator()(Comment* x)                = 0;+    virtual T operator()(If* x)                     = 0;+    virtual T operator()(For* x)                    = 0;+    virtual T operator()(Each* x)                   = 0;+    virtual T operator()(While* x)                  = 0;+    virtual T operator()(Return* x)                 = 0;+    virtual T operator()(Content* x)                = 0;+    virtual T operator()(Extension* x)              = 0;+    virtual T operator()(Definition* x)             = 0;+    virtual T operator()(Mixin_Call* x)             = 0;+    // expressions+    virtual T operator()(List* x)                   = 0;+    virtual T operator()(Map* x)                    = 0;+    virtual T operator()(Binary_Expression* x)      = 0;+    virtual T operator()(Unary_Expression* x)       = 0;+    virtual T operator()(Function_Call* x)          = 0;+    virtual T operator()(Function_Call_Schema* x)   = 0;+    virtual T operator()(Variable* x)               = 0;+    virtual T operator()(Textual* x)                = 0;+    virtual T operator()(Number* x)                 = 0;+    virtual T operator()(Color* x)                  = 0;+    virtual T operator()(Boolean* x)                = 0;+    virtual T operator()(String_Schema* x)          = 0;+    virtual T operator()(String_Constant* x)        = 0;+    virtual T operator()(Feature_Query* x)          = 0;+    virtual T operator()(Feature_Query_Condition* x)= 0;+    virtual T operator()(Media_Query* x)            = 0;+    virtual T operator()(Media_Query_Expression* x) = 0;+    virtual T operator()(At_Root_Expression* x)     = 0;+    virtual T operator()(Null* x)                   = 0;+    virtual T operator()(Parent_Selector* x)        = 0;+    // parameters and arguments+    virtual T operator()(Parameter* x)              = 0;+    virtual T operator()(Parameters* x)             = 0;+    virtual T operator()(Argument* x)               = 0;+    virtual T operator()(Arguments* x)              = 0;+    // selectors+    virtual T operator()(Selector_Schema* x)        = 0;+    virtual T operator()(Selector_Reference* x)     = 0;+    virtual T operator()(Selector_Placeholder* x)   = 0;+    virtual T operator()(Type_Selector* x)          = 0;+    virtual T operator()(Selector_Qualifier* x)     = 0;+    virtual T operator()(Attribute_Selector* x)     = 0;+    virtual T operator()(Pseudo_Selector* x)        = 0;+    virtual T operator()(Wrapped_Selector* x)       = 0;+    virtual T operator()(Compound_Selector* x)      = 0;+    virtual T operator()(Complex_Selector* x)       = 0;+    virtual T operator()(Selector_List* x)          = 0;++    template <typename U>+    T fallback(U x) { return T(); }+  };++  template <typename T, typename D>+  class Operation_CRTP : public Operation<T> {+  public:+    virtual T operator()(AST_Node* x)               { return static_cast<D*>(this)->fallback(x); }+    virtual ~Operation_CRTP()                       = 0;+    // statements+    virtual T operator()(Block* x)                  { return static_cast<D*>(this)->fallback(x); }+    virtual T operator()(Ruleset* x)                { return static_cast<D*>(this)->fallback(x); }+    virtual T operator()(Propset* x)                { return static_cast<D*>(this)->fallback(x); }+    virtual T operator()(Bubble* x)                 { return static_cast<D*>(this)->fallback(x); }+    virtual T operator()(Feature_Block* x)          { return static_cast<D*>(this)->fallback(x); }+    virtual T operator()(Media_Block* x)            { return static_cast<D*>(this)->fallback(x); }+    virtual T operator()(At_Root_Block* x)          { return static_cast<D*>(this)->fallback(x); }+    virtual T operator()(At_Rule* x)                { return static_cast<D*>(this)->fallback(x); }+    virtual T operator()(Keyframe_Rule* x)          { return static_cast<D*>(this)->fallback(x); }+    virtual T operator()(Declaration* x)            { return static_cast<D*>(this)->fallback(x); }+    virtual T operator()(Assignment* x)             { return static_cast<D*>(this)->fallback(x); }+    virtual T operator()(Import* x)                 { return static_cast<D*>(this)->fallback(x); }+    virtual T operator()(Import_Stub* x)            { return static_cast<D*>(this)->fallback(x); }+    virtual T operator()(Warning* x)                { return static_cast<D*>(this)->fallback(x); }+    virtual T operator()(Error* x)                  { return static_cast<D*>(this)->fallback(x); }+    virtual T operator()(Debug* x)                  { return static_cast<D*>(this)->fallback(x); }+    virtual T operator()(Comment* x)                { return static_cast<D*>(this)->fallback(x); }+    virtual T operator()(If* x)                     { return static_cast<D*>(this)->fallback(x); }+    virtual T operator()(For* x)                    { return static_cast<D*>(this)->fallback(x); }+    virtual T operator()(Each* x)                   { return static_cast<D*>(this)->fallback(x); }+    virtual T operator()(While* x)                  { return static_cast<D*>(this)->fallback(x); }+    virtual T operator()(Return* x)                 { return static_cast<D*>(this)->fallback(x); }+    virtual T operator()(Content* x)                { return static_cast<D*>(this)->fallback(x); }+    virtual T operator()(Extension* x)              { return static_cast<D*>(this)->fallback(x); }+    virtual T operator()(Definition* x)             { return static_cast<D*>(this)->fallback(x); }+    virtual T operator()(Mixin_Call* x)             { return static_cast<D*>(this)->fallback(x); }+    // expressions+    virtual T operator()(List* x)                   { return static_cast<D*>(this)->fallback(x); }+    virtual T operator()(Map* x)                    { return static_cast<D*>(this)->fallback(x); }+    virtual T operator()(Binary_Expression* x)      { return static_cast<D*>(this)->fallback(x); }+    virtual T operator()(Unary_Expression* x)       { return static_cast<D*>(this)->fallback(x); }+    virtual T operator()(Function_Call* x)          { return static_cast<D*>(this)->fallback(x); }+    virtual T operator()(Function_Call_Schema* x)   { return static_cast<D*>(this)->fallback(x); }+    virtual T operator()(Variable* x)               { return static_cast<D*>(this)->fallback(x); }+    virtual T operator()(Textual* x)                { return static_cast<D*>(this)->fallback(x); }+    virtual T operator()(Number* x)                 { return static_cast<D*>(this)->fallback(x); }+    virtual T operator()(Color* x)                  { return static_cast<D*>(this)->fallback(x); }+    virtual T operator()(Boolean* x)                { return static_cast<D*>(this)->fallback(x); }+    virtual T operator()(String_Schema* x)          { return static_cast<D*>(this)->fallback(x); }+    virtual T operator()(String_Constant* x)        { return static_cast<D*>(this)->fallback(x); }+    virtual T operator()(Feature_Query* x)          { return static_cast<D*>(this)->fallback(x); }+    virtual T operator()(Feature_Query_Condition* x){ return static_cast<D*>(this)->fallback(x); }+    virtual T operator()(Media_Query* x)            { return static_cast<D*>(this)->fallback(x); }+    virtual T operator()(Media_Query_Expression* x) { return static_cast<D*>(this)->fallback(x); }+    virtual T operator()(At_Root_Expression* x)     { return static_cast<D*>(this)->fallback(x); }+    virtual T operator()(Null* x)                   { return static_cast<D*>(this)->fallback(x); }+    virtual T operator()(Parent_Selector* x)        { return static_cast<D*>(this)->fallback(x); }+    // parameters and arguments+    virtual T operator()(Parameter* x)              { return static_cast<D*>(this)->fallback(x); }+    virtual T operator()(Parameters* x)             { return static_cast<D*>(this)->fallback(x); }+    virtual T operator()(Argument* x)               { return static_cast<D*>(this)->fallback(x); }+    virtual T operator()(Arguments* x)              { return static_cast<D*>(this)->fallback(x); }+    // selectors+    virtual T operator()(Selector_Schema* x)        { return static_cast<D*>(this)->fallback(x); }+    virtual T operator()(Selector_Reference* x)     { return static_cast<D*>(this)->fallback(x); }+    virtual T operator()(Selector_Placeholder* x)   { return static_cast<D*>(this)->fallback(x); }+    virtual T operator()(Type_Selector* x)          { return static_cast<D*>(this)->fallback(x); }+    virtual T operator()(Selector_Qualifier* x)     { return static_cast<D*>(this)->fallback(x); }+    virtual T operator()(Attribute_Selector* x)     { return static_cast<D*>(this)->fallback(x); }+    virtual T operator()(Pseudo_Selector* x)        { return static_cast<D*>(this)->fallback(x); }+    virtual T operator()(Wrapped_Selector* x)       { return static_cast<D*>(this)->fallback(x); }+    virtual T operator()(Compound_Selector* x)      { return static_cast<D*>(this)->fallback(x); }+    virtual T operator()(Complex_Selector* x)       { return static_cast<D*>(this)->fallback(x); }+    virtual T operator()(Selector_List* x)          { return static_cast<D*>(this)->fallback(x); }++    template <typename U>+    T fallback(U x)                                 { return T(); }+  };+  template<typename T, typename D>+  inline Operation_CRTP<T, D>::~Operation_CRTP()    { }++}++#endif
+ libsass/output.cpp view
@@ -0,0 +1,400 @@+#include "ast.hpp"+#include "output.hpp"+#include "to_string.hpp"++namespace Sass {+  using namespace std;++  Output::Output(Context* ctx)+  : Inspect(Emitter(ctx)),+    charset(""),+    top_imports(0),+    top_comments(0)+  {}++  Output::~Output() { }++  void Output::fallback_impl(AST_Node* n)+  {+    return n->perform(this);+  }++  void Output::operator()(Import* imp)+  {+    top_imports.push_back(imp);+  }++  OutputBuffer Output::get_buffer(void)+  {++    Emitter emitter(ctx);+    Inspect inspect(emitter);++    size_t size_com = top_comments.size();+    for (size_t i = 0; i < size_com; i++) {+      top_comments[i]->perform(&inspect);+      inspect.append_mandatory_linefeed();+    }++    size_t size_imp = top_imports.size();+    for (size_t i = 0; i < size_imp; i++) {+      top_imports[i]->perform(&inspect);+      inspect.append_mandatory_linefeed();+    }++    // flush scheduled outputs+    inspect.finalize();+    // prepend buffer on top+    prepend_output(inspect.output());+    // make sure we end with a linefeed+    if (!ends_with(wbuf.buffer, ctx->linefeed)) {+      // if the output is not completely empty+      if (!wbuf.buffer.empty()) append_string(ctx->linefeed);+    }++    // search for unicode char+    for(const char& chr : wbuf.buffer) {+      // skip all ascii chars+      if (chr >= 0) continue;+      // declare the charset+      if (output_style() != COMPRESSED)+        charset = "@charset \"UTF-8\";"+                  + ctx->linefeed;+      else charset = "\xEF\xBB\xBF";+      // abort search+      break;+    }++    // add charset as first line, before comments and imports+    if (!charset.empty()) prepend_string(charset);++    return wbuf;++  }++  void Output::operator()(Comment* c)+  {+    To_String to_string(ctx);+    string txt = c->text()->perform(&to_string);+    // if (indentation && txt == "/**/") return;+    bool important = c->is_important();+    if (output_style() != COMPRESSED || important) {+      if (buffer().size() + top_imports.size() == 0) {+        top_comments.push_back(c);+      } else {+        in_comment = true;+        append_indentation();+        c->text()->perform(this);+        in_comment = false;+        if (indentation == 0) {+          append_mandatory_linefeed();+        } else {+          append_optional_linefeed();+        }+      }+    }+  }++  void Output::operator()(Ruleset* r)+  {+    Selector* s     = r->selector();+    Block*    b     = r->block();+    bool      decls = false;++    // Filter out rulesets that aren't printable (process its children though)+    if (!Util::isPrintable(r, output_style())) {+      for (size_t i = 0, L = b->length(); i < L; ++i) {+        Statement* stm = (*b)[i];+        if (dynamic_cast<Has_Block*>(stm)) {+          stm->perform(this);+        }+      }+      return;+    }++    if (b->has_non_hoistable()) {+      decls = true;+      if (output_style() == NESTED) indentation += r->tabs();+      if (ctx && ctx->source_comments) {+        stringstream ss;+        append_indentation();+        ss << "/* line " << r->pstate().line+1 << ", " << r->pstate().path << " */";+        append_string(ss.str());+        append_optional_linefeed();+      }+      s->perform(this);+      append_scope_opener(b);+      for (size_t i = 0, L = b->length(); i < L; ++i) {+        Statement* stm = (*b)[i];+        bool bPrintExpression = true;+        // Check print conditions+        if (typeid(*stm) == typeid(Declaration)) {+          Declaration* dec = static_cast<Declaration*>(stm);+          if (dec->value()->concrete_type() == Expression::STRING) {+            String_Constant* valConst = static_cast<String_Constant*>(dec->value());+            string val(valConst->value());+            if (dynamic_cast<String_Quoted*>(valConst)) {+              if (!valConst->quote_mark() && val.empty()) {+                bPrintExpression = false;+              }+            }+          }+          else if (dec->value()->concrete_type() == Expression::LIST) {+            List* list = static_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* item = (*list)[list_i];+              if (!item->is_invisible()) all_invisible = false;+            }+            if (all_invisible) bPrintExpression = false;+          }+        }+        // Print if OK+        if (!stm->is_hoistable() && bPrintExpression) {+          stm->perform(this);+        }+      }+      if (output_style() == NESTED) indentation -= r->tabs();+      append_scope_closer(b);+    }++    if (b->has_hoistable()) {+      if (decls) ++indentation;+      for (size_t i = 0, L = b->length(); i < L; ++i) {+        Statement* stm = (*b)[i];+        if (stm->is_hoistable()) {+          stm->perform(this);+        }+      }+      if (decls) --indentation;+    }+  }++  void Output::operator()(Keyframe_Rule* r)+  {+    Block* b = r->block();+    Selector* v = r->selector();++    if (v) {+      v->perform(this);+    }++    if (!b) {+      append_colon_separator();+      return;+    }++    append_scope_opener();+    for (size_t i = 0, L = b->length(); i < L; ++i) {+      Statement* stm = (*b)[i];+      if (!stm->is_hoistable()) {+        stm->perform(this);+        if (i < L - 1) append_special_linefeed();+      }+    }++    for (size_t i = 0, L = b->length(); i < L; ++i) {+      Statement* stm = (*b)[i];+      if (stm->is_hoistable()) {+        stm->perform(this);+      }+    }++    append_scope_closer();+  }++  void Output::operator()(Feature_Block* f)+  {+    if (f->is_invisible()) return;++    Feature_Query* q    = f->feature_queries();+    Block* b            = f->block();++    // Filter out feature blocks that aren't printable (process its children though)+    if (!Util::isPrintable(f, output_style())) {+      for (size_t i = 0, L = b->length(); i < L; ++i) {+        Statement* stm = (*b)[i];+        if (dynamic_cast<Has_Block*>(stm)) {+          stm->perform(this);+        }+      }+      return;+    }++    if (output_style() == NESTED) indentation += f->tabs();+    append_indentation();+    append_token("@supports", f);+    append_mandatory_space();+    q->perform(this);+    append_scope_opener();++    Selector* e = f->selector();+    if (e && b->has_non_hoistable()) {+      // JMA - hoisted, output the non-hoistable in a nested block, followed by the hoistable+      e->perform(this);+      append_scope_opener();++      for (size_t i = 0, L = b->length(); i < L; ++i) {+        Statement* stm = (*b)[i];+        if (!stm->is_hoistable()) {+          stm->perform(this);+        }+      }++      append_scope_closer();++      for (size_t i = 0, L = b->length(); i < L; ++i) {+        Statement* stm = (*b)[i];+        if (stm->is_hoistable()) {+          stm->perform(this);+        }+      }+    }+    else {+      // JMA - not hoisted, just output in order+      for (size_t i = 0, L = b->length(); i < L; ++i) {+        Statement* stm = (*b)[i];+        stm->perform(this);+        if (i < L - 1) append_special_linefeed();+      }+    }++    if (output_style() == NESTED) indentation -= f->tabs();++    append_scope_closer();++  }++  void Output::operator()(Media_Block* m)+  {+    if (m->is_invisible()) return;++    List*  q     = m->media_queries();+    Block* b     = m->block();++    // Filter out media blocks that aren't printable (process its children though)+    if (!Util::isPrintable(m, output_style())) {+      for (size_t i = 0, L = b->length(); i < L; ++i) {+        Statement* stm = (*b)[i];+        if (dynamic_cast<Has_Block*>(stm)) {+          stm->perform(this);+        }+      }+      return;+    }+    if (output_style() == NESTED) indentation += m->tabs();+    append_indentation();+    append_token("@media", m);+    append_mandatory_space();+    in_media_block = true;+    q->perform(this);+    in_media_block = false;+    append_scope_opener();++    Selector* e = m->selector();+    if (e && b->has_non_hoistable()) {+      // JMA - hoisted, output the non-hoistable in a nested block, followed by the hoistable+      e->perform(this);+      append_scope_opener();++      for (size_t i = 0, L = b->length(); i < L; ++i) {+        Statement* stm = (*b)[i];+        if (!stm->is_hoistable()) {+          stm->perform(this);+        }+      }++      append_scope_closer();++      for (size_t i = 0, L = b->length(); i < L; ++i) {+        Statement* stm = (*b)[i];+        if (stm->is_hoistable()) {+          stm->perform(this);+        }+      }+    }+    else {+      // JMA - not hoisted, just output in order+      for (size_t i = 0, L = b->length(); i < L; ++i) {+        Statement* stm = (*b)[i];+        stm->perform(this);+        if (i < L - 1) append_special_linefeed();+      }+    }++    if (output_style() == NESTED) indentation -= m->tabs();+    append_scope_closer();+  }++  void Output::operator()(At_Rule* a)+  {+    string      kwd   = a->keyword();+    Selector*   s     = a->selector();+    Expression* v     = a->value();+    Block*      b     = a->block();++    append_indentation();+    append_token(kwd, a);+    if (s) {+      append_mandatory_space();+      in_wrapped = true;+      s->perform(this);+      in_wrapped = false;+    }+    else if (v) {+      append_mandatory_space();+      v->perform(this);+    }+    if (!b) {+      append_delimiter();+      return;+    }++    if (b->is_invisible() || b->length() == 0) {+      return append_string(" {}");+    }++    append_scope_opener();++    for (size_t i = 0, L = b->length(); i < L; ++i) {+      Statement* stm = (*b)[i];+      if (!stm->is_hoistable()) {+        stm->perform(this);+        if (i < L - 1) append_special_linefeed();+      }+    }++    for (size_t i = 0, L = b->length(); i < L; ++i) {+      Statement* stm = (*b)[i];+      if (stm->is_hoistable()) {+        stm->perform(this);+        if (i < L - 1) append_special_linefeed();+      }+    }++    append_scope_closer();+  }++  void Output::operator()(String_Quoted* s)+  {+    if (s->quote_mark()) {+      append_token(quote(s->value(), s->quote_mark()), s);+    } else if (!in_comment) {+      append_token(string_to_output(s->value()), s);+    } else {+      append_token(s->value(), s);+    }+  }++  void Output::operator()(String_Constant* s)+  {+    if (String_Quoted* quoted = dynamic_cast<String_Quoted*>(s)) {+      return Output::operator()(quoted);+    } else if (!in_comment) {+      append_token(string_to_output(s->value()), s);+    } else {+      append_token(s->value(), s);+    }+  }++}
+ libsass/output.hpp view
@@ -0,0 +1,56 @@+#ifndef SASS_OUTPUT_H+#define SASS_OUTPUT_H++#include <string>+#include <vector>++#include "util.hpp"+#include "inspect.hpp"+#include "operation.hpp"++namespace Sass {+  class Context;+  using namespace std;++  // Refactor to make it generic to find linefeed (look behind)+  inline bool ends_with(std::string const & value, std::string const & ending)+  {+    if (ending.size() > value.size()) return false;+    return std::equal(ending.rbegin(), ending.rend(), value.rbegin());+  }++  class Output : public Inspect {+  protected:+    using Inspect::operator();++  public:+    // change to Emitter+    Output(Context* ctx);+    virtual ~Output();++  protected:+    string charset;+    vector<Import*> top_imports;+    vector<Comment*> top_comments;++  public:+    OutputBuffer get_buffer(void);++    virtual void operator()(Ruleset*);+    // virtual void operator()(Propset*);+    virtual void operator()(Feature_Block*);+    virtual void operator()(Media_Block*);+    virtual void operator()(At_Rule*);+    virtual void operator()(Keyframe_Rule*);+    virtual void operator()(Import*);+    virtual void operator()(Comment*);+    virtual void operator()(String_Quoted*);+    virtual void operator()(String_Constant*);++    void fallback_impl(AST_Node* n);++  };++}++#endif
+ libsass/parser.cpp view
@@ -0,0 +1,2228 @@+#include <cstdlib>+#include <iostream>+#include <vector>+#include "parser.hpp"+#include "file.hpp"+#include "inspect.hpp"+#include "to_string.hpp"+#include "constants.hpp"+#include "util.hpp"+#include "prelexer.hpp"+#include "sass_functions.h"++#include <typeinfo>++namespace Sass {+  using namespace std;+  using namespace Constants;++  Parser Parser::from_c_str(const char* str, Context& ctx, ParserState pstate)+  {+    Parser p(ctx, pstate);+    p.source   = str;+    p.position = p.source;+    p.end      = str + strlen(str);+    Block* root = new (ctx.mem) Block(pstate);+    p.block_stack.push_back(root);+    root->is_root(true);+    return p;+  }++  Parser Parser::from_c_str(const char* beg, const char* end, Context& ctx, ParserState pstate)+  {+    Parser p(ctx, pstate);+    p.source   = beg;+    p.position = p.source;+    p.end      = end;+    Block* root = new (ctx.mem) Block(pstate);+    p.block_stack.push_back(root);+    root->is_root(true);+    return p;+  }++  bool Parser::peek_newline(const char* start)+  {+    return peek_linefeed(start ? start : position);+  }++  Parser Parser::from_token(Token t, Context& ctx, ParserState pstate)+  {+    Parser p(ctx, pstate);+    p.source   = t.begin;+    p.position = p.source;+    p.end      = t.end;+    Block* root = new (ctx.mem) Block(pstate);+    p.block_stack.push_back(root);+    root->is_root(true);+    return p;+  }++  Block* Parser::parse()+  {+    Block* root = new (ctx.mem) Block(pstate);+    block_stack.push_back(root);+    root->is_root(true);+    read_bom();++    if (ctx.queue.size() == 1) {+      Import* pre = new (ctx.mem) Import(pstate);+      string load_path(ctx.queue[0].load_path);+      do_import(load_path, pre, ctx.c_headers, false);+      ctx.head_imports = ctx.queue.size() - 1;+      if (!pre->urls().empty()) (*root) << pre;+      if (!pre->files().empty()) {+        for (size_t i = 0, S = pre->files().size(); i < S; ++i) {+          (*root) << new (ctx.mem) Import_Stub(pstate, pre->files()[i]);+        }+      }+    }++    lex< optional_spaces >();+    Selector_Lookahead lookahead_result;+    while (position < end) {+      parse_block_comments(root);+      if (peek< kwd_import >()) {+        Import* imp = parse_import();+        if (!imp->urls().empty()) (*root) << imp;+        if (!imp->files().empty()) {+          for (size_t i = 0, S = imp->files().size(); i < S; ++i) {+            (*root) << new (ctx.mem) Import_Stub(pstate, imp->files()[i]);+          }+        }+        if (!lex< one_plus< exactly<';'> > >()) error("top-level @import directive must be terminated by ';'", pstate);+      }+      else if (peek< kwd_mixin >() || peek< kwd_function >()) {+        (*root) << parse_definition();+      }+      else if (peek< variable >()) {+        (*root) << parse_assignment();+        if (!lex< one_plus< exactly<';'> > >()) error("top-level variable binding must be terminated by ';'", pstate);+      }+      /*else if (peek< sequence< optional< exactly<'*'> >, alternatives< identifier_schema, identifier >, optional_spaces, exactly<':'>, optional_spaces, exactly<'{'> > >(position)) {+        (*root) << parse_propset();+      }*/+      else if (peek< kwd_include >() /* || peek< exactly<'+'> >() */) {+        Mixin_Call* mixin_call = parse_mixin_call();+        (*root) << mixin_call;+        if (!mixin_call->block() && !lex< one_plus< exactly<';'> > >()) error("top-level @include directive must be terminated by ';'", pstate);+      }+      else if (peek< kwd_if_directive >()) {+        (*root) << parse_if_directive();+      }+      else if (peek< kwd_for_directive >()) {+        (*root) << parse_for_directive();+      }+      else if (peek< kwd_each_directive >()) {+        (*root) << parse_each_directive();+      }+      else if (peek< kwd_while_directive >()) {+        (*root) << parse_while_directive();+      }+      else if (peek< kwd_media >()) {+        (*root) << parse_media_block();+      }+      else if (peek< kwd_at_root >()) {+        (*root) << parse_at_root_block();+      }+      else if (peek< kwd_supports >()) {+        (*root) << parse_feature_block();+      }+      else if (peek< kwd_warn >()) {+        (*root) << parse_warning();+        if (!lex< one_plus< exactly<';'> > >()) error("top-level @warn directive must be terminated by ';'", pstate);+      }+      else if (peek< kwd_err >()) {+        (*root) << parse_error();+        if (!lex< one_plus< exactly<';'> > >()) error("top-level @error directive must be terminated by ';'", pstate);+      }+      else if (peek< kwd_dbg >()) {+        (*root) << parse_debug();+        if (!lex< one_plus< exactly<';'> > >()) error("top-level @debug directive must be terminated by ';'", pstate);+      }+      // ignore the @charset directive for now+      else if (lex< exactly< charset_kwd > >()) {+        lex< quoted_string >();+        lex< one_plus< exactly<';'> > >();+      }+      else if (peek< at_keyword >()) {+        At_Rule* at_rule = parse_at_rule();+        (*root) << at_rule;+        if (!at_rule->block() && !lex< one_plus< exactly<';'> > >()) error("top-level directive must be terminated by ';'", pstate);+      }+      else if ((lookahead_result = lookahead_for_selector(position)).found) {+        (*root) << parse_ruleset(lookahead_result);+      }+      else if (peek< exactly<';'> >()) {+        lex< one_plus< exactly<';'> > >();+      }+      else {+        lex< css_whitespace >();+        if (position >= end) break;+        error("invalid top-level expression", after_token);+      }+      lex< optional_spaces >();+    }+    block_stack.pop_back();+    return root;+  }++  void Parser::add_single_file (Import* imp, string import_path) {++    string extension;+    string unquoted(unquote(import_path));+    if (unquoted.length() > 4) { // 2 quote marks + the 4 chars in .css+      // a string constant is guaranteed to end with a quote mark, so make sure to skip it when indexing from the end+      extension = unquoted.substr(unquoted.length() - 4, 4);+    }++    if (extension == ".css") {+      String_Constant* loc = new (ctx.mem) String_Constant(pstate, unquote(import_path));+      Argument* loc_arg = new (ctx.mem) Argument(pstate, loc);+      Arguments* loc_args = new (ctx.mem) Arguments(pstate);+      (*loc_args) << loc_arg;+      Function_Call* new_url = new (ctx.mem) Function_Call(pstate, "url", loc_args);+      imp->urls().push_back(new_url);+    }+    else {+      string current_dir = File::dir_name(path);+      string resolved(ctx.add_file(current_dir, unquoted));+      if (resolved.empty()) error("file to import not found or unreadable: " + unquoted + "\nCurrent dir: " + current_dir, pstate);+      imp->files().push_back(resolved);+    }++  }++  void Parser::import_single_file (Import* imp, string import_path) {++    if (!unquote(import_path).substr(0, 7).compare("http://") ||+        !unquote(import_path).substr(0, 8).compare("https://") ||+        !unquote(import_path).substr(0, 2).compare("//"))+    {+      imp->urls().push_back(new (ctx.mem) String_Quoted(pstate, import_path));+    }+    else {+      add_single_file(imp, import_path);+    }++  }++  bool Parser::do_import(const string& import_path, Import* imp, vector<Sass_Importer_Entry> importers, bool only_one)+  {+    bool has_import = false;+    string load_path = unquote(import_path);+    for (auto importer : importers) {+      // int priority = sass_importer_get_priority(importer);+      Sass_Importer_Fn fn = sass_importer_get_function(importer);+      if (Sass_Import_List includes =+          fn(load_path.c_str(), importer, ctx.c_compiler)+      ) {+        Sass_Import_List list = includes;+        while (*includes) {+          Sass_Import_Entry include = *includes;+          const char *file = sass_import_get_path(include);+          char* source = sass_import_take_source(include);+          size_t line = sass_import_get_error_line(include);+          size_t column = sass_import_get_error_column(include);+          const char* message = sass_import_get_error_message(include);+          if (message) {+            if (line == string::npos && column == string::npos) error(message, pstate);+            else error(message, ParserState(message, source, Position(line, column)));+          } else if (source) {+            if (file) {+              ctx.add_source(file, load_path, source);+              imp->files().push_back(file);+            } else {+              ctx.add_source(load_path, load_path, source);+              imp->files().push_back(load_path);+            }+          } else if(file) {+            import_single_file(imp, file);+          }+          ++includes;+        }+        // deallocate returned memory+        sass_delete_import_list(list);+        // set success flag+        has_import = true;+        // break import chain+        if (only_one) return true;+      }+    }+    // return result+    return has_import;+  }++  Import* Parser::parse_import()+  {+    lex< kwd_import >();+    Import* imp = new (ctx.mem) Import(pstate);+    bool first = true;+    do {+      while (lex< block_comment >());+      if (lex< quoted_string >()) {+        if (!do_import(lexed, imp, ctx.c_importers, true))+        {+          // push single file import+          import_single_file(imp, lexed);+        }+      }+      else if (lex< uri_prefix >()) {+        Arguments* args = new (ctx.mem) Arguments(pstate);+        Function_Call* result = new (ctx.mem) Function_Call(pstate, "url", args);+        if (lex < uri_value >()) { // chunk seems to work too!+          String* the_url = parse_interpolated_chunk(lexed);+          *args << new (ctx.mem) Argument(the_url->pstate(), the_url);+        }+        else {+          error("malformed URL", pstate);+        }+        if (!lex< exactly<')'> >()) error("URI is missing ')'", pstate);+        imp->urls().push_back(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<','> >());+    return imp;+  }++  Definition* Parser::parse_definition()+  {+    Definition::Type which_type = Definition::MIXIN;+    if      (lex< kwd_mixin >())    which_type = Definition::MIXIN;+    else if (lex< kwd_function >()) which_type = Definition::FUNCTION;+    string which_str(lexed);+    if (!lex< identifier >()) error("invalid name in " + which_str + " definition", pstate);+    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* params = parse_parameters();+    if (!peek< exactly<'{'> >()) error("body for " + which_str + " " + name + " must begin with a '{'", pstate);+    if (which_type == Definition::MIXIN) stack.push_back(mixin_def);+    else stack.push_back(function_def);+    Block* body = parse_block();+    stack.pop_back();+    Definition* def = new (ctx.mem) Definition(source_position_of_def, name, params, body, &ctx, which_type);+    return def;+  }++  Parameters* Parser::parse_parameters()+  {+    string name(lexed);+    Position position = after_token;+    Parameters* params = new (ctx.mem) Parameters(pstate);+    if (lex_css< exactly<'('> >()) {+      // if there's anything there at all+      if (!peek_css< exactly<')'> >()) {+        do (*params) << 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* Parser::parse_parameter()+  {+    while (lex< alternatives < spaces, block_comment > >());+    lex< variable >();+    string name(Util::normalize_underscores(lexed));+    ParserState pos = pstate;+    Expression* val = 0;+    bool is_rest = false;+    while (lex< alternatives < spaces, block_comment > >());+    if (lex< exactly<':'> >()) { // there's a default value+      while (lex< block_comment >());+      val = parse_space_list();+      val->is_delayed(false);+    }+    else if (lex< exactly< ellipsis > >()) {+      is_rest = true;+    }+    Parameter* p = new (ctx.mem) Parameter(pos, name, val, is_rest);+    return p;+  }++  Mixin_Call* Parser::parse_mixin_call()+  {+    lex< kwd_include >() /* || lex< exactly<'+'> >() */;+    if (!lex< identifier >()) error("invalid name in @include directive", pstate);+    ParserState source_position_of_call = pstate;+    string name(Util::normalize_underscores(lexed));+    Arguments* args = parse_arguments();+    Block* content = 0;+    if (peek< exactly<'{'> >()) {+      content = parse_block();+    }+    Mixin_Call* the_call = new (ctx.mem) Mixin_Call(source_position_of_call, name, args, content);+    return the_call;+  }++  Arguments* Parser::parse_arguments(bool has_url)+  {+    string name(lexed);+    Position position = after_token;+    Arguments* args = new (ctx.mem) Arguments(pstate);+    if (lex_css< exactly<'('> >()) {+      // if there's anything there at all+      if (!peek_css< exactly<')'> >()) {+        do (*args) << parse_argument(has_url);+        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* Parser::parse_argument(bool has_url)+  {++    Argument* arg;+    // some urls can look like line comments (parse literally - chunk would not work)+    if (has_url && lex< sequence < uri_value, lookahead < loosely<')'> > > >(false)) {+      String* the_url = parse_interpolated_chunk(lexed);+      arg = new (ctx.mem) Argument(the_url->pstate(), the_url);+    }+    else if (peek_css< sequence < variable, optional_css_comments, exactly<':'> > >()) {+      lex_css< variable >();+      string name(Util::normalize_underscores(lexed));+      ParserState p = pstate;+      lex_css< exactly<':'> >();+      Expression* val = parse_space_list();+      val->is_delayed(false);+      arg = new (ctx.mem) Argument(p, val, name);+    }+    else {+      bool is_arglist = false;+      bool is_keyword = false;+      Expression* val = parse_space_list();+      val->is_delayed(false);+      if (lex_css< exactly< ellipsis > >()) {+        if (val->concrete_type() == Expression::MAP) is_keyword = true;+        else is_arglist = true;+      }+      arg = new (ctx.mem) Argument(pstate, val, "", is_arglist, is_keyword);+    }+    return arg;+  }++  Assignment* Parser::parse_assignment()+  {+    lex< variable >();+    string name(Util::normalize_underscores(lexed));+    ParserState var_source_position = pstate;+    if (!lex< exactly<':'> >()) error("expected ':' after " + name + " in assignment statement", pstate);+    Expression* val = parse_list();+    val->is_delayed(false);+    bool is_default = false;+    bool is_global = false;+    while (peek< default_flag >() || peek< global_flag >()) {+      is_default = lex< default_flag >() || is_default;+      is_global = lex< global_flag >() || is_global;+    }+    Assignment* var = new (ctx.mem) Assignment(var_source_position, name, val, is_default, is_global);+    return var;+  }++  /* not used anymore - remove?+  Propset* Parser::parse_propset()+  {+    String* property_segment;+    if (peek< sequence< optional< exactly<'*'> >, identifier_schema > >()) {+      property_segment = parse_identifier_schema();+    }+    else {+      lex< sequence< optional< exactly<'*'> >, identifier > >();+      property_segment = new (ctx.mem) String_Quoted(pstate, lexed);+    }+    Propset* propset = new (ctx.mem) Propset(pstate, property_segment);+    lex< exactly<':'> >();++    if (!peek< exactly<'{'> >()) error("expected a '{' after namespaced property", pstate);++    propset->block(parse_block());++    propset->tabs(indentation);++    return propset;+  } */++  Ruleset* Parser::parse_ruleset(Selector_Lookahead lookahead)+  {+    Selector* sel;+    if (lookahead.has_interpolants) {+      sel = parse_selector_schema(lookahead.found);+    }+    else {+      sel = parse_selector_group();+    }+    bool old_in_at_root = in_at_root;+    lex < css_comments >();+    in_at_root = false;+    ParserState r_source_position = pstate;+    if (!peek< exactly<'{'> >()) error("expected a '{' after the selector", pstate);+    Block* block = parse_block();+    in_at_root = old_in_at_root;+    old_in_at_root = false;+    Ruleset* ruleset = new (ctx.mem) Ruleset(r_source_position, sel, block);+    return ruleset;+  }++  Selector_Schema* Parser::parse_selector_schema(const char* end_of_selector)+  {+    lex< optional_spaces >();+    const char* i = position;+    String_Schema* schema = new (ctx.mem) String_Schema(pstate);+    while (i < end_of_selector) {+      // try to parse mutliple interpolants+      if (const char* p = find_first_in_interval< exactly<hash_lbrace> >(i, end_of_selector)) {+        // accumulate the preceding segment if the position has advanced+        if (i < p) (*schema) << new (ctx.mem) String_Quoted(pstate, string(i, p));+        // skip to the delimiter by skipping occurences in quoted strings+        const char* j = skip_over_scopes< exactly<hash_lbrace>, exactly<rbrace> >(p + 2, end_of_selector);+        Expression* interpolant = Parser::from_c_str(p+2, j, ctx, pstate).parse_list();+        interpolant->is_interpolant(true);+        (*schema) << interpolant;+        i = j;+      }+      // no more interpolants have been found+      // add the last segment if there is one+      else {+        if (i < end_of_selector) (*schema) << new (ctx.mem) String_Quoted(pstate, string(i, end_of_selector));+        break;+      }+    }+    position = end_of_selector;+    Selector_Schema* selector_schema = new (ctx.mem) Selector_Schema(pstate, schema);+    selector_schema->media_block(last_media_block);+    selector_schema->last_block(block_stack.back());+    return selector_schema;+  }++  Selector_List* Parser::parse_selector_group()+  {+    bool reloop = true;+    To_String to_string(&ctx);+    lex< css_whitespace >();+    Selector_List* group = new (ctx.mem) Selector_List(pstate);+    group->media_block(last_media_block);+    group->last_block(block_stack.back());+    do {+      reloop = false;+      if (peek< alternatives <+            exactly<'{'>,+            exactly<'}'>,+            exactly<')'>,+            exactly<';'>+          > >())+        break; // in case there are superfluous commas at the end+      Complex_Selector* comb = parse_selector_combination();+      if (!comb->has_reference() && !in_at_root) {+        ParserState sel_source_position = pstate;+        Selector_Reference* ref = new (ctx.mem) Selector_Reference(sel_source_position);+        Compound_Selector* ref_wrap = new (ctx.mem) Compound_Selector(sel_source_position);+        ref_wrap->media_block(last_media_block);+        ref_wrap->last_block(block_stack.back());+        (*ref_wrap) << ref;+        if (!comb->head()) {+          comb->head(ref_wrap);+          comb->has_reference(true);+        }+        else {+          comb = new (ctx.mem) Complex_Selector(sel_source_position, Complex_Selector::ANCESTOR_OF, ref_wrap, comb);+          comb->media_block(last_media_block);+          comb->last_block(block_stack.back());+          comb->has_reference(true);+        }+        if (peek_newline()) ref_wrap->has_line_break(true);+      }+      while (peek_css< exactly<','> >())+      {+        // consume everything up and including the comma speparator+        reloop = lex< sequence < optional_css_comments, exactly<','> > >() != 0;+        // remember line break (also between some commas)+        if (peek_newline()) comb->has_line_feed(true);+        if (comb->tail() && peek_newline()) comb->tail()->has_line_feed(true);+        if (comb->tail() && comb->tail()->head() && peek_newline()) comb->tail()->head()->has_line_feed(true);+        // remember line break (also between some commas)+      }+      (*group) << comb;+    }+    while (reloop);+    while (lex< optional >()) {+      group->is_optional(true);+    }+    return group;+  }++  Complex_Selector* Parser::parse_selector_combination()+  {+    Position sel_source_position(-1);+    Compound_Selector* lhs;+    if (peek_css< alternatives <+          exactly<'+'>,+          exactly<'~'>,+          exactly<'>'>+        > >())+    // no selector before the combinator+    { lhs = 0; }+    else {+      lhs = parse_simple_selector_sequence();+      sel_source_position = before_token;+      lhs->has_line_break(peek_newline());+    }++    Complex_Selector::Combinator cmb;+    if      (lex< exactly<'+'> >()) cmb = Complex_Selector::ADJACENT_TO;+    else if (lex< exactly<'~'> >()) cmb = Complex_Selector::PRECEDES;+    else if (lex< exactly<'>'> >()) cmb = Complex_Selector::PARENT_OF;+    else                            cmb = Complex_Selector::ANCESTOR_OF;+    bool cpx_lf = peek_newline();++    Complex_Selector* rhs;+    if (peek_css< alternatives <+                exactly<','>,+                exactly<')'>,+                exactly<'{'>,+                exactly<'}'>,+                exactly<';'>,+                optional+        > >())+    // no selector after the combinator+    { rhs = 0; }+    else {+      rhs = parse_selector_combination();+      sel_source_position = before_token;+    }+    if (!sel_source_position.line) sel_source_position = before_token;+    Complex_Selector* cpx = new (ctx.mem) Complex_Selector(ParserState(path, source, sel_source_position), cmb, lhs, rhs);+    cpx->media_block(last_media_block);+    cpx->last_block(block_stack.back());+    if (cpx_lf) cpx->has_line_break(cpx_lf);+    return cpx;+  }++  Compound_Selector* Parser::parse_simple_selector_sequence()+  {+    Compound_Selector* seq = new (ctx.mem) Compound_Selector(pstate);+    seq->media_block(last_media_block);+    seq->last_block(block_stack.back());+    bool sawsomething = false;+    if (lex< exactly<'&'> >()) {+      // check if we have a parent selector on the root level block+      if (block_stack.back() && block_stack.back()->is_root()) {+        //error("Base-level rules cannot contain the parent-selector-referencing character '&'.", pstate);+      }+      (*seq) << new (ctx.mem) Selector_Reference(pstate);+      sawsomething = true;+      // if you see a space after a &, then you're done+      if(peek< spaces >() || peek< alternatives < spaces, exactly<';'> > >()) {+        return seq;+      }+    }+    if (sawsomething && lex_css< sequence< negate< functional >, alternatives< identifier_alnums, universal, quoted_string, dimension, percentage, number > > >()) {+      // saw an ampersand, then allow type selectors with arbitrary number of hyphens at the beginning+      (*seq) << new (ctx.mem) Type_Selector(pstate, unquote(lexed));+    } else if (lex_css< sequence< negate< functional >, alternatives< type_selector, universal, quoted_string, dimension, percentage, number > > >()) {+      // if you see a type selector+      (*seq) << new (ctx.mem) Type_Selector(pstate, lexed);+      sawsomething = true;+    }+    if (!sawsomething) {+      // don't blindly do this if you saw a & or selector+      (*seq) << parse_simple_selector();+    }++    while (!peek< spaces >(position) &&+           !(peek_css < alternatives <+               exactly<'+'>,+               exactly<'~'>,+               exactly<'>'>,+               exactly<','>,+               exactly<')'>,+               exactly<'{'>,+               exactly<'}'>,+               exactly<';'>+             > >(position))) {+      (*seq) << parse_simple_selector();+    }+    return seq;+  }++  Simple_Selector* Parser::parse_simple_selector()+  {+    lex < css_comments >();+    if (lex< alternatives < id_name, class_name > >()) {+      return new (ctx.mem) Selector_Qualifier(pstate, unquote(lexed));+    }+    else if (lex< quoted_string >()) {+      return new (ctx.mem) Type_Selector(pstate, unquote(lexed));+    }+    else if (lex< alternatives < number, kwd_sel_deep > >()) {+      return new (ctx.mem) Type_Selector(pstate, lexed);+    }+    else if (peek< pseudo_not >()) {+      return parse_negated_selector();+    }+    else if (peek< exactly<':'> >(position) || peek< functional >()) {+      return parse_pseudo_selector();+    }+    else if (peek< exactly<'['> >(position)) {+      return parse_attribute_selector();+    }+    else if (lex< placeholder >()) {+      Selector_Placeholder* sel = new (ctx.mem) Selector_Placeholder(pstate, unquote(lexed));+      sel->media_block(last_media_block);+      sel->last_block(block_stack.back());+      return sel;+    }+    else {+      error("invalid selector after " + lexed.to_string(), pstate);+    }+    // unreachable statement+    return 0;+  }++  Wrapped_Selector* Parser::parse_negated_selector()+  {+    lex< pseudo_not >();+    string name(lexed);+    ParserState nsource_position = pstate;+    Selector* negated = parse_selector_group();+    if (!lex< exactly<')'> >()) {+      error("negated selector is missing ')'", pstate);+    }+    return new (ctx.mem) Wrapped_Selector(nsource_position, name, negated);+  }++  Simple_Selector* Parser::parse_pseudo_selector() {+    if (lex< sequence< pseudo_prefix, functional > >() || lex< functional >()) {+      string name(lexed);+      String* expr = 0;+      ParserState p = pstate;+      Selector* wrapped = 0;+      if (lex< alternatives< even, odd > >()) {+        expr = new (ctx.mem) String_Quoted(p, lexed);+      }+      else if (peek< binomial >(position)) {+        lex< sequence< optional< coefficient >, exactly<'n'> > >();+        String_Constant* var_coef = new (ctx.mem) String_Quoted(p, lexed);+        lex< sign >();+        String_Constant* op = new (ctx.mem) String_Quoted(p, lexed);+        // Binary_Expression::Type op = (lexed == "+" ? Binary_Expression::ADD : Binary_Expression::SUB);+        lex< one_plus < digit > >();+        String_Constant* constant = new (ctx.mem) String_Quoted(p, lexed);+        // expr = new (ctx.mem) Binary_Expression(p, op, var_coef, constant);+        String_Schema* schema = new (ctx.mem) String_Schema(p, 3);+        *schema << var_coef << op << constant;+        expr = schema;+      }+      else if (peek< sequence< optional<sign>,+                               zero_plus<digit>,+                               exactly<'n'>,+                               optional_css_whitespace,+                               exactly<')'> > >()) {+        lex< sequence< optional<sign>,+                       zero_plus<digit>,+                       exactly<'n'> > >();+        expr = new (ctx.mem) String_Quoted(p, lexed);+      }+      else if (lex< sequence< optional<sign>, one_plus < digit > > >()) {+        expr = new (ctx.mem) String_Quoted(p, lexed);+      }+      else if (peek< sequence< identifier, optional_css_whitespace, exactly<')'> > >()) {+        lex< identifier >();+        expr = new (ctx.mem) String_Quoted(p, lexed);+      }+      else if (lex< quoted_string >()) {+        expr = new (ctx.mem) String_Quoted(p, lexed);+      }+      else if (peek< exactly<')'> >()) {+        expr = new (ctx.mem) String_Constant(p, "");+      }+      else {+        wrapped = parse_selector_group();+      }+      if (!lex< exactly<')'> >()) error("unterminated argument to " + name + "...)", pstate);+      if (wrapped) {+        return new (ctx.mem) Wrapped_Selector(p, name, wrapped);+      }+      return new (ctx.mem) Pseudo_Selector(p, name, expr);+    }+    else if (lex < sequence< pseudo_prefix, identifier > >()) {+      return new (ctx.mem) Pseudo_Selector(pstate, unquote(lexed));+    }+    else {+      error("unrecognized pseudo-class or pseudo-element", pstate);+    }+    // unreachable statement+    return 0;+  }++  Attribute_Selector* Parser::parse_attribute_selector()+  {+    lex_css< exactly<'['> >();+    ParserState p = pstate;+    if (!lex_css< attribute_name >()) error("invalid attribute name in attribute selector", pstate);+    string name(lexed);+    if (lex_css< exactly<']'> >()) return new (ctx.mem) 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);+    }+    string matcher(lexed);++    String* value = 0;+    if (lex_css< identifier >()) {+      value = new (ctx.mem) 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< exactly<']'> >()) error("unterminated attribute selector for " + name, pstate);+    return new (ctx.mem) Attribute_Selector(p, name, matcher, value);+  }++  /* parse block comment and add to block */+  void Parser::parse_block_comments(Block* block)+  {+    while (lex< block_comment >()) {+      bool is_important = lexed.begin[2] == '!';+      String*  contents = parse_interpolated_chunk(lexed);+      (*block) << new (ctx.mem) Comment(pstate, contents, is_important);+    }+  }++  Block* Parser::parse_block()+  {+    lex< exactly<'{'> >();+    bool semicolon = false;+    Selector_Lookahead lookahead_result;+    Block* block = new (ctx.mem) Block(pstate);+    block_stack.push_back(block);+    lex< zero_plus < alternatives < space, line_comment > > >();+    // JMA - ensure that a block containing only block_comments is parsed+    parse_block_comments(block);++    while (!lex< exactly<'}'> >()) {+      parse_block_comments(block);+      if (semicolon) {+        if (!lex< one_plus< exactly<';'> > >()) {+          error("non-terminal statement or declaration must end with ';'", pstate);+        }+        semicolon = false;+        parse_block_comments(block);+        if (lex< sequence< exactly<'}'>, zero_plus< exactly<';'> > > >()) break;+      }+      else if (peek< kwd_import >(position)) {+        if (stack.back() == mixin_def || stack.back() == function_def) {+          lex< kwd_import >(); // to adjust the before_token number+          error("@import directives are not allowed inside mixins and functions", pstate);+        }+        Import* imp = parse_import();+        if (!imp->urls().empty()) (*block) << imp;+        if (!imp->files().empty()) {+          for (size_t i = 0, S = imp->files().size(); i < S; ++i) {+            (*block) << new (ctx.mem) Import_Stub(pstate, imp->files()[i]);+          }+        }+        semicolon = true;+      }+      else if (lex< variable >()) {+        (*block) << parse_assignment();+        semicolon = true;+      }+      else if (lex< line_comment >()) {+        // throw line comments away+      }+      else if (peek< kwd_if_directive >()) {+        (*block) << parse_if_directive();+      }+      else if (peek< kwd_for_directive >()) {+        (*block) << parse_for_directive();+      }+      else if (peek< kwd_each_directive >()) {+        (*block) << parse_each_directive();+      }+      else if (peek < kwd_while_directive >()) {+        (*block) << parse_while_directive();+      }+      else if (lex < kwd_return_directive >()) {+        (*block) << new (ctx.mem) Return(pstate, parse_list());+        semicolon = true;+      }+      else if (peek< kwd_warn >()) {+        (*block) << parse_warning();+        semicolon = true;+      }+      else if (peek< kwd_err >()) {+        (*block) << parse_error();+        semicolon = true;+      }+      else if (peek< kwd_dbg >()) {+        (*block) << parse_debug();+        semicolon = true;+      }+      else if (stack.back() == function_def) {+        error("only variable declarations and control directives are allowed inside functions", pstate);+      }+      else if (peek< kwd_mixin >() || peek< kwd_function >()) {+        (*block) << parse_definition();+      }+      else if (peek< kwd_include >(position)) {+        Mixin_Call* the_call = parse_mixin_call();+        (*block) << the_call;+        // don't need a semicolon after a content block+        semicolon = (the_call->block()) ? false : true;+      }+      else if (lex< kwd_content >()) {+        if (stack.back() != mixin_def) {+          error("@content may only be used within a mixin", pstate);+        }+        (*block) << new (ctx.mem) Content(pstate);+        semicolon = true;+      }+      /*+      else if (peek< exactly<'+'> >()) {+        (*block) << parse_mixin_call();+        semicolon = true;+      }+      */+      else if (lex< kwd_extend >()) {+        Selector_Lookahead lookahead = lookahead_for_extension_target(position);+        if (!lookahead.found) error("invalid selector for @extend", pstate);+        Selector* target;+        if (lookahead.has_interpolants) target = parse_selector_schema(lookahead.found);+        else                            target = parse_selector_group();+        (*block) << new (ctx.mem) Extension(pstate, target);+        semicolon = true;+      }+      else if (peek< kwd_media >()) {+        (*block) << parse_media_block();+      }+      else if (peek< kwd_supports >()) {+        (*block) << parse_feature_block();+      }+      else if (peek< kwd_at_root >()) {+        (*block) << parse_at_root_block();+      }+      // ignore the @charset directive for now+      else if (lex< exactly< charset_kwd > >()) {+        lex< quoted_string >();+        lex< one_plus< exactly<';'> > >();+      }+      else if (peek< at_keyword >()) {+        At_Rule* at_rule = parse_at_rule();+        (*block) << at_rule;+        if (!at_rule->block()) semicolon = true;+      }+      else if ((lookahead_result = lookahead_for_selector(position)).found) {+        (*block) << parse_ruleset(lookahead_result);+      }/* not used anymore - remove?+      else if (peek< sequence< optional< exactly<'*'> >, alternatives< identifier_schema, identifier >, optional_spaces, exactly<':'>, optional_spaces, exactly<'{'> > >(position)) {+        (*block) << parse_propset();+      }*/+      else if (!peek< exactly<';'> >()) {+        bool indent = ! peek< sequence< optional< exactly<'*'> >, alternatives< identifier_schema, identifier >, optional_spaces, exactly<':'>, optional_spaces, exactly<'{'> > >(position);+        /* not used anymore - remove?+        if (peek< sequence< optional< exactly<'*'> >, identifier_schema, exactly<':'>, exactly<'{'> > >()) {+          (*block) << parse_propset();+        }+        else if (peek< sequence< optional< exactly<'*'> >, identifier, exactly<':'>, exactly<'{'> > >()) {+          (*block) << parse_propset();+        }+        else */ {+          Declaration* decl = parse_declaration();+          decl->tabs(indentation);+          (*block) << decl;+          if (peek< exactly<'{'> >()) {+            // parse a propset that rides on the declaration's property+            if (indent) indentation++;+            Propset* ps = new (ctx.mem) Propset(pstate, decl->property(), parse_block());+            if (indent) indentation--;+            (*block) << ps;+          }+          else {+            // finish and let the semicolon get munched+            semicolon = true;+          }+        }+      }+      else lex< one_plus< exactly<';'> > >();+      parse_block_comments(block);+    }+    block_stack.pop_back();+    return block;+  }++  Declaration* Parser::parse_declaration() {+    String* prop = 0;+    if (peek< sequence< optional< exactly<'*'> >, identifier_schema > >()) {+      prop = parse_identifier_schema();+    }+    else if (lex< sequence< optional< exactly<'*'> >, identifier > >()) {+      prop = new (ctx.mem) String_Quoted(pstate, lexed);+    }+    else {+      error("invalid property name", pstate);+    }+    const string property(lexed);+    if (!lex_css< one_plus< exactly<':'> > >()) error("property \"" + property + "\" must be followed by a ':'", pstate);+    if (peek_css< exactly<';'> >()) error("style declaration must contain a value", pstate);+    if (peek_css< static_value >()) {+      return new (ctx.mem) Declaration(prop->pstate(), prop, parse_static_value()/*, lex<important>()*/);+    }+    else {+      Expression* list_ex = parse_list();+      if (List* list = dynamic_cast<List*>(list_ex)) {+        if (list->length() == 0 && !peek< exactly <'{'> >()) {+          css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was ");+        }+      }+      return new (ctx.mem) Declaration(prop->pstate(), prop, list_ex/*, lex<important>()*/);+    }+  }++  // parse +/- and return false if negative+  bool Parser::parse_number_prefix()+  {+    bool positive = true;+    while(true) {+      if (lex < block_comment >()) continue;+      if (lex < number_prefix >()) continue;+      if (lex < exactly < '-' > >()) {+        positive = !positive;+        continue;+      }+      break;+    }+    return positive;+  }++  Expression* Parser::parse_map()+  {+    To_String to_string(&ctx);+    Expression* key = parse_list();++    // it's not a map so return the lexed value as a list value+    if (!peek< exactly<':'> >())+    { return key; }++    lex< exactly<':'> >();++    Expression* value = parse_space_list();++    Map* map = new (ctx.mem) Map(pstate, 1);+    (*map) << make_pair(key, value);++    while (lex_css< exactly<','> >())+    {+      // allow trailing commas - #495+      if (peek_css< exactly<')'> >(position))+      { break; }++      Expression* key = parse_list();++      if (!(lex< exactly<':'> >()))+      { error("invalid syntax", pstate); }++      Expression* value = parse_space_list();++      (*map) << make_pair(key, value);+    }++    if (map->has_duplicate_key())+    { error("Duplicate key \"" + map->get_duplicate_key()->perform(&to_string) + "\" in map " + map->perform(&to_string) + ".", pstate); }++    return map;+  }++  Expression* Parser::parse_list()+  {+    return parse_comma_list();+  }++  Expression* Parser::parse_comma_list()+  {+    if (peek< alternatives <+          // exactly<'!'>,+          // exactly<':'>,+          exactly<';'>,+          exactly<'}'>,+          exactly<'{'>,+          exactly<')'>,+          exactly<ellipsis>+        > >(position))+    { return new (ctx.mem) List(pstate, 0); }+    Expression* list1 = parse_space_list();+    // if it's a singleton, return it directly; don't wrap it+    if (!peek< exactly<','> >(position)) return list1;++    List* comma_list = new (ctx.mem) List(pstate, 2, List::COMMA);+    (*comma_list) << list1;++    while (lex< exactly<','> >())+    {+      if (peek< alternatives <+            // exactly<'!'>,+            exactly<';'>,+            exactly<'}'>,+            exactly<'{'>,+            exactly<')'>,+            exactly<':'>,+            exactly<ellipsis>+          > >(position)+      ) { break; }+      Expression* list = parse_space_list();+      (*comma_list) << list;+    }++    return comma_list;+  }++  Expression* Parser::parse_space_list()+  {+    Expression* disj1 = parse_disjunction();+    // if it's a singleton, return it directly; don't wrap it+    if (peek< alternatives <+          // exactly<'!'>,+          exactly<';'>,+          exactly<'}'>,+          exactly<'{'>,+          exactly<')'>,+          exactly<','>,+          exactly<':'>,+          exactly<ellipsis>,+          default_flag,+          global_flag+        > >(position)+    ) { return disj1; }++    List* space_list = new (ctx.mem) List(pstate, 2, List::SPACE);+    (*space_list) << disj1;++    while (!(peek< alternatives <+               // exactly<'!'>,+               exactly<';'>,+               exactly<'}'>,+               exactly<'{'>,+               exactly<')'>,+               exactly<','>,+               exactly<':'>,+               exactly<ellipsis>,+               default_flag,+               global_flag+           > >(position))+    ) {+      (*space_list) << parse_disjunction();+    }++    return space_list;+  }++  Expression* Parser::parse_disjunction()+  {+    Expression* conj1 = parse_conjunction();+    // if it's a singleton, return it directly; don't wrap it+    if (!peek_css< kwd_or >()) return conj1;++    vector<Expression*> operands;+    while (lex_css< kwd_or >())+      operands.push_back(parse_conjunction());++    return fold_operands(conj1, operands, Binary_Expression::OR);+  }++  Expression* Parser::parse_conjunction()+  {+    Expression* rel1 = parse_relation();+    // if it's a singleton, return it directly; don't wrap it+    if (!peek_css< kwd_and >()) return rel1;++    vector<Expression*> operands;+    while (lex_css< kwd_and >())+      operands.push_back(parse_relation());++    return fold_operands(rel1, operands, Binary_Expression::AND);+  }++  Expression* Parser::parse_relation()+  {+    Expression* expr1 = parse_expression();+    // if it's a singleton, return it directly; don't wrap it+    if (!(peek< alternatives <+            kwd_eq,+            kwd_neq,+            kwd_gte,+            kwd_gt,+            kwd_lte,+            kwd_lt+          > >(position)))+    { return expr1; }++    Binary_Expression::Type op+    = lex<kwd_eq>()  ? Binary_Expression::EQ+    : lex<kwd_neq>() ? Binary_Expression::NEQ+    : lex<kwd_gte>() ? Binary_Expression::GTE+    : lex<kwd_lte>() ? Binary_Expression::LTE+    : lex<kwd_gt>()  ? Binary_Expression::GT+    : lex<kwd_lt>()  ? Binary_Expression::LT+    :                 Binary_Expression::LT; // whatever++    Expression* expr2 = parse_expression();++    return new (ctx.mem) Binary_Expression(expr1->pstate(), op, expr1, expr2);+  }++  Expression* Parser::parse_expression()+  {+    Expression* term1 = parse_term();+    // if it's a singleton, return it directly; don't wrap it+    if (!(peek< exactly<'+'> >(position) ||+          (peek< no_spaces >(position) && peek< sequence< negate< unsigned_number >, exactly<'-'>, negate< space > > >(position)) ||+          (peek< sequence< negate< unsigned_number >, exactly<'-'>, negate< unsigned_number > > >(position))) ||+          peek< identifier >(position))+    { return term1; }++    vector<Expression*> operands;+    vector<Binary_Expression::Type> operators;+    while (lex< exactly<'+'> >() || lex< sequence< negate< digit >, exactly<'-'> > >()) {+      operators.push_back(lexed.to_string() == "+" ? Binary_Expression::ADD : Binary_Expression::SUB);+      operands.push_back(parse_term());+    }++    return fold_operands(term1, operands, operators);+  }++  Expression* Parser::parse_term()+  {+    Expression* factor = parse_factor();+    // Special case: Ruby sass never tries to modulo if the lhs contains an interpolant+    if (peek_css< exactly<'%'> >(position) && factor->concrete_type() == Expression::STRING) {+      String_Schema* ss = dynamic_cast<String_Schema*>(factor);+      if (ss && ss->has_interpolants()) return factor;+    }+    // if it's a singleton, return it directly; don't wrap it+    if (!peek< class_char< static_ops > >(position)) return factor;+    // parse more factors and operators+    vector<Expression*> operands; // factors+    vector<Binary_Expression::Type> operators; // ops+    while (lex_css< class_char< static_ops > >()) {+      switch(*lexed.begin) {+        case '*': operators.push_back(Binary_Expression::MUL); break;+        case '/': operators.push_back(Binary_Expression::DIV); break;+        case '%': operators.push_back(Binary_Expression::MOD); break;+        default: throw runtime_error("unknown static op parsed"); break;+      }+      operands.push_back(parse_factor());+    }+    // operands and operators to binary expression+    return fold_operands(factor, operands, operators);+  }++  Expression* Parser::parse_factor()+  {+    if (lex< exactly<'('> >()) {+      Expression* value = parse_map();+      if (!lex< exactly<')'> >()) error("unclosed parenthesis", pstate);+      value->is_delayed(false);+      // make sure wrapped lists and division expressions are non-delayed within parentheses+      if (value->concrete_type() == Expression::LIST) {+        List* l = static_cast<List*>(value);+        if (!l->empty()) (*l)[0]->is_delayed(false);+      } else if (typeid(*value) == typeid(Binary_Expression)) {+        Binary_Expression* b = static_cast<Binary_Expression*>(value);+        Binary_Expression* lhs = static_cast<Binary_Expression*>(b->left());+        if (lhs && lhs->type() == Binary_Expression::DIV) lhs->is_delayed(false);+      }+      return value;+    }+    else if (peek< ie_property >()) {+      return parse_ie_property();+    }+    else if (peek< ie_keyword_arg >()) {+      return parse_ie_keyword_arg();+    }+    else if (peek< exactly< calc_kwd > >() ||+             peek< exactly< moz_calc_kwd > >() ||+             peek< exactly< ms_calc_kwd > >() ||+             peek< exactly< webkit_calc_kwd > >()) {+      return parse_calc_function();+    }+    else if (peek< functional_schema >()) {+      return parse_function_call_schema();+    }+    else if (peek< sequence< identifier_schema, negate< exactly<'%'> > > >()) {+      return parse_identifier_schema();+    }+    else if (peek< functional >()) {+      return parse_function_call();+    }+    else if (lex< sequence< exactly<'+'>, optional_css_whitespace, negate< number > > >()) {+      return new (ctx.mem) Unary_Expression(pstate, Unary_Expression::PLUS, parse_factor());+    }+    else if (lex< sequence< exactly<'-'>, optional_css_whitespace, negate< number> > >()) {+      return new (ctx.mem) Unary_Expression(pstate, Unary_Expression::MINUS, parse_factor());+    }+    else if (lex< sequence< kwd_not, css_whitespace > >()) {+      return new (ctx.mem) Unary_Expression(pstate, Unary_Expression::NOT, parse_factor());+    }+    else if (peek < sequence < one_plus < alternatives < css_whitespace, exactly<'-'>, exactly<'+'> > >, number > >()) {+      if (parse_number_prefix()) return parse_value(); // prefix is positive+      return new (ctx.mem) Unary_Expression(pstate, Unary_Expression::MINUS, parse_value());+    }+    else {+      return parse_value();+    }+  }++  Expression* Parser::parse_value()+  {+    lex< css_comments >();+    if (lex< ampersand >())+    {+      return new (ctx.mem) Parent_Selector(pstate, parse_selector_group()); }++    if (lex< important >())+    { return new (ctx.mem) String_Constant(pstate, "!important"); }++    const char* stop;+    if ((stop = peek< value_schema >()))+    { return parse_value_schema(stop); }++    if (lex< kwd_true >())+    { return new (ctx.mem) Boolean(pstate, true); }++    if (lex< kwd_false >())+    { return new (ctx.mem) Boolean(pstate, false); }++    if (lex< kwd_null >())+    { return new (ctx.mem) Null(pstate); }++    if (lex< identifier >()) {+      String_Constant* str = new (ctx.mem) String_Quoted(pstate, lexed);+      // Dont' delay this string if it is a name color. Fixes #652.+      str->is_delayed(ctx.names_to_colors.count(unquote(lexed)) == 0);+      return str;+    }++    if (lex< percentage >())+    { return new (ctx.mem) Textual(pstate, Textual::PERCENTAGE, lexed); }++    // match hex number first because 0x000 looks like a number followed by an indentifier+    if (lex< alternatives< hex, hex0 > >())+    { return new (ctx.mem) Textual(pstate, Textual::HEX, lexed); }++    // also handle the 10em- foo special case+    if (lex< sequence< dimension, optional< sequence< exactly<'-'>, negate< digit > > > > >())+    { return new (ctx.mem) Textual(pstate, Textual::DIMENSION, lexed); }++    if (lex< number >())+    { return new (ctx.mem) Textual(pstate, Textual::NUMBER, lexed); }++    if (peek< quoted_string >())+    { return parse_string(); }++    if (lex< variable >())+    { return new (ctx.mem) Variable(pstate, Util::normalize_underscores(lexed)); }++    // Special case handling for `%` proceeding an interpolant.+    if (lex< sequence< exactly<'%'>, optional< percentage > > >())+    { return new (ctx.mem) String_Quoted(pstate, lexed); }++    error("error reading values after " + lexed.to_string(), pstate);++    // unreachable statement+    return 0;+  }++  // this parses interpolation inside other strings+  // means the result should later be quoted again+  String* Parser::parse_interpolated_chunk(Token chunk, bool constant)+  {+    const char* i = chunk.begin;+    // see if there any interpolants+    const char* p = find_first_in_interval< exactly<hash_lbrace> >(i, chunk.end);+    if (!p) {+      String_Quoted* str_quoted = new (ctx.mem) String_Quoted(pstate, string(i, chunk.end));+      if (!constant && str_quoted->quote_mark()) str_quoted->quote_mark('*');+      str_quoted->is_delayed(true);+      return str_quoted;+    }++    String_Schema* schema = new (ctx.mem) String_Schema(pstate);+    while (i < chunk.end) {+      p = find_first_in_interval< exactly<hash_lbrace> >(i, chunk.end);+      if (p) {+        if (i < p) {+          // accumulate the preceding segment if it's nonempty+          (*schema) << new (ctx.mem) String_Quoted(pstate, string(i, p));+        }+        // we need to skip anything inside strings+        // create a new target in parser/prelexer+        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* interp_node = Parser::from_token(Token(p+2, j), ctx, pstate).parse_list();+          interp_node->is_interpolant(true);+          (*schema) << 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) << new (ctx.mem) String_Quoted(pstate, string(i, chunk.end));+        break;+      }+      ++ i;+    }+    return schema;+  }++  String_Constant* Parser::parse_static_value()+  {+    lex< static_value >();+    Token str(lexed);+    --str.end;+    --position;+    String_Constant* str_node = new (ctx.mem) String_Constant(pstate, str);+    // str_node->is_delayed(true);+    return str_node;+  }++  String* Parser::parse_string()+  {+    lex< quoted_string >();+    Token token(lexed);+    return parse_interpolated_chunk(token);+  }++  String* 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> >(str.begin, str.end);+    if (!p) {+      String_Constant* str_node = new (ctx.mem) String_Constant(pstate, normalize_wspace(string(str.begin, str.end)));+      str_node->is_delayed(true);+      return str_node;+    }++    String_Schema* schema = new (ctx.mem) String_Schema(pstate);+    while (i < str.end) {+      p = find_first_in_interval< exactly<hash_lbrace> >(i, str.end);+      if (p) {+        if (i < p) {+          (*schema) << new (ctx.mem) String_Constant(pstate, string(i, p)); // accumulate the preceding segment if it's nonempty+        }+        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* interp_node = Parser::from_token(Token(p+2, j), ctx, pstate).parse_list();+          interp_node->is_interpolant(true);+          (*schema) << 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) << new (ctx.mem) String_Constant(pstate, string(i, str.end));+        break;+      }+    }+    return schema;+  }++  String* Parser::parse_ie_keyword_arg()+  {+    String_Schema* kwd_arg = new (ctx.mem) String_Schema(pstate, 3);+    if (lex< variable >()) {+      *kwd_arg << new (ctx.mem) Variable(pstate, Util::normalize_underscores(lexed));+    } else {+      lex< alternatives< identifier_schema, identifier > >();+      *kwd_arg << new (ctx.mem) String_Quoted(pstate, lexed);+    }+    lex< exactly<'='> >();+    *kwd_arg << new (ctx.mem) String_Quoted(pstate, lexed);+    if (peek< variable >()) *kwd_arg << parse_list();+    else if (lex< number >()) *kwd_arg << new (ctx.mem) Textual(pstate, Textual::NUMBER, Util::normalize_decimals(lexed));+    else if (lex< alternatives< identifier_schema, identifier, number, hexa, hex > >()) {+      *kwd_arg << new (ctx.mem) String_Quoted(pstate, lexed);+    }+    return kwd_arg;+  }++  String_Schema* Parser::parse_value_schema(const char* stop)+  {+    String_Schema* schema = new (ctx.mem) String_Schema(pstate);+    size_t num_items = 0;+    while (position < stop) {+      if (lex< interpolant >()) {+        Token insides(Token(lexed.begin + 2, lexed.end - 1));+        Expression* interp_node = Parser::from_token(insides, ctx, pstate).parse_list();+        interp_node->is_interpolant(true);+        (*schema) << interp_node;+      }+      else if (lex< exactly<'%'> >()) {+        (*schema) << new (ctx.mem) String_Constant(pstate, lexed);+      }+      else if (lex< identifier >()) {+        (*schema) << new (ctx.mem) String_Quoted(pstate, lexed);+      }+      else if (lex< percentage >()) {+        (*schema) << new (ctx.mem) Textual(pstate, Textual::PERCENTAGE, lexed);+      }+      else if (lex< dimension >()) {+        (*schema) << new (ctx.mem) Textual(pstate, Textual::DIMENSION, lexed);+      }+      else if (lex< number >()) {+        (*schema) << new (ctx.mem) Textual(pstate, Textual::NUMBER, lexed);+      }+      else if (lex< hex >()) {+        (*schema) << new (ctx.mem) Textual(pstate, Textual::HEX, unquote(lexed));+      }+      else if (lex< quoted_string >()) {+        (*schema) << new (ctx.mem) String_Constant(pstate, lexed);+      }+      else if (lex< variable >()) {+        (*schema) << new (ctx.mem) Variable(pstate, Util::normalize_underscores(lexed));+      }+      else {+        error("error parsing interpolated value", pstate);+      }+      ++num_items;+    }+    return schema;+  }++  /* not used anymore - remove?+  String_Schema* Parser::parse_url_schema()+  {+    String_Schema* schema = new (ctx.mem) String_Schema(pstate);++    while (position < end) {+      if (position[0] == '/') {+        lexed = Token(position, position+1, before_token);+        (*schema) << new (ctx.mem) String_Quoted(pstate, lexed);+        ++position;+      }+      else if (lex< interpolant >()) {+        Token insides(Token(lexed.begin + 2, lexed.end - 1, before_token));+        Expression* interp_node = Parser::from_token(insides, ctx, pstate).parse_list();+        interp_node->is_interpolant(true);+        (*schema) << interp_node;+      }+      else if (lex< sequence< identifier, exactly<':'> > >()) {+        (*schema) << new (ctx.mem) String_Quoted(pstate, lexed);+      }+      else if (lex< filename >()) {+        (*schema) << new (ctx.mem) String_Quoted(pstate, lexed);+      }+      else {+        error("error parsing interpolated url", pstate);+      }+    }+    return schema;+  } */++  // this parses interpolation outside other strings+  // means the result must not be quoted again later+  String* Parser::parse_identifier_schema()+  {+    // first lex away whatever we have found+    lex< sequence< optional< exactly<'*'> >, 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> >(id.begin, id.end);+    if (!p) {+      return new (ctx.mem) String_Quoted(pstate, string(id.begin, id.end));+    }++    String_Schema* schema = new (ctx.mem) String_Schema(pstate);+    while (i < id.end) {+      p = find_first_in_interval< exactly<hash_lbrace> >(i, id.end);+      if (p) {+        if (i < p) {+          // accumulate the preceding segment if it's nonempty+          (*schema) << new (ctx.mem) String_Constant(pstate, string(i, p));+        }+        // we need to skip anything inside strings+        // create a new target in parser/prelexer+        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* interp_node = Parser::from_token(Token(p+2, j), ctx, pstate).parse_list();+          interp_node->is_interpolant(true);+          (*schema) << 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) (*schema) << new (ctx.mem) String_Quoted(pstate, string(i, id.end));+        break;+      }+    }+    return schema;+  }++  Function_Call* Parser::parse_calc_function()+  {+    lex< identifier >();+    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< exactly<')'> >();++    Argument* arg = new (ctx.mem) Argument(arg_pos, parse_interpolated_chunk(Token(arg_beg, arg_end)));+    Arguments* args = new (ctx.mem) Arguments(arg_pos);+    *args << arg;+    return new (ctx.mem) Function_Call(call_pos, name, args);+  }++  Function_Call* Parser::parse_function_call()+  {+    lex< identifier >();+    string name(lexed);+    Arguments* args = parse_arguments(name == "url");+    return new (ctx.mem) Function_Call(pstate, name, args);+  }++  Function_Call_Schema* Parser::parse_function_call_schema()+  {+    String* name = parse_identifier_schema();+    ParserState source_position_of_call = pstate;++    Function_Call_Schema* the_call = new (ctx.mem) Function_Call_Schema(source_position_of_call, name, parse_arguments());+    return the_call;+  }++  If* Parser::parse_if_directive(bool else_if)+  {+    lex< kwd_if_directive >() || (else_if && lex< exactly<if_after_else_kwd> >());+    ParserState if_source_position = pstate;+    Expression* predicate = parse_list();+    predicate->is_delayed(false);+    if (!peek< exactly<'{'> >()) error("expected '{' after the predicate for @if", pstate);+    Block* consequent = parse_block();+    Block* alternative = 0;++    if (lex< elseif_directive >()) {+      alternative = new (ctx.mem) Block(pstate);+      (*alternative) << parse_if_directive(true);+    }+    else if (lex< kwd_else_directive >()) {+      if (!peek< exactly<'{'> >()) {+        error("expected '{' after @else", pstate);+      }+      else {+        alternative = parse_block();+      }+    }+    return new (ctx.mem) If(if_source_position, predicate, consequent, alternative);+  }++  For* Parser::parse_for_directive()+  {+    lex< kwd_for_directive >();+    ParserState for_source_position = pstate;+    if (!lex< variable >()) error("@for directive requires an iteration variable", pstate);+    string var(Util::normalize_underscores(lexed));+    if (!lex< kwd_from >()) error("expected 'from' keyword in @for directive", pstate);+    Expression* lower_bound = parse_expression();+    lower_bound->is_delayed(false);+    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* upper_bound = parse_expression();+    upper_bound->is_delayed(false);+    if (!peek< exactly<'{'> >()) error("expected '{' after the upper bound in @for directive", pstate);+    Block* body = parse_block();+    return new (ctx.mem) For(for_source_position, var, lower_bound, upper_bound, body, inclusive);+  }++  Each* Parser::parse_each_directive()+  {+    lex < kwd_each_directive >();+    ParserState each_source_position = pstate;+    if (!lex< variable >()) error("@each directive requires an iteration variable", pstate);+    vector<string> vars;+    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* list = parse_list();+    list->is_delayed(false);+    if (list->concrete_type() == Expression::LIST) {+      List* l = static_cast<List*>(list);+      for (size_t i = 0, L = l->length(); i < L; ++i) {+        (*l)[i]->is_delayed(false);+      }+    }+    if (!peek< exactly<'{'> >()) error("expected '{' after the upper bound in @each directive", pstate);+    Block* body = parse_block();+    return new (ctx.mem) Each(each_source_position, vars, list, body);+  }++  While* Parser::parse_while_directive()+  {+    lex< kwd_while_directive >();+    ParserState while_source_position = pstate;+    Expression* predicate = parse_list();+    predicate->is_delayed(false);+    Block* body = parse_block();+    return new (ctx.mem) While(while_source_position, predicate, body);+  }++  Media_Block* Parser::parse_media_block()+  {+    lex< kwd_media >();+    ParserState media_source_position = pstate;++    List* media_queries = parse_media_queries();++    if (!peek< exactly<'{'> >()) {+      error("expected '{' in media query", pstate);+    }+    Media_Block* media_block = new (ctx.mem) Media_Block(media_source_position, media_queries, 0);+    Media_Block* prev_media_block = last_media_block;+    last_media_block = media_block;+    media_block->block(parse_block());+    last_media_block = prev_media_block;++    return media_block;+  }++  List* Parser::parse_media_queries()+  {+    List* media_queries = new (ctx.mem) List(pstate, 0, List::COMMA);+    if (!peek< exactly<'{'> >()) (*media_queries) << parse_media_query();+    while (lex< exactly<','> >()) (*media_queries) << parse_media_query();+    return media_queries;+  }++  // Expression* Parser::parse_media_query()+  Media_Query* Parser::parse_media_query()+  {+    Media_Query* media_query = new (ctx.mem) Media_Query(pstate);++    if (lex< exactly< not_kwd > >()) media_query->is_negated(true);+    else if (lex< exactly< only_kwd > >()) media_query->is_restricted(true);++    if (peek< identifier_schema >()) media_query->media_type(parse_identifier_schema());+    else if (lex< identifier >())    media_query->media_type(parse_interpolated_chunk(lexed));+    else                             (*media_query) << parse_media_expression();++    while (lex< exactly< and_kwd > >()) (*media_query) << parse_media_expression();+    if (peek< identifier_schema >()) {+      String_Schema* schema = new (ctx.mem) String_Schema(pstate);+      *schema << media_query->media_type();+      *schema << new (ctx.mem) String_Constant(pstate, " ");+      *schema << parse_identifier_schema();+      media_query->media_type(schema);+    }+    while (lex< exactly< and_kwd > >()) (*media_query) << parse_media_expression();+    return media_query;+  }++  Media_Query_Expression* Parser::parse_media_expression()+  {+    if (peek< identifier_schema >()) {+      String* ss = parse_identifier_schema();+      return new (ctx.mem) Media_Query_Expression(pstate, ss, 0, true);+    }+    if (!lex< exactly<'('> >()) {+      error("media query expression must begin with '('", pstate);+    }+    Expression* feature = 0;+    if (peek< exactly<')'> >()) {+      error("media feature required in media query expression", pstate);+    }+    feature = parse_expression();+    Expression* expression = 0;+    if (lex< exactly<':'> >()) {+      expression = parse_list();+    }+    if (!lex< exactly<')'> >()) {+      error("unclosed parenthesis in media query expression", pstate);+    }+    return new (ctx.mem) Media_Query_Expression(feature->pstate(), feature, expression);+  }++  Feature_Block* Parser::parse_feature_block()+  {+    lex< kwd_supports >();+    ParserState supports_source_position = pstate;++    Feature_Query* feature_queries = parse_feature_queries();++    if (!peek< exactly<'{'> >()) {+      error("expected '{' in feature query", pstate);+    }+    Block* block = parse_block();++    return new (ctx.mem) Feature_Block(supports_source_position, feature_queries, block);+  }++  Feature_Query* Parser::parse_feature_queries()+  {+    Feature_Query* fq = new (ctx.mem) Feature_Query(pstate);+    Feature_Query_Condition* cond = new (ctx.mem) Feature_Query_Condition(pstate);+    cond->is_root(true);+    while (!peek< exactly<')'> >(position) && !peek< exactly<'{'> >(position))+      (*cond) << parse_feature_query();+    (*fq) << cond;++    if (fq->empty()) error("expected @supports condition (e.g. (display: flexbox))", pstate);++    return fq;+  }++  Feature_Query_Condition* Parser::parse_feature_query()+  {+    if (peek< kwd_not >(position)) return parse_supports_negation();+    else if (peek< kwd_and >(position)) return parse_supports_conjunction();+    else if (peek< kwd_or >(position)) return parse_supports_disjunction();+    else if (peek< exactly<'('> >(position)) return parse_feature_query_in_parens();+    else return parse_supports_declaration();+  }++  Feature_Query_Condition* Parser::parse_feature_query_in_parens()+  {+    Feature_Query_Condition* cond = new (ctx.mem) Feature_Query_Condition(pstate);++    if (!lex< exactly<'('> >()) error("@supports declaration expected '('", pstate);+    while (!peek< exactly<')'> >(position) && !peek< exactly<'{'> >(position))+      (*cond) << parse_feature_query();+    if (!lex< exactly<')'> >()) error("unclosed parenthesis in @supports declaration", pstate);++    return (cond->length() == 1) ? (*cond)[0] : cond;+  }++  Feature_Query_Condition* Parser::parse_supports_negation()+  {+    lex< kwd_not >();++    Feature_Query_Condition* cond = parse_feature_query();+    cond->operand(Feature_Query_Condition::NOT);++    return cond;+  }++  Feature_Query_Condition* Parser::parse_supports_conjunction()+  {+    lex< kwd_and >();++    Feature_Query_Condition* cond = parse_feature_query();+    cond->operand(Feature_Query_Condition::AND);++    return cond;+  }++  Feature_Query_Condition* Parser::parse_supports_disjunction()+  {+    lex< kwd_or >();++    Feature_Query_Condition* cond = parse_feature_query();+    cond->operand(Feature_Query_Condition::OR);++    return cond;+  }++  Feature_Query_Condition* Parser::parse_supports_declaration()+  {+    Declaration* declaration = parse_declaration();+    Feature_Query_Condition* cond = new (ctx.mem) Feature_Query_Condition(declaration->pstate(),+                                                                          1,+                                                                          declaration->property(),+                                                                          declaration->value());+    return cond;+  }++  At_Root_Block* Parser::parse_at_root_block()+  {+    lex<kwd_at_root>();+    ParserState at_source_position = pstate;+    Block* body = 0;+    At_Root_Expression* expr = 0;+    Selector_Lookahead lookahead_result;+    in_at_root = true;+    if (peek< exactly<'('> >()) {+      expr = parse_at_root_expression();+      body = parse_block();+    }+    else if (peek< exactly<'{'> >()) {+      body = parse_block();+    }+    else if ((lookahead_result = lookahead_for_selector(position)).found) {+      Ruleset* r = parse_ruleset(lookahead_result);+      body = new (ctx.mem) Block(r->pstate(), 1);+      *body << r;+    }+    in_at_root = false;+    At_Root_Block* at_root = new (ctx.mem) At_Root_Block(at_source_position, body);+    if (expr) at_root->expression(expr);+    return at_root;+  }++  At_Root_Expression* Parser::parse_at_root_expression()+  {+    lex< exactly<'('> >();+    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 ");+    }++    Declaration* declaration = parse_declaration();+    List* value = new (ctx.mem) List(declaration->value()->pstate(), 1);++    if (declaration->value()->concrete_type() == Expression::LIST) {+        value = static_cast<List*>(declaration->value());+    }+    else *value << declaration->value();++    At_Root_Expression* cond = new (ctx.mem) At_Root_Expression(declaration->pstate(),+                                                                declaration->property(),+                                                                value);+    if (!lex< exactly<')'> >()) error("unclosed parenthesis in @at-root expression", pstate);+    return cond;+  }++  At_Rule* Parser::parse_at_rule()+  {+    lex<at_keyword>();+    string kwd(lexed);+    ParserState at_source_position = pstate;+    Selector* sel = 0;+    Expression* val = 0;+    Selector_Lookahead lookahead = lookahead_for_extension_target(position);+    if (lookahead.found) {+      if (lookahead.has_interpolants) {+        sel = parse_selector_schema(lookahead.found);+      }+      else {+        sel = parse_selector_group();+      }+    }+    else if (!(peek<exactly<'{'> >() || peek<exactly<'}'> >() || peek<exactly<';'> >())) {+      val = parse_list();+    }+    Block* body = 0;+    if (peek< exactly<'{'> >()) body = parse_block();+    At_Rule* rule = new (ctx.mem) At_Rule(at_source_position, kwd, sel, body);+    if (!sel) rule->value(val);+    return rule;+  }++  Warning* Parser::parse_warning()+  {+    lex< kwd_warn >();+    return new (ctx.mem) Warning(pstate, parse_list());+  }++  Error* Parser::parse_error()+  {+    lex< kwd_err >();+    return new (ctx.mem) Error(pstate, parse_list());+  }++  Debug* Parser::parse_debug()+  {+    lex< kwd_dbg >();+    return new (ctx.mem) Debug(pstate, parse_list());+  }++  Selector_Lookahead Parser::lookahead_for_selector(const char* start)+  {+    const char* p = start ? start : position;+    const char* q;+    bool saw_stuff = false;+    bool saw_interpolant = false;++    while ((q = peek< identifier >(p))                             ||+           (q = peek< hyphens_and_identifier >(p))                 ||+           (q = peek< hyphens_and_name >(p))                       ||+           (q = peek< type_selector >(p))                          ||+           (q = peek< id_name >(p))                                ||+           (q = peek< class_name >(p))                             ||+           (q = peek< sequence< pseudo_prefix, identifier > >(p))  ||+           (q = peek< percentage >(p))                             ||+           (q = peek< dimension >(p))                              ||+           (q = peek< quoted_string >(p))                          ||+           (q = peek< exactly<'*'> >(p))                           ||+           (q = peek< exactly<sel_deep_kwd> >(p))                           ||+           (q = peek< exactly<'('> >(p))                           ||+           (q = peek< exactly<')'> >(p))                           ||+           (q = peek< exactly<'['> >(p))                           ||+           (q = peek< exactly<']'> >(p))                           ||+           (q = peek< exactly<'+'> >(p))                           ||+           (q = peek< exactly<'~'> >(p))                           ||+           (q = peek< exactly<'>'> >(p))                           ||+           (q = peek< exactly<','> >(p))                           ||+           (saw_stuff && (q = peek< exactly<'-'> >(p)))            ||+           (q = peek< binomial >(p))                               ||+           (q = peek< block_comment >(p))                          ||+           (q = peek< sequence< optional<sign>,+                                zero_plus<digit>,+                                exactly<'n'> > >(p))               ||+           (q = peek< sequence< optional<sign>,+                                one_plus<digit> > >(p))                     ||+           (q = peek< number >(p))                                 ||+           (q = peek< sequence< exactly<'&'>,+                                identifier_alnums > >(p))        ||+           (q = peek< exactly<'&'> >(p))                           ||+           (q = peek< exactly<'%'> >(p))                           ||+           (q = peek< alternatives<exact_match,+                                   class_match,+                                   dash_match,+                                   prefix_match,+                                   suffix_match,+                                   substring_match> >(p))          ||+           (q = peek< sequence< exactly<'.'>, interpolant > >(p))  ||+           (q = peek< sequence< exactly<'#'>, interpolant > >(p))  ||+           (q = peek< sequence< one_plus< exactly<'-'> >, interpolant > >(p))  ||+           (q = peek< sequence< pseudo_prefix, interpolant > >(p)) ||+           (q = peek< interpolant >(p))) {+      saw_stuff = true;+      p = q;+      if (*(p - 1) == '}') saw_interpolant = true;+    }++    Selector_Lookahead result;+    result.found            = saw_stuff && peek< exactly<'{'> >(p) ? p : 0;+    result.has_interpolants = saw_interpolant;++    return result;+  }++  Selector_Lookahead Parser::lookahead_for_extension_target(const char* start)+  {+    const char* p = start ? start : position;+    const char* q;+    bool saw_interpolant = false;+    bool saw_stuff = false;++    while ((q = peek< identifier >(p))                             ||+           (q = peek< type_selector >(p))                          ||+           (q = peek< id_name >(p))                                ||+           (q = peek< class_name >(p))                             ||+           (q = peek< sequence< pseudo_prefix, identifier > >(p))  ||+           (q = peek< percentage >(p))                             ||+           (q = peek< dimension >(p))                              ||+           (q = peek< quoted_string >(p))                          ||+           (q = peek< exactly<'*'> >(p))                           ||+           (q = peek< exactly<'('> >(p))                           ||+           (q = peek< exactly<')'> >(p))                           ||+           (q = peek< exactly<'['> >(p))                           ||+           (q = peek< exactly<']'> >(p))                           ||+           (q = peek< exactly<'+'> >(p))                           ||+           (q = peek< exactly<'~'> >(p))                           ||+           (q = peek< exactly<'>'> >(p))                           ||+           (q = peek< exactly<','> >(p))                           ||+           (saw_stuff && (q = peek< exactly<'-'> >(p)))            ||+           (q = peek< binomial >(p))                               ||+           (q = peek< block_comment >(p))                          ||+           (q = peek< sequence< optional<sign>,+                                zero_plus<digit>,+                                exactly<'n'> > >(p))               ||+           (q = peek< sequence< optional<sign>,+                                one_plus<digit> > >(p))                     ||+           (q = peek< number >(p))                                 ||+           (q = peek< sequence< exactly<'&'>,+                                identifier_alnums > >(p))        ||+           (q = peek< exactly<'&'> >(p))                           ||+           (q = peek< exactly<'%'> >(p))                           ||+           (q = peek< alternatives<exact_match,+                                   class_match,+                                   dash_match,+                                   prefix_match,+                                   suffix_match,+                                   substring_match> >(p))          ||+           (q = peek< sequence< exactly<'.'>, interpolant > >(p))  ||+           (q = peek< sequence< exactly<'#'>, interpolant > >(p))  ||+           (q = peek< sequence< one_plus< exactly<'-'> >, interpolant > >(p))  ||+           (q = peek< sequence< pseudo_prefix, interpolant > >(p)) ||+           (q = peek< interpolant >(p))                            ||+           (q = peek< optional >(p))) {+      p = q;+      if (*(p - 1) == '}') saw_interpolant = true;+      saw_stuff = true;+    }++    Selector_Lookahead result;+    result.found            = peek< alternatives< exactly<';'>, exactly<'}'>, exactly<'{'> > >(p) && saw_stuff ? p : 0;+    result.has_interpolants = saw_interpolant;++    return result;+  }++  void Parser::read_bom()+  {+    size_t skip = 0;+    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* Parser::fold_operands(Expression* base, vector<Expression*>& operands, Binary_Expression::Type op)+  {+    for (size_t i = 0, S = operands.size(); i < S; ++i) {+      base = new (ctx.mem) Binary_Expression(pstate, op, base, operands[i]);+      Binary_Expression* b = static_cast<Binary_Expression*>(base);+      if (op == Binary_Expression::DIV && b->left()->is_delayed() && b->right()->is_delayed()) {+        base->is_delayed(true);+      }+      else {+        b->left()->is_delayed(false);+        b->right()->is_delayed(false);+      }+    }+    return base;+  }++  Expression* Parser::fold_operands(Expression* base, vector<Expression*>& operands, vector<Binary_Expression::Type>& ops)+  {+    for (size_t i = 0, S = operands.size(); i < S; ++i) {+      base = new (ctx.mem) Binary_Expression(base->pstate(), ops[i], base, operands[i]);+      Binary_Expression* b = static_cast<Binary_Expression*>(base);+      if (ops[i] == Binary_Expression::DIV && b->left()->is_delayed() && b->right()->is_delayed()) {+        base->is_delayed(true);+      }+      else {+        b->left()->is_delayed(false);+        b->right()->is_delayed(false);+      }+    }+    return base;+  }++  void Parser::error(string msg, Position pos)+  {+    throw Sass_Error(Sass_Error::syntax, 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 string& msg, const string& prefix, const string& middle)+  {+    int max_len = 14;+    const char* pos = peek < optional_spaces >();+    bool ellipsis_left = false;+    const char* pos_left(pos);+    while (*pos_left && pos_left >= source) {+      if (pos - pos_left > max_len) {+        ellipsis_left = true;+        break;+      }+      if (*pos_left == '\r') break;+      if (*pos_left == '\n') break;+      -- pos_left;+    }+    bool ellipsis_right = false;+    const char* pos_right(pos);+    while (*pos_right && pos_right <= end) {+      if (pos_right - pos > max_len) {+        ellipsis_right = true;+        break;+      }+      if (*pos_right == '\r') break;+      if (*pos_right == '\n') break;+      ++ pos_right;+    }+    string left(pos_left, pos);+    string right(pos, pos_right);+    if (ellipsis_left) left = ellipsis + left;+    if (ellipsis_right) right = right + ellipsis;+    // now pass new message to the more generic error function+    error(msg + prefix + quote(left) + middle + quote(right), pstate);+  }++}
+ libsass/parser.hpp view
@@ -0,0 +1,280 @@+#ifndef SASS_PARSER_H+#define SASS_PARSER_H++#include <map>+#include <vector>+#include <iostream>++#include "ast.hpp"+#include "position.hpp"+#include "context.hpp"+#include "position.hpp"+#include "prelexer.hpp"++struct Selector_Lookahead {+  const char* found;+  bool has_interpolants;+};++namespace Sass {+  using std::string;+  using std::vector;+  using std::map;+  using namespace Prelexer;++  class Parser : public ParserState {+  private:+    void add_single_file (Import* imp, string import_path);+    void import_single_file (Import* imp, string import_path);+  public:+    class AST_Node;++    enum Syntactic_Context { nothing, mixin_def, function_def };+    bool do_import(const string& import_path, Import* imp, vector<Sass_Importer_Entry> importers, bool only_one = true);++    Context& ctx;+    vector<Block*> block_stack;+    vector<Syntactic_Context> stack;+    Media_Block* last_media_block;+    const char* source;+    const char* position;+    const char* end;+    Position before_token;+    Position after_token;+    ParserState pstate;+    int indentation;+++    Token lexed;+    bool in_at_root;++    Parser(Context& ctx, const ParserState& pstate)+    : ParserState(pstate), ctx(ctx), block_stack(0), stack(0), last_media_block(0),+      source(0), position(0), end(0), before_token(pstate), after_token(pstate), pstate("[NULL]"), indentation(0)+    { in_at_root = false; stack.push_back(nothing); }++    // static Parser from_string(const string& src, Context& ctx, ParserState pstate = ParserState("[STRING]"));+    static Parser from_c_str(const char* src, Context& ctx, ParserState pstate = ParserState("[CSTRING]"));+    static Parser from_c_str(const char* beg, const char* end, Context& ctx, ParserState pstate = ParserState("[CSTRING]"));+    static Parser from_token(Token t, Context& ctx, ParserState pstate = ParserState("[TOKEN]"));++#ifdef __clang__++    // lex and peak uses the template parameter to branch on the action, which+    // triggers clangs tautological comparison on the single-comparison+    // branches. This is not a bug, just a merging of behaviour into+    // one function++#pragma clang diagnostic push+#pragma clang diagnostic ignored "-Wtautological-compare"++#endif+++    bool peek_newline(const char* start = 0);++    // skip over spaces, tabs and line comments+    template <prelexer mx>+    const char* sneak(const char* start = 0)+    {++      // maybe use optional start position from arguments?+      const char* it_position = start ? start : position;++      // skip white-space?+      if (mx == spaces ||+          mx == no_spaces ||+          mx == css_comments ||+          mx == css_whitespace ||+          mx == optional_spaces ||+          mx == optional_css_comments ||+          mx == optional_css_whitespace+      ) {+        return it_position;+      }++      // skip over spaces, tabs and sass line comments+      const char* pos = optional_css_whitespace(it_position);+      // always return a valid position+      return pos ? pos : it_position;++    }++    // peek will only skip over space, tabs and line comment+    // return the position where the lexer match will occur+    template <prelexer mx>+    const char* peek(const char* start = 0)+    {++      // sneak up to the actual token we want to lex+      // this should skip over white-space if desired+      const char* it_before_token = sneak < mx >(start);++      // match the given prelexer+      return mx(it_before_token);++    }++    // white-space handling is built into the lexer+    // this way you do not need to parse it yourself+    // some matchers don't accept certain white-space+    // we do not support start arg, since we manipulate+    // sourcemap offset and we modify the position pointer!+    // lex will only skip over space, tabs and line comment+    template <prelexer mx>+    const char* lex(bool lazy = true)+    {++      // position considered before lexed token+      // we can skip whitespace or comments for+      // lazy developers (but we need control)+      const char* it_before_token = position;++      // sneak up to the actual token we want to lex+      // this should skip over white-space if desired+      if (lazy) it_before_token = sneak < mx >(position);++      // now call matcher to get position after token+      const char* it_after_token = mx(it_before_token);++      // assertion that we got a valid match+      if (it_after_token == 0) return 0;+      // assertion that we actually lexed something+      if (it_after_token == it_before_token) return 0;++      // create new lexed token object (holds all parse result information)+      lexed = Token(position, it_before_token, it_after_token);++      // advance position (add whitespace before current token)+      before_token = after_token.add(position, it_before_token);++      // update after_token position for current token+      after_token.add(it_before_token, it_after_token);++      // ToDo: could probably do this incremetal on original object (API wants offset?)+      pstate = ParserState(path, source, lexed, before_token, after_token - before_token);++      // advance internal char iterator+      return position = it_after_token;++    }++    // lex_css skips over space, tabs, line and block comment+    // all block comments will be consumed and thrown away+    // source-map position will point to token after the comment+    template <prelexer mx>+    const char* lex_css()+    {+      // throw away comments+      // update srcmap position+      lex < css_comments >();+      // now lex a token+      return lex< mx >();+    }++    // all block comments will be skipped and thrown away+    template <prelexer mx>+    const char* peek_css(const char* start = 0)+    {+      // now peek a token (skip comments first)+      return peek< mx >(peek < css_comments >(start));+    }++#ifdef __clang__++#pragma clang diagnostic pop++#endif++    void error(string msg, Position pos);+    // generate message with given and expected sample+    // text before and in the middle are configurable+    void css_error(const string& msg,+                   const string& prefix = " after ",+                   const string& middle = ", was: ");+    void read_bom();++    Block* parse();+    Import* parse_import();+    Definition* parse_definition();+    Parameters* parse_parameters();+    Parameter* parse_parameter();+    Mixin_Call* parse_mixin_call();+    Arguments* parse_arguments(bool has_url = false);+    Argument* parse_argument(bool has_url = false);+    Assignment* parse_assignment();+    // Propset* parse_propset();+    Ruleset* parse_ruleset(Selector_Lookahead lookahead);+    Selector_Schema* parse_selector_schema(const char* end_of_selector);+    Selector_List* parse_selector_group();+    Complex_Selector* parse_selector_combination();+    Compound_Selector* parse_simple_selector_sequence();+    Simple_Selector* parse_simple_selector();+    Wrapped_Selector* parse_negated_selector();+    Simple_Selector* parse_pseudo_selector();+    Attribute_Selector* parse_attribute_selector();+    Block* parse_block();+    bool parse_number_prefix();+    Declaration* parse_declaration();+    Expression* parse_map_value();+    Expression* parse_map();+    Expression* parse_list();+    Expression* parse_comma_list();+    Expression* parse_space_list();+    Expression* parse_disjunction();+    Expression* parse_conjunction();+    Expression* parse_relation();+    Expression* parse_expression();+    Expression* parse_term();+    Expression* parse_factor();+    Expression* parse_value();+    Function_Call* parse_calc_function();+    Function_Call* parse_function_call();+    Function_Call_Schema* parse_function_call_schema();+    String* parse_interpolated_chunk(Token, bool constant = false);+    String* parse_string();+    String_Constant* parse_static_value();+    String* parse_ie_property();+    String* parse_ie_keyword_arg();+    String_Schema* parse_value_schema(const char* stop);+    String* parse_identifier_schema();+    // String_Schema* parse_url_schema();+    If* parse_if_directive(bool else_if = false);+    For* parse_for_directive();+    Each* parse_each_directive();+    While* parse_while_directive();+    Media_Block* parse_media_block();+    List* parse_media_queries();+    Media_Query* parse_media_query();+    Media_Query_Expression* parse_media_expression();+    Feature_Block* parse_feature_block();+    Feature_Query* parse_feature_queries();+    Feature_Query_Condition* parse_feature_query();+    Feature_Query_Condition* parse_feature_query_in_parens();+    Feature_Query_Condition* parse_supports_negation();+    Feature_Query_Condition* parse_supports_conjunction();+    Feature_Query_Condition* parse_supports_disjunction();+    Feature_Query_Condition* parse_supports_declaration();+    At_Root_Block* parse_at_root_block();+    At_Root_Expression* parse_at_root_expression();+    At_Rule* parse_at_rule();+    Warning* parse_warning();+    Error* parse_error();+    Debug* parse_debug();++    void parse_block_comments(Block* block);++    Selector_Lookahead lookahead_for_selector(const char* start = 0);+    Selector_Lookahead lookahead_for_extension_target(const char* start = 0);++    Expression* fold_operands(Expression* base, vector<Expression*>& operands, Binary_Expression::Type op);+    Expression* fold_operands(Expression* base, vector<Expression*>& operands, vector<Binary_Expression::Type>& ops);++    void throw_syntax_error(string message, size_t ln = 0);+    void throw_read_error(string message, size_t ln = 0);+  };++  size_t check_bom_chars(const char* src, const char *end, const unsigned char* bom, size_t len);+}++#endif
+ libsass/paths.hpp view
@@ -0,0 +1,74 @@+#ifndef SASS_PATHS_H+#define SASS_PATHS_H++#include <string>+#include <vector>+#include <sstream>+#include <iostream>++using namespace std;++template<typename T>+string vector_to_string(vector<T> v)+{+  stringstream buffer;+  buffer << "[";++  if (!v.empty())+  {  buffer << v[0]; }+  else+  { buffer << "]"; }++  if (v.size() == 1)+  { buffer << "]"; }+  else+  {+    for (size_t i = 1, S = v.size(); i < S; ++i) buffer << ", " << v[i];+    buffer << "]";+  }++  return buffer.str();+}++namespace Sass {++  using namespace std;++  template<typename T>+  vector<vector<T> > paths(vector<vector<T> > strata, size_t from_end = 0)+  {+    if (strata.empty()) {+      return vector<vector<T> >();+    }++    size_t end = strata.size() - from_end;+    if (end <= 1) {+      vector<vector<T> > starting_points;+      starting_points.reserve(strata[0].size());+      for (size_t i = 0, S = strata[0].size(); i < S; ++i) {+        vector<T> starting_point;+        starting_point.push_back(strata[0][i]);+        starting_points.push_back(starting_point);+      }+      return starting_points;+    }++    vector<vector<T> > up_to_here = paths(strata, from_end + 1);+    vector<T>          here       = strata[end-1];++    vector<vector<T> > branches;+    branches.reserve(up_to_here.size() * here.size());+    for (size_t i = 0, S1 = up_to_here.size(); i < S1; ++i) {+      for (size_t j = 0, S2 = here.size(); j < S2; ++j) {+        vector<T> branch = up_to_here[i];+        branch.push_back(here[j]);+        branches.push_back(branch);+      }+    }++    return branches;+  }++}++#endif
+ libsass/plugins.cpp view
@@ -0,0 +1,168 @@+#ifdef _WIN32+#include <windows.h>+#else+#include <sys/types.h>+#include <dirent.h>+#include <errno.h>+#include <dlfcn.h>+#endif++#include <iostream>+#include "output.hpp"+#include "plugins.hpp"++#define npos string::npos++namespace Sass {++  Plugins::Plugins(void) { }+  Plugins::~Plugins(void) { }++  // check if plugin is compatible with this version+  // plugins may be linked static against libsass+  // we try to be compatible between major versions+  inline bool compatibility(const char* their_version)+  {+// const char* their_version = "3.1.2";+    // first check if anyone has an unknown version+    const char* our_version = libsass_version();+    if (!strcmp(their_version, "[na]")) return false;+    if (!strcmp(our_version, "[na]")) return false;++    // find the position of the second dot+    size_t pos = string(our_version).find('.', 0);+    if (pos != npos) pos = string(our_version).find('.', pos + 1);++    // if we do not have two dots we fallback to compare complete string+    if (pos == npos) { return strcmp(their_version, our_version) ? 0 : 1; }+    // otherwise only compare up to the second dot (major versions)+    else { return strncmp(their_version, our_version, pos) ? 0 : 1; }++  }++  // load one specific plugin+  bool Plugins::load_plugin (const string& path)+  {++    typedef const char* (*__plugin_version__)(void);+    typedef Sass_Function_List (*__plugin_load_fns__)(void);+    typedef Sass_Importer_List (*__plugin_load_imps__)(void);++    if (LOAD_LIB(plugin, path))+    {+      // try to load initial function to query libsass version suppor+      if (LOAD_LIB_FN(__plugin_version__, plugin_version, "libsass_get_version"))+      {+        // get the libsass version of the plugin+        if (!compatibility(plugin_version())) return false;+        // try to get import address for "libsass_load_functions"+        if (LOAD_LIB_FN(__plugin_load_fns__, plugin_load_functions, "libsass_load_functions"))+        {+          Sass_Function_List fns = plugin_load_functions();+          while (fns && *fns) { functions.push_back(*fns); ++ fns; }+        }+        // try to get import address for "libsass_load_importers"+        if (LOAD_LIB_FN(__plugin_load_imps__, plugin_load_importers, "libsass_load_importers"))+        {+          Sass_Importer_List imps = plugin_load_importers();+          while (imps && *imps) { importers.push_back(*imps); ++ imps; }+        }+        // try to get import address for "libsass_load_headers"+        if (LOAD_LIB_FN(__plugin_load_imps__, plugin_load_headers, "libsass_load_headers"))+        {+          Sass_Importer_List imps = plugin_load_headers();+          while (imps && *imps) { headers.push_back(*imps); ++ imps; }+        }+        // success+        return true;+      }+      else+      {+        // print debug message to stderr (should not happen)+        cerr << "failed loading 'libsass_support' in <" << path << ">" << endl;+        if (const char* dlsym_error = dlerror()) cerr << dlsym_error << endl;+        CLOSE_LIB(plugin);+      }+    }+    else+    {+      // print debug message to stderr (should not happen)+      cerr << "failed loading plugin <" << path << ">" << endl;+      if (const char* dlopen_error = dlerror()) cerr << dlopen_error << endl;+    }++    return false;++  }++  size_t Plugins::load_plugins(const string& path)+  {++    // count plugins+    size_t loaded = 0;++    #ifdef _WIN32++      try+      {++        // use wchar (utf16)+        WIN32_FIND_DATAW data;+        // trailing slash is guaranteed+        string globsrch(path + "*.dll");+        // convert to wide chars (utf16) for system call+        wstring wglobsrch(UTF_8::convert_to_utf16(globsrch));+        HANDLE hFile = FindFirstFileW(wglobsrch.c_str(), &data);+        // check if system called returned a result+        // ToDo: maybe we should print a debug message+        if (hFile == INVALID_HANDLE_VALUE) return -1;++        // read directory+        while (true)+        {+          try+          {+            // the system will report the filenames with wide chars (utf16)+            string entry = UTF_8::convert_from_utf16(data.cFileName);+            // check if file ending matches exactly+            if (!ends_with(entry, ".dll")) continue;+            // load the plugin and increase counter+            if (load_plugin(path + entry)) ++ loaded;+            // check if there should be more entries+            if (GetLastError() == ERROR_NO_MORE_FILES) break;+            // load next entry (check for return type)+            if (!FindNextFileW(hFile, &data)) break;+          }+          catch (...)+          {+            // report the error to the console (should not happen)+            // seems like we got strange data from the system call?+            cerr << "filename in plugin path has invalid utf8?" << endl;+          }+        }+      }+      catch (utf8::invalid_utf8)+      {+        // report the error to the console (should not happen)+        // implementors should make sure to provide valid utf8+        cerr << "plugin path contains invalid utf8" << endl;+      }++    #else++      DIR *dp;+      struct dirent *dirp;+      if((dp  = opendir(path.c_str())) == NULL) return -1;+      while ((dirp = readdir(dp)) != NULL) {+        if (!ends_with(dirp->d_name, ".so")) continue;+        if (load_plugin(path + dirp->d_name)) ++ loaded;+      }+      closedir(dp);++    #endif+    return loaded;++  }++}+
+ libsass/plugins.hpp view
@@ -0,0 +1,58 @@+#ifndef SASS_PLUGINS_H+#define SASS_PLUGINS_H++#include <string>+#include <vector>+#include "utf8_string.hpp"+#include "sass_functions.h"++#ifdef _WIN32++  #define LOAD_LIB(var, path) HMODULE var = LoadLibraryW(UTF_8::convert_to_utf16(path).c_str())+  #define LOAD_LIB_WCHR(var, path_wide_str) HMODULE var = LoadLibraryW(path_wide_str.c_str())+  #define LOAD_LIB_FN(type, var, name) type var = (type) GetProcAddress(plugin, name)+  #define CLOSE_LIB(var) FreeLibrary(var)++  #ifndef dlerror+  #define dlerror() 0+  #endif++#else++  #define LOAD_LIB(var, path) void* var = dlopen(path.c_str(), RTLD_LAZY)+  #define LOAD_LIB_FN(type, var, name) type var = (type) dlsym(plugin, name)+  #define CLOSE_LIB(var) dlclose(var)++#endif++namespace Sass {++  using namespace std;++  class Plugins {++    public: // c-tor+      Plugins(void);+      ~Plugins(void);++    public: // methods+      // load one specific plugin+      bool load_plugin(const string& path);+      // load all plugins from a directory+      size_t load_plugins(const string& path);++    public: // public accessors+      const vector<Sass_Importer_Entry> get_headers(void) { return headers; };+      const vector<Sass_Importer_Entry> get_importers(void) { return importers; };+      const vector<Sass_Function_Entry> get_functions(void) { return functions; };++    private: // private vars+      vector<Sass_Importer_Entry> headers;+      vector<Sass_Importer_Entry> importers;+      vector<Sass_Function_Entry> functions;++  };++}++#endif
+ libsass/position.cpp view
@@ -0,0 +1,163 @@+#include "position.hpp"++namespace Sass {++  using namespace std;++  Offset::Offset(const char* string)+  : line(0), column(0)+  {+    *this = inc(string, string + strlen(string));+  }++  Offset::Offset(const string& text)+  : line(0), column(0)+  {+    *this = inc(text.c_str(), text.c_str() + text.size());+  }++  Offset::Offset(const size_t line, const size_t column)+  : line(line), column(column) { }++  // init/create instance from const char substring+  Offset Offset::init(const char* beg, const char* end)+  {+    Offset offset(0, 0);+    if (end == 0) {+      end += strlen(beg);+    }+    offset.add(beg, end);+    return offset;+  }++  // increase offset by given string (mostly called by lexer)+  // increase line counter and count columns on the last line+  // ToDo: make the col count utf8 aware+  Offset Offset::add(const char* begin, const char* end)+  {+    if (end == 0) return *this;+    while (begin < end && *begin) {+      if (*begin == '\n') {+        ++ line;+        // start new line+        column = 0;+      } else {+        ++ column;+      }+      ++begin;+    }+    return *this;+  }++  // increase offset by given string (mostly called by lexer)+  // increase line counter and count columns on the last line+  Offset Offset::inc(const char* begin, const char* end) const+  {+    Offset offset(line, column);+    offset.add(begin, end);+    return offset;+  }++  bool Offset::operator== (const Offset &pos) const+  {+    return line == pos.line && column == pos.column;+  }++  bool Offset::operator!= (const Offset &pos) const+  {+    return line != pos.line || column != pos.column;+  }++  void Offset::operator+= (const Offset &off)+  {+    *this = Offset(line + off.line, off.line > 0 ? off.column : column + off.column);+  }++  Offset Offset::operator+ (const Offset &off) const+  {+    return Offset(line + off.line, off.line > 0 ? off.column : column + off.column);+  }++  Offset Offset::operator- (const Offset &off) const+  {+    return Offset(line - off.line, off.line == line ? column - off.column : column);+  }++  Position::Position(const size_t file)+  : Offset(0, 0), file(file) { }++  Position::Position(const size_t file, const Offset& offset)+  : Offset(offset), file(file) { }++  Position::Position(const size_t line, const size_t column)+  : Offset(line, column), file(-1) { }++  Position::Position(const size_t file, const size_t line, const size_t column)+  : Offset(line, column), file(file) { }+++  ParserState::ParserState(string path, const char* src, const size_t file)+  : Position(file, 0, 0), path(path), src(src), offset(0, 0), token() { }++  ParserState::ParserState(string path, const char* src, Position position, Offset offset)+  : Position(position), path(path), src(src), offset(offset), token() { }++  ParserState::ParserState(string path, const char* src, Token token, Position position, Offset offset)+  : Position(position), path(path), src(src), offset(offset), token(token) { }++  Position Position::add(const char* begin, const char* end)+  {+    Offset::add(begin, end);+    return *this;+  }++  Position Position::inc(const char* begin, const char* end) const+  {+    Offset offset(line, column);+    offset = offset.inc(begin, end);+    return Position(file, offset);+  }++  bool Position::operator== (const Position &pos) const+  {+    return file == pos.file && line == pos.line && column == pos.column;+  }++  bool Position::operator!= (const Position &pos) const+  {+    return file == pos.file || line != pos.line || column != pos.column;+  }++  void Position::operator+= (const Offset &off)+  {+    *this = Position(file, line + off.line, off.line > 0 ? off.column : column + off.column);+  }++  const Position Position::operator+ (const Offset &off) const+  {+    return Position(file, line + off.line, off.line > 0 ? off.column : column + off.column);+  }++  const Offset Position::operator- (const Offset &off) const+  {+    return Offset(line - off.line, off.line == line ? column - off.column : column);+  }++  /* not used anymore - remove?+  std::ostream& operator<<(std::ostream& strm, const Offset& off)+  {+    if (off.line == string::npos) strm << "-1:"; else strm << off.line << ":";+    if (off.column == string::npos) strm << "-1"; else strm << off.column;+    return strm;+  } */++  /* not used anymore - remove?+  std::ostream& operator<<(std::ostream& strm, const Position& pos)+  {+    if (pos.file != string::npos) strm << pos.file << ":";+    if (pos.line == string::npos) strm << "-1:"; else strm << pos.line << ":";+    if (pos.column == string::npos) strm << "-1"; else strm << pos.column;+    return strm;+  } */++}
+ libsass/position.hpp view
@@ -0,0 +1,120 @@+#ifndef SASS_POSITION_H+#define SASS_POSITION_H++#include <string>+#include <cstring>+#include <cstdlib>+#include <sstream>+#include <iostream>++namespace Sass {++  using namespace std;++  class Offset {++    public: // c-tor+      Offset(const char* string);+      Offset(const string& text);+      Offset(const size_t line, const size_t column);++      // return new position, incremented by the given string+      Offset add(const char* begin, const char* end);+      Offset inc(const char* begin, const char* end) const;++      // init/create instance from const char substring+      static Offset init(const char* beg, const char* end);++    public: // overload operators for position+      void operator+= (const Offset &pos);+      bool operator== (const Offset &pos) const;+      bool operator!= (const Offset &pos) const;+      Offset operator+ (const Offset &off) const;+      Offset operator- (const Offset &off) const;++    public: // overload output stream operator+      // friend ostream& operator<<(ostream& strm, const Offset& off);++    public:+      Offset off() { return *this; };++    public:+      size_t line;+      size_t column;++  };++  class Position : public Offset {++    public: // c-tor+      Position(const size_t file); // line(0), column(0)+      Position(const size_t file, const Offset& offset);+      Position(const size_t line, const size_t column); // file(-1)+      Position(const size_t file, const size_t line, const size_t column);++    public: // overload operators for position+      void operator+= (const Offset &off);+      bool operator== (const Position &pos) const;+      bool operator!= (const Position &pos) const;+      const Position operator+ (const Offset &off) const;+      const Offset operator- (const Offset &off) const;+      // return new position, incremented by the given string+      Position add(const char* begin, const char* end);+      Position inc(const char* begin, const char* end) const;++    public: // overload output stream operator+      // friend ostream& operator<<(ostream& strm, const Position& pos);++    public:+      size_t file;++  };++  // Token type for representing lexed chunks of text+  class Token {+  public:+    const char* prefix;+    const char* begin;+    const char* end;++    Token()+    : prefix(0), begin(0), end(0) { }+    Token(const char* b, const char* e)+    : prefix(b), begin(b), end(e) { }+    Token(const char* str)+    : prefix(str), begin(str), end(str + strlen(str)) { }+    Token(const char* p, const char* b, const char* e)+    : prefix(p), begin(b), end(e) { }++    size_t length()    const { return end - begin; }+    string ws_before() const { return string(prefix, begin); }+    string to_string() const { return string(begin, end); }++    operator bool()   { return begin && end && begin >= end; }+    operator string() { return to_string(); }++    bool operator==(Token t)  { return to_string() == t.to_string(); }+  };++  class ParserState : public Position {++    public: // c-tor+      ParserState(string path, const char* src = 0, const size_t file = string::npos);+      ParserState(string path, const char* src, Position position, Offset offset = Offset(0, 0));+      ParserState(string path, const char* src, Token token, Position position, Offset offset = Offset(0, 0));++    public: // down casts+      Offset off() { return *this; };+      Position pos() { return *this; };++    public:+      string path;+      const char* src;+      Offset offset;+      Token token;++  };++}++#endif
+ libsass/posix/getopt.c view
@@ -0,0 +1,562 @@+/*	$OpenBSD: getopt_long.c,v 1.23 2007/10/31 12:34:57 chl Exp $	*/+/*	$NetBSD: getopt_long.c,v 1.15 2002/01/31 22:43:40 tv Exp $	*/++/*+ * Copyright (c) 2002 Todd C. Miller <Todd.Miller@courtesan.com>+ *+ * Permission to use, copy, modify, and distribute this software for any+ * purpose with or without fee is hereby granted, provided that the above+ * copyright notice and this permission notice appear in all copies.+ *+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.+ *+ * Sponsored in part by the Defense Advanced Research Projects+ * Agency (DARPA) and Air Force Research Laboratory, Air Force+ * Materiel Command, USAF, under agreement number F39502-99-1-0512.+ */+/*-+ * Copyright (c) 2000 The NetBSD Foundation, Inc.+ * All rights reserved.+ *+ * This code is derived from software contributed to The NetBSD Foundation+ * by Dieter Baron and Thomas Klausner.+ *+ * Redistribution and use in source and binary forms, with or without+ * modification, are permitted provided that the following conditions+ * are met:+ * 1. Redistributions of source code must retain the above copyright+ *    notice, this list of conditions and the following disclaimer.+ * 2. Redistributions in binary form must reproduce the above copyright+ *    notice, this list of conditions and the following disclaimer in the+ *    documentation and/or other materials provided with the distribution.+ *+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE+ * POSSIBILITY OF SUCH DAMAGE.+ */++#include <errno.h>+#include <stdlib.h>+#include <string.h>+#include <getopt.h>+#include <stdarg.h>+#include <stdio.h>+#include <windows.h>++#define	REPLACE_GETOPT		/* use this getopt as the system getopt(3) */++#ifdef REPLACE_GETOPT+int	opterr = 1;		/* if error message should be printed */+int	optind = 1;		/* index into parent argv vector */+int	optopt = '?';		/* character checked for validity */+#undef	optreset		/* see getopt.h */+#define	optreset		__mingw_optreset+int	optreset;		/* reset getopt */+char    *optarg;		/* argument associated with option */+#endif++#define PRINT_ERROR	((opterr) && (*options != ':'))++#define FLAG_PERMUTE	0x01	/* permute non-options to the end of argv */+#define FLAG_ALLARGS	0x02	/* treat non-options as args to option "-1" */+#define FLAG_LONGONLY	0x04	/* operate as getopt_long_only */++/* return values */+#define	BADCH		(int)'?'+#define	BADARG		((*options == ':') ? (int)':' : (int)'?')+#define	INORDER 	(int)1++#ifndef __CYGWIN__+#define __progname __argv[0]+#else+extern char __declspec(dllimport) *__progname;+#endif++#ifdef __CYGWIN__+static char EMSG[] = "";+#else+#define	EMSG		""+#endif++static int getopt_internal(int, char * const *, const char *,+			   const struct option *, int *, int);+static int parse_long_options(char * const *, const char *,+			      const struct option *, int *, int);+static int gcd(int, int);+static void permute_args(int, int, int, char * const *);++static char *place = EMSG; /* option letter processing */++/* XXX: set optreset to 1 rather than these two */+static int nonopt_start = -1; /* first non option argument (for permute) */+static int nonopt_end = -1;   /* first option after non options (for permute) */++/* Error messages */+static const char recargchar[] = "option requires an argument -- %c";+static const char recargstring[] = "option requires an argument -- %s";+static const char ambig[] = "ambiguous option -- %.*s";+static const char noarg[] = "option doesn't take an argument -- %.*s";+static const char illoptchar[] = "unknown option -- %c";+static const char illoptstring[] = "unknown option -- %s";++static void+_vwarnx(const char *fmt,va_list ap)+{+  (void)fprintf(stderr,"%s: ",__progname);+  if (fmt != NULL)+    (void)vfprintf(stderr,fmt,ap);+  (void)fprintf(stderr,"\n");+}++static void+warnx(const char *fmt,...)+{+  va_list ap;+  va_start(ap,fmt);+  _vwarnx(fmt,ap);+  va_end(ap);+}++/*+ * Compute the greatest common divisor of a and b.+ */+static int+gcd(int a, int b)+{+	int c;++	c = a % b;+	while (c != 0) {+		a = b;+		b = c;+		c = a % b;+	}++	return (b);+}++/*+ * Exchange the block from nonopt_start to nonopt_end with the block+ * from nonopt_end to opt_end (keeping the same order of arguments+ * in each block).+ */+static void+permute_args(int panonopt_start, int panonopt_end, int opt_end,+	char * const *nargv)+{+	int cstart, cyclelen, i, j, ncycle, nnonopts, nopts, pos;+	char *swap;++	/*+	 * compute lengths of blocks and number and size of cycles+	 */+	nnonopts = panonopt_end - panonopt_start;+	nopts = opt_end - panonopt_end;+	ncycle = gcd(nnonopts, nopts);+	cyclelen = (opt_end - panonopt_start) / ncycle;++	for (i = 0; i < ncycle; i++) {+		cstart = panonopt_end+i;+		pos = cstart;+		for (j = 0; j < cyclelen; j++) {+			if (pos >= panonopt_end)+				pos -= nnonopts;+			else+				pos += nopts;+			swap = nargv[pos];+			/* LINTED const cast */+			((char **) nargv)[pos] = nargv[cstart];+			/* LINTED const cast */+			((char **)nargv)[cstart] = swap;+		}+	}+}++/*+ * parse_long_options --+ *	Parse long options in argc/argv argument vector.+ * Returns -1 if short_too is set and the option does not match long_options.+ */+static int+parse_long_options(char * const *nargv, const char *options,+	const struct option *long_options, int *idx, int short_too)+{+	char *current_argv, *has_equal;+	size_t current_argv_len;+	int i, ambiguous, match;++#define IDENTICAL_INTERPRETATION(_x, _y)                                \+	(long_options[(_x)].has_arg == long_options[(_y)].has_arg &&    \+	 long_options[(_x)].flag == long_options[(_y)].flag &&          \+	 long_options[(_x)].val == long_options[(_y)].val)++	current_argv = place;+	match = -1;+	ambiguous = 0;++	optind++;++	if ((has_equal = strchr(current_argv, '=')) != NULL) {+		/* argument found (--option=arg) */+		current_argv_len = has_equal - current_argv;+		has_equal++;+	} else+		current_argv_len = strlen(current_argv);++	for (i = 0; long_options[i].name; i++) {+		/* find matching long option */+		if (strncmp(current_argv, long_options[i].name,+		    current_argv_len))+			continue;++		if (strlen(long_options[i].name) == current_argv_len) {+			/* exact match */+			match = i;+			ambiguous = 0;+			break;+		}+		/*+		 * If this is a known short option, don't allow+		 * a partial match of a single character.+		 */+		if (short_too && current_argv_len == 1)+			continue;++		if (match == -1)	/* partial match */+			match = i;+		else if (!IDENTICAL_INTERPRETATION(i, match))+			ambiguous = 1;+	}+	if (ambiguous) {+		/* ambiguous abbreviation */+		if (PRINT_ERROR)+			warnx(ambig, (int)current_argv_len,+			     current_argv);+		optopt = 0;+		return (BADCH);+	}+	if (match != -1) {		/* option found */+		if (long_options[match].has_arg == no_argument+		    && has_equal) {+			if (PRINT_ERROR)+				warnx(noarg, (int)current_argv_len,+				     current_argv);+			/*+			 * XXX: GNU sets optopt to val regardless of flag+			 */+			if (long_options[match].flag == NULL)+				optopt = long_options[match].val;+			else+				optopt = 0;+			return (BADARG);+		}+		if (long_options[match].has_arg == required_argument ||+		    long_options[match].has_arg == optional_argument) {+			if (has_equal)+				optarg = has_equal;+			else if (long_options[match].has_arg ==+			    required_argument) {+				/*+				 * optional argument doesn't use next nargv+				 */+				optarg = nargv[optind++];+			}+		}+		if ((long_options[match].has_arg == required_argument)+		    && (optarg == NULL)) {+			/*+			 * Missing argument; leading ':' indicates no error+			 * should be generated.+			 */+			if (PRINT_ERROR)+				warnx(recargstring,+				    current_argv);+			/*+			 * XXX: GNU sets optopt to val regardless of flag+			 */+			if (long_options[match].flag == NULL)+				optopt = long_options[match].val;+			else+				optopt = 0;+			--optind;+			return (BADARG);+		}+	} else {			/* unknown option */+		if (short_too) {+			--optind;+			return (-1);+		}+		if (PRINT_ERROR)+			warnx(illoptstring, current_argv);+		optopt = 0;+		return (BADCH);+	}+	if (idx)+		*idx = match;+	if (long_options[match].flag) {+		*long_options[match].flag = long_options[match].val;+		return (0);+	} else+		return (long_options[match].val);+#undef IDENTICAL_INTERPRETATION+}++/*+ * getopt_internal --+ *	Parse argc/argv argument vector.  Called by user level routines.+ */+static int+getopt_internal(int nargc, char * const *nargv, const char *options,+	const struct option *long_options, int *idx, int flags)+{+	char *oli;				/* option letter list index */+	int optchar, short_too;+	static int posixly_correct = -1;++	if (options == NULL)+		return (-1);++	/*+	 * XXX Some GNU programs (like cvs) set optind to 0 instead of+	 * XXX using optreset.  Work around this braindamage.+	 */+	if (optind == 0)+		optind = optreset = 1;++	/*+	 * Disable GNU extensions if POSIXLY_CORRECT is set or options+	 * string begins with a '+'.+	 *+	 * CV, 2009-12-14: Check POSIXLY_CORRECT anew if optind == 0 or+	 *                 optreset != 0 for GNU compatibility.+	 */+	if (posixly_correct == -1 || optreset != 0)+		posixly_correct = (getenv("POSIXLY_CORRECT") != NULL);+	if (*options == '-')+		flags |= FLAG_ALLARGS;+	else if (posixly_correct || *options == '+')+		flags &= ~FLAG_PERMUTE;+	if (*options == '+' || *options == '-')+		options++;++	optarg = NULL;+	if (optreset)+		nonopt_start = nonopt_end = -1;+start:+	if (optreset || !*place) {		/* update scanning pointer */+		optreset = 0;+		if (optind >= nargc) {          /* end of argument vector */+			place = EMSG;+			if (nonopt_end != -1) {+				/* do permutation, if we have to */+				permute_args(nonopt_start, nonopt_end,+				    optind, nargv);+				optind -= nonopt_end - nonopt_start;+			}+			else if (nonopt_start != -1) {+				/*+				 * If we skipped non-options, set optind+				 * to the first of them.+				 */+				optind = nonopt_start;+			}+			nonopt_start = nonopt_end = -1;+			return (-1);+		}+		if (*(place = nargv[optind]) != '-' ||+		    (place[1] == '\0' && strchr(options, '-') == NULL)) {+			place = EMSG;		/* found non-option */+			if (flags & FLAG_ALLARGS) {+				/*+				 * GNU extension:+				 * return non-option as argument to option 1+				 */+				optarg = nargv[optind++];+				return (INORDER);+			}+			if (!(flags & FLAG_PERMUTE)) {+				/*+				 * If no permutation wanted, stop parsing+				 * at first non-option.+				 */+				return (-1);+			}+			/* do permutation */+			if (nonopt_start == -1)+				nonopt_start = optind;+			else if (nonopt_end != -1) {+				permute_args(nonopt_start, nonopt_end,+				    optind, nargv);+				nonopt_start = optind -+				    (nonopt_end - nonopt_start);+				nonopt_end = -1;+			}+			optind++;+			/* process next argument */+			goto start;+		}+		if (nonopt_start != -1 && nonopt_end == -1)+			nonopt_end = optind;++		/*+		 * If we have "-" do nothing, if "--" we are done.+		 */+		if (place[1] != '\0' && *++place == '-' && place[1] == '\0') {+			optind++;+			place = EMSG;+			/*+			 * We found an option (--), so if we skipped+			 * non-options, we have to permute.+			 */+			if (nonopt_end != -1) {+				permute_args(nonopt_start, nonopt_end,+				    optind, nargv);+				optind -= nonopt_end - nonopt_start;+			}+			nonopt_start = nonopt_end = -1;+			return (-1);+		}+	}++	/*+	 * Check long options if:+	 *  1) we were passed some+	 *  2) the arg is not just "-"+	 *  3) either the arg starts with -- we are getopt_long_only()+	 */+	if (long_options != NULL && place != nargv[optind] &&+	    (*place == '-' || (flags & FLAG_LONGONLY))) {+		short_too = 0;+		if (*place == '-')+			place++;		/* --foo long option */+		else if (*place != ':' && strchr(options, *place) != NULL)+			short_too = 1;		/* could be short option too */++		optchar = parse_long_options(nargv, options, long_options,+		    idx, short_too);+		if (optchar != -1) {+			place = EMSG;+			return (optchar);+		}+	}++	if ((optchar = (int)*place++) == (int)':' ||+	    (optchar == (int)'-' && *place != '\0') ||+	    (oli = strchr(options, optchar)) == NULL) {+		/*+		 * If the user specified "-" and  '-' isn't listed in+		 * options, return -1 (non-option) as per POSIX.+		 * Otherwise, it is an unknown option character (or ':').+		 */+		if (optchar == (int)'-' && *place == '\0')+			return (-1);+		if (!*place)+			++optind;+		if (PRINT_ERROR)+			warnx(illoptchar, optchar);+		optopt = optchar;+		return (BADCH);+	}+	if (long_options != NULL && optchar == 'W' && oli[1] == ';') {+		/* -W long-option */+		if (*place)			/* no space */+			/* NOTHING */;+		else if (++optind >= nargc) {	/* no arg */+			place = EMSG;+			if (PRINT_ERROR)+				warnx(recargchar, optchar);+			optopt = optchar;+			return (BADARG);+		} else				/* white space */+			place = nargv[optind];+		optchar = parse_long_options(nargv, options, long_options,+		    idx, 0);+		place = EMSG;+		return (optchar);+	}+	if (*++oli != ':') {			/* doesn't take argument */+		if (!*place)+			++optind;+	} else {				/* takes (optional) argument */+		optarg = NULL;+		if (*place)			/* no white space */+			optarg = place;+		else if (oli[1] != ':') {	/* arg not optional */+			if (++optind >= nargc) {	/* no arg */+				place = EMSG;+				if (PRINT_ERROR)+					warnx(recargchar, optchar);+				optopt = optchar;+				return (BADARG);+			} else+				optarg = nargv[optind];+		}+		place = EMSG;+		++optind;+	}+	/* dump back option letter */+	return (optchar);+}++#ifdef REPLACE_GETOPT+/*+ * getopt --+ *	Parse argc/argv argument vector.+ *+ * [eventually this will replace the BSD getopt]+ */+int+getopt(int nargc, char * const *nargv, const char *options)+{++	/*+	 * We don't pass FLAG_PERMUTE to getopt_internal() since+	 * the BSD getopt(3) (unlike GNU) has never done this.+	 *+	 * Furthermore, since many privileged programs call getopt()+	 * before dropping privileges it makes sense to keep things+	 * as simple (and bug-free) as possible.+	 */+	return (getopt_internal(nargc, nargv, options, NULL, NULL, 0));+}+#endif /* REPLACE_GETOPT */++/*+ * getopt_long --+ *	Parse argc/argv argument vector.+ */+int+getopt_long(int nargc, char * const *nargv, const char *options,+    const struct option *long_options, int *idx)+{++	return (getopt_internal(nargc, nargv, options, long_options, idx,+	    FLAG_PERMUTE));+}++/*+ * getopt_long_only --+ *	Parse argc/argv argument vector.+ */+int+getopt_long_only(int nargc, char * const *nargv, const char *options,+    const struct option *long_options, int *idx)+{++	return (getopt_internal(nargc, nargv, options, long_options, idx,+	    FLAG_PERMUTE|FLAG_LONGONLY));+}
+ libsass/posix/getopt.h view
@@ -0,0 +1,95 @@+#ifndef __GETOPT_H__+/**+ * DISCLAIMER+ * This file has no copyright assigned and is placed in the Public Domain.+ * This file is a part of the w64 mingw-runtime package.+ *+ * The w64 mingw-runtime package and its code is distributed in the hope that it + * will be useful but WITHOUT ANY WARRANTY.  ALL WARRANTIES, EXPRESSED OR + * IMPLIED ARE HEREBY DISCLAIMED.  This includes but is not limited to + * warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.+ */++#define __GETOPT_H__++/* All the headers include this file. */+#include <crtdefs.h>++#ifdef __cplusplus+extern "C" {+#endif++extern int optind;		/* index of first non-option in argv      */+extern int optopt;		/* single option character, as parsed     */+extern int opterr;		/* flag to enable built-in diagnostics... */+				/* (user may set to zero, to suppress)    */++extern char *optarg;		/* pointer to argument of current option  */++extern int getopt(int nargc, char * const *nargv, const char *options);++#ifdef _BSD_SOURCE+/*+ * BSD adds the non-standard `optreset' feature, for reinitialisation+ * of `getopt' parsing.  We support this feature, for applications which+ * proclaim their BSD heritage, before including this header; however,+ * to maintain portability, developers are advised to avoid it.+ */+# define optreset  __mingw_optreset+extern int optreset;+#endif+#ifdef __cplusplus+}+#endif+/*+ * POSIX requires the `getopt' API to be specified in `unistd.h';+ * thus, `unistd.h' includes this header.  However, we do not want+ * to expose the `getopt_long' or `getopt_long_only' APIs, when+ * included in this manner.  Thus, close the standard __GETOPT_H__+ * declarations block, and open an additional __GETOPT_LONG_H__+ * specific block, only when *not* __UNISTD_H_SOURCED__, in which+ * to declare the extended API.+ */+#endif /* !defined(__GETOPT_H__) */++#if !defined(__UNISTD_H_SOURCED__) && !defined(__GETOPT_LONG_H__)+#define __GETOPT_LONG_H__++#ifdef __cplusplus+extern "C" {+#endif++struct option		/* specification for a long form option...	*/+{+  const char *name;		/* option name, without leading hyphens */+  int         has_arg;		/* does it take an argument?		*/+  int        *flag;		/* where to save its status, or NULL	*/+  int         val;		/* its associated status value		*/+};++enum    		/* permitted values for its `has_arg' field...	*/+{+  no_argument = 0,      	/* option never takes an argument	*/+  required_argument,		/* option always requires an argument	*/+  optional_argument		/* option may take an argument		*/+};++extern int getopt_long(int nargc, char * const *nargv, const char *options,+    const struct option *long_options, int *idx);+extern int getopt_long_only(int nargc, char * const *nargv, const char *options,+    const struct option *long_options, int *idx);+/*+ * Previous MinGW implementation had...+ */+#ifndef HAVE_DECL_GETOPT+/*+ * ...for the long form API only; keep this for compatibility.+ */+# define HAVE_DECL_GETOPT	1+#endif++#ifdef __cplusplus+}+#endif++#endif /* !defined(__UNISTD_H_SOURCED__) && !defined(__GETOPT_LONG_H__) */
+ libsass/prelexer.cpp view
@@ -0,0 +1,724 @@+#include <cctype>+#include <cstddef>+#include <iostream>+#include <iomanip>+#include "util.hpp"+#include "position.hpp"+#include "prelexer.hpp"+#include "constants.hpp"+++namespace Sass {+  // using namespace Lexer;+  using namespace Constants;++  namespace Prelexer {+++    // Match a line comment (/.*?(?=\n|\r\n?|\Z)/.+    const char* line_comment(const char* src)+    {+      return sequence<+               exactly <+                 slash_slash+               >,+               non_greedy<+                 any_char,+                 end_of_line+               >+             >(src);+    }++    // Match a block comment.+    const char* block_comment(const char* src)+    {+      return sequence<+               zero_plus < space >,+               delimited_by<slash_star, star_slash, false> >(src);+    }+    /* not use anymore - remove?+    const char* block_comment_prefix(const char* src) {+      return exactly<slash_star>(src);+    }+    // Match either comment.+    const char* comment(const char* src) {+      return line_comment(src);+    }+    */++    // Match zero plus white-space or line_comments+    const char* optional_css_whitespace(const char* src) {+      return zero_plus< alternatives<spaces, line_comment> >(src);+    }+    const char* css_whitespace(const char* src) {+      return one_plus< alternatives<spaces, line_comment> >(src);+    }+    // Match optional_css_whitepace plus block_comments+    const char* optional_css_comments(const char* src) {+      return zero_plus< alternatives<spaces, line_comment, block_comment> >(src);+    }+    const char* css_comments(const char* src) {+      return one_plus< alternatives<spaces, line_comment, block_comment> >(src);+    }++    // Match one backslash escaped char /\\./+    const char* escape_seq(const char* src)+    {+      return sequence<+        exactly<'\\'>,+        any_char+      >(src);+    }++    // Match identifier start+    const char* identifier_alpha(const char* src)+    {+      return alternatives<+               alpha,+               unicode,+               exactly<'-'>,+               exactly<'_'>,+               escape_seq+             >(src);+    }++    // Match identifier after start+    const char* identifier_alnum(const char* src)+    {+      return alternatives<+               alnum,+               unicode,+               exactly<'-'>,+               exactly<'_'>,+               escape_seq+             >(src);+    }++    // Match CSS identifiers.+    const char* identifier(const char* src)+    {+      return sequence<+               zero_plus< exactly<'-'> >,+               one_plus < identifier_alpha >,+               zero_plus < identifier_alnum >+               // word_boundary not needed+             >(src);+    }++    const char* identifier_alnums(const char* src)+    {+      return one_plus< identifier_alnum >(src);+    }++    // Match number prefix ([\+\-]+)+    const char* number_prefix(const char* src) {+      return alternatives <+        exactly < '+' >,+        sequence <+          exactly < '-' >,+          optional_css_whitespace,+          exactly< '-' >+        >+      >(src);+    }++    // Match interpolant schemas+    const char* identifier_schema(const char* src) {+      // follows this pattern: (x*ix*)+ ... well, not quite+      return sequence< one_plus< sequence< zero_plus< alternatives< identifier, exactly<'-'> > >,+                                 interpolant,+                                 zero_plus< alternatives< identifier, number, exactly<'-'> > > > >,+                       negate< exactly<'%'> > >(src);+    }++    // interpolants can be recursive/nested+    const char* interpolant(const char* src) {+      return recursive_scopes< exactly<hash_lbrace>, exactly<rbrace> >(src);+    }++    // $re_squote = /'(?:$re_itplnt|\\.|[^'])*'/+    const char* single_quoted_string(const char* src) {+      // match a single quoted string, while skipping interpolants+      return sequence <+        exactly <'\''>,+        zero_plus <+          alternatives <+            // skip all escaped chars first+            escape_seq,+            // skip interpolants+            interpolant,+            // skip non delimiters+            any_char_but < '\'' >+          >+        >,+        exactly <'\''>+      >(src);+    }++    // $re_dquote = /"(?:$re_itp|\\.|[^"])*"/+    const char* double_quoted_string(const char* src) {+      // match a single quoted string, while skipping interpolants+      return sequence <+        exactly <'"'>,+        zero_plus <+          alternatives <+            // skip all escaped chars first+            escape_seq,+            // skip interpolants+            interpolant,+            // skip non delimiters+            any_char_but < '"' >+          >+        >,+        exactly <'"'>+      >(src);+    }++    // $re_quoted = /(?:$re_squote|$re_dquote)/+    const char* quoted_string(const char* src) {+      // match a quoted string, while skipping interpolants+      return alternatives<+        single_quoted_string,+        double_quoted_string+      >(src);+    }++    const char* value_schema(const char* src) {+      // follows this pattern: ([xyz]*i[xyz]*)++      return one_plus< sequence< zero_plus< alternatives< identifier, percentage, dimension, hex, number, quoted_string > >,+                                 interpolant,+                                 zero_plus< alternatives< identifier, percentage, dimension, hex, number, quoted_string, exactly<'%'> > > > >(src);+    }++    /* not used anymore - remove?+    const char* filename(const char* src) {+      return one_plus< alternatives< identifier, number, exactly<'.'> > >(src);+    }+    */++    // Match CSS '@' keywords.+    const char* at_keyword(const char* src) {+      return sequence<exactly<'@'>, identifier>(src);+    }++    const char* kwd_sel_deep(const char* src) {+      return word<sel_deep_kwd>(src);+    }++    const char* kwd_import(const char* src) {+      return word<import_kwd>(src);+    }++    const char* kwd_at_root(const char* src) {+      return word<at_root_kwd>(src);+    }++    const char* kwd_with_directive(const char* src) {+      return word<with_kwd>(src);+    }++    const char* kwd_without_directive(const char* src) {+      return word<without_kwd>(src);+    }++    const char* kwd_media(const char* src) {+      return word<media_kwd>(src);+    }++    const char* kwd_supports(const char* src) {+      return word<supports_kwd>(src);+    }++    const char* kwd_mixin(const char* src) {+      return word<mixin_kwd>(src);+    }++    const char* kwd_function(const char* src) {+      return word<function_kwd>(src);+    }++    const char* kwd_return_directive(const char* src) {+      return word<return_kwd>(src);+    }++    const char* kwd_include(const char* src) {+      return word<include_kwd>(src);+    }++    const char* kwd_content(const char* src) {+      return word<content_kwd>(src);+    }++    const char* kwd_extend(const char* src) {+      return word<extend_kwd>(src);+    }+++    const char* kwd_if_directive(const char* src) {+      return word<if_kwd>(src);+    }++    const char* kwd_else_directive(const char* src) {+      return word<else_kwd>(src);+    }+    const char* elseif_directive(const char* src) {+      return sequence< exactly< else_kwd >,+                                optional_css_whitespace,+                                word< if_after_else_kwd > >(src);+    }++    const char* kwd_for_directive(const char* src) {+      return word<for_kwd>(src);+    }++    const char* kwd_from(const char* src) {+      return word<from_kwd>(src);+    }++    const char* kwd_to(const char* src) {+      return word<to_kwd>(src);+    }++    const char* kwd_through(const char* src) {+      return word<through_kwd>(src);+    }++    const char* kwd_each_directive(const char* src) {+      return word<each_kwd>(src);+    }++    const char* kwd_in(const char* src) {+      return word<in_kwd>(src);+    }++    const char* kwd_while_directive(const char* src) {+      return word<while_kwd>(src);+    }++    const char* name(const char* src) {+      return one_plus< alternatives< alnum,+                                     exactly<'-'>,+                                     exactly<'_'>,+                                     exactly<'\\'> > >(src);+    }++    const char* kwd_warn(const char* src) {+      return word<warn_kwd>(src);+    }++    const char* kwd_err(const char* src) {+      return word<error_kwd>(src);+    }++    const char* kwd_dbg(const char* src) {+      return word<debug_kwd>(src);+    }++    /* not used anymore - remove?+    const char* directive(const char* src) {+      return sequence< exactly<'@'>, identifier >(src);+    } */++    const char* kwd_null(const char* src) {+      return word<null_kwd>(src);+    }++    // Match CSS type selectors+    const char* namespace_prefix(const char* src) {+      return sequence< optional< alternatives< identifier, exactly<'*'> > >,+                       exactly<'|'> >(src);+    }+    const char* type_selector(const char* src) {+      return sequence< optional<namespace_prefix>, identifier>(src);+    }+    const char* hyphens_and_identifier(const char* src) {+      return sequence< zero_plus< exactly< '-' > >, identifier >(src);+    }+    const char* hyphens_and_name(const char* src) {+      return sequence< zero_plus< exactly< '-' > >, name >(src);+    }+    const char* universal(const char* src) {+      return sequence< optional<namespace_prefix>, exactly<'*'> >(src);+    }+    // Match CSS id names.+    const char* id_name(const char* src) {+      return sequence<exactly<'#'>, name>(src);+    }+    // Match CSS class names.+    const char* class_name(const char* src) {+      return sequence<exactly<'.'>, identifier>(src);+    }+    // Attribute name in an attribute selector.+    const char* attribute_name(const char* src) {+      return alternatives< sequence< optional<namespace_prefix>, identifier>,+                           identifier >(src);+    }+    // match placeholder selectors+    const char* placeholder(const char* src) {+      return sequence<exactly<'%'>, identifier>(src);+    }+    // Match CSS numeric constants.++    const char* sign(const char* src) {+      return class_char<sign_chars>(src);+    }+    const char* unsigned_number(const char* src) {+      return alternatives<sequence< zero_plus<digits>,+                                    exactly<'.'>,+                                    one_plus<digits> >,+                          digits>(src);+    }+    const char* number(const char* src) {+      return sequence< optional<sign>, unsigned_number>(src);+    }+    const char* coefficient(const char* src) {+      return alternatives< sequence< optional<sign>, digits >,+                           sign >(src);+    }+    const char* binomial(const char* src) {+      return sequence< optional<sign>,+                       optional<digits>,+                       exactly<'n'>,+                       zero_plus < space >,+                       sign,+                       zero_plus < space >,+                       digits >(src);+    }+    const char* percentage(const char* src) {+      return sequence< number, exactly<'%'> >(src);+    }+    const char* ampersand(const char* src) {+      return exactly<'&'>(src);+    }++    /* not used anymore - remove?+    const char* em(const char* src) {+      return sequence< number, exactly<em_kwd> >(src);+    } */+    const char* dimension(const char* src) {+      return sequence<number, one_plus< alpha > >(src);+    }+    const char* hex(const char* src) {+      const char* p = sequence< exactly<'#'>, one_plus<xdigit> >(src);+      ptrdiff_t len = p - src;+      return (len != 4 && len != 7) ? 0 : p;+    }+    const char* hexa(const char* src) {+      const char* p = sequence< exactly<'#'>, one_plus<xdigit> >(src);+      ptrdiff_t len = p - src;+      return (len != 4 && len != 7 && len != 9) ? 0 : p;+    }+    const char* hex0(const char* src) {+      const char* p = sequence< exactly<'0'>, exactly<'x'>, one_plus<xdigit> >(src);+      ptrdiff_t len = p - src;+      return (len != 5 && len != 8) ? 0 : p;+    }++    /* no longer used - remove?+    const char* rgb_prefix(const char* src) {+      return word<rgb_kwd>(src);+    }*/+    // Match CSS uri specifiers.++    const char* uri_prefix(const char* src) {+      return exactly<url_kwd>(src);+    }+    const char* uri_value(const char* src)+    {+      return+      zero_plus <+        alternatives <+          alnum,+          exactly <'/'>,+          class_char < uri_chars >+        >+      >(src);+    }++    // TODO: rename the following two functions+    /* no longer used - remove?+    const char* uri(const char* src) {+      return sequence< exactly<url_kwd>,+                       optional<spaces>,+                       quoted_string,+                       optional<spaces>,+                       exactly<')'> >(src);+    }*/+    /* no longer used - remove?+    const char* url_value(const char* src) {+      return sequence< optional< sequence< identifier, exactly<':'> > >, // optional protocol+                       one_plus< sequence< zero_plus< exactly<'/'> >, filename > >, // one or more folders and/or trailing filename+                       optional< exactly<'/'> > >(src);+    }*/+    /* no longer used - remove?+    const char* url_schema(const char* src) {+      return sequence< optional< sequence< identifier, exactly<':'> > >, // optional protocol+                       filename_schema >(src); // optional trailing slash+    }*/+    // Match CSS "!important" keyword.+    const char* important(const char* src) {+      return sequence< exactly<'!'>,+                       optional_css_whitespace,+                       word<important_kwd> >(src);+    }+    // Match CSS "!optional" keyword.+    const char* optional(const char* src) {+      return sequence< exactly<'!'>,+      optional_css_whitespace,+      word<optional_kwd> >(src);+    }+    // Match Sass "!default" keyword.+    const char* default_flag(const char* src) {+      return sequence< exactly<'!'>,+                       optional_css_whitespace,+                       word<default_kwd> >(src);+    }+    // Match Sass "!global" keyword.+    const char* global_flag(const char* src) {+      return sequence< exactly<'!'>,+                       optional_css_whitespace,+                       word<global_kwd> >(src);+    }+    // Match CSS pseudo-class/element prefixes.+    const char* pseudo_prefix(const char* src) {+      return sequence< exactly<':'>, optional< exactly<':'> > >(src);+    }+    // Match CSS function call openers.+    const char* functional_schema(const char* src) {+      return sequence< identifier_schema, exactly<'('> >(src);+    }+    const char* functional(const char* src) {+      return sequence< identifier, exactly<'('> >(src);+    }+    // Match the CSS negation pseudo-class.+    const char* pseudo_not(const char* src) {+      return word< pseudo_not_kwd >(src);+    }+    // Match CSS 'odd' and 'even' keywords for functional pseudo-classes.+    const char* even(const char* src) {+      return word<even_kwd>(src);+    }+    const char* odd(const char* src) {+      return word<odd_kwd>(src);+    }+    // Match CSS attribute-matching operators.+    const char* exact_match(const char* src) { return exactly<'='>(src); }+    const char* class_match(const char* src) { return exactly<tilde_equal>(src); }+    const char* dash_match(const char* src) { return exactly<pipe_equal>(src); }+    const char* prefix_match(const char* src) { return exactly<caret_equal>(src); }+    const char* suffix_match(const char* src) { return exactly<dollar_equal>(src); }+    const char* substring_match(const char* src) { return exactly<star_equal>(src); }+    // Match CSS combinators.+    /* not used anymore - remove?+    const char* adjacent_to(const char* src) {+      return sequence< optional_spaces, exactly<'+'> >(src);+    }+    const char* precedes(const char* src) {+      return sequence< optional_spaces, exactly<'~'> >(src);+    }+    const char* parent_of(const char* src) {+      return sequence< optional_spaces, exactly<'>'> >(src);+    }+    const char* ancestor_of(const char* src) {+      return sequence< spaces, negate< exactly<'{'> > >(src);+    }*/++    // Match SCSS variable names.+    const char* variable(const char* src) {+      return sequence<exactly<'$'>, identifier>(src);+    }++    // Match Sass boolean keywords.+    const char* kwd_true(const char* src) {+      return word<true_kwd>(src);+    }+    const char* kwd_false(const char* src) {+      return word<false_kwd>(src);+    }+    const char* kwd_and(const char* src) {+      return word<and_kwd>(src);+    }+    const char* kwd_or(const char* src) {+      return word<or_kwd>(src);+    }+    const char* kwd_not(const char* src) {+      return word<not_kwd>(src);+    }+    const char* kwd_eq(const char* src) {+      return exactly<eq>(src);+    }+    const char* kwd_neq(const char* src) {+      return exactly<neq>(src);+    }+    const char* kwd_gt(const char* src) {+      return exactly<gt>(src);+    }+    const char* kwd_gte(const char* src) {+      return exactly<gte>(src);+    }+    const char* kwd_lt(const char* src) {+      return exactly<lt>(src);+    }+    const char* kwd_lte(const char* src) {+      return exactly<lte>(src);+    }++    // match specific IE syntax+    const char* ie_progid(const char* src) {+      return sequence <+        word<progid_kwd>,+        exactly<':'>,+        alternatives< identifier_schema, identifier >,+        zero_plus< sequence<+          exactly<'.'>,+          alternatives< identifier_schema, identifier >+        > >,+        zero_plus < sequence<+          exactly<'('>,+          optional_css_whitespace,+          optional < sequence<+            alternatives< variable, identifier_schema, identifier >,+            optional_css_whitespace,+            exactly<'='>,+            optional_css_whitespace,+            alternatives< variable, identifier_schema, identifier, quoted_string, number, hexa >,+            zero_plus< sequence<+              optional_css_whitespace,+              exactly<','>,+              optional_css_whitespace,+              sequence<+                alternatives< variable, identifier_schema, identifier >,+                optional_css_whitespace,+                exactly<'='>,+                optional_css_whitespace,+                alternatives< variable, identifier_schema, identifier, quoted_string, number, hexa >+              >+            > >+          > >,+          optional_css_whitespace,+          exactly<')'>+        > >+      >(src);+    }+    const char* ie_expression(const char* src) {+      return sequence < word<expression_kwd>, delimited_by< '(', ')', true> >(src);+    }+    const char* ie_property(const char* src) {+      return alternatives < ie_expression, ie_progid >(src);+    }++    // const char* ie_args(const char* src) {+    //   return sequence< alternatives< ie_keyword_arg, value_schema, quoted_string, interpolant, number, identifier, delimited_by< '(', ')', true> >,+    //                    zero_plus< sequence< optional_css_whitespace, exactly<','>, optional_css_whitespace, alternatives< ie_keyword_arg, value_schema, quoted_string, interpolant, number, identifier, delimited_by<'(', ')', true> > > > >(src);+    // }++    const char* ie_keyword_arg(const char* src) {+      return sequence<+        alternatives< variable, identifier_schema, identifier >,+        optional_css_whitespace,+        exactly<'='>,+        optional_css_whitespace,+        alternatives< variable, identifier_schema, identifier, quoted_string, number, hexa >+      >(src);+    }++    // Path matching functions.+    /* not used anymore - remove?+    const char* folder(const char* src) {+      return sequence< zero_plus< any_char_except<'/'> >,+                       exactly<'/'> >(src);+    }+    const char* folders(const char* src) {+      return zero_plus< folder >(src);+    }*/+    /* not used anymore - remove?+    const char* chunk(const char* src) {+      char inside_str = 0;+      const char* p = src;+      size_t depth = 0;+      while (true) {+        if (!*p) {+          return 0;+        }+        else if (!inside_str && (*p == '"' || *p == '\'')) {+          inside_str = *p;+        }+        else if (*p == inside_str && *(p-1) != '\\') {+          inside_str = 0;+        }+        else if (*p == '(' && !inside_str) {+          ++depth;+        }+        else if (*p == ')' && !inside_str) {+          if (depth == 0) return p;+          else            --depth;+        }+        ++p;+      }+      // unreachable+      return 0;+    }+    */++    // follow the CSS spec more closely and see if this helps us scan URLs correctly+    /* not used anymore - remove?+    const char* NL(const char* src) {+      return alternatives< exactly<'\n'>,+                           sequence< exactly<'\r'>, exactly<'\n'> >,+                           exactly<'\r'>,+                           exactly<'\f'> >(src);+    }*/++    /* not used anymore - remove?+    const char* H(const char* src) {+      return std::isxdigit(*src) ? src+1 : 0;+    }*/++    /* not used anymore - remove?+    const char* unicode(const char* src) {+      return sequence< exactly<'\\'>,+                       between<H, 1, 6>,+                       optional< class_char<url_space_chars> > >(src);+    }*/++    /* not used anymore - remove?+    const char* ESCAPE(const char* src) {+      return alternatives< unicode, class_char<escape_chars> >(src);+    }*/++    const char* static_string(const char* src) {+      const char* pos = src;+      const char * s = quoted_string(pos);+      Token t(pos, s);+      const unsigned int p = count_interval< interpolant >(t.begin, t.end);+      return (p == 0) ? t.end : 0;+    }++    const char* static_component(const char* src) {+      return alternatives< identifier,+                           static_string,+                           percentage,+                           hex,+                           number,+                           sequence< exactly<'!'>, word<important_kwd> >+                          >(src);+    }++    const char* static_value(const char* src) {+      return sequence< static_component,+                       zero_plus < sequence<+                                   alternatives<+                                     sequence< optional_spaces, alternatives<+                                       exactly < '/' >,+                                       exactly < ',' >,+                                       exactly < ' ' >+                                     >, optional_spaces >,+                                     spaces+                                   >,+                                   static_component+                       > >,+                       alternatives< exactly<';'>, exactly<'}'> >+                      >(src);+    }+  }+}
+ libsass/prelexer.hpp view
@@ -0,0 +1,360 @@+#ifndef SASS_PRELEXER_H+#define SASS_PRELEXER_H++#include <cstring>+#include "lexer.hpp"++namespace Sass {+  // using namespace Lexer;+  namespace Prelexer {++    //####################################+    // KEYWORD "REGEX" MATCHERS+    //####################################++    // Match Sass boolean keywords.+    const char* kwd_true(const char* src);+    const char* kwd_false(const char* src);+    const char* kwd_and(const char* src);+    const char* kwd_or(const char* src);+    const char* kwd_not(const char* src);+    const char* kwd_eq(const char* src);+    const char* kwd_neq(const char* src);+    const char* kwd_gt(const char* src);+    const char* kwd_gte(const char* src);+    const char* kwd_lt(const char* src);+    const char* kwd_lte(const char* src);++    // Match standard control chars+    const char* kwd_at(const char* src);+    const char* kwd_dot(const char* src);+    const char* kwd_comma(const char* src);+    const char* kwd_colon(const char* src);+    const char* kwd_slash(const char* src);+    const char* kwd_star(const char* src);+    const char* kwd_plus(const char* src);+    const char* kwd_minus(const char* src);++    //####################################+    // SPECIAL "REGEX" CONSTRUCTS+    //####################################++    // Match a sequence of characters delimited by the supplied chars.+    template <char beg, char end, bool esc>+    const char* delimited_by(const char* src) {+      src = exactly<beg>(src);+      if (!src) return 0;+      const char* stop;+      while (1) {+        if (!*src) return 0;+        stop = exactly<end>(src);+        if (stop && (!esc || *(src - 1) != '\\')) return stop;+        src = stop ? stop : src + 1;+      }+    }++    // skip to delimiter (mx) inside given range+    // this will savely skip over all quoted strings+    // recursive skip stuff delimited by start/stop+    // first start/opener must be consumed already!+    template<prelexer start, prelexer stop>+    const char* skip_over_scopes(const char* src, const char* end = 0) {++      size_t level = 0;+      bool in_squote = false;+      bool in_dquote = false;+      // bool in_braces = false;++      while (*src) {++        // check for abort condition+        if (end && src >= end) break;++        // has escaped sequence?+        if (*src == '\\') {+          ++ src; // skip this (and next)+        }+        else if (*src == '"') {+          in_dquote = ! in_dquote;+        }+        else if (*src == '\'') {+          in_squote = ! in_squote;+        }+        else if (in_dquote || in_squote) {+          // take everything literally+        }++        // find another opener inside?+        else if (start(src)) {+          ++ level; // increase counter+        }++        // look for the closer (maybe final, maybe not)+        else if (const char* final = stop(src)) {+          // only close one level?+          if (level > 0) -- level;+          // return position at end of stop+          // delimiter may be multiple chars+          else return final;+        }++        // next+        ++ src;+      }++      return 0;+    }++    // Match a sequence of characters delimited by the supplied chars.+    template <prelexer start, prelexer stop>+    const char* recursive_scopes(const char* src) {+      // parse opener+      src = start(src);+      // abort if not found+      if (!src) return 0;+      // parse the rest until final closer+      return skip_over_scopes<start, stop>(src);+    }++    // Match a sequence of characters delimited by the supplied strings.+    template <const char* beg, const char* end, bool esc>+    const char* delimited_by(const char* src) {+      src = exactly<beg>(src);+      if (!src) return 0;+      const char* stop;+      while (1) {+        if (!*src) return 0;+        stop = exactly<end>(src);+        if (stop && (!esc || *(src - 1) != '\\')) return stop;+        src = stop ? stop : src + 1;+      }+    }++    // Tries to match a certain number of times (between the supplied interval).+    template<prelexer mx, size_t lo, size_t hi>+    const char* between(const char* src) {+      for (size_t i = 0; i < lo; ++i) {+        src = mx(src);+        if (!src) return 0;+      }+      for (size_t i = lo; i <= hi; ++i) {+        const char* new_src = mx(src);+        if (!new_src) return src;+        src = new_src;+      }+      return src;+    }++    // Match a line comment.+    const char* line_comment(const char* src);+    const char* line_comment_prefix(const char* src);++    // Match a block comment.+    const char* block_comment(const char* src);+    const char* block_comment_prefix(const char* src);+    // Match either.+    const char* comment(const char* src);+    // Match double- and single-quoted strings.+    const char* double_quoted_string(const char* src);+    const char* single_quoted_string(const char* src);+    const char* quoted_string(const char* src);+    // Match interpolants.+    const char* interpolant(const char* src);+    // Match number prefix ([\+\-]+)+    const char* number_prefix(const char* src);++    // Match zero plus white-space or line_comments+    const char* optional_css_whitespace(const char* src);+    const char* css_whitespace(const char* src);+    // Match optional_css_whitepace plus block_comments+    const char* optional_css_comments(const char* src);+    const char* css_comments(const char* src);++    // Match one backslash escaped char+    const char* escape_seq(const char* src);++    // Match CSS css variables.+    const char* custom_property_name(const char* src);+    // Match a CSS identifier.+    const char* identifier(const char* src);+    const char* identifier_alpha(const char* src);+    const char* identifier_alnum(const char* src);+    const char* identifier_alnums(const char* src);+    // Match selector names.+    // const char* sel_ident(const char* src);+    const char* kwd_sel_deep(const char* src);++    // Match interpolant schemas+    const char* identifier_schema(const char* src);+    const char* value_schema(const char* src);+    // const char* filename(const char* src);+    // const char* filename_schema(const char* src);+    // const char* url_schema(const char* src);+    // const char* url_value(const char* src);+    const char* vendor_prefix(const char* src);+    // Match CSS '@' keywords.+    const char* at_keyword(const char* src);+    const char* kwd_import(const char* src);+    const char* kwd_at_root(const char* src);+    const char* kwd_with_directive(const char* src);+    const char* kwd_without_directive(const char* src);+    const char* kwd_media(const char* src);+    const char* kwd_supports(const char* src);+    // const char* keyframes(const char* src);+    // const char* keyf(const char* src);+    const char* kwd_mixin(const char* src);+    const char* kwd_function(const char* src);+    const char* kwd_return_directive(const char* src);+    const char* kwd_include(const char* src);+    const char* kwd_content(const char* src);+    const char* kwd_extend(const char* src);++    const char* kwd_if_directive(const char* src);+    const char* kwd_else_directive(const char* src);+    const char* elseif_directive(const char* src);++    const char* kwd_for_directive(const char* src);+    const char* kwd_from(const char* src);+    const char* kwd_to(const char* src);+    const char* kwd_through(const char* src);++    const char* kwd_each_directive(const char* src);+    const char* kwd_in(const char* src);++    const char* kwd_while_directive(const char* src);++    const char* kwd_warn(const char* src);+    const char* kwd_err(const char* src);+    const char* kwd_dbg(const char* src);++    const char* kwd_null(const char* src);++    // Match CSS type selectors+    const char* namespace_prefix(const char* src);+    const char* type_selector(const char* src);+    const char* hyphens_and_identifier(const char* src);+    const char* hyphens_and_name(const char* src);+    const char* universal(const char* src);+    // Match CSS id names.+    const char* id_name(const char* src);+    // Match CSS class names.+    const char* class_name(const char* src);+    // Attribute name in an attribute selector+    const char* attribute_name(const char* src);+    // Match placeholder selectors.+    const char* placeholder(const char* src);+    // Match CSS numeric constants.+    const char* sign(const char* src);+    const char* unsigned_number(const char* src);+    const char* number(const char* src);+    const char* coefficient(const char* src);+    const char* binomial(const char* src);+    const char* percentage(const char* src);+    const char* ampersand(const char* src);+    const char* dimension(const char* src);+    const char* hex(const char* src);+    const char* hexa(const char* src);+    const char* hex0(const char* src);+    // const char* rgb_prefix(const char* src);+    // Match CSS uri specifiers.+    const char* uri_prefix(const char* src);+    const char* uri_value(const char* src);+    // Match CSS "!important" keyword.+    const char* important(const char* src);+    // Match CSS "!optional" keyword.+    const char* optional(const char* src);+    // Match Sass "!default" keyword.+    const char* default_flag(const char* src);+    const char* global_flag(const char* src);+    // Match CSS pseudo-class/element prefixes+    const char* pseudo_prefix(const char* src);+    // Match CSS function call openers.+    const char* functional(const char* src);+    const char* functional_schema(const char* src);+    const char* pseudo_not(const char* src);+    // Match CSS 'odd' and 'even' keywords for functional pseudo-classes.+    const char* even(const char* src);+    const char* odd(const char* src);+    // Match CSS attribute-matching operators.+    const char* exact_match(const char* src);+    const char* class_match(const char* src);+    const char* dash_match(const char* src);+    const char* prefix_match(const char* src);+    const char* suffix_match(const char* src);+    const char* substring_match(const char* src);+    // Match CSS combinators.+    // const char* adjacent_to(const char* src);+    // const char* precedes(const char* src);+    // const char* parent_of(const char* src);+    // const char* ancestor_of(const char* src);++    // Match SCSS variable names.+    const char* variable(const char* src);++    // IE stuff+    const char* ie_progid(const char* src);+    const char* ie_expression(const char* src);+    const char* ie_property(const char* src);+    const char* ie_keyword_arg(const char* src);++    // match urls+    const char* url(const char* src);++    // Path matching functions.+    // const char* folder(const char* src);+    // const char* folders(const char* src);+++    const char* static_string(const char* src);+    const char* static_component(const char* src);+    const char* static_value(const char* src);++    // Utility functions for finding and counting characters in a string.+    template<char c>+    const char* find_first(const char* src) {+      while (*src && *src != c) ++src;+      return *src ? src : 0;+    }+    template<prelexer mx>+    const char* find_first(const char* src) {+      while (*src && !mx(src)) ++src;+      return *src ? src : 0;+    }+    template<prelexer mx>+    const char* find_first_in_interval(const char* beg, const char* end) {+      bool esc = false;+      while ((beg < end) && *beg) {+        if (esc) esc = false;+        else if (*beg == '\\') esc = true;+        else if (mx(beg)) return beg;+        ++beg;+      }+      return 0;+    }+    template <prelexer mx>+    unsigned int count_interval(const char* beg, const char* end) {+      unsigned int counter = 0;+      bool esc = false;+      while (beg < end && *beg) {+        const char* p;+        if (esc) {+          esc = false;+          ++beg;+        } else if (*beg == '\\') {+          esc = true;+          ++beg;+        } else if ((p = mx(beg))) {+          ++counter;+          beg = p;+        }+        else {+          ++beg;+        }+      }+      return counter;+    }++  }+}++#endif
+ libsass/remove_placeholders.cpp view
@@ -0,0 +1,59 @@+#include "remove_placeholders.hpp"+#include "context.hpp"+#include "inspect.hpp"+#include "to_string.hpp"+#include <iostream>++namespace Sass {++    Remove_Placeholders::Remove_Placeholders(Context& ctx)+    : ctx(ctx)+    { }++    template<typename T>+    void Remove_Placeholders::clean_selector_list(T r) {++        // Create a new selector group without placeholders+        Selector_List* sl = static_cast<Selector_List*>(r->selector());++        if (sl) {+            Selector_List* new_sl = new (ctx.mem) Selector_List(sl->pstate());++            for (size_t i = 0, L = sl->length(); i < L; ++i) {+                if (!(*sl)[i]->has_placeholder()) {+                    *new_sl << (*sl)[i];+                }+            }++            // Set the new placeholder selector list+            r->selector(new_sl);+        }++        // Iterate into child blocks+        Block* b = r->block();++        for (size_t i = 0, L = b->length(); i < L; ++i) {+            Statement* stm = (*b)[i];+            stm->perform(this);+        }+    }++    void Remove_Placeholders::operator()(Block* b) {+        for (size_t i = 0, L = b->length(); i < L; ++i) {+            (*b)[i]->perform(this);+        }+    }++    void Remove_Placeholders::operator()(Ruleset* r) {+        clean_selector_list(r);+    }++    void Remove_Placeholders::operator()(Media_Block* m) {+        clean_selector_list(m);+    }++    void Remove_Placeholders::operator()(At_Rule* a) {+        if (a->block()) a->block()->perform(this);+    }++}
+ libsass/remove_placeholders.hpp view
@@ -0,0 +1,43 @@+#ifndef SASS_REMOVE_PLACEHOLDERS_H+#define SASS_REMOVE_PLACEHOLDERS_H++#pragma once++#include <iostream>++#include "ast.hpp"+#include "operation.hpp"++namespace Sass {++    using namespace std;++    class Context;++    class Remove_Placeholders : public Operation_CRTP<void, Remove_Placeholders> {++        Context&          ctx;++        void fallback_impl(AST_Node* n) {};++    public:+        Remove_Placeholders(Context&);+        virtual ~Remove_Placeholders() { }++        using Operation<void>::operator();++        void operator()(Block*);+        void operator()(Ruleset*);+        void operator()(Media_Block*);+        void operator()(At_Rule*);++        template <typename T>+        void clean_selector_list(T r);++        template <typename U>+        void fallback(U x) { return fallback_impl(x); }+    };++}++#endif
+ libsass/sass.cpp view
@@ -0,0 +1,43 @@+#include <cstdlib>+#include <cstring>+#include <vector>+#include <sstream>++#include "sass.h"+#include "file.hpp"+#include "util.hpp"++extern "C" {+  using namespace std;+  using namespace Sass;+  using namespace File;++  // caller must free the returned memory+  char* ADDCALL sass_string_quote (const char *str, const char quote_mark)+  {+    string quoted = quote(str, quote_mark);+    return sass_strdup(quoted.c_str());+  }++  // caller must free the returned memory+  char* ADDCALL sass_string_unquote (const char *str)+  {+    string unquoted = unquote(str);+    return sass_strdup(unquoted.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[])+  {+    string resolved(find_file(file, paths));+    return sass_strdup(resolved.c_str());+  }++  // Get compiled libsass version+  const char* ADDCALL libsass_version(void)+  {+    return LIBSASS_VERSION;+  }++}
+ libsass/sass.h view
@@ -0,0 +1,68 @@+#ifndef SASS_H+#define SASS_H++#include <stddef.h>+#include <stdbool.h>++#ifdef __GNUC__+  #define DEPRECATED(func) func __attribute__ ((deprecated))+#elif defined(_MSC_VER)+  #define DEPRECATED(func) __declspec(deprecated) func+#else+  #pragma message("WARNING: You need to implement DEPRECATED for this compiler")+  #define DEPRECATED(func) func+#endif++#ifdef _WIN32++  /* You should define ADD_EXPORTS *only* when building the DLL. */+  #ifdef ADD_EXPORTS+    #define ADDAPI __declspec(dllexport)+	#define ADDCALL __cdecl+  #else+    #define ADDAPI+	#define ADDCALL+  #endif++#else /* _WIN32 not defined. */++  /* Define with no value on non-Windows OSes. */+  #define ADDAPI+  #define ADDCALL++#endif++// include API headers+#include "sass_version.h"+#include "sass_values.h"+#include "sass_functions.h"++/* Make sure functions are exported with C linkage under C++ compilers. */+#ifdef __cplusplus+extern "C" {+#endif+++// Different render styles+enum Sass_Output_Style {+  SASS_STYLE_NESTED,+  SASS_STYLE_EXPANDED,+  SASS_STYLE_COMPACT,+  SASS_STYLE_COMPRESSED+};++// Some convenient string helper function+ADDAPI char* ADDCALL sass_string_quote (const char* str, const char quote_mark);+ADDAPI char* ADDCALL sass_string_unquote (const char* str);++// 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[]);++// Get compiled libsass version+ADDAPI const char* ADDCALL libsass_version(void);++#ifdef __cplusplus+} // __cplusplus defined.+#endif++#endif
+ libsass/sass2scss.cpp view
@@ -0,0 +1,835 @@+// include library+#include <stack>+#include <string>+#include <cstring>+#include <cstdlib>+#include <sstream>+#include <iostream>+#include <stdio.h>++///*+//+// src comments: comments in sass syntax (staring with //)+// css comments: multiline comments in css syntax (starting with /*)+//+// KEEP_COMMENT: keep src comments in the resulting css code+// STRIP_COMMENT: strip out all comments (either src or css)+// CONVERT_COMMENT: convert all src comments to css comments+//+//*/++// our own header+#include "sass2scss.h"++// using std::string+using namespace std;++// add namespace for c+++namespace Sass+{++	// return the actual prettify value from options+	#define PRETTIFY(converter) (converter.options - (converter.options & 248))+	// query the options integer to check if the option is enables+	#define KEEP_COMMENT(converter) ((converter.options & SASS2SCSS_KEEP_COMMENT) == SASS2SCSS_KEEP_COMMENT)+	#define STRIP_COMMENT(converter) ((converter.options & SASS2SCSS_STRIP_COMMENT) == SASS2SCSS_STRIP_COMMENT)+	#define CONVERT_COMMENT(converter) ((converter.options & SASS2SCSS_CONVERT_COMMENT) == SASS2SCSS_CONVERT_COMMENT)++	// some makros to access the indentation stack+	#define INDENT(converter) (converter.indents.top())++	// some makros to query comment parser status+	#define IS_PARSING(converter) (converter.comment == "")+	#define IS_COMMENT(converter) (converter.comment != "")+	#define IS_SRC_COMMENT(converter) (converter.comment == "//" && ! CONVERT_COMMENT(converter))+	#define IS_CSS_COMMENT(converter) (converter.comment == "/*" || (converter.comment == "//" && CONVERT_COMMENT(converter)))++	// pretty printer helper function+	static string closer (const converter& converter)+	{+		return PRETTIFY(converter) == 0 ? " }" :+		     PRETTIFY(converter) <= 1 ? " }" :+		       "\n" + INDENT(converter) + "}";+	}++	// pretty printer helper function+	static string opener (const converter& converter)+	{+		return PRETTIFY(converter) == 0 ? " { " :+		     PRETTIFY(converter) <= 2 ? " {" :+		       "\n" + INDENT(converter) + "{";+	}++	// check if the given string is a pseudo selector+	// needed to differentiate from sass property syntax+	static bool isPseudoSelector (string& sel)+	{++		size_t len = sel.length();+		if (len < 1) return false;+		size_t pos = sel.find_first_not_of("abcdefghijklmnopqrstuvwxyz-ABCDEFGHIJKLMNOPQRSTUVWXYZ", 1);+		if (pos != string::npos) sel.erase(pos, string::npos);+		size_t i = sel.length();+		while (i -- > 0) { sel.at(i) = tolower(sel.at(i)); }++		// CSS Level 1 - Recommendation+		if (sel == ":link") return true;+		if (sel == ":visited") return true;+		if (sel == ":active") return true;++		// CSS Level 2 (Revision 1) - Recommendation+		if (sel == ":lang") return true;+		if (sel == ":first-child") return true;+		if (sel == ":hover") return true;+		if (sel == ":focus") return true;+		// disabled - also valid properties+		// if (sel == ":left") return true;+		// if (sel == ":right") return true;+		if (sel == ":first") return true;++		// Selectors Level 3 - Recommendation+		if (sel == ":target") return true;+		if (sel == ":root") return true;+		if (sel == ":nth-child") return true;+		if (sel == ":nth-last-of-child") return true;+		if (sel == ":nth-of-type") return true;+		if (sel == ":nth-last-of-type") return true;+		if (sel == ":last-child") return true;+		if (sel == ":first-of-type") return true;+		if (sel == ":last-of-type") return true;+		if (sel == ":only-child") return true;+		if (sel == ":only-of-type") return true;+		if (sel == ":empty") return true;+		if (sel == ":not") return true;++		// CSS Basic User Interface Module Level 3 - Working Draft+		if (sel == ":default") return true;+		if (sel == ":valid") return true;+		if (sel == ":invalid") return true;+		if (sel == ":in-range") return true;+		if (sel == ":out-of-range") return true;+		if (sel == ":required") return true;+		if (sel == ":optional") return true;+		if (sel == ":read-only") return true;+		if (sel == ":read-write") return true;+		if (sel == ":dir") return true;+		if (sel == ":enabled") return true;+		if (sel == ":disabled") return true;+		if (sel == ":checked") return true;+		if (sel == ":indeterminate") return true;+		if (sel == ":nth-last-child") return true;++		// Selectors Level 4 - Working Draft+		if (sel == ":any-link") return true;+		if (sel == ":local-link") return true;+		if (sel == ":scope") return true;+		if (sel == ":active-drop-target") return true;+		if (sel == ":valid-drop-target") return true;+		if (sel == ":invalid-drop-target") return true;+		if (sel == ":current") return true;+		if (sel == ":past") return true;+		if (sel == ":future") return true;+		if (sel == ":placeholder-shown") return true;+		if (sel == ":user-error") return true;+		if (sel == ":blank") return true;+		if (sel == ":nth-match") return true;+		if (sel == ":nth-last-match") return true;+		if (sel == ":nth-column") return true;+		if (sel == ":nth-last-column") return true;+		if (sel == ":matches") return true;++		// Fullscreen API - Living Standard+		if (sel == ":fullscreen") return true;++		// not a pseudo selector+		return false;++	}++	// check if there is some char data+	// will ignore everything in comments+	static bool hasCharData (string& sass)+	{++		size_t col_pos = 0;++		while (true)+		{++			// try to find some meaningfull char+			col_pos = sass.find_first_not_of(" \t\n\v\f\r", col_pos);++			// there was no meaningfull char found+			if (col_pos == string::npos) return false;++			// found a multiline comment opener+			if (sass.substr(col_pos, 2) == "/*")+			{+				// find the multiline comment closer+				col_pos = sass.find("*/", col_pos);+				// maybe we did not find the closer here+				if (col_pos == string::npos) return false;+				// skip closer+				col_pos += 2;+			}+			else+			{+				return true;+			}++		}++	}+	// EO hasCharData++	// find src comment opener+	// correctly skips quoted strings+	static size_t findCommentOpener (string& sass)+	{++		size_t col_pos = 0;+		bool apoed = false;+		bool quoted = false;+		bool comment = false;+		size_t brackets = 0;++		while (col_pos != string::npos)+		{++			// process all interesting chars+			col_pos = sass.find_first_of("\"\'/\\*()", col_pos);++			// assertion for valid result+			if (col_pos != string::npos)+			{+				char character = sass.at(col_pos);++				if (character == '(')+				{+					if (!quoted && !apoed) brackets ++;+				}+				else if (character == ')')+				{+					if (!quoted && !apoed) brackets --;+				}+				else if (character == '\"')+				{+					// invert quote bool+					if (!apoed && !comment) quoted = !quoted;+				}+				else if (character == '\'')+				{+					// invert quote bool+					if (!quoted && !comment) apoed = !apoed;+				}+				else if (col_pos > 0 && character == '/')+				{+					if (sass.at(col_pos - 1) == '*')+					{+						comment = false;+					}+					// next needs to be a slash too+					else if (sass.at(col_pos - 1) == '/')+					{+						// only found if not in single or double quote, bracket or comment+						if (!quoted && !apoed && !comment && brackets == 0) return col_pos - 1;+					}+				}+				else if (character == '\\')+				{+					// skip next char if in quote+					if (quoted || apoed) col_pos ++;+				}+				// this might be a comment opener+				else if (col_pos > 0 && character == '*')+				{+					// opening a multiline comment+					if (sass.at(col_pos - 1) == '/')+					{+						// we are now in a comment+						if (!quoted && !apoed) comment = true;+					}+				}++				// skip char+				col_pos ++;++			}++		}+		// EO while++		return col_pos;++	}+	// EO findCommentOpener++	// remove multiline comments from sass string+	// correctly skips quoted strings+	static string removeMultilineComment (string &sass)+	{++		string clean = "";+		size_t col_pos = 0;+		size_t open_pos = 0;+		size_t close_pos = 0;+		bool apoed = false;+		bool quoted = false;+		bool comment = false;++		// process sass til string end+		while (col_pos != string::npos)+		{++			// process all interesting chars+			col_pos = sass.find_first_of("\"\'/\\*", col_pos);++			// assertion for valid result+			if (col_pos != string::npos)+			{+				char character = sass.at(col_pos);++				// found quoted string delimiter+				if (character == '\"')+				{+					if (!apoed && !comment) quoted = !quoted;+				}+				else if (character == '\'')+				{+					if (!quoted && !comment) apoed = !apoed;+				}+				// found possible comment closer+				else if (character == '/')+				{+					// look back to see if it is actually a closer+					if (comment && col_pos > 0 && sass.at(col_pos - 1) == '*')+					{+						close_pos = col_pos + 1; comment = false;+					}+				}+				else if (character == '\\')+				{+					// skip escaped char+					if (quoted || apoed) col_pos ++;+				}+				// this might be a comment opener+				else if (character == '*')+				{+					// look back to see if it is actually an opener+					if (!quoted && !apoed && col_pos > 0 && sass.at(col_pos - 1) == '/')+					{+						comment = true; open_pos = col_pos - 1;+						clean += sass.substr(close_pos, open_pos - close_pos);+					}+				}++				// skip char+				col_pos ++;++			}++		}+		// EO while++		// add final parts (add half open comment text)+		if (comment) clean += sass.substr(open_pos);+		else clean += sass.substr(close_pos);++		// return string+		return clean;++	}+	// EO removeMultilineComment++	// right trim a given string+	string rtrim(const string &sass)+	{+		string trimmed = sass;+		size_t pos_ws = trimmed.find_last_not_of(" \t\n\v\f\r");+		if (pos_ws != string::npos)+		{ trimmed.erase(pos_ws + 1); }+		else { trimmed.clear(); }+		return trimmed;+	}+	// EO rtrim++	// flush whitespace and print additional text, but+	// only print additional chars and buffer whitespace+	string flush (string& sass, converter& converter)+	{++		// return flushed+		string scss = "";++		// print whitespace buffer+		scss += PRETTIFY(converter) > 0 ?+		        converter.whitespace : "";+		// reset whitespace buffer+		converter.whitespace = "";++		// remove possible newlines from string+		size_t pos_right = sass.find_last_not_of("\n\r");+		if (pos_right == string::npos) return scss;++		// get the linefeeds from the string+		string lfs = sass.substr(pos_right + 1);+		sass = sass.substr(0, pos_right + 1);++		// find some source comment opener+		size_t comment_pos = findCommentOpener(sass);+		// check if there was a source comment+		if (comment_pos != string::npos)+		{+			// convert comment (but only outside other coments)+			if (CONVERT_COMMENT(converter) && !IS_COMMENT(converter))+			{+				// convert to multiline comment+				sass.at(comment_pos + 1) = '*';+				// add comment node to the whitespace+				sass += " */";+			}+			// not at line start+			if (comment_pos > 0)+			{+				// also include whitespace before the actual comment opener+				size_t ws_pos = sass.find_last_not_of(SASS2SCSS_FIND_WHITESPACE, comment_pos - 1);+				comment_pos = ws_pos == string::npos ? 0 : ws_pos + 1;+			}+			if (!STRIP_COMMENT(converter))+			{+				// add comment node to the whitespace+				converter.whitespace += sass.substr(comment_pos);+			}+			else+			{+				// sass = removeMultilineComments(sass);+			}+			// update the actual sass code+			sass = sass.substr(0, comment_pos);+		}++		// add newline as getline discharged it+		converter.whitespace += lfs + "\n";++		// maybe remove any leading whitespace+		if (PRETTIFY(converter) == 0)+		{+			// remove leading whitespace and update string+			size_t pos_left = sass.find_first_not_of(SASS2SCSS_FIND_WHITESPACE);+			if (pos_left != string::npos) sass = sass.substr(pos_left);+		}++		// add flushed data+		scss += sass;++		// return string+		return scss;++	}+	// EO flush++	// process a line of the sass text+	string process (string& sass, converter& converter)+	{++		// resulting string+		string scss = "";++		// strip multi line comments+		if (STRIP_COMMENT(converter))+		{+			sass = removeMultilineComment(sass);+		}++		// right trim input+		sass = rtrim(sass);++		// get postion of first meaningfull character in string+		size_t pos_left = sass.find_first_not_of(SASS2SCSS_FIND_WHITESPACE);++		// special case for final run+		if (converter.end_of_file) pos_left = 0;++		// maybe has only whitespace+		if (pos_left == string::npos)+		{+			// just add complete whitespace+			converter.whitespace += sass + "\n";+		}+		// have meaningfull first char+		else+		{++			// extract and store indentation string+			string indent = sass.substr(0, pos_left);++			// check if current line starts a comment+			string open = sass.substr(pos_left, 2);++			// line has less or same indentation+			// finalize previous open parser context+			if (indent.length() <= INDENT(converter).length())+			{++				// close multilinie comment+				if (IS_CSS_COMMENT(converter))+				{+					// check if comments will be stripped anyway+					if (!STRIP_COMMENT(converter)) scss += " */";+				}+				// close src comment comment+				else if (IS_SRC_COMMENT(converter))+				{+					// add a newline to avoid closer on same line+					// this would put the bracket in the comment node+					// no longer needed since we parse them correctly+					// if (KEEP_COMMENT(converter)) scss += "\n";+				}+				// close css properties+				else if (converter.property)+				{+					// add closer unless in concat mode+					if (!converter.comma)+					{+						// if there was no colon we have a selector+						// looks like there were no inner properties+						if (converter.selector) scss += " {}";+						// add final semicolon+						else if (!converter.semicolon) scss += ";";+					}+				}++				// reset comment state+				converter.comment = "";++			}++			// make sure we close every "higher" block+			while (indent.length() < INDENT(converter).length())+			{+				// pop stacked context+				converter.indents.pop();+				// print close bracket+				if (IS_PARSING(converter))+				{ scss += closer(converter); }+				else { scss += " */"; }+				// reset comment state+				converter.comment = "";+			}++			// reset converter state+			converter.selector = false;++			// check if we have sass property syntax+			if (sass.substr(pos_left, 1) == ":" && sass.substr(pos_left, 2) != "::")+			{++				// default to a selector+				// change back if property found+				converter.selector = true;+				// get postion of first whitespace char+				size_t pos_wspace = sass.find_first_of(SASS2SCSS_FIND_WHITESPACE, pos_left);+				// assertion check for valid result+				if (pos_wspace != string::npos)+				{+					// get the possible pseudo selector+					string pseudo = sass.substr(pos_left, pos_wspace - pos_left);+					// get position of the first real property value char+					// pseudo selectors get this far, but have no actual value+					size_t pos_value =  sass.find_first_not_of(SASS2SCSS_FIND_WHITESPACE, pos_wspace);+					// assertion check for valid result+					if (pos_value != string::npos)+					{+						// only process if not (fallowed by a semicolon or is a pseudo selector)+						if (!(sass.at(pos_value) == ':' || isPseudoSelector(pseudo)))+						{+							// create new string by interchanging the colon sign for property and value+							sass = indent + sass.substr(pos_left + 1, pos_wspace - pos_left - 1) + ":" + sass.substr(pos_wspace);+							// try to find a colon in the current line, but only ...+							size_t pos_colon = sass.find_first_not_of(":", pos_left);+							// assertion for valid result+							if (pos_colon != string::npos)+							{+								// ... after the first word (skip begining colons)+								pos_colon = sass.find_first_of(":", pos_colon);+								// it is a selector if there was no colon found+								converter.selector = pos_colon == string::npos;+							}+						}+					}+				}++			}++			// terminate some statements immediately+			else if (+				sass.substr(pos_left, 5) == "@warn" ||+				sass.substr(pos_left, 6) == "@debug" ||+				sass.substr(pos_left, 6) == "@error" ||+				sass.substr(pos_left, 8) == "@charset"+			) { sass = indent + sass.substr(pos_left); }+			// replace some specific sass shorthand directives (if not fallowed by a white space character)+			else if (sass.substr(pos_left, 1) == "=" && sass.find_first_of(SASS2SCSS_FIND_WHITESPACE, pos_left) != pos_left + 1)+			{ sass = indent + "@mixin " + sass.substr(pos_left + 1); }+			else if (sass.substr(pos_left, 1) == "+" && sass.find_first_of(SASS2SCSS_FIND_WHITESPACE, pos_left) != pos_left + 1)+			{ sass = indent + "@include " + sass.substr(pos_left + 1); }++			// add quotes for import if needed+			else if (sass.substr(pos_left, 7) == "@import")+			{+				// get positions for the actual import url+				size_t pos_import = sass.find_first_of(SASS2SCSS_FIND_WHITESPACE, pos_left + 7);+				size_t pos_quote = sass.find_first_not_of(SASS2SCSS_FIND_WHITESPACE, pos_import);+				// leave proper urls untouched+				if (sass.substr(pos_quote, 4) != "url(")+				{+					// check if the url appears to be already quoted+					if (sass.substr(pos_quote, 1) != "\"" && sass.substr(pos_quote, 1) != "\'")+					{+						// get position of the last char on the line+						size_t pos_end = sass.find_last_not_of(SASS2SCSS_FIND_WHITESPACE);+						// assertion check for valid result+						if (pos_end != string::npos)+						{+							// add quotes around the full line after the import statement+							sass = sass.substr(0, pos_quote) + "\"" + sass.substr(pos_quote, pos_end - pos_quote + 1) + "\"";+						}+					}+				}++			}+			else if (+				sass.substr(pos_left, 7) != "@return" &&+				sass.substr(pos_left, 7) != "@extend" &&+				sass.substr(pos_left, 8) != "@content"+			) {++				// try to find a colon in the current line, but only ...+				size_t pos_colon = sass.find_first_not_of(":", pos_left);+				// assertion for valid result+				if (pos_colon != string::npos)+				{+					// ... after the first word (skip begining colons)+					pos_colon = sass.find_first_of(":", pos_colon);+					// it is a selector if there was no colon found+					converter.selector = pos_colon == string::npos;+				}++			}++			// current line has more indentation+			if (indent.length() >= INDENT(converter).length())+			{+				// not in comment mode+				if (IS_PARSING(converter))+				{+					// has meaningfull chars+					if (hasCharData(sass))+					{+						// is probably a property+						// also true for selectors+						converter.property = true;+					}+				}+			}+			// current line has more indentation+			if (indent.length() > INDENT(converter).length())+			{+				// not in comment mode+				if (IS_PARSING(converter))+				{+					// had meaningfull chars+					if (converter.property)+					{+						// print block opener+						scss += opener(converter);+						// push new stack context+						converter.indents.push("");+						// store block indentation+						INDENT(converter) = indent;+					}+				}+				// is and will be a src comment+				else if (!IS_CSS_COMMENT(converter))+				{+					// scss does not allow multiline src comments+					// therefore add forward slashes to all lines+					sass.at(INDENT(converter).length()+0) = '/';+					// there is an edge case here if indentation+					// is minimal (will overwrite the fist char)+					sass.at(INDENT(converter).length()+1) = '/';+					// could code around that, but I dont' think+					// this will ever be the cause for any trouble+				}+			}++			// line is opening a new comment+			if (open == "/*" || open == "//")+			{+				// reset the property state+				converter.property = false;+				// close previous comment+				if (IS_CSS_COMMENT(converter) && open != "")+				{+					if (!STRIP_COMMENT(converter) && !CONVERT_COMMENT(converter)) scss += " */";+				}+				// force single line comments+				// into a correct css comment+				if (CONVERT_COMMENT(converter))+				{+					if (IS_PARSING(converter))+					{ sass.at(pos_left + 1) = '*'; }+				}+				// set comment flag+				converter.comment = open;++			}++			// flush data only under certain conditions+			if (!(+				// strip css and src comments if option is set+				(IS_COMMENT(converter) && STRIP_COMMENT(converter)) ||+				// strip src comment even if strip option is not set+				// but only if the keep src comment option is not set+				(IS_SRC_COMMENT(converter) && ! KEEP_COMMENT(converter))+			))+			{+				// flush data and buffer whitespace+				scss += flush(sass, converter);+			}++			// get postion of last meaningfull char+			size_t pos_right = sass.find_last_not_of(SASS2SCSS_FIND_WHITESPACE);++			// check for invalid result+			if (pos_right != string::npos)+			{++				// get the last meaningfull char+				string close = sass.substr(pos_right, 1);++				// check if next line should be concatenated (list mode)+				converter.comma = IS_PARSING(converter) && close == ",";+				converter.semicolon = IS_PARSING(converter) && close == ";";++				// check if we have more than+				// one meaningfull char+				if (pos_right > 0)+				{++					// get the last two chars from string+					string close = sass.substr(pos_right - 1, 2);+					// update parser status for expicitly closed comment+					if (close == "*/") converter.comment = "";++				}++			}+			// EO have meaningfull chars from end++		}+		// EO have meaningfull chars from start++		// return scss+		return scss;++	}+	// EO process++	// read line with either CR, LF or CR LF format+	// http://stackoverflow.com/a/6089413/1550314+	static std::istream& safeGetline(std::istream& is, std::string& t)+	{+		t.clear();++		// The characters in the stream are read one-by-one using a std::streambuf.+		// That is faster than reading them one-by-one using the std::istream.+		// Code that uses streambuf this way must be guarded by a sentry object.+		// The sentry object performs various tasks,+		// such as thread synchronization and updating the stream state.++		std::istream::sentry se(is, true);+		std::streambuf* sb = is.rdbuf();++		for(;;) {+			int c = sb->sbumpc();+			switch (c) {+				case '\n':+					return is;+				case '\r':+					if(sb->sgetc() == '\n')+						sb->sbumpc();+					return is;+				case EOF:+					// Also handle the case when the last line has no line ending+					if(t.empty())+						is.setstate(std::ios::eofbit);+					return is;+				default:+					t += (char)c;+			}+		}+	}++	// the main converter function for c+++	char* sass2scss (const string sass, const int options)+	{++		// local variables+		string line;+		string scss = "";+		stringstream stream(sass);++		// create converter variable+		converter converter;+		// initialise all options+		converter.comma = false;+		converter.property = false;+		converter.selector = false;+		converter.semicolon = false;+		converter.end_of_file = false;+		converter.comment = "";+		converter.whitespace = "";+		converter.indents.push("");+		converter.options = options;++		// read line by line and process them+		while(safeGetline(stream, line) && !stream.eof())+		{ scss += process(line, converter); }++		// create mutable string+		string closer = "";+		// set the end of file flag+		converter.end_of_file = true;+		// process to close all open blocks+		scss += process(closer, converter);++		// allocate new memory on the heap+		// caller has to free it after use+		char * cstr = (char*) malloc (scss.length() + 1);+		// create a copy of the string+		strcpy (cstr, scss.c_str());+		// return pointer+		return &cstr[0];++	}+	// EO sass2scss++}+// EO namespace++// implement for c+extern "C"+{++	char* ADDCALL sass2scss (const char* sass, const int options)+	{+		return Sass::sass2scss(sass, options);+	}++	// Get compiled sass2scss version+	const char* ADDCALL sass2scss_version(void) {+		return SASS2SCSS_VERSION;+	}++}
+ libsass/sass2scss.h view
@@ -0,0 +1,115 @@+#ifndef SASS2SCSS_H+#define SASS2SCSS_H++#ifdef _WIN32++  /* You should define ADD_EXPORTS *only* when building the DLL. */+  #ifdef ADD_EXPORTS+    #define ADDAPI __declspec(dllexport)+	#define ADDCALL __cdecl+  #else+    #define ADDAPI+	#define ADDCALL+  #endif++#else /* _WIN32 not defined. */++  /* Define with no value on non-Windows OSes. */+  #define ADDAPI+  #define ADDCALL++#endif++#ifdef __cplusplus++#include <stack>+#include <string>+#include <cstring>+#include <sstream>+#include <iostream>++#ifndef SASS2SCSS_VERSION+#define SASS2SCSS_VERSION "1.0.3"+#endif++// using std::string+using namespace std;++// add namespace for c+++namespace Sass+{++	// pretty print options+	const int SASS2SCSS_PRETTIFY_0 = 0;+	const int SASS2SCSS_PRETTIFY_1 = 1;+	const int SASS2SCSS_PRETTIFY_2 = 2;+	const int SASS2SCSS_PRETTIFY_3 = 3;++	// remove one-line comment+	const int SASS2SCSS_KEEP_COMMENT    =  32;+	// remove multi-line comments+	const int SASS2SCSS_STRIP_COMMENT   =  64;+	// convert one-line to multi-line+	const int SASS2SCSS_CONVERT_COMMENT = 128;++	// String for finding something interesting+	const string SASS2SCSS_FIND_WHITESPACE = " \t\n\v\f\r";++	// converter struct+	// holding all states+	struct converter+	{+		// bit options+		int options;+		// is selector+		bool selector;+		// concat lists+		bool comma;+		// has property+		bool property;+		// has semicolon+		bool semicolon;+		// comment context+		string comment;+		// flag end of file+		bool end_of_file;+		// whitespace buffer+		string whitespace;+		// context/block stack+		stack<string> indents;+	};++	// function only available in c++ code+	char* sass2scss (const string sass, const int options);++}+// EO namespace++// declare for c+extern "C" {+#endif++	// prettyfy print options+	#define SASS2SCSS_PRETTIFY_0   0+	#define SASS2SCSS_PRETTIFY_1   1+	#define SASS2SCSS_PRETTIFY_2   2+	#define SASS2SCSS_PRETTIFY_3   3++	// keep one-line comments+	#define SASS2SCSS_KEEP_COMMENT     32+	// remove multi-line comments+	#define SASS2SCSS_STRIP_COMMENT    64+	// convert one-line to multi-line+	#define SASS2SCSS_CONVERT_COMMENT  128++	// available to c and c++ code+	ADDAPI char* ADDCALL sass2scss (const char* sass, const int options);++	// Get compiled sass2scss version+	ADDAPI const char* ADDCALL sass2scss_version(void);++#ifdef __cplusplus+} // __cplusplus defined.+#endif++#endif
+ libsass/sass_context.cpp view
@@ -0,0 +1,894 @@+#ifdef _WIN32+#include <io.h>+#define LFEED "\n"+#else+#include <unistd.h>+#define LFEED "\n"+#endif++#include <cstring>+#include <stdexcept>+#include "file.hpp"+#include "json.hpp"+#include "util.hpp"+#include "context.hpp"+#include "sass_values.h"+#include "sass_context.h"+#include "ast_fwd_decl.hpp"+#include "error_handling.hpp"++extern "C" {+  using namespace std;+  using namespace Sass;++  // Input behaviours+  enum Sass_Input_Style {+    SASS_CONTEXT_NULL,+    SASS_CONTEXT_FILE,+    SASS_CONTEXT_DATA,+    SASS_CONTEXT_FOLDER+  };++  // simple linked list+  struct string_list {+    string_list* next;+    char* string;+  };++  // sass config options structure+  struct Sass_Options {++    // Precision for fractional numbers+    int precision;++    // Output style for the generated css code+    // A value from above SASS_STYLE_* constants+    enum Sass_Output_Style output_style;++    // Emit comments in the generated CSS indicating+    // the corresponding source line.+    bool source_comments;++    // embed sourceMappingUrl as data uri+    bool source_map_embed;++    // embed include contents in maps+    bool source_map_contents;++    // Disable sourceMappingUrl in css output+    bool omit_source_map_url;++    // Treat source_string as sass (as opposed to scss)+    bool is_indented_syntax_src;++    // The input path is used for source map+    // generation. It can be used to define+    // something with string compilation or to+    // overload the input file path. It is+    // set to "stdin" for data contexts and+    // to the input file on file contexts.+    char* input_path;++    // The output path is used for source map+    // generation. Libsass will not write to+    // this file, it is just used to create+    // information in source-maps etc.+    char* output_path;++    // String to be used for indentation+    const char* indent;+    // String to be used to for line feeds+    const char* linefeed;++    // Colon-separated list of paths+    // Semicolon-separated on Windows+    // Maybe use array interface instead?+    char* include_path;+    char* plugin_path;++    // Include paths (linked string list)+    struct string_list* include_paths;+    // Plugin paths (linked string list)+    struct string_list* plugin_paths;++    // Path to source map file+    // Enables source map generation+    // Used to create sourceMappingUrl+    char* source_map_file;++    // Directly inserted in source maps+    char* source_map_root;++    // Custom functions that can be called from sccs code+    Sass_Function_List c_functions;++    // List of custom importers+    Sass_Importer_List c_importers;++    // List of custom headers+    Sass_Importer_List c_headers;++  };++  // base for all contexts+  struct Sass_Context : Sass_Options+  {++    // store context type info+    enum Sass_Input_Style type;++    // generated output data+    char* output_string;++    // generated source map json+    char* source_map_string;++    // error status+    int error_status;+    char* error_json;+    char* error_text;+    char* error_message;+    // error position+    char* error_file;+    size_t error_line;+    size_t error_column;+    const char* error_src;++    // report imported files+    char** included_files;++  };++  // struct for file compilation+  struct Sass_File_Context : Sass_Context {++    // no additional fields required+    // input_path is already on options++  };++  // struct for data compilation+  struct Sass_Data_Context : Sass_Context {++    // provided source string+    char* source_string;++  };++  // link c and cpp context+  struct Sass_Compiler {+    // progress status+    Sass_Compiler_State state;+    // original c context+    Sass_Context* c_ctx;+    // Sass::Context+    Context* cpp_ctx;+    // Sass::Block+    Block* root;+  };++  static void copy_options(struct Sass_Options* to, struct Sass_Options* from) { *to = *from; }++  #define IMPLEMENT_SASS_OPTION_ACCESSOR(type, option) \+    type ADDCALL sass_option_get_##option (struct Sass_Options* options) { return options->option; } \+    void ADDCALL sass_option_set_##option (struct Sass_Options* options, type option) { options->option = option; }+  #define IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(type, option) \+    type ADDCALL sass_option_get_##option (struct Sass_Options* options) { return options->option; } \+    void ADDCALL sass_option_set_##option (struct Sass_Options* options, type option) \+    { free(options->option); options->option = option ? sass_strdup(option) : 0; }++  #define IMPLEMENT_SASS_CONTEXT_GETTER(type, option) \+    type ADDCALL sass_context_get_##option (struct Sass_Context* ctx) { return ctx->option; }+  #define IMPLEMENT_SASS_CONTEXT_TAKER(type, option) \+    type sass_context_take_##option (struct Sass_Context* ctx) \+    { type foo = ctx->option; ctx->option = 0; return foo; }++  // helper for safe access to c_ctx+  static const char* safe_str (const char* str) {+    return str == NULL ? "" : str;+  }++  static void copy_strings(const std::vector<std::string>& strings, char*** array) {+    int num = static_cast<int>(strings.size());+    char** arr = (char**) malloc(sizeof(char*) * (num + 1));+    if (arr == 0) throw(bad_alloc());++    for(int i = 0; i < num; i++) {+      arr[i] = (char*) malloc(sizeof(char) * (strings[i].size() + 1));+      if (arr[i] == 0) throw(bad_alloc());+      std::copy(strings[i].begin(), strings[i].end(), arr[i]);+      arr[i][strings[i].size()] = '\0';+    }++    arr[num] = 0;+    *array = arr;+  }++  static void free_string_array(char ** arr) {+    if(!arr)+        return;++    char **it = arr;+    while (it && (*it)) {+      free(*it);+      ++it;+    }++    free(arr);+  }++  static int handle_errors(Sass_Context* c_ctx) {+    try {+     throw;+    }+    catch (Sass_Error& e) {+      stringstream msg_stream;+      string cwd(Sass::File::get_cwd());+      JsonNode* json_err = json_mkobject();+      json_append_member(json_err, "status", json_mknumber(1));+      json_append_member(json_err, "file", json_mkstring(e.pstate.path.c_str()));+      json_append_member(json_err, "line", json_mknumber(e.pstate.line+1));+      json_append_member(json_err, "column", json_mknumber(e.pstate.column+1));+      json_append_member(json_err, "message", json_mkstring(e.message.c_str()));+      string rel_path(Sass::File::resolve_relative_path(e.pstate.path, cwd, cwd));++      string msg_prefix("Error: ");+      bool got_newline = false;+      msg_stream << msg_prefix;+      for (char chr : e.message) {+        if (chr == '\n') {+          got_newline = true;+        } else if (got_newline) {+          msg_stream << string(msg_prefix.size(), ' ');+          got_newline = false;+        }+        msg_stream << chr;+      }+      if (!got_newline) msg_stream << "\n";+      msg_stream << string(msg_prefix.size(), ' ');+      msg_stream << " on line " << e.pstate.line+1 << " of " << rel_path << "\n";++      // now create the code trace (ToDo: maybe have util functions?)+      if (e.pstate.line != string::npos && e.pstate.column != string::npos) {+        size_t line = e.pstate.line;+        const char* line_beg = e.pstate.src;+        while (line_beg && *line_beg && line) {+          if (*line_beg == '\n') -- line;+          ++ line_beg;+        }+        const char* line_end = line_beg;+        while (line_end && *line_end && *line_end != '\n') {+          if (*line_end == '\n') break;+          if (*line_end == '\r') break;+          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 << ">> " << string(line_beg + move_in, line_end - shorten) << "\n";+        msg_stream << "   " << string(e.pstate.column - move_in, '-') << "^\n";+      }++      c_ctx->error_json = json_stringify(json_err, "  ");;+      c_ctx->error_message = sass_strdup(msg_stream.str().c_str());+      c_ctx->error_text = strdup(e.message.c_str());+      c_ctx->error_status = 1;+      c_ctx->error_file = sass_strdup(e.pstate.path.c_str());+      c_ctx->error_line = e.pstate.line+1;+      c_ctx->error_column = e.pstate.column+1;+      c_ctx->error_src = e.pstate.src;+      c_ctx->output_string = 0;+      c_ctx->source_map_string = 0;+      json_delete(json_err);+    }+    catch(bad_alloc& ba) {+      stringstream msg_stream;+      JsonNode* json_err = json_mkobject();+      msg_stream << "Unable to allocate memory: " << ba.what() << endl;+      json_append_member(json_err, "status", json_mknumber(2));+      json_append_member(json_err, "message", json_mkstring(ba.what()));+      c_ctx->error_json = json_stringify(json_err, "  ");;+      c_ctx->error_message = sass_strdup(msg_stream.str().c_str());+      c_ctx->error_text = strdup(ba.what());+      c_ctx->error_status = 2;+      c_ctx->output_string = 0;+      c_ctx->source_map_string = 0;+      json_delete(json_err);+    }+    catch (std::exception& e) {+      stringstream msg_stream;+      JsonNode* json_err = json_mkobject();+      msg_stream << "Error: " << e.what() << endl;+      json_append_member(json_err, "status", json_mknumber(3));+      json_append_member(json_err, "message", json_mkstring(e.what()));+      c_ctx->error_json = json_stringify(json_err, "  ");;+      c_ctx->error_message = sass_strdup(msg_stream.str().c_str());+      c_ctx->error_text = strdup(e.what());+      c_ctx->error_status = 3;+      c_ctx->output_string = 0;+      c_ctx->source_map_string = 0;+      json_delete(json_err);+    }+    catch (string& e) {+      stringstream msg_stream;+      JsonNode* json_err = json_mkobject();+      msg_stream << "Error: " << e << endl;+      json_append_member(json_err, "status", json_mknumber(4));+      json_append_member(json_err, "message", json_mkstring(e.c_str()));+      c_ctx->error_json = json_stringify(json_err, "  ");;+      c_ctx->error_message = sass_strdup(msg_stream.str().c_str());+      c_ctx->error_text = strdup(e.c_str());+      c_ctx->error_status = 4;+      c_ctx->output_string = 0;+      c_ctx->source_map_string = 0;+      json_delete(json_err);+    }+    catch (...) {+      stringstream msg_stream;+      JsonNode* json_err = json_mkobject();+      msg_stream << "Unknown error occurred" << endl;+      json_append_member(json_err, "status", json_mknumber(5));+      json_append_member(json_err, "message", json_mkstring("unknown"));+      c_ctx->error_json = json_stringify(json_err, "  ");;+      c_ctx->error_message = sass_strdup(msg_stream.str().c_str());+      c_ctx->error_text = strdup("unknown");+      c_ctx->error_status = 5;+      c_ctx->output_string = 0;+      c_ctx->source_map_string = 0;+      json_delete(json_err);+    }+    return c_ctx->error_status;+  }++  // generic compilation function (not exported, use file/data compile instead)+  static Sass_Compiler* sass_prepare_context (Sass_Context* c_ctx, Context::Data cpp_opt) throw()+  {+    try {++      // get input/output path from options+      string input_path = safe_str(c_ctx->input_path);+      string output_path = safe_str(c_ctx->output_path);+      // maybe we can extract an output path from input path+      if (output_path == "" && input_path != "") {+        int lastindex = static_cast<int>(input_path.find_last_of("."));+        output_path = (lastindex > -1 ? input_path.substr(0, lastindex) : input_path) + ".css";+      }++      // convert include path linked list to static array+      struct string_list* inc = c_ctx->include_paths;+      // very poor loop to get the length of the linked list+      size_t inc_size = 0; while (inc) { inc_size ++; inc = inc->next; }+      // create char* array to hold all paths plus null terminator+      const char** include_paths = (const char**) calloc(inc_size + 1, sizeof(char*));+      if (include_paths == 0) throw(bad_alloc());+      // reset iterator+      inc = c_ctx->include_paths;+      // copy over the paths+      for (size_t i = 0; inc; i++) {+        include_paths[i] = inc->string;+        inc = inc->next;+      }++      // convert plugin path linked list to static array+      struct string_list* imp = c_ctx->plugin_paths;+      // very poor loop to get the length of the linked list+      size_t imp_size = 0; while (imp) { imp_size ++; imp = imp->next; }+      // create char* array to hold all paths plus null terminator+      const char** plugin_paths = (const char**) calloc(imp_size + 1, sizeof(char*));+      if (plugin_paths == 0) throw(bad_alloc());+      // reset iterator+      imp = c_ctx->plugin_paths;+      // copy over the paths+      for (size_t i = 0; imp; i++) {+        plugin_paths[i] = imp->string;+        imp = imp->next;+      }++      // transfer the options to c+++      cpp_opt.c_compiler(0)+             .c_options(c_ctx)+             .input_path(input_path)+             .output_path(output_path)+             .output_style((Output_Style) c_ctx->output_style)+             .is_indented_syntax_src(c_ctx->is_indented_syntax_src)+             .source_comments(c_ctx->source_comments)+             .source_map_file(safe_str(c_ctx->source_map_file))+             .source_map_root(safe_str(c_ctx->source_map_root))+             .source_map_embed(c_ctx->source_map_embed)+             .source_map_contents(c_ctx->source_map_contents)+             .omit_source_map_url(c_ctx->omit_source_map_url)+             .include_paths_c_str(c_ctx->include_path)+             .plugin_paths_c_str(c_ctx->plugin_path)+             // .include_paths_array(include_paths)+             // .plugin_paths_array(plugin_paths)+             .include_paths(vector<string>())+             .plugin_paths(vector<string>())+             .precision(c_ctx->precision)+             .linefeed(c_ctx->linefeed)+             .indent(c_ctx->indent);++      // create new c++ Context+      Context* cpp_ctx = new Context(cpp_opt);+      // free intermediate data+      free(include_paths);+      free(plugin_paths);++      // register our custom functions+      if (c_ctx->c_functions) {+        auto this_func_data = c_ctx->c_functions;+        while (this_func_data && *this_func_data) {+          cpp_ctx->add_c_function(*this_func_data);+          ++this_func_data;+        }+      }++      // register our custom headers+      if (c_ctx->c_headers) {+        auto this_head_data = c_ctx->c_headers;+        while (this_head_data && *this_head_data) {+          cpp_ctx->add_c_header(*this_head_data);+          ++this_head_data;+        }+      }++      // register our custom importers+      if (c_ctx->c_importers) {+        auto this_imp_data = c_ctx->c_importers;+        while (this_imp_data && *this_imp_data) {+          cpp_ctx->add_c_importer(*this_imp_data);+          ++this_imp_data;+        }+      }++      // reset error status+      c_ctx->error_json = 0;+      c_ctx->error_text = 0;+      c_ctx->error_message = 0;+      c_ctx->error_status = 0;+      // reset error position+      c_ctx->error_src = 0;+      c_ctx->error_file = 0;+      c_ctx->error_line = string::npos;+      c_ctx->error_column = string::npos;++      // allocate a new compiler instance+      Sass_Compiler* compiler = (struct Sass_Compiler*) calloc(1, sizeof(struct Sass_Compiler));+      compiler->state = SASS_COMPILER_CREATED;++      // store in sass compiler+      compiler->c_ctx = c_ctx;+      compiler->cpp_ctx = cpp_ctx;+      cpp_ctx->c_compiler = compiler;++      // use to parse block+      return compiler;++    }+    // pass errors to generic error handler+    catch (...) { handle_errors(c_ctx); }++    // error+    return 0;++  }++  static Block* sass_parse_block (Sass_Compiler* compiler) throw()+  {++    // assert valid pointer+    if (compiler == 0) return 0;+    // The cpp context must be set by now+    Context* cpp_ctx = compiler->cpp_ctx;+    Sass_Context* c_ctx = compiler->c_ctx;+    // We will take care to wire up the rest+    compiler->cpp_ctx->c_compiler = compiler;+    compiler->state = SASS_COMPILER_PARSED;++    try {++      // get input/output path from options+      string input_path = safe_str(c_ctx->input_path);+      string output_path = safe_str(c_ctx->output_path);++      // parsed root block+      Block* root = 0;++      // maybe skip some entries of included files+      // we do not include stdin for data contexts+      size_t skip = 0;++      // dispatch to the correct render function+      if (c_ctx->type == SASS_CONTEXT_FILE) {+        root = cpp_ctx->parse_file();+      } else if (c_ctx->type == SASS_CONTEXT_DATA) {+        root = cpp_ctx->parse_string();+        skip = 1; // skip first entry of includes+      }++      // skip all prefixed files?+      skip += cpp_ctx->head_imports;++      // copy the included files on to the context (dont forget to free)+      if (root) copy_strings(cpp_ctx->get_included_files(skip), &c_ctx->included_files);++      // return parsed block+      return root;++    }+    // pass errors to generic error handler+    catch (...) { handle_errors(c_ctx); }++    // error+    return 0;++  }++  // generic compilation function (not exported, use file/data compile instead)+  static int sass_compile_context (Sass_Context* c_ctx, Context::Data cpp_opt)+  {++    // prepare sass compiler with context and options+    Sass_Compiler* compiler = sass_prepare_context(c_ctx, cpp_opt);++    try {+      // call each compiler step+      sass_compiler_parse(compiler);+      sass_compiler_execute(compiler);+    }+    // pass errors to generic error handler+    catch (...) { handle_errors(c_ctx); }++    sass_delete_compiler(compiler);++    return c_ctx->error_status;+  }++  inline void init_options (struct Sass_Options* options)+  {+    options->precision = 5;+    options->indent = "  ";+    options->linefeed = LFEED;+  }++  Sass_Options* ADDCALL sass_make_options (void)+  {+    struct Sass_Options* options = (struct Sass_Options*) calloc(1, sizeof(struct Sass_Options));+    if (options == 0) { cerr << "Error allocating memory for options" << endl; return 0; }+    init_options(options);+    return options;+  }++  Sass_File_Context* ADDCALL sass_make_file_context(const char* input_path)+  {+    struct Sass_File_Context* ctx = (struct Sass_File_Context*) calloc(1, sizeof(struct Sass_File_Context));+    if (ctx == 0) { cerr << "Error allocating memory for file context" << endl; return 0; }+    ctx->type = SASS_CONTEXT_FILE;+    init_options(ctx);+    try {+      if (input_path == 0) { throw(runtime_error("File context created without an input path")); }+      if (*input_path == 0) { throw(runtime_error("File context created with empty input path")); }+      sass_option_set_input_path(ctx, input_path);+    } catch (...) {+      handle_errors(ctx);+    }+    return ctx;+  }++  Sass_Data_Context* ADDCALL sass_make_data_context(char* source_string)+  {+    struct Sass_Data_Context* ctx = (struct Sass_Data_Context*) calloc(1, sizeof(struct Sass_Data_Context));+    if (ctx == 0) { cerr << "Error allocating memory for data context" << endl; return 0; }+    ctx->type = SASS_CONTEXT_DATA;+    init_options(ctx);+    try {+      if (source_string == 0) { throw(runtime_error("Data context created without a source string")); }+      if (*source_string == 0) { throw(runtime_error("Data context created with empty source string")); }+      ctx->source_string = source_string;+    } catch (...) {+      handle_errors(ctx);+    }+    return ctx;+  }++  struct Sass_Compiler* ADDCALL sass_make_file_compiler (struct Sass_File_Context* c_ctx)+  {+    if (c_ctx == 0) return 0;+    Context::Data cpp_opt = Context::Data();+    cpp_opt.entry_point(c_ctx->input_path);+    return sass_prepare_context(c_ctx, cpp_opt);+  }++  struct Sass_Compiler* ADDCALL sass_make_data_compiler (struct Sass_Data_Context* c_ctx)+  {+    if (c_ctx == 0) return 0;+    Context::Data cpp_opt = Context::Data();+    cpp_opt.source_c_str(c_ctx->source_string);+    return sass_prepare_context(c_ctx, cpp_opt);+  }++  int ADDCALL sass_compile_data_context(Sass_Data_Context* data_ctx)+  {+    if (data_ctx == 0) return 1;+    Sass_Context* c_ctx = data_ctx;+    if (c_ctx->error_status)+      return c_ctx->error_status;+    Context::Data cpp_opt = Context::Data();+    try {+      if (data_ctx->source_string == 0) { throw(runtime_error("Data context has no source string")); }+      if (*data_ctx->source_string == 0) { throw(runtime_error("Data context has empty source string")); }+      cpp_opt.source_c_str(data_ctx->source_string);+    }+    catch (...) { return handle_errors(c_ctx) | 1; }+    return sass_compile_context(c_ctx, cpp_opt);+  }++  int ADDCALL sass_compile_file_context(Sass_File_Context* file_ctx)+  {+    if (file_ctx == 0) return 1;+    Sass_Context* c_ctx = file_ctx;+    if (c_ctx->error_status)+      return c_ctx->error_status;+    Context::Data cpp_opt = Context::Data();+    try {+      if (file_ctx->input_path == 0) { throw(runtime_error("File context has no input path")); }+      if (*file_ctx->input_path == 0) { throw(runtime_error("File context has empty input path")); }+      cpp_opt.entry_point(file_ctx->input_path);+    }+    catch (...) { return handle_errors(c_ctx) | 1; }+    return sass_compile_context(c_ctx, cpp_opt);+  }++  int ADDCALL sass_compiler_parse(struct Sass_Compiler* compiler)+  {+    if (compiler == 0) return 1;+    if (compiler->state == SASS_COMPILER_PARSED) return 0;+    if (compiler->state != SASS_COMPILER_CREATED) return -1;+    if (compiler->c_ctx == NULL) return 1;+    if (compiler->cpp_ctx == NULL) return 1;+    if (compiler->c_ctx->error_status)+      return compiler->c_ctx->error_status;+    // parse the context we have set up (file or data)+    compiler->root = sass_parse_block(compiler);+    // success+    return 0;+  }++  int ADDCALL sass_compiler_execute(struct Sass_Compiler* compiler)+  {+    if (compiler == 0) return 1;+    if (compiler->state == SASS_COMPILER_EXECUTED) return 0;+    if (compiler->state != SASS_COMPILER_PARSED) return -1;+    if (compiler->c_ctx == NULL) return 1;+    if (compiler->cpp_ctx == NULL) return 1;+    if (compiler->root == NULL) return 1;+    if (compiler->c_ctx->error_status)+      return compiler->c_ctx->error_status;+    compiler->state = SASS_COMPILER_EXECUTED;+    Context* cpp_ctx = (Context*) compiler->cpp_ctx;+    Block* root = (Block*) compiler->root;+    // compile the parsed root block+    try { compiler->c_ctx->output_string = cpp_ctx->compile_block(root); }+    // pass catched errors to generic error handler+    catch (...) { return handle_errors(compiler->c_ctx) | 1; }+    // generate source map json and store on context+    compiler->c_ctx->source_map_string = cpp_ctx->generate_source_map();+    // success+    return 0;+  }++  // helper function, not exported, only accessible locally+  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 inc paths+    if (options->plugin_paths) {+      struct string_list* cur;+      struct string_list* next;+      cur = options->plugin_paths;+      while (cur) {+        next = cur->next;+        free(cur->string);+        free(cur);+        cur = next;+      }+    }+    // Deallocate inc paths+    if (options->include_paths) {+      struct string_list* cur;+      struct string_list* next;+      cur = options->include_paths;+      while (cur) {+        next = cur->next;+        free(cur->string);+        free(cur);+        cur = next;+      }+    }+    // Free custom functions+    free(options->c_functions);+    // Free custom importers+    free(options->c_importers);+    free(options->c_headers);+    // Reset our pointers+    options->c_functions = 0;+    options->c_importers = 0;+    options->c_headers = 0;+    options->plugin_paths = 0;+    options->include_paths = 0;+  }++  // helper function, not exported, only accessible locally+  // sass_free_context is also defined in old sass_interface+  static void sass_clear_context (struct Sass_Context* ctx)+  {+    if (ctx == 0) return;+    // release the allocated memory (mostly via sass_strdup)+    if (ctx->output_string)     free(ctx->output_string);+    if (ctx->source_map_string) free(ctx->source_map_string);+    if (ctx->error_message)     free(ctx->error_message);+    if (ctx->error_text)        free(ctx->error_text);+    if (ctx->error_json)        free(ctx->error_json);+    if (ctx->error_file)        free(ctx->error_file);+    if (ctx->input_path)        free(ctx->input_path);+    if (ctx->output_path)       free(ctx->output_path);+    if (ctx->include_path)      free(ctx->include_path);+    if (ctx->source_map_file)   free(ctx->source_map_file);+    if (ctx->source_map_root)   free(ctx->source_map_root);+    free_string_array(ctx->included_files);+    // play safe and reset properties+    ctx->output_string = 0;+    ctx->source_map_string = 0;+    ctx->error_message = 0;+    ctx->error_text = 0;+    ctx->error_json = 0;+    ctx->error_file = 0;+    ctx->input_path = 0;+    ctx->output_path = 0;+    ctx->include_path = 0;+    ctx->source_map_file = 0;+    ctx->source_map_root = 0;+    ctx->included_files = 0;+    // now clear the options+    sass_clear_options(ctx);+  }++  void ADDCALL sass_delete_compiler (struct Sass_Compiler* compiler)+  {+    if (compiler == 0) return;+    Context* cpp_ctx = (Context*) compiler->cpp_ctx;+    compiler->cpp_ctx = 0;+    delete cpp_ctx;+    free(compiler);+  }++  // Deallocate all associated memory with contexts+  void ADDCALL sass_delete_file_context (struct Sass_File_Context* ctx) { sass_clear_context(ctx); free(ctx); }+  void ADDCALL sass_delete_data_context (struct Sass_Data_Context* ctx) { sass_clear_context(ctx); free(ctx); }++  // Getters for sass context from specific implementations+  struct Sass_Context* ADDCALL sass_file_context_get_context(struct Sass_File_Context* ctx) { return ctx; }+  struct Sass_Context* ADDCALL sass_data_context_get_context(struct Sass_Data_Context* ctx) { return ctx; }++  // Getters for context options from Sass_Context+  struct Sass_Options* ADDCALL sass_context_get_options(struct Sass_Context* ctx) { return ctx; }+  struct Sass_Options* ADDCALL sass_file_context_get_options(struct Sass_File_Context* ctx) { return ctx; }+  struct Sass_Options* ADDCALL sass_data_context_get_options(struct Sass_Data_Context* ctx) { return ctx; }+  void ADDCALL sass_file_context_set_options (struct Sass_File_Context* ctx, struct Sass_Options* opt) { copy_options(ctx, opt); }+  void ADDCALL sass_data_context_set_options (struct Sass_Data_Context* ctx, struct Sass_Options* opt) { copy_options(ctx, opt); }++  // Getters for Sass_Compiler options (get conected sass context)+  enum Sass_Compiler_State ADDCALL sass_compiler_get_state(struct Sass_Compiler* compiler) { return compiler->state; }+  struct Sass_Context* ADDCALL sass_compiler_get_context(struct Sass_Compiler* compiler) { return compiler->c_ctx; }+  // Getters for Sass_Compiler options (query import stack)+  size_t ADDCALL sass_compiler_get_import_stack_size(struct Sass_Compiler* compiler) { return compiler->cpp_ctx->import_stack.size(); }+  Sass_Import_Entry ADDCALL sass_compiler_get_last_import(struct Sass_Compiler* compiler) { return compiler->cpp_ctx->import_stack.back(); }+  Sass_Import_Entry ADDCALL sass_compiler_get_import_entry(struct Sass_Compiler* compiler, size_t idx) { return compiler->cpp_ctx->import_stack[idx]; }++  // Calculate the size of the stored null terminated array+  size_t ADDCALL sass_context_get_included_files_size (struct Sass_Context* ctx)+  { size_t l = 0; auto i = ctx->included_files; while (i && *i) { ++i; ++l; } return l; }++  // Create getter and setters for options+  IMPLEMENT_SASS_OPTION_ACCESSOR(int, precision);+  IMPLEMENT_SASS_OPTION_ACCESSOR(enum Sass_Output_Style, output_style);+  IMPLEMENT_SASS_OPTION_ACCESSOR(bool, source_comments);+  IMPLEMENT_SASS_OPTION_ACCESSOR(bool, source_map_embed);+  IMPLEMENT_SASS_OPTION_ACCESSOR(bool, source_map_contents);+  IMPLEMENT_SASS_OPTION_ACCESSOR(bool, omit_source_map_url);+  IMPLEMENT_SASS_OPTION_ACCESSOR(bool, is_indented_syntax_src);+  IMPLEMENT_SASS_OPTION_ACCESSOR(Sass_Function_List, c_functions);+  IMPLEMENT_SASS_OPTION_ACCESSOR(Sass_Importer_List, c_importers);+  IMPLEMENT_SASS_OPTION_ACCESSOR(Sass_Importer_List, c_headers);+  IMPLEMENT_SASS_OPTION_ACCESSOR(const char*, indent);+  IMPLEMENT_SASS_OPTION_ACCESSOR(const char*, linefeed);+  IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(const char*, input_path);+  IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(const char*, output_path);+  IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(const char*, plugin_path);+  IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(const char*, include_path);+  IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(const char*, source_map_file);+  IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(const char*, source_map_root);++  // Create getter and setters for context+  IMPLEMENT_SASS_CONTEXT_GETTER(int, error_status);+  IMPLEMENT_SASS_CONTEXT_GETTER(const char*, error_json);+  IMPLEMENT_SASS_CONTEXT_GETTER(const char*, error_message);+  IMPLEMENT_SASS_CONTEXT_GETTER(const char*, error_text);+  IMPLEMENT_SASS_CONTEXT_GETTER(const char*, error_file);+  IMPLEMENT_SASS_CONTEXT_GETTER(size_t, error_line);+  IMPLEMENT_SASS_CONTEXT_GETTER(size_t, error_column);+  IMPLEMENT_SASS_CONTEXT_GETTER(const char*, error_src);+  IMPLEMENT_SASS_CONTEXT_GETTER(const char*, output_string);+  IMPLEMENT_SASS_CONTEXT_GETTER(const char*, source_map_string);+  IMPLEMENT_SASS_CONTEXT_GETTER(char**, included_files);++  // Take ownership of memory (value on context is set to 0)+  IMPLEMENT_SASS_CONTEXT_TAKER(char*, error_json);+  IMPLEMENT_SASS_CONTEXT_TAKER(char*, error_message);+  IMPLEMENT_SASS_CONTEXT_TAKER(char*, error_text);+  IMPLEMENT_SASS_CONTEXT_TAKER(char*, error_file);+  IMPLEMENT_SASS_CONTEXT_TAKER(char*, output_string);+  IMPLEMENT_SASS_CONTEXT_TAKER(char*, source_map_string);+  IMPLEMENT_SASS_CONTEXT_TAKER(char**, included_files);++  // Push function for include paths (no manipulation support for now)+  void ADDCALL sass_option_push_include_path(struct Sass_Options* options, const char* path)+  {++    struct string_list* include_path = (struct string_list*) calloc(1, sizeof(struct string_list));+    if (include_path == 0) return;+    include_path->string = path ? sass_strdup(path) : 0;+    struct string_list* last = options->include_paths;+    if (!options->include_paths) {+      options->include_paths = include_path;+    } else {+      while (last->next)+        last = last->next;+      last->next = include_path;+    }++  }++  // Push function for plugin paths (no manipulation support for now)+  void ADDCALL sass_option_push_plugin_path(struct Sass_Options* options, const char* path)+  {++    struct string_list* plugin_path = (struct string_list*) calloc(1, sizeof(struct string_list));+    if (plugin_path == 0) return;+    plugin_path->string = path ? sass_strdup(path) : 0;+    struct string_list* last = options->plugin_paths;+    if (!options->plugin_paths) {+      options->plugin_paths = plugin_path;+    } else {+      while (last->next)+        last = last->next;+      last->next = plugin_path;+    }++  }++}
+ libsass/sass_context.h view
@@ -0,0 +1,149 @@+#ifndef SASS_C_CONTEXT_H+#define SASS_C_CONTEXT_H++#include <stddef.h>+#include <stdbool.h>+#include "sass.h"++#ifdef __cplusplus+extern "C" {+#endif+++// Forward declaration+struct Sass_Compiler;++// Forward declaration+struct Sass_Options; // base struct+struct Sass_Context; // : Sass_Options+struct Sass_File_Context; // : Sass_Context+struct Sass_Data_Context; // : Sass_Context++// Compiler states+enum Sass_Compiler_State {+  SASS_COMPILER_CREATED,+  SASS_COMPILER_PARSED,+  SASS_COMPILER_EXECUTED+};++// Create and initialize an option struct+ADDAPI struct Sass_Options* ADDCALL sass_make_options (void);+// Create and initialize a specific context+ADDAPI struct Sass_File_Context* ADDCALL sass_make_file_context (const char* input_path);+ADDAPI struct Sass_Data_Context* ADDCALL sass_make_data_context (char* source_string);++// Call the compilation step for the specific context+ADDAPI int ADDCALL sass_compile_file_context (struct Sass_File_Context* ctx);+ADDAPI int ADDCALL sass_compile_data_context (struct Sass_Data_Context* ctx);++// Create a sass compiler instance for more control+ADDAPI struct Sass_Compiler* ADDCALL sass_make_file_compiler (struct Sass_File_Context* file_ctx);+ADDAPI struct Sass_Compiler* ADDCALL sass_make_data_compiler (struct Sass_Data_Context* data_ctx);++// Execute the different compilation steps individually+// Usefull if you only want to query the included files+ADDAPI int ADDCALL sass_compiler_parse(struct Sass_Compiler* compiler);+ADDAPI int ADDCALL sass_compiler_execute(struct Sass_Compiler* compiler);++// Release all memory allocated with the compiler+// This does _not_ include any contexts or options+ADDAPI void ADDCALL sass_delete_compiler(struct Sass_Compiler* compiler);++// Release all memory allocated and also ourself+ADDAPI void ADDCALL sass_delete_file_context (struct Sass_File_Context* ctx);+ADDAPI void ADDCALL sass_delete_data_context (struct Sass_Data_Context* ctx);++// Getters for context from specific implementation+ADDAPI struct Sass_Context* ADDCALL sass_file_context_get_context (struct Sass_File_Context* file_ctx);+ADDAPI struct Sass_Context* ADDCALL sass_data_context_get_context (struct Sass_Data_Context* data_ctx);++// Getters for Context_Options from Sass_Context+ADDAPI struct Sass_Options* ADDCALL sass_context_get_options (struct Sass_Context* ctx);+ADDAPI struct Sass_Options* ADDCALL sass_file_context_get_options (struct Sass_File_Context* file_ctx);+ADDAPI struct Sass_Options* ADDCALL sass_data_context_get_options (struct Sass_Data_Context* data_ctx);+ADDAPI void ADDCALL sass_file_context_set_options (struct Sass_File_Context* file_ctx, struct Sass_Options* opt);+ADDAPI void ADDCALL sass_data_context_set_options (struct Sass_Data_Context* data_ctx, struct Sass_Options* opt);+++// Getters for Context_Option values+ADDAPI int ADDCALL sass_option_get_precision (struct Sass_Options* options);+ADDAPI enum Sass_Output_Style ADDCALL sass_option_get_output_style (struct Sass_Options* options);+ADDAPI bool ADDCALL sass_option_get_source_comments (struct Sass_Options* options);+ADDAPI bool ADDCALL sass_option_get_source_map_embed (struct Sass_Options* options);+ADDAPI bool ADDCALL sass_option_get_source_map_contents (struct Sass_Options* options);+ADDAPI bool ADDCALL sass_option_get_omit_source_map_url (struct Sass_Options* options);+ADDAPI bool ADDCALL sass_option_get_is_indented_syntax_src (struct Sass_Options* options);+ADDAPI const char* ADDCALL sass_option_get_indent (struct Sass_Options* options);+ADDAPI const char* ADDCALL sass_option_get_linefeed (struct Sass_Options* options);+ADDAPI const char* ADDCALL sass_option_get_input_path (struct Sass_Options* options);+ADDAPI const char* ADDCALL sass_option_get_output_path (struct Sass_Options* options);+ADDAPI const char* ADDCALL sass_option_get_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);+ADDAPI Sass_Importer_List ADDCALL sass_option_get_c_importers (struct Sass_Options* options);+ADDAPI Sass_Function_List ADDCALL sass_option_get_c_functions (struct Sass_Options* options);++// Setters for Context_Option values+ADDAPI void ADDCALL sass_option_set_precision (struct Sass_Options* options, int precision);+ADDAPI void ADDCALL sass_option_set_output_style (struct Sass_Options* options, enum Sass_Output_Style output_style);+ADDAPI void ADDCALL sass_option_set_source_comments (struct Sass_Options* options, bool source_comments);+ADDAPI void ADDCALL sass_option_set_source_map_embed (struct Sass_Options* options, bool source_map_embed);+ADDAPI void ADDCALL sass_option_set_source_map_contents (struct Sass_Options* options, bool source_map_contents);+ADDAPI void ADDCALL sass_option_set_omit_source_map_url (struct Sass_Options* options, bool omit_source_map_url);+ADDAPI void ADDCALL sass_option_set_is_indented_syntax_src (struct Sass_Options* options, bool is_indented_syntax_src);+ADDAPI void ADDCALL sass_option_set_indent (struct Sass_Options* options, const char* indent);+ADDAPI void ADDCALL sass_option_set_linefeed (struct Sass_Options* options, const char* linefeed);+ADDAPI void ADDCALL sass_option_set_input_path (struct Sass_Options* options, const char* input_path);+ADDAPI void ADDCALL sass_option_set_output_path (struct Sass_Options* options, const char* output_path);+ADDAPI void ADDCALL sass_option_set_plugin_path (struct Sass_Options* options, const char* plugin_path);+ADDAPI void ADDCALL sass_option_set_include_path (struct Sass_Options* options, const char* include_path);+ADDAPI void ADDCALL sass_option_set_source_map_file (struct Sass_Options* options, const char* source_map_file);+ADDAPI void ADDCALL sass_option_set_source_map_root (struct Sass_Options* options, const char* source_map_root);+ADDAPI void ADDCALL sass_option_set_c_headers (struct Sass_Options* options, Sass_Importer_List c_headers);+ADDAPI void ADDCALL sass_option_set_c_importers (struct Sass_Options* options, Sass_Importer_List c_importers);+ADDAPI void ADDCALL sass_option_set_c_functions (struct Sass_Options* options, Sass_Function_List c_functions);+++// Getters for Sass_Context values+ADDAPI const char* ADDCALL sass_context_get_output_string (struct Sass_Context* ctx);+ADDAPI int ADDCALL sass_context_get_error_status (struct Sass_Context* ctx);+ADDAPI const char* ADDCALL sass_context_get_error_json (struct Sass_Context* ctx);+ADDAPI const char* ADDCALL sass_context_get_error_text (struct Sass_Context* ctx);+ADDAPI const char* ADDCALL sass_context_get_error_message (struct Sass_Context* ctx);+ADDAPI const char* ADDCALL sass_context_get_error_file (struct Sass_Context* ctx);+ADDAPI const char* ADDCALL sass_context_get_error_src (struct Sass_Context* ctx);+ADDAPI size_t ADDCALL sass_context_get_error_line (struct Sass_Context* ctx);+ADDAPI size_t ADDCALL sass_context_get_error_column (struct Sass_Context* ctx);+ADDAPI const char* ADDCALL sass_context_get_source_map_string (struct Sass_Context* ctx);+ADDAPI char** ADDCALL sass_context_get_included_files (struct Sass_Context* ctx);++// Calculate the size of the stored null terminated array+ADDAPI size_t ADDCALL sass_context_get_included_files_size (struct Sass_Context* ctx);++// Take ownership of memory (value on context is set to 0)+ADDAPI char* ADDCALL sass_context_take_error_json (struct Sass_Context* ctx);+ADDAPI char* ADDCALL sass_context_take_error_text (struct Sass_Context* ctx);+ADDAPI char* ADDCALL sass_context_take_error_message (struct Sass_Context* ctx);+ADDAPI char* ADDCALL sass_context_take_error_file (struct Sass_Context* ctx);+ADDAPI char* ADDCALL sass_context_take_output_string (struct Sass_Context* ctx);+ADDAPI char* ADDCALL sass_context_take_source_map_string (struct Sass_Context* ctx);+ADDAPI char** ADDCALL sass_context_take_included_files (struct Sass_Context* ctx);++// Getters for Sass_Compiler options+ADDAPI enum Sass_Compiler_State ADDCALL sass_compiler_get_state(struct Sass_Compiler* compiler);+ADDAPI struct Sass_Context* ADDCALL sass_compiler_get_context(struct Sass_Compiler* compiler);+ADDAPI 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);++// 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);++#ifdef __cplusplus+} // __cplusplus defined.+#endif++#endif
+ libsass/sass_functions.cpp view
@@ -0,0 +1,173 @@+#ifdef _WIN32+#include <io.h>+#else+#include <unistd.h>+#endif++#include <cstring>+#include "util.hpp"+#include "context.hpp"+#include "sass_functions.h"++extern "C" {+  using namespace std;+  using namespace Sass;++  // Struct to hold custom function callback+  struct Sass_Function {+    const char*      signature;+    Sass_Function_Fn function;+    void*            cookie;+  };++  Sass_Function_List ADDCALL sass_make_function_list(size_t length)+  {+    return (Sass_Function_List) calloc(length + 1, sizeof(Sass_Function_Entry));+  }++  Sass_Function_Entry ADDCALL sass_make_function(const char* signature, Sass_Function_Fn function, void* cookie)+  {+    Sass_Function_Entry cb = (Sass_Function_Entry) calloc(1, sizeof(Sass_Function));+    if (cb == 0) return 0;+    cb->signature = signature;+    cb->function = function;+    cb->cookie = cookie;+    return cb;+  }++  // Setters and getters for callbacks on function lists+  Sass_Function_Entry ADDCALL sass_function_get_list_entry(Sass_Function_List list, size_t pos) { return list[pos]; }+  void sass_function_set_list_entry(Sass_Function_List list, size_t pos, Sass_Function_Entry cb) { list[pos] = cb; }++  const char* ADDCALL sass_function_get_signature(Sass_Function_Entry cb) { return cb->signature; }+  Sass_Function_Fn ADDCALL sass_function_get_function(Sass_Function_Entry cb) { return cb->function; }+  void* ADDCALL sass_function_get_cookie(Sass_Function_Entry cb) { return cb->cookie; }++  // External import entry+  struct Sass_Import {+    char* path;+    char* base;+    char* source;+    char* srcmap;+    // error handling+    char* error;+    size_t line;+    size_t column;+  };++  // Struct to hold importer callback+  struct Sass_Importer {+    Sass_Importer_Fn importer;+    double           priority;+    void*            cookie;+  };++  Sass_Importer_Entry ADDCALL sass_make_importer(Sass_Importer_Fn importer, double priority, void* cookie)+  {+    Sass_Importer_Entry cb = (Sass_Importer_Entry) calloc(1, sizeof(Sass_Importer));+    if (cb == 0) return 0;+    cb->importer = importer;+    cb->priority = priority;+    cb->cookie = cookie;+    return cb;+  }++  Sass_Importer_Fn ADDCALL sass_importer_get_function(Sass_Importer_Entry cb) { return cb->importer; }+  double ADDCALL sass_importer_get_priority (Sass_Importer_Entry cb) { return cb->priority; }+  void* ADDCALL sass_importer_get_cookie(Sass_Importer_Entry cb) { return cb->cookie; }++  // Just in case we have some stray import structs+  void ADDCALL sass_delete_importer (Sass_Importer_Entry cb)+  {+    free(cb);+  }++  // Creator for sass custom importer function list+  Sass_Importer_List ADDCALL sass_make_importer_list(size_t length)+  {+    return (Sass_Importer_List) calloc(length + 1, sizeof(Sass_Importer_Entry));+  }++  // Creator for sass custom importer return argument list+  Sass_Import_List ADDCALL sass_make_import_list(size_t length)+  {+    return (Sass_Import**) calloc(length + 1, sizeof(Sass_Import*));+  }++  // Creator for a single import entry returned by the custom importer inside the list+  // We take ownership of the memory for source and srcmap (freed when context is destroyd)+  Sass_Import_Entry ADDCALL sass_make_import(const char* path, const char* base, char* source, char* srcmap)+  {+    Sass_Import* v = (Sass_Import*) calloc(1, sizeof(Sass_Import));+    if (v == 0) return 0;+    v->path = path ? sass_strdup(path) : 0;+    v->base = base ? sass_strdup(base) : 0;+    v->source = source;+    v->srcmap = srcmap;+    v->error = 0;+    v->line = -1;+    v->column = -1;+    return v;+  }++  // Older style, but somehow still valid - keep around or deprecate?+  Sass_Import_Entry ADDCALL sass_make_import_entry(const char* path, char* source, char* srcmap)+  {+    return sass_make_import(path, path, source, srcmap);+  }++  // Upgrade a normal import entry to throw an error (original path can be re-used by error reporting)+  Sass_Import_Entry ADDCALL sass_import_set_error(Sass_Import_Entry import, const char* error, size_t line, size_t col)+  {+    if (import == 0) return 0;+    if (import->error) free(import->error);+    import->error = error ? strdup(error) : 0;+    import->line = line ? line : -1;+    import->column = col ? col : -1;+    return import;+  }++  // Setters and getters for entries on the import list+  void ADDCALL sass_import_set_list_entry(Sass_Import_List list, size_t idx, Sass_Import_Entry entry) { list[idx] = entry; }+  Sass_Import_Entry ADDCALL sass_import_get_list_entry(Sass_Import_List list, size_t idx) { return list[idx]; }++  // Deallocator for the allocated memory+  void ADDCALL sass_delete_import_list(Sass_Import_List list)+  {+    Sass_Import_List it = list;+    if (list == 0) return;+    while(*list) {+      sass_delete_import(*list);+      ++list;+    }+    free(it);+  }++  // Just in case we have some stray import structs+  void ADDCALL sass_delete_import(Sass_Import_Entry import)+  {+    free(import->path);+    free(import->base);+    free(import->source);+    free(import->srcmap);+    free(import->error);+    free(import);+  }++  // Getter for import entry+  const char* ADDCALL sass_import_get_path(Sass_Import_Entry entry) { return entry->path; }+  const char* ADDCALL sass_import_get_base(Sass_Import_Entry entry) { return entry->base; }+  const char* ADDCALL sass_import_get_source(Sass_Import_Entry entry) { return entry->source; }+  const char* ADDCALL sass_import_get_srcmap(Sass_Import_Entry entry) { return entry->srcmap; }++  // Getter for import error entry+  size_t ADDCALL sass_import_get_error_line(Sass_Import_Entry entry) { return entry->line; }+  size_t ADDCALL sass_import_get_error_column(Sass_Import_Entry entry) { return entry->column; }+  const char* ADDCALL sass_import_get_error_message(Sass_Import_Entry entry) { return entry->error; }++  // Explicit functions to take ownership of the memory+  // Resets our own property since we do not know if it is still alive+  char* ADDCALL sass_import_take_source(Sass_Import_Entry entry) { char* ptr = entry->source; entry->source = 0; return ptr; }+  char* ADDCALL sass_import_take_srcmap(Sass_Import_Entry entry) { char* ptr = entry->srcmap; entry->srcmap = 0; return ptr; }++}
+ libsass/sass_functions.h view
@@ -0,0 +1,108 @@+#ifndef SASS_C_FUNCTIONS_H+#define SASS_C_FUNCTIONS_H++#include <stddef.h>+#include <stdbool.h>+#include "sass.h"++#ifdef __cplusplus+extern "C" {+#endif+++// Forward declaration+struct Sass_Import;+struct Sass_Options;+struct Sass_Compiler;+struct Sass_Importer;+struct Sass_Function;++// Typedef helpers for import lists+typedef struct Sass_Import (*Sass_Import_Entry);+typedef struct Sass_Import* (*Sass_Import_List);+// Typedef helpers for custom importer lists+typedef struct Sass_Importer (*Sass_Importer_Entry);+typedef struct Sass_Importer* (*Sass_Importer_List);+// Typedef defining importer signature and return type+typedef Sass_Import_List (*Sass_Importer_Fn)+  (const char* url, Sass_Importer_Entry cb, struct Sass_Compiler* compiler);++// Typedef helpers for custom functions lists+typedef struct Sass_Function (*Sass_Function_Entry);+typedef struct Sass_Function* (*Sass_Function_List);+// Typedef defining function signature and return type+typedef union Sass_Value* (*Sass_Function_Fn)+  (const union Sass_Value*, Sass_Function_Entry cb, struct Sass_Options* options);+++// 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);+++// Creators for custom importer callback (with some additional pointer)+// The pointer is mostly used to store the callback into the actual binding+ADDAPI Sass_Importer_Entry ADDCALL sass_make_importer (Sass_Importer_Fn importer, double priority, void* cookie);++// Getters for import function descriptors+ADDAPI Sass_Importer_Fn ADDCALL sass_importer_get_function (Sass_Importer_Entry cb);+ADDAPI double ADDCALL sass_importer_get_priority (Sass_Importer_Entry cb);+ADDAPI void* ADDCALL sass_importer_get_cookie (Sass_Importer_Entry cb);++// Deallocator for associated memory+ADDAPI void ADDCALL sass_delete_importer (Sass_Importer_Entry cb);++// Creator for sass custom importer return argument list+ADDAPI Sass_Import_List ADDCALL sass_make_import_list (size_t length);+// Creator for a single import entry returned by the custom importer inside the list+ADDAPI Sass_Import_Entry ADDCALL sass_make_import_entry (const char* path, char* source, char* srcmap);+ADDAPI Sass_Import_Entry ADDCALL sass_make_import (const char* path, const char* base, char* source, char* srcmap);+// set error message to abort import and to print out a message (path from existing object is used in output)+ADDAPI Sass_Import_Entry ADDCALL sass_import_set_error(Sass_Import_Entry import, const char* message, size_t line, size_t col);++// Setters to insert an entry into the import list (you may also use [] access directly)+// Since we are dealing with pointers they should have a guaranteed and fixed size+ADDAPI void ADDCALL sass_import_set_list_entry (Sass_Import_List list, size_t idx, Sass_Import_Entry entry);+ADDAPI Sass_Import_Entry ADDCALL sass_import_get_list_entry (Sass_Import_List list, size_t idx);++// Getters for import entry+ADDAPI const char* ADDCALL sass_import_get_path (Sass_Import_Entry);+ADDAPI const char* ADDCALL sass_import_get_base (Sass_Import_Entry);+ADDAPI const char* ADDCALL sass_import_get_source (Sass_Import_Entry);+ADDAPI const char* ADDCALL sass_import_get_srcmap (Sass_Import_Entry);+// Explicit functions to take ownership of these items+// The property on our struct will be reset to NULL+ADDAPI char* ADDCALL sass_import_take_source (Sass_Import_Entry);+ADDAPI char* ADDCALL sass_import_take_srcmap (Sass_Import_Entry);+// Getters from import error entry+ADDAPI size_t ADDCALL sass_import_get_error_line (Sass_Import_Entry);+ADDAPI size_t ADDCALL sass_import_get_error_column (Sass_Import_Entry);+ADDAPI const char* ADDCALL sass_import_get_error_message (Sass_Import_Entry);++// Deallocator for associated memory (incl. entries)+ADDAPI void ADDCALL sass_delete_import_list (Sass_Import_List);+// Just in case we have some stray import structs+ADDAPI void ADDCALL sass_delete_import (Sass_Import_Entry);++++// Creators for sass function list and function descriptors+ADDAPI Sass_Function_List ADDCALL sass_make_function_list (size_t length);+ADDAPI Sass_Function_Entry ADDCALL sass_make_function (const char* signature, Sass_Function_Fn cb, void* cookie);++// Setters and getters for callbacks on function lists+ADDAPI Sass_Function_Entry ADDCALL sass_function_get_list_entry(Sass_Function_List list, size_t pos);+ADDAPI void ADDCALL sass_function_set_list_entry(Sass_Function_List list, size_t pos, Sass_Function_Entry cb);++// Getters for custom function descriptors+ADDAPI const char* ADDCALL sass_function_get_signature (Sass_Function_Entry cb);+ADDAPI Sass_Function_Fn ADDCALL sass_function_get_function (Sass_Function_Entry cb);+ADDAPI void* ADDCALL sass_function_get_cookie (Sass_Function_Entry cb);+++#ifdef __cplusplus+} // __cplusplus defined.+#endif++#endif
+ libsass/sass_interface.cpp view
@@ -0,0 +1,287 @@+#ifdef _WIN32+#include <io.h>+#define LFEED "\n"+#else+#include <unistd.h>+#define LFEED "\n"+#endif++#include <string>+#include <cstdlib>+#include <cstring>+#include <sstream>+#include <iostream>++#include "util.hpp"+#include "context.hpp"+#include "inspect.hpp"+#include "error_handling.hpp"+#include "sass_interface.h"+++extern "C" {+  using namespace std;++  sass_context* sass_new_context()+  { return (sass_context*) calloc(1, sizeof(sass_context)); }++  // helper for safe access to c_ctx+  static const char* safe_str (const char* str) {+    return str == NULL ? "" : str;+  }++  static void copy_strings(const std::vector<std::string>& strings, char*** array, int skip = 0) {+    int num = static_cast<int>(strings.size());+    char** arr = (char**) malloc(sizeof(char*) * (num + 1));++    for(int i = skip; i < num; i++) {+      arr[i-skip] = (char*) malloc(sizeof(char) * (strings[i].size() + 1));+      std::copy(strings[i].begin(), strings[i].end(), arr[i-skip]);+      arr[i-skip][strings[i].size()] = '\0';+    }++    arr[num-skip] = 0;+    *array = arr;+  }++  static void free_string_array(char ** arr) {+    if(!arr)+        return;++    char **it = arr;+    while (it && (*it)) {+      free(*it);+      ++it;+    }++    free(arr);+  }++  void sass_free_context(sass_context* ctx)+  {+    if (ctx->output_string)     free(ctx->output_string);+    if (ctx->source_map_string) free(ctx->source_map_string);+    if (ctx->error_message)     free(ctx->error_message);+    if (ctx->c_functions)       free(ctx->c_functions);++    free_string_array(ctx->included_files);++    free(ctx);+  }++  sass_file_context* sass_new_file_context()+  { return (sass_file_context*) calloc(1, sizeof(sass_file_context)); }++  void sass_free_file_context(sass_file_context* ctx)+  {+    if (ctx->output_string)     free(ctx->output_string);+    if (ctx->source_map_string) free(ctx->source_map_string);+    if (ctx->error_message)     free(ctx->error_message);+    if (ctx->c_functions)       free(ctx->c_functions);++    free_string_array(ctx->included_files);++    free(ctx);+  }++  sass_folder_context* sass_new_folder_context()+  { return (sass_folder_context*) calloc(1, sizeof(sass_folder_context)); }++  void sass_free_folder_context(sass_folder_context* ctx)+  {+    free_string_array(ctx->included_files);+    free(ctx);+  }++  int sass_compile(sass_context* c_ctx)+  {+    using namespace Sass;+    try {+      string input_path = safe_str(c_ctx->input_path);+      int lastindex = static_cast<int>(input_path.find_last_of("."));+      string output_path;+      if (!c_ctx->output_path) {+        if (input_path != "") {+          output_path = (lastindex > -1 ? input_path.substr(0, lastindex) : input_path) + ".css";+        }+      }+      else {+          output_path = c_ctx->output_path;+      }+      Context cpp_ctx(+        Context::Data().source_c_str(c_ctx->source_string)+                       .output_path(output_path)+                       .output_style((Output_Style) c_ctx->options.output_style)+                       .is_indented_syntax_src(c_ctx->options.is_indented_syntax_src)+                       .source_comments(c_ctx->options.source_comments)+                       .source_map_file(safe_str(c_ctx->options.source_map_file))+                       .source_map_root(safe_str(c_ctx->options.source_map_root))+                       .source_map_embed(c_ctx->options.source_map_embed)+                       .source_map_contents(c_ctx->options.source_map_contents)+                       .omit_source_map_url(c_ctx->options.omit_source_map_url)+                       .include_paths_c_str(c_ctx->options.include_paths)+                       .plugin_paths_c_str(c_ctx->options.plugin_paths)+                       // .include_paths_array(0)+                       // .plugin_paths_array(0)+                       .include_paths(vector<string>())+                       .plugin_paths(vector<string>())+                       .precision(c_ctx->options.precision ? c_ctx->options.precision : 5)+                       .indent(c_ctx->options.indent ? c_ctx->options.indent : "  ")+                       .linefeed(c_ctx->options.linefeed ? c_ctx->options.linefeed : LFEED)+      );+      if (c_ctx->c_functions) {+        Sass_Function_List this_func_data = c_ctx->c_functions;+        while ((this_func_data) && (*this_func_data)) {+          cpp_ctx.c_functions.push_back(*this_func_data);+          ++this_func_data;+        }+      }+      c_ctx->output_string = cpp_ctx.compile_string();+      c_ctx->source_map_string = cpp_ctx.generate_source_map();+      c_ctx->error_message = 0;+      c_ctx->error_status = 0;++      copy_strings(cpp_ctx.get_included_files(1), &c_ctx->included_files, 1);+    }+    catch (Sass_Error& e) {+      stringstream msg_stream;+      msg_stream << e.pstate.path << ":" << e.pstate.line << ": " << e.message << endl;+      c_ctx->error_message = sass_strdup(msg_stream.str().c_str());+      c_ctx->error_status = 1;+      c_ctx->output_string = 0;+      c_ctx->source_map_string = 0;+    }+    catch(bad_alloc& ba) {+      stringstream msg_stream;+      msg_stream << "Unable to allocate memory: " << ba.what() << endl;+      c_ctx->error_message = sass_strdup(msg_stream.str().c_str());+      c_ctx->error_status = 1;+      c_ctx->output_string = 0;+      c_ctx->source_map_string = 0;+    }+    catch (std::exception& e) {+      stringstream msg_stream;+      msg_stream << "Error: " << e.what() << endl;+      c_ctx->error_message = sass_strdup(msg_stream.str().c_str());+      c_ctx->error_status = 1;+      c_ctx->output_string = 0;+      c_ctx->source_map_string = 0;+    }+    catch (string& e) {+      stringstream msg_stream;+      msg_stream << "Error: " << e << endl;+      c_ctx->error_message = sass_strdup(msg_stream.str().c_str());+      c_ctx->error_status = 1;+      c_ctx->output_string = 0;+      c_ctx->source_map_string = 0;+    }+    catch (...) {+      // couldn't find the specified file in the include paths; report an error+      stringstream msg_stream;+      msg_stream << "Unknown error occurred" << endl;+      c_ctx->error_message = sass_strdup(msg_stream.str().c_str());+      c_ctx->error_status = 1;+      c_ctx->output_string = 0;+      c_ctx->source_map_string = 0;+    }+    return 0;+  }++  int sass_compile_file(sass_file_context* c_ctx)+  {+    using namespace Sass;+    try {+      string input_path = safe_str(c_ctx->input_path);+      int lastindex = static_cast<int>(input_path.find_last_of("."));+      string output_path;+      if (!c_ctx->output_path) {+          output_path = (lastindex > -1 ? input_path.substr(0, lastindex) : input_path) + ".css";+      }+      else {+          output_path = c_ctx->output_path;+      }+      Context cpp_ctx(+        Context::Data().entry_point(input_path)+                       .output_path(output_path)+                       .output_style((Output_Style) c_ctx->options.output_style)+                       .is_indented_syntax_src(c_ctx->options.is_indented_syntax_src)+                       .source_comments(c_ctx->options.source_comments)+                       .source_map_file(safe_str(c_ctx->options.source_map_file))+                       .source_map_root(safe_str(c_ctx->options.source_map_root))+                       .source_map_embed(c_ctx->options.source_map_embed)+                       .source_map_contents(c_ctx->options.source_map_contents)+                       .omit_source_map_url(c_ctx->options.omit_source_map_url)+                       .include_paths_c_str(c_ctx->options.include_paths)+                       .plugin_paths_c_str(c_ctx->options.plugin_paths)+                       // .include_paths_array(0)+                       // .plugin_paths_array(0)+                       .include_paths(vector<string>())+                       .plugin_paths(vector<string>())+                       .precision(c_ctx->options.precision ? c_ctx->options.precision : 5)+                       .indent(c_ctx->options.indent ? c_ctx->options.indent : "  ")+                       .linefeed(c_ctx->options.linefeed ? c_ctx->options.linefeed : LFEED)+      );+      if (c_ctx->c_functions) {+        Sass_Function_List this_func_data = c_ctx->c_functions;+        while ((this_func_data) && (*this_func_data)) {+          cpp_ctx.c_functions.push_back(*this_func_data);+          ++this_func_data;+        }+      }+      c_ctx->output_string = cpp_ctx.compile_file();+      c_ctx->source_map_string = cpp_ctx.generate_source_map();+      c_ctx->error_message = 0;+      c_ctx->error_status = 0;++      copy_strings(cpp_ctx.get_included_files(), &c_ctx->included_files);+    }+    catch (Sass_Error& e) {+      stringstream msg_stream;+      msg_stream << e.pstate.path << ":" << e.pstate.line << ": " << e.message << endl;+      c_ctx->error_message = sass_strdup(msg_stream.str().c_str());+      c_ctx->error_status = 1;+      c_ctx->output_string = 0;+      c_ctx->source_map_string = 0;+    }+    catch(bad_alloc& ba) {+      stringstream msg_stream;+      msg_stream << "Unable to allocate memory: " << ba.what() << endl;+      c_ctx->error_message = sass_strdup(msg_stream.str().c_str());+      c_ctx->error_status = 1;+      c_ctx->output_string = 0;+      c_ctx->source_map_string = 0;+    }+    catch (std::exception& e) {+      stringstream msg_stream;+      msg_stream << "Error: " << e.what() << endl;+      c_ctx->error_message = sass_strdup(msg_stream.str().c_str());+      c_ctx->error_status = 1;+      c_ctx->output_string = 0;+      c_ctx->source_map_string = 0;+    }+    catch (string& e) {+      stringstream msg_stream;+      msg_stream << "Error: " << e << endl;+      c_ctx->error_message = sass_strdup(msg_stream.str().c_str());+      c_ctx->error_status = 1;+      c_ctx->output_string = 0;+      c_ctx->source_map_string = 0;+    }+    catch (...) {+      // couldn't find the specified file in the include paths; report an error+      stringstream msg_stream;+      msg_stream << "Unknown error occurred" << endl;+      c_ctx->error_message = sass_strdup(msg_stream.str().c_str());+      c_ctx->error_status = 1;+      c_ctx->output_string = 0;+      c_ctx->source_map_string = 0;+    }+    return 0;+  }++  int sass_compile_folder(sass_folder_context* c_ctx)+  {+    return 1;+  }++}
+ libsass/sass_interface.h view
@@ -0,0 +1,102 @@+#ifndef SASS_C_INTERFACE_H+#define SASS_C_INTERFACE_H++#include <stddef.h>+#include <stdbool.h>+#include "sass.h"++#ifdef __cplusplus+extern "C" {+#endif+++// Please ensure there are no null values.+// Thar be dragons.+struct sass_options {+  // Output style for the generated css code+  // A value from above SASS_STYLE_* constants+  int output_style;+  // If you want inline source comments+  bool source_comments;+  // Path to source map file+  // Enables the source map generating+  // Used to create sourceMappingUrl+  const char* source_map_file;+  // Disable sourceMappingUrl in css output+  bool omit_source_map_url;+  // embed sourceMappingUrl as data uri+  bool source_map_embed;+  // embed include contents in maps+  bool source_map_contents;+  // Pass-through as sourceRoot property+  const char* source_map_root;+  // Treat source_string as sass (as opposed to scss)+  bool is_indented_syntax_src;+  // Colon-separated list of paths+  // Semicolon-separated on Windows+  const char* include_paths;+  const char* plugin_paths;+  // String to be used for indentation+  const char* indent;+  // String to be used to for line feeds+  const char* linefeed;+  // Precision for outputting fractional numbers+  int precision;+};++struct sass_context {+  const char* input_path;+  const char* output_path;+  const char* source_string;+  char* output_string;+  char* source_map_string;+  struct sass_options options;+  int error_status;+  char* error_message;+  Sass_Function_List c_functions;+  char** included_files;+  int num_included_files;+};++struct sass_file_context {+  const char* input_path;+  const char* output_path;+  char* output_string;+  char* source_map_string;+  struct sass_options options;+  int error_status;+  char* error_message;+  Sass_Function_List c_functions;+  char** included_files;+  int num_included_files;+};++struct sass_folder_context {+  const char* search_path;+  const char* output_path;+  struct sass_options options;+  int error_status;+  char* error_message;+  Sass_Function_List c_functions;+  char** included_files;+  int num_included_files;+};++struct sass_context* sass_new_context (void);+struct sass_file_context* sass_new_file_context (void);+struct sass_folder_context* sass_new_folder_context (void);++void sass_free_context (struct sass_context* ctx);+void sass_free_file_context (struct sass_file_context* ctx);+void sass_free_folder_context(struct sass_folder_context* ctx);++int sass_compile (struct sass_context* ctx);+int sass_compile_file (struct sass_file_context* ctx);+int sass_compile_folder (struct sass_folder_context* ctx);+++#ifdef __cplusplus+}+#endif++#endif
+ libsass/sass_util.cpp view
@@ -0,0 +1,132 @@+#include "node.hpp"+#include "to_string.hpp"++namespace Sass {+++  /*+		This is the equivalent of ruby's Sass::Util.paths.++    # Return an array of all possible paths through the given arrays.+    #+    # @param arrs [NodeCollection<NodeCollection<Node>>]+    # @return [NodeCollection<NodeCollection<Node>>]+    #+    # @example+    #   paths([[1, 2], [3, 4], [5]]) #=>+    #     # [[1, 3, 5],+    #     #  [2, 3, 5],+    #     #  [1, 4, 5],+    #     #  [2, 4, 5]]++   The following is the modified version of the ruby code that was more portable to C++. You+   should be able to drop it into ruby 3.2.19 and get the same results from ruby sass.++    def paths(arrs)+     	// I changed the inject and maps to an iterative approach to make it easier to implement in C+++      loopStart = [[]]++      for arr in arrs do+        permutations = []+        for e in arr do+          for path in loopStart do+            permutations.push(path + [e])+          end+        end+        loopStart = permutations+      end+    end+	*/+  Node paths(const Node& arrs, Context& ctx) {+    To_String to_string(&ctx);++    Node loopStart = Node::createCollection();+    loopStart.collection()->push_back(Node::createCollection());++    for (NodeDeque::iterator arrsIter = arrs.collection()->begin(), arrsEndIter = arrs.collection()->end();+    	arrsIter != arrsEndIter; ++arrsIter) {++      Node& arr = *arrsIter;++      Node permutations = Node::createCollection();++      for (NodeDeque::iterator arrIter = arr.collection()->begin(), arrIterEnd = arr.collection()->end();+      	arrIter != arrIterEnd; ++arrIter) {++        Node& e = *arrIter;++        for (NodeDeque::iterator loopStartIter = loopStart.collection()->begin(), loopStartIterEnd = loopStart.collection()->end();+          loopStartIter != loopStartIterEnd; ++loopStartIter) {++          Node& path = *loopStartIter;++          Node newPermutation = Node::createCollection();+          newPermutation.plus(path);+          newPermutation.collection()->push_back(e);++          permutations.collection()->push_back(newPermutation);+        }+      }++      loopStart = permutations;+    }++    return loopStart;+  }+++	/*+  This is the equivalent of ruby sass' Sass::Util.flatten and [].flatten.+  Sass::Util.flatten requires the number of levels to flatten, while+  [].flatten doesn't and will flatten the entire array. This function+  supports both.++  # Flattens the first `n` nested arrays. If n == -1, all arrays will be flattened+  #+  # @param arr [NodeCollection] The array to flatten+  # @param n [int] The number of levels to flatten+  # @return [NodeCollection] The flattened array++  The following is the modified version of the ruby code that was more portable to C++. You+  should be able to drop it into ruby 3.2.19 and get the same results from ruby sass.++  def flatten(arr, n = -1)+    if n != -1 and n == 0 then+      return arr+    end++    flattened = []++    for e in arr do+      if e.is_a?(Array) then+        flattened.concat(flatten(e, n - 1))+      else+        flattened << e+      end+    end++    return flattened+  end+  */+  Node flatten(const Node& arr, Context& ctx, int n = -1) {+    if (n != -1 && n == 0) {+      return arr;+    }++    Node flattened = Node::createCollection();++    for (NodeDeque::iterator iter = arr.collection()->begin(), iterEnd = arr.collection()->end();+    	iter != iterEnd; iter++) {+    	Node& e = *iter;++      if (e.isCollection()) {+      	Node recurseFlattened = flatten(e, ctx, n - 1);+        flattened.collection()->insert(flattened.collection()->end(), recurseFlattened.collection()->begin(), recurseFlattened.collection()->end());+      } else {+      	flattened.collection()->push_back(e);+      }+    }++    return flattened;+  }+}
+ libsass/sass_util.hpp view
@@ -0,0 +1,260 @@+#ifndef SASS_SASS_UTIL_H+#define SASS_SASS_UTIL_H++#include <deque>+#include <iostream>++#include "ast.hpp"+#include "node.hpp"+#include "debug.hpp"++namespace Sass {+++  using namespace std;+++  /*+   This is for ports of functions in the Sass:Util module.+   */+++  /*+    # Return a Node collection of all possible paths through the given Node collection of Node collections.+    #+    # @param arrs [NodeCollection<NodeCollection<Node>>]+    # @return [NodeCollection<NodeCollection<Node>>]+    #+    # @example+    #   paths([[1, 2], [3, 4], [5]]) #=>+    #     # [[1, 3, 5],+    #     #  [2, 3, 5],+    #     #  [1, 4, 5],+    #     #  [2, 4, 5]]+  */+  Node paths(const Node& arrs, Context& ctx);+++  /*+  This class is a default implementation of a Node comparator that can be passed to the lcs function below.+  It uses operator== for equality comparision. It then returns one if the Nodes are equal.+  */+  class DefaultLcsComparator {+  public:+    bool operator()(const Node& one, const Node& two, Node& out) const {+      // TODO: Is this the correct C++ interpretation?+      // block ||= proc {|a, b| a == b && a}+      if (one == two) {+        out = one;+        return true;+      }++      return false;+    }+  };+++  typedef vector<vector<int> > LCSTable;+++  /*+  This is the equivalent of ruby's Sass::Util.lcs_backtrace.++  # Computes a single longest common subsequence for arrays x and y.+  # Algorithm from http://en.wikipedia.org/wiki/Longest_common_subsequence_problem#Reading_out_an_LCS+  */+  template<typename ComparatorType>+  Node lcs_backtrace(const LCSTable& c, const Node& x, const Node& y, int i, int j, const ComparatorType& comparator) {+    DEBUG_PRINTLN(LCS, "LCSBACK: X=" << x << " Y=" << y << " I=" << i << " J=" << j)++    if (i == 0 || j == 0) {+      DEBUG_PRINTLN(LCS, "RETURNING EMPTY")+      return Node::createCollection();+    }++    NodeDeque& xChildren = *(x.collection());+    NodeDeque& yChildren = *(y.collection());++    Node compareOut = Node::createNil();+    if (comparator(xChildren[i], yChildren[j], compareOut)) {+      DEBUG_PRINTLN(LCS, "RETURNING AFTER ELEM COMPARE")+      Node result = lcs_backtrace(c, x, y, i - 1, j - 1, comparator);+      result.collection()->push_back(compareOut);+      return result;+    }++    if (c[i][j - 1] > c[i - 1][j]) {+      DEBUG_PRINTLN(LCS, "RETURNING AFTER TABLE COMPARE")+      return lcs_backtrace(c, x, y, i, j - 1, comparator);+    }++    DEBUG_PRINTLN(LCS, "FINAL RETURN")+    return lcs_backtrace(c, x, y, i - 1, j, comparator);+  }+++  /*+  This is the equivalent of ruby's Sass::Util.lcs_table.++  # Calculates the memoization table for the Least Common Subsequence algorithm.+  # Algorithm from http://en.wikipedia.org/wiki/Longest_common_subsequence_problem#Computing_the_length_of_the_LCS+  */+  template<typename ComparatorType>+  void lcs_table(const Node& x, const Node& y, const ComparatorType& comparator, LCSTable& out) {+    DEBUG_PRINTLN(LCS, "LCSTABLE: X=" << x << " Y=" << y)++    NodeDeque& xChildren = *(x.collection());+    NodeDeque& yChildren = *(y.collection());++    LCSTable c(xChildren.size(), vector<int>(yChildren.size()));++    // These shouldn't be necessary since the vector will be initialized to 0 already.+    // x.size.times {|i| c[i][0] = 0}+    // y.size.times {|j| c[0][j] = 0}++    for (size_t i = 1; i < xChildren.size(); i++) {+      for (size_t j = 1; j < yChildren.size(); j++) {+        Node compareOut = Node::createNil();++        if (comparator(xChildren[i], yChildren[j], compareOut)) {+          c[i][j] = c[i - 1][j - 1] + 1;+        } else {+          c[i][j] = max(c[i][j - 1], c[i - 1][j]);+        }+      }+    }++    out = c;+  }+++  /*+  This is the equivalent of ruby's Sass::Util.lcs.++  # Computes a single longest common subsequence for `x` and `y`.+  # If there are more than one longest common subsequences,+  # the one returned is that which starts first in `x`.++  # @param x [NodeCollection]+  # @param y [NodeCollection]+  # @comparator An equality check between elements of `x` and `y`.+  # @return [NodeCollection] The LCS++  http://en.wikipedia.org/wiki/Longest_common_subsequence_problem+  */+  template<typename ComparatorType>+  Node lcs(Node& x, Node& y, const ComparatorType& comparator, Context& ctx) {+    DEBUG_PRINTLN(LCS, "LCS: X=" << x << " Y=" << y)++    Node newX = Node::createCollection();+    newX.collection()->push_back(Node::createNil());+    newX.plus(x);++    Node newY = Node::createCollection();+    newY.collection()->push_back(Node::createNil());+    newY.plus(y);++    LCSTable table;+    lcs_table(newX, newY, comparator, table);++    return lcs_backtrace(table, newX, newY, static_cast<int>(newX.collection()->size()) - 1, static_cast<int>(newY.collection()->size()) - 1, comparator);+  }+++  /*+  This is the equivalent of ruby sass' Sass::Util.flatten and [].flatten.+  Sass::Util.flatten requires the number of levels to flatten, while+  [].flatten doesn't and will flatten the entire array. This function+  supports both.++  # Flattens the first `n` nested arrays. If n == -1, all arrays will be flattened+  #+  # @param arr [NodeCollection] The array to flatten+  # @param n [int] The number of levels to flatten+  # @return [NodeCollection] The flattened array+  */+  Node flatten(const Node& arr, Context& ctx, int n = -1);+++  /*+  This is the equivalent of ruby's Sass::Util.group_by_to_a.++  # Performs the equivalent of `enum.group_by.to_a`, but with a guaranteed+  # order. Unlike [#hash_to_a], the resulting order isn't sorted key order;+  # instead, it's the same order as `#group_by` has under Ruby 1.9 (key+  # appearance order).+  #+  # @param enum [Enumerable]+  # @return [Array<[Object, Array]>] An array of pairs.++  TODO: update @param and @return once I know what those are.++  The following is the modified version of the ruby code that was more portable to C++. You+  should be able to drop it into ruby 3.2.19 and get the same results from ruby sass.++    def group_by_to_a(enum, &block)+      order = {}++      arr = []++      grouped = {}++      for e in enum do+        key = block[e]+        unless order.include?(key)+          order[key] = order.size+        end++        if not grouped.has_key?(key) then+          grouped[key] = [e]+        else+          grouped[key].push(e)+        end+      end++      grouped.each do |key, vals|+        arr[order[key]] = [key, vals]+      end++      arr+    end++  */+  template<typename EnumType, typename KeyType, typename KeyFunctorType>+  void group_by_to_a(vector<EnumType>& enumeration, KeyFunctorType& keyFunc, vector<pair<KeyType, vector<EnumType> > >& arr /*out*/) {++    map<unsigned int, KeyType> order;++    map<KeyType, vector<EnumType> > grouped;++    for (typename vector<EnumType>::iterator enumIter = enumeration.begin(), enumIterEnd = enumeration.end(); enumIter != enumIterEnd; enumIter++) {+      EnumType& e = *enumIter;++      KeyType key = keyFunc(e);++      if (grouped.find(key) == grouped.end()) {+        order.insert(make_pair(order.size(), key));++        vector<EnumType> newCollection;+        newCollection.push_back(e);+        grouped.insert(make_pair(key, newCollection));+      } else {+        vector<EnumType>& collection = grouped.at(key);+        collection.push_back(e);+      }+    }++    for (unsigned int index = 0; index < order.size(); index++) {+      KeyType& key = order.at(index);+      vector<EnumType>& values = grouped.at(key);++      pair<KeyType, vector<EnumType> > grouping = make_pair(key, values);++      arr.push_back(grouping);+    }+  }+++}++#endif
+ libsass/sass_values.cpp view
@@ -0,0 +1,339 @@+#ifdef _WIN32+#include <io.h>+#else+#include <unistd.h>+#endif++#include <cstdlib>+#include <cstring>+#include "util.hpp"+#include "sass_values.h"++extern "C" {+  using namespace std;+  using namespace Sass;++  struct Sass_Unknown {+    enum Sass_Tag tag;+  };++  struct Sass_Boolean {+    enum Sass_Tag tag;+    bool          value;+  };++  struct Sass_Number {+    enum Sass_Tag tag;+    double        value;+    char*         unit;+  };++  struct Sass_Color {+    enum Sass_Tag tag;+    double        r;+    double        g;+    double        b;+    double        a;+  };++  struct Sass_String {+    enum Sass_Tag tag;+    char*         value;+  };++  struct Sass_List {+    enum Sass_Tag       tag;+    enum Sass_Separator separator;+    size_t              length;+    // null terminated "array"+    union Sass_Value**  values;+  };++  struct Sass_Map {+    enum Sass_Tag        tag;+    size_t               length;+    struct Sass_MapPair* pairs;+  };++  struct Sass_Null {+    enum Sass_Tag tag;+  };++  struct Sass_Error {+    enum Sass_Tag tag;+    char*         message;+  };++  struct Sass_Warning {+    enum Sass_Tag tag;+    char*         message;+  };++  union Sass_Value {+    struct Sass_Unknown unknown;+    struct Sass_Boolean boolean;+    struct Sass_Number  number;+    struct Sass_Color   color;+    struct Sass_String  string;+    struct Sass_List    list;+    struct Sass_Map     map;+    struct Sass_Null    null;+    struct Sass_Error   error;+    struct Sass_Warning   warning;+  };++  struct Sass_MapPair {+    union Sass_Value* key;+    union Sass_Value* value;+  };++  // Return the sass tag for a generic sass value+  enum Sass_Tag ADDCALL sass_value_get_tag(const union Sass_Value* v) { return v->unknown.tag; }++  // Check value for specified type+  bool ADDCALL sass_value_is_null(const union Sass_Value* v) { return v->unknown.tag == SASS_NULL; }+  bool ADDCALL sass_value_is_number(const union Sass_Value* v) { return v->unknown.tag == SASS_NUMBER; }+  bool ADDCALL sass_value_is_string(const union Sass_Value* v) { return v->unknown.tag == SASS_STRING; }+  bool ADDCALL sass_value_is_boolean(const union Sass_Value* v) { return v->unknown.tag == SASS_BOOLEAN; }+  bool ADDCALL sass_value_is_color(const union Sass_Value* v) { return v->unknown.tag == SASS_COLOR; }+  bool ADDCALL sass_value_is_list(const union Sass_Value* v) { return v->unknown.tag == SASS_LIST; }+  bool ADDCALL sass_value_is_map(const union Sass_Value* v) { return v->unknown.tag == SASS_MAP; }+  bool ADDCALL sass_value_is_error(const union Sass_Value* v) { return v->unknown.tag == SASS_ERROR; }+  bool ADDCALL sass_value_is_warning(const union Sass_Value* v) { return v->unknown.tag == SASS_WARNING; }++  // Getters and setters for Sass_Number+  double ADDCALL sass_number_get_value(const union Sass_Value* v) { return v->number.value; }+  void ADDCALL sass_number_set_value(union Sass_Value* v, double value) { v->number.value = value; }+  const char* ADDCALL sass_number_get_unit(const union Sass_Value* v) { return v->number.unit; }+  void ADDCALL sass_number_set_unit(union Sass_Value* v, char* unit) { v->number.unit = unit; }++  // Getters and setters for Sass_String+  const char* ADDCALL sass_string_get_value(const union Sass_Value* v) { return v->string.value; }+  void ADDCALL sass_string_set_value(union Sass_Value* v, char* value) { v->string.value = value; }++  // Getters and setters for Sass_Boolean+  bool ADDCALL sass_boolean_get_value(const union Sass_Value* v) { return v->boolean.value; }+  void ADDCALL sass_boolean_set_value(union Sass_Value* v, bool value) { v->boolean.value = value; }++  // Getters and setters for Sass_Color+  double ADDCALL sass_color_get_r(const union Sass_Value* v) { return v->color.r; }+  void ADDCALL sass_color_set_r(union Sass_Value* v, double r) { v->color.r = r; }+  double ADDCALL sass_color_get_g(const union Sass_Value* v) { return v->color.g; }+  void ADDCALL sass_color_set_g(union Sass_Value* v, double g) { v->color.g = g; }+  double ADDCALL sass_color_get_b(const union Sass_Value* v) { return v->color.b; }+  void ADDCALL sass_color_set_b(union Sass_Value* v, double b) { v->color.b = b; }+  double ADDCALL sass_color_get_a(const union Sass_Value* v) { return v->color.a; }+  void ADDCALL sass_color_set_a(union Sass_Value* v, double a) { v->color.a = a; }++  // Getters and setters for Sass_List+  size_t ADDCALL sass_list_get_length(const union Sass_Value* v) { return v->list.length; }+  enum Sass_Separator ADDCALL sass_list_get_separator(const union Sass_Value* v) { return v->list.separator; }+  void ADDCALL sass_list_set_separator(union Sass_Value* v, enum Sass_Separator separator) { v->list.separator = separator; }+  // Getters and setters for Sass_List values+  union Sass_Value* ADDCALL sass_list_get_value(const union Sass_Value* v, size_t i) { return v->list.values[i]; }+  void ADDCALL sass_list_set_value(union Sass_Value* v, size_t i, union Sass_Value* value) { v->list.values[i] = value; }++  // Getters and setters for Sass_Map+  size_t ADDCALL sass_map_get_length(const union Sass_Value* v) { return v->map.length; }+  // Getters and setters for Sass_List keys and values+  union Sass_Value* ADDCALL sass_map_get_key(const union Sass_Value* v, size_t i) { return v->map.pairs[i].key; }+  union Sass_Value* ADDCALL sass_map_get_value(const union Sass_Value* v, size_t i) { return v->map.pairs[i].value; }+  void ADDCALL sass_map_set_key(union Sass_Value* v, size_t i, union Sass_Value* key) { v->map.pairs[i].key = key; }+  void ADDCALL sass_map_set_value(union Sass_Value* v, size_t i, union Sass_Value* val) { v->map.pairs[i].value = val; }++  // Getters and setters for Sass_Error+  char* ADDCALL sass_error_get_message(const union Sass_Value* v) { return v->error.message; };+  void ADDCALL sass_error_set_message(union Sass_Value* v, char* msg) { v->error.message = msg; };++  // Getters and setters for Sass_Warning+  char* ADDCALL sass_warning_get_message(const union Sass_Value* v) { return v->warning.message; };+  void ADDCALL sass_warning_set_message(union Sass_Value* v, char* msg) { v->warning.message = msg; };++  // Creator functions for all value types++  union Sass_Value* ADDCALL sass_make_boolean(bool val)+  {+    Sass_Value* v = (Sass_Value*) calloc(1, sizeof(Sass_Value));+    if (v == 0) return 0;+    v->boolean.tag = SASS_BOOLEAN;+    v->boolean.value = val;+    return v;+  }++  union Sass_Value* ADDCALL sass_make_number(double val, const char* unit)+  {+    Sass_Value* v = (Sass_Value*) calloc(1, sizeof(Sass_Value));+    if (v == 0) return 0;+    v->number.tag = SASS_NUMBER;+    v->number.value = val;+    v->number.unit = unit ? sass_strdup(unit) : 0;+    if (v->number.unit == 0) { free(v); return 0; }+    return v;+  }++  union Sass_Value* ADDCALL sass_make_color(double r, double g, double b, double a)+  {+    Sass_Value* v = (Sass_Value*) calloc(1, sizeof(Sass_Value));+    if (v == 0) return 0;+    v->color.tag = SASS_COLOR;+    v->color.r = r;+    v->color.g = g;+    v->color.b = b;+    v->color.a = a;+    return v;+  }++  union Sass_Value* ADDCALL sass_make_string(const char* val)+  {+    Sass_Value* v = (Sass_Value*) calloc(1, sizeof(Sass_Value));+    if (v == 0) return 0;+    v->string.tag = SASS_STRING;+    v->string.value = val ? sass_strdup(val) : 0;+    if (v->string.value == 0) { free(v); return 0; }+    return v;+  }++  union Sass_Value* ADDCALL sass_make_list(size_t len, enum Sass_Separator sep)+  {+    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.values = (union Sass_Value**) calloc(len, sizeof(union Sass_Value));+    if (v->list.values == 0) { free(v); return 0; }+    return v;+  }++  union Sass_Value* ADDCALL sass_make_map(size_t len)+  {+    Sass_Value* v = (Sass_Value*) calloc(1, sizeof(Sass_Value));+    if (v == 0) return 0;+    v->map.tag = SASS_MAP;+    v->map.length = len;+    v->map.pairs = (struct Sass_MapPair*) calloc(len, sizeof(struct Sass_MapPair));+    if (v->map.pairs == 0) { free(v); return 0; }+    return v;+  }++  union Sass_Value* ADDCALL sass_make_null(void)+  {+    Sass_Value* v = (Sass_Value*) calloc(1, sizeof(Sass_Value));+    if (v == 0) return 0;+    v->null.tag = SASS_NULL;+    return v;+  }++  union Sass_Value* ADDCALL sass_make_error(const char* msg)+  {+    Sass_Value* v = (Sass_Value*) calloc(1, sizeof(Sass_Value));+    if (v == 0) return 0;+    v->error.tag = SASS_ERROR;+    v->error.message = msg ? sass_strdup(msg) : 0;+    if (v->error.message == 0) { free(v); return 0; }+    return v;+  }++  union Sass_Value* ADDCALL sass_make_warning(const char* msg)+  {+    Sass_Value* v = (Sass_Value*) calloc(1, sizeof(Sass_Value));+    if (v == 0) return 0;+    v->warning.tag = SASS_WARNING;+    v->warning.message = msg ? sass_strdup(msg) : 0;+    if (v->warning.message == 0) { free(v); return 0; }+    return v;+  }++  // will free all associated sass values+  void ADDCALL sass_delete_value(union Sass_Value* val) {++    size_t i;+    if (val == 0) return;+    switch(val->unknown.tag) {+        case SASS_NULL: {+        }   break;+        case SASS_BOOLEAN: {+        }   break;+        case SASS_NUMBER: {+                free(val->number.unit);+        }   break;+        case SASS_COLOR: {+        }   break;+        case SASS_STRING: {+                free(val->string.value);+        }   break;+        case SASS_LIST: {+                for (i=0; i<val->list.length; i++) {+                    sass_delete_value(val->list.values[i]);+                }+                free(val->list.values);+        }   break;+        case SASS_MAP: {+                for (i=0; i<val->map.length; i++) {+                    sass_delete_value(val->map.pairs[i].key);+                    sass_delete_value(val->map.pairs[i].value);+                }+                free(val->map.pairs);+        }   break;+        case SASS_ERROR: {+                free(val->error.message);+        }   break;+        case SASS_WARNING: {+                free(val->error.message);+        }   break;+    }++    free(val);++    }++  // Make a deep cloned copy of the given sass value+  union Sass_Value* ADDCALL sass_clone_value (const union Sass_Value* val)+  {++    size_t i;+    if (val == 0) return 0;+    switch(val->unknown.tag) {+        case SASS_NULL: {+                return sass_make_null();+        }   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_make_string(val->string.value);+        }   break;+        case SASS_LIST: {+                union Sass_Value* list = sass_make_list(val->list.length, val->list.separator);+                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++) {+                    map->map.pairs[i].key = sass_clone_value(val->map.pairs[i].key);+                    map->map.pairs[i].value = sass_clone_value(val->map.pairs[i].value);+                }+                return map;+        }   break;+        case SASS_ERROR: {+                return sass_make_error(val->error.message);+        }   break;+        case SASS_WARNING: {+                return sass_make_warning(val->warning.message);+        }   break;+    }++    return 0;++  }++}
+ libsass/sass_values.h view
@@ -0,0 +1,124 @@+#ifndef SASS_C_VALUES_H+#define SASS_C_VALUES_H++#include <stddef.h>+#include <stdbool.h>+#include "sass.h"++#ifdef __cplusplus+extern "C" {+#endif+++// Forward declaration+union Sass_Value;++// Type for Sass values+enum Sass_Tag {+  SASS_BOOLEAN,+  SASS_NUMBER,+  SASS_COLOR,+  SASS_STRING,+  SASS_LIST,+  SASS_MAP,+  SASS_NULL,+  SASS_ERROR,+  SASS_WARNING+};++// Tags for denoting Sass list separators+enum Sass_Separator {+  SASS_COMMA,+  SASS_SPACE+};++// Return the sass tag for a generic sass value+// Check is needed before accessing specific values!+ADDAPI enum Sass_Tag ADDCALL sass_value_get_tag (const union Sass_Value* v);++// Check value to be of a specific type+// Can also be used before accessing properties!+ADDAPI bool ADDCALL sass_value_is_null (const union Sass_Value* v);+ADDAPI bool ADDCALL sass_value_is_number (const union Sass_Value* v);+ADDAPI bool ADDCALL sass_value_is_string (const union Sass_Value* v);+ADDAPI bool ADDCALL sass_value_is_boolean (const union Sass_Value* v);+ADDAPI bool ADDCALL sass_value_is_color (const union Sass_Value* v);+ADDAPI bool ADDCALL sass_value_is_list (const union Sass_Value* v);+ADDAPI bool ADDCALL sass_value_is_map (const union Sass_Value* v);+ADDAPI bool ADDCALL sass_value_is_error (const union Sass_Value* v);+ADDAPI bool ADDCALL sass_value_is_warning (const union Sass_Value* v);++// Getters and setters for Sass_Number+ADDAPI double ADDCALL sass_number_get_value (const union Sass_Value* v);+ADDAPI void ADDCALL sass_number_set_value (union Sass_Value* v, double value);+ADDAPI const char* ADDCALL sass_number_get_unit (const union Sass_Value* v);+ADDAPI void ADDCALL sass_number_set_unit (union Sass_Value* v, char* unit);++// Getters and setters for Sass_String+ADDAPI const char* ADDCALL sass_string_get_value (const union Sass_Value* v);+ADDAPI void ADDCALL sass_string_set_value (union Sass_Value* v, char* value);++// Getters and setters for Sass_Boolean+ADDAPI bool ADDCALL sass_boolean_get_value (const union Sass_Value* v);+ADDAPI void ADDCALL sass_boolean_set_value (union Sass_Value* v, bool value);++// Getters and setters for Sass_Color+ADDAPI double ADDCALL sass_color_get_r (const union Sass_Value* v);+ADDAPI void ADDCALL sass_color_set_r (union Sass_Value* v, double r);+ADDAPI double ADDCALL sass_color_get_g (const union Sass_Value* v);+ADDAPI void ADDCALL sass_color_set_g (union Sass_Value* v, double g);+ADDAPI double ADDCALL sass_color_get_b (const union Sass_Value* v);+ADDAPI void ADDCALL sass_color_set_b (union Sass_Value* v, double b);+ADDAPI double ADDCALL sass_color_get_a (const union Sass_Value* v);+ADDAPI void ADDCALL sass_color_set_a (union Sass_Value* v, double a);++// Getter for the number of items in list+ADDAPI size_t ADDCALL sass_list_get_length (const union Sass_Value* v);+// Getters and setters for Sass_List+ADDAPI enum Sass_Separator ADDCALL sass_list_get_separator (const union Sass_Value* v);+ADDAPI void ADDCALL sass_list_set_separator (union Sass_Value* v, enum Sass_Separator value);+// Getters and setters for Sass_List values+ADDAPI union Sass_Value* ADDCALL sass_list_get_value (const union Sass_Value* v, size_t i);+ADDAPI void ADDCALL sass_list_set_value (union Sass_Value* v, size_t i, union Sass_Value* value);++// Getter for the number of items in map+ADDAPI size_t ADDCALL sass_map_get_length (const union Sass_Value* v);+// Getters and setters for Sass_Map keys and values+ADDAPI union Sass_Value* ADDCALL sass_map_get_key (const union Sass_Value* v, size_t i);+ADDAPI void ADDCALL sass_map_set_key (union Sass_Value* v, size_t i, union Sass_Value*);+ADDAPI union Sass_Value* ADDCALL sass_map_get_value (const union Sass_Value* v, size_t i);+ADDAPI void ADDCALL sass_map_set_value (union Sass_Value* v, size_t i, union Sass_Value*);++// Getters and setters for Sass_Error+ADDAPI char* ADDCALL sass_error_get_message (const union Sass_Value* v);+ADDAPI void ADDCALL sass_error_set_message (union Sass_Value* v, char* msg);++// Getters and setters for Sass_Warning+ADDAPI char* ADDCALL sass_warning_get_message (const union Sass_Value* v);+ADDAPI void ADDCALL sass_warning_set_message (union Sass_Value* v, char* msg);++// Creator functions for all value types+ADDAPI union Sass_Value* ADDCALL sass_make_null    (void);+ADDAPI union Sass_Value* ADDCALL sass_make_boolean (bool val);+ADDAPI union Sass_Value* ADDCALL sass_make_string  (const char* val);+ADDAPI union Sass_Value* ADDCALL sass_make_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_map     (size_t len);+ADDAPI union Sass_Value* ADDCALL sass_make_error   (const char* msg);+ADDAPI union Sass_Value* ADDCALL sass_make_warning (const char* msg);++// Generic destructor function for all types+// Will release memory of all associated Sass_Values+// Means we will delete recursively for lists and maps+ADDAPI void ADDCALL sass_delete_value (union Sass_Value* val);++// Make a deep cloned copy of the given sass value+ADDAPI union Sass_Value* ADDCALL sass_clone_value (const union Sass_Value* val);+++#ifdef __cplusplus+} // __cplusplus defined.+#endif++#endif
+ libsass/sass_version.h view
@@ -0,0 +1,8 @@+#ifndef SASS_VERSION_H+#define SASS_VERSION_H++#ifndef LIBSASS_VERSION+#define LIBSASS_VERSION "[NA]"+#endif++#endif
+ libsass/source_map.cpp view
@@ -0,0 +1,184 @@+#include <string>+#include <sstream>+#include <iostream>+#include <cstddef>+#include <iomanip>++#include "ast.hpp"+#include "json.hpp"+#include "context.hpp"+#include "position.hpp"+#include "source_map.hpp"++namespace Sass {+  using std::ptrdiff_t;+  SourceMap::SourceMap() : current_position(0, 0, 0), file("stdin") { }+  SourceMap::SourceMap(const string& file) : current_position(0, 0, 0), file(file) { }++  string SourceMap::generate_source_map(Context &ctx) {++    const bool include_sources = ctx.source_map_contents;+    const vector<string> includes = ctx.include_links;+    const vector<const char*> sources = ctx.sources;++    JsonNode* json_srcmap = json_mkobject();++    json_append_member(json_srcmap, "version", json_mknumber(3));++    // pass-through sourceRoot option+    if (!ctx.source_map_root.empty()) {+      JsonNode* root = json_mkstring(ctx.source_map_root.c_str());+      json_append_member(json_srcmap, "sourceRoot", root);+    }++    const char *include = file.c_str();+    JsonNode *json_include = json_mkstring(include);+    json_append_member(json_srcmap, "file", json_include);++    JsonNode *json_includes = json_mkarray();+    for (size_t i = 0; i < source_index.size(); ++i) {+      const char *include = includes[source_index[i]].c_str();+      JsonNode *json_include = json_mkstring(include);+      json_append_element(json_includes, json_include);+    }+    json_append_member(json_srcmap, "sources", json_includes);++    JsonNode *json_contents = json_mkarray();+    if (include_sources) {+      for (size_t i = 0; i < source_index.size(); ++i) {+        const char *content = sources[source_index[i]];+        JsonNode *json_content = json_mkstring(content);+        json_append_element(json_contents, json_content);+      }+    }+    json_append_member(json_srcmap, "sourcesContent", json_contents);++    string mappings = serialize_mappings();+    JsonNode *json_mappings = json_mkstring(mappings.c_str());+    json_append_member(json_srcmap, "mappings", json_mappings);++    JsonNode *json_names = json_mkarray();+    // so far we have no implementation for names+    // no problem as we do not alter any identifiers+    json_append_member(json_srcmap, "names", json_names);++    char *str = json_stringify(json_srcmap, "\t");+    string result = string(str);+    free(str);+    json_delete(json_srcmap);+    return result;+  }++  string SourceMap::serialize_mappings() {+    string result = "";++    size_t previous_generated_line = 0;+    size_t previous_generated_column = 0;+    size_t previous_original_line = 0;+    size_t previous_original_column = 0;+    size_t previous_original_file = 0;+    for (size_t i = 0; i < mappings.size(); ++i) {+      const size_t generated_line = mappings[i].generated_position.line;+      const size_t generated_column = mappings[i].generated_position.column;+      const size_t original_line = mappings[i].original_position.line;+      const size_t original_column = mappings[i].original_position.column;+      const size_t original_file = mappings[i].original_position.file;++      if (generated_line != previous_generated_line) {+        previous_generated_column = 0;+        if (generated_line > previous_generated_line) {+          result += std::string(generated_line - previous_generated_line, ';');+          previous_generated_line = generated_line;+        }+      }+      else if (i > 0) {+        result += ",";+      }++      // generated column+      result += base64vlq.encode(static_cast<int>(generated_column) - static_cast<int>(previous_generated_column));+      previous_generated_column = generated_column;+      // file+      result += base64vlq.encode(static_cast<int>(original_file) - static_cast<int>(previous_original_file));+      previous_original_file = original_file;+      // source line+      result += base64vlq.encode(static_cast<int>(original_line) - static_cast<int>(previous_original_line));+      previous_original_line = original_line;+      // source column+      result += base64vlq.encode(static_cast<int>(original_column) - static_cast<int>(previous_original_column));+      previous_original_column = original_column;+    }++    return result;+  }++  void SourceMap::prepend(const OutputBuffer& out)+  {+    Offset size(out.smap.current_position);+    for (Mapping mapping : out.smap.mappings) {+      if (mapping.generated_position.line > size.line) {+        throw(runtime_error("prepend sourcemap has illegal line"));+      }+      if (mapping.generated_position.line == size.line) {+        if (mapping.generated_position.column > size.column) {+          throw(runtime_error("prepend sourcemap has illegal column"));+        }+      }+    }+    // will adjust the offset+    prepend(Offset(out.buffer));+    // now add the new mappings+    VECTOR_UNSHIFT(mappings, out.smap.mappings);+  }++  void SourceMap::append(const OutputBuffer& out)+  {+    append(Offset(out.buffer));+  }++  void SourceMap::prepend(const Offset& offset)+  {+    if (offset.line != 0 || offset.column != 0) {+      for (Mapping& mapping : mappings) {+        // move stuff on the first old line+        if (mapping.generated_position.line == 0) {+          mapping.generated_position.column += offset.column;+        }+        // make place for the new lines+        mapping.generated_position.line += offset.line;+      }+    }+    if (current_position.line == 0) {+      current_position.column += offset.column;+    }+    current_position.line += offset.line;+  }++  void SourceMap::append(const Offset& offset)+  {+    current_position += offset;+  }++  void SourceMap::add_open_mapping(AST_Node* node)+  {+    mappings.push_back(Mapping(node->pstate(), current_position));+  }++  void SourceMap::add_close_mapping(AST_Node* node)+  {+    mappings.push_back(Mapping(node->pstate() + node->pstate().offset, current_position));+  }++  ParserState SourceMap::remap(const ParserState& pstate) {+    for (size_t i = 0; i < mappings.size(); ++i) {+      if (+        mappings[i].generated_position.file == pstate.file &&+        mappings[i].generated_position.line == pstate.line &&+        mappings[i].generated_position.column == pstate.column+      ) return ParserState(pstate.path, pstate.src, mappings[i].original_position, pstate.offset);+    }+    return ParserState(pstate.path, pstate.src, Position(-1, -1, -1), Offset(0, 0));++  }++}
+ libsass/source_map.hpp view
@@ -0,0 +1,65 @@+#ifndef SASS_SOURCE_MAP_H+#define SASS_SOURCE_MAP_H++#include <vector>++#include "ast_fwd_decl.hpp"+#include "base64vlq.hpp"+#include "position.hpp"+#include "mapping.hpp"++#define VECTOR_PUSH(vec, ins) vec.insert(vec.end(), ins.begin(), ins.end())+#define VECTOR_UNSHIFT(vec, ins) vec.insert(vec.begin(), ins.begin(), ins.end())++namespace Sass {+  using std::vector;++  class Context;+  class OutputBuffer;++  class SourceMap {++  public:+    vector<size_t> source_index;+    SourceMap();+    SourceMap(const string& file);++    void setFile(const string& str) {+      file = str;+    }+    void append(const Offset& offset);+    void prepend(const Offset& offset);+    void append(const OutputBuffer& out);+    void prepend(const OutputBuffer& out);+    void add_open_mapping(AST_Node* node);+    void add_close_mapping(AST_Node* node);++    string generate_source_map(Context &ctx);+    ParserState remap(const ParserState& pstate);++  private:++    string serialize_mappings();++    vector<Mapping> mappings;+    Position current_position;+public:+    string file;+private:+    Base64VLQ base64vlq;+  };++  class OutputBuffer {+    public:+      OutputBuffer(void)+      : buffer(""),+        smap()+      { }+    public:+      string buffer;+      SourceMap smap;+  };++}++#endif
+ libsass/subset_map.hpp view
@@ -0,0 +1,147 @@+#ifndef SASS_SUBSET_MAP_H+#define SASS_SUBSET_MAP_H++#include <map>+#include <set>+#include <vector>+#include <algorithm>+#include <iterator>+#include <iostream>+#include <sstream>++// using namespace std;++// template<typename T>+// string vector_to_string(vector<T> v)+// {+//   stringstream buffer;+//   buffer << "[";++//   if (!v.empty())+//   {  buffer << v[0]; }+//   else+//   { buffer << "]"; }++//   if (v.size() == 1)+//   { buffer << "]"; }+//   else+//   {+//     for (size_t i = 1, S = v.size(); i < S; ++i) buffer << ", " << v[i];+//     buffer << "]";+//   }++//   return buffer.str();+// }++// template<typename T>+// string set_to_string(set<T> v)+// {+//   stringstream buffer;+//   buffer << "[";+//   typename set<T>::iterator i = v.begin();+//   if (!v.empty())+//   {  buffer << *i; }+//   else+//   { buffer << "]"; }++//   if (v.size() == 1)+//   { buffer << "]"; }+//   else+//   {+//     for (++i; i != v.end(); ++i) buffer << ", " << *i;+//     buffer << "]";+//   }++//   return buffer.str();+// }++namespace Sass {+  using namespace std;++  template<typename F, typename S, typename T>+  struct triple {+    F first;+    S second;+    T third;++    triple(const F& f, const S& s, const T& t) : first(f), second(s), third(t) { }+  };++  template<typename F, typename S, typename T>+  triple<F, S, T> make_triple(const F& f, const S& s, const T& t)+  { return triple<F, S, T>(f, s, t); }++  template<typename K, typename V>+  class Subset_Map {+  private:+    vector<V> values_;+    map<K, vector<triple<vector<K>, set<K>, size_t> > > hash_;+  public:+    void put(const vector<K>& s, const V& value);+    vector<pair<V, vector<K> > > get_kv(const vector<K>& s);+    vector<V> get_v(const vector<K>& s);+    bool empty() { return values_.empty(); }+    void clear() { values_.clear(); hash_.clear(); }+    const vector<V> values(void) { return values_; }+  };++  template<typename K, typename V>+  void Subset_Map<K, V>::put(const vector<K>& s, const V& value)+  {+    if (s.empty()) throw "internal error: subset map keys may not be empty";+    size_t index = values_.size();+    values_.push_back(value);+    set<K> ss;+    for (size_t i = 0, S = s.size(); i < S; ++i)+    { ss.insert(s[i]); }+    for (size_t i = 0, S = s.size(); i < S; ++i)+    {+      hash_[s[i]];+      hash_[s[i]].push_back(make_triple(s, ss, index));+    }+  }++  template<typename K, typename V>+  vector<pair<V, vector<K> > > Subset_Map<K, V>::get_kv(const vector<K>& s)+  {+    vector<K> sorted = s;+    sort(sorted.begin(), sorted.end());+    vector<pair<size_t, vector<K> > > indices;+    for (size_t i = 0, S = s.size(); i < S; ++i) {+      if (!hash_.count(s[i])) {+        continue;+      }+      vector<triple<vector<K>, set<K>, size_t> > subsets = hash_[s[i]];+      // cerr << "length of subsets: " << subsets.size() << endl;+      for (size_t j = 0, T = subsets.size(); j < T; ++j) {+        if (!includes(sorted.begin(), sorted.end(), subsets[j].second.begin(), subsets[j].second.end())) {+          // cout << vector_to_string(s) << " doesn't include " << set_to_string(subsets[j].second) << endl;+          continue;+        }+        indices.push_back(make_pair(subsets[j].third, subsets[j].first));+        // cerr << "pushed " << subsets[j].third << " and " << vector_to_string(subsets[j].first) << " onto indices" << endl;+      }+    }+    sort(indices.begin(), indices.end());+    typename vector<pair<size_t, vector<K> > >::iterator indices_end = unique(indices.begin(), indices.end());+    indices.resize(distance(indices.begin(), indices_end));++    vector<pair<V, vector<K> > > results;+    for (size_t i = 0, S = indices.size(); i < S; ++i) {+      results.push_back(make_pair(values_[indices[i].first], indices[i].second));+    }+    return results;+  }++  template<typename K, typename V>+  vector<V> Subset_Map<K, V>::get_v(const vector<K>& s)+  {+    vector<pair<V, vector<K> > > kvs = get_kv(s);+    vector<V> results;+    for (size_t i = 0, S = kvs.size(); i < S; ++i) results.push_back(kvs[i].first);+    return results;+  }++}++#endif
+ libsass/to_c.cpp view
@@ -0,0 +1,61 @@+#include "to_c.hpp"+#include "ast.hpp"++#include "sass_values.h"++namespace Sass {+  using namespace std;++  Sass_Value* To_C::fallback_impl(AST_Node* n)+  { return sass_make_null(); }++  Sass_Value* To_C::operator()(Boolean* b)+  { return sass_make_boolean(b->value()); }++  Sass_Value* To_C::operator()(Number* n)+  { return sass_make_number(n->value(), n->unit().c_str()); }++  Sass_Value* To_C::operator()(Color* c)+  { return sass_make_color(c->r(), c->g(), c->b(), c->a()); }++  Sass_Value* To_C::operator()(String_Constant* s)+  { return sass_make_string(s->value().c_str()); }++  Sass_Value* To_C::operator()(List* l)+  {+    Sass_Value* v = sass_make_list(l->length(), l->separator() == List::COMMA ? SASS_COMMA : SASS_SPACE);+    for (size_t i = 0, L = l->length(); i < L; ++i) {+      sass_list_set_value(v, i, (*l)[i]->perform(this));+    }+    return v;+  }++  Sass_Value* To_C::operator()(Map* m)+  {+    Sass_Value* v = sass_make_map(m->length());+    int i = 0;+    for (auto key : m->keys()) {+      sass_map_set_key(v, i, key->perform(this));+      sass_map_set_value(v, i, m->at(key)->perform(this));+      i++;+    }+    return v;+  }++  Sass_Value* To_C::operator()(Arguments* a)+  {+    Sass_Value* v = sass_make_list(a->length(), SASS_COMMA);+    for (size_t i = 0, L = a->length(); i < L; ++i) {+      sass_list_set_value(v, i, (*a)[i]->perform(this));+    }+    return v;+  }++  Sass_Value* To_C::operator()(Argument* a)+  { return a->value()->perform(this); }++  // not strictly necessary because of the fallback+  Sass_Value* To_C::operator()(Null* n)+  { return sass_make_null(); }++};
+ libsass/to_c.hpp view
@@ -0,0 +1,44 @@+#ifndef SASS_TO_C_H+#define SASS_TO_C_H++#include "operation.hpp"+#include "sass_values.h"++namespace Sass {+  using namespace std;++  class AST_Node;+  class Boolean;+  class Number;+  class String_Constant;+  class List;+  class Map;+  class Null;++  class To_C : public Operation_CRTP<Sass_Value*, To_C> {++    Sass_Value* fallback_impl(AST_Node* n);++  public:++    To_C() { }+    virtual ~To_C() { }+    using Operation<Sass_Value*>::operator();++    Sass_Value* operator()(Boolean*);+    Sass_Value* operator()(Number*);+    Sass_Value* operator()(Color*);+    Sass_Value* operator()(String_Constant*);+    Sass_Value* operator()(List*);+    Sass_Value* operator()(Map*);+    Sass_Value* operator()(Null*);+    Sass_Value* operator()(Arguments*);+    Sass_Value* operator()(Argument*);++    template <typename U>+    Sass_Value* fallback(U x) { return fallback_impl(x); }+  };++}++#endif
+ libsass/to_string.cpp view
@@ -0,0 +1,34 @@+#include <cmath>+#include <sstream>+#include <iomanip>+#include <iostream>++#include "ast.hpp"+#include "inspect.hpp"+#include "context.hpp"+#include "to_string.hpp"++namespace Sass {+  using namespace std;++  To_String::To_String(Context* ctx)+  : ctx(ctx) { }+  To_String::~To_String() { }++  inline string To_String::fallback_impl(AST_Node* n)+  {+    Emitter emitter(ctx);+    Inspect i(emitter);+    i.in_declaration_list = true;+    n->perform(&i);+    return i.get_buffer();+  }++  inline string To_String::operator()(String_Constant* s)+  {+    return s->value();+  }++  inline string To_String::operator()(Null* n)+  { return ""; }+}
+ libsass/to_string.hpp view
@@ -0,0 +1,34 @@+#ifndef SASS_TO_STRING_H+#define SASS_TO_STRING_H++#include <string>++#include "operation.hpp"++namespace Sass {+  using namespace std;++  class Context;+  class Null;++  class To_String : public Operation_CRTP<string, To_String> {+    // import all the class-specific methods and override as desired+    using Operation<string>::operator();+    // override this to define a catch-all+    string fallback_impl(AST_Node* n);++    Context* ctx;++  public:+    To_String(Context* ctx = 0);+    virtual ~To_String();++    string operator()(Null* n);+    string operator()(String_Constant*);++    template <typename U>+    string fallback(U n) { return fallback_impl(n); }+  };+}++#endif
+ libsass/units.cpp view
@@ -0,0 +1,55 @@+#include "units.hpp"++#define PI 3.14159265358979323846++namespace Sass {++  double conversion_factors[10][10] = {+             /*  in         cm         pc         mm         pt         px                 deg        grad       rad        turn      */+    /* in   */ { 1,         2.54,      6,         25.4,      72,        96,                1,         1,         1,         1         },+    /* cm   */ { 1.0/2.54,  1,         6.0/2.54,  10,        72.0/2.54, 96.0/2.54,         1,         1,         1,         1         },+    /* pc   */ { 1.0/6.0,   2.54/6.0,  1,         25.4/6.0,  72.0/6.0,  96.0/6.0,          1,         1,         1,         1         },+    /* mm   */ { 1.0/25.4,  1.0/10.0,  6.0/25.4,  1,         72.0/25.4, 96.0/25.4,         1,         1,         1,         1         },+    /* pt   */ { 1.0/72.0,  2.54/72.0, 6.0/72.0,  25.4/72.0, 1,         96.0/72.0,         1,         1,         1,         1         },+    /* px   */ { 1.0/96.0,  2.54/96.0, 6.0/96.0,  25.4/96.0, 72.0/96.0, 1,                 1,         1,         1,         1         },+    /* deg  */ { 1       ,  1        , 1       ,  1        , 1        , 1,                 1,         40.0/36.0, PI/180.0,  1.0/360.0 },+    /* grad */ { 1       ,  1        , 1       ,  1        , 1        , 1,                 36.0/40.0, 1,         PI/200.0,  1.0/400.0 },+    /* rad  */ { 1       ,  1        , 1       ,  1        , 1        , 1,                 180.0/PI,  200.0/PI,  1,         PI/2.0    },+    /* turn */ { 1       ,  1        , 1       ,  1        , 1        , 1,                 360.0/1.0, 400.0/1.0, 2.0*PI,    1         }+  };++  Unit string_to_unit(const string& s)+  {+    if      (s == "in") return IN;+    else if (s == "cm") return CM;+    else if (s == "pc") return PC;+    else if (s == "mm") return MM;+    else if (s == "pt") return PT;+    else if (s == "px") return PX;+    else if (s == "deg") return DEG;+    else if (s == "grad") return GRAD;+    else if (s == "rad") return RAD;+    else if (s == "turn") return TURN;+    else                return INCOMMENSURABLE;+  }++  double conversion_factor(const string& s1, const string& s2)+  {+    Unit u1 = string_to_unit(s1);+    Unit u2 = string_to_unit(s2);+    double factor;+    if (u1 == INCOMMENSURABLE || u2 == INCOMMENSURABLE)+      factor = 0;+    else+      factor = conversion_factors[u1][u2];+    return factor;+  }++  /* not used anymore - remove?+  double convert(double n, const string& from, const string& to)+  {+    double factor = conversion_factor(from, to);+    return factor ? factor * n : n;+  } */++}
+ libsass/units.hpp view
@@ -0,0 +1,15 @@+#ifndef SASS_UNITS_H+#define SASS_UNITS_H++#include <string>++namespace Sass {+  using namespace std;+  enum Unit { IN, CM, PC, MM, PT, PX, DEG, GRAD, RAD, TURN, INCOMMENSURABLE };+  extern double conversion_factors[10][10];+  Unit string_to_unit(const string&);+  double conversion_factor(const string&, const string&);+  // double convert(double, const string&, const string&);+}++#endif
+ libsass/utf8.h view
@@ -0,0 +1,34 @@+// Copyright 2006 Nemanja Trifunovic++/*+Permission is hereby granted, free of charge, to any person or organization+obtaining a copy of the software and accompanying documentation covered by+this license (the "Software") to use, reproduce, display, distribute,+execute, and transmit the Software, and to prepare derivative works of the+Software, and to permit third-parties to whom the Software is furnished to+do so, all subject to the following:++The copyright notices in the Software and this entire statement, including+the above license grant, this restriction and the following disclaimer,+must be included in all copies of the Software, in whole or in part, and+all derivative works of the Software, unless such copies or derivative+works are solely in the form of machine-executable object code generated by+a source language processor.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT+SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE+FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER+DEALINGS IN THE SOFTWARE.+*/+++#ifndef UTF8_FOR_CPP_2675DCD0_9480_4c0c_B92A_CC14C027B731+#define UTF8_FOR_CPP_2675DCD0_9480_4c0c_B92A_CC14C027B731++#include "utf8/checked.h"+#include "utf8/unchecked.h"++#endif // header guard
+ libsass/utf8/checked.h view
@@ -0,0 +1,327 @@+// Copyright 2006 Nemanja Trifunovic++/*+Permission is hereby granted, free of charge, to any person or organization+obtaining a copy of the software and accompanying documentation covered by+this license (the "Software") to use, reproduce, display, distribute,+execute, and transmit the Software, and to prepare derivative works of the+Software, and to permit third-parties to whom the Software is furnished to+do so, all subject to the following:++The copyright notices in the Software and this entire statement, including+the above license grant, this restriction and the following disclaimer,+must be included in all copies of the Software, in whole or in part, and+all derivative works of the Software, unless such copies or derivative+works are solely in the form of machine-executable object code generated by+a source language processor.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT+SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE+FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER+DEALINGS IN THE SOFTWARE.+*/+++#ifndef UTF8_FOR_CPP_CHECKED_H_2675DCD0_9480_4c0c_B92A_CC14C027B731+#define UTF8_FOR_CPP_CHECKED_H_2675DCD0_9480_4c0c_B92A_CC14C027B731++#include "core.h"+#include <stdexcept>++namespace utf8+{+    // Base for the exceptions that may be thrown from the library+    class exception : public ::std::exception {+    };++    // Exceptions that may be thrown from the library functions.+    class invalid_code_point : public exception {+        uint32_t cp;+    public:+        invalid_code_point(uint32_t cp) : cp(cp) {}+        virtual const char* what() const throw() { return "Invalid code point"; }+        uint32_t code_point() const {return cp;}+    };++    class invalid_utf8 : public exception {+        uint8_t u8;+    public:+        invalid_utf8 (uint8_t u) : u8(u) {}+        virtual const char* what() const throw() { return "Invalid UTF-8"; }+        uint8_t utf8_octet() const {return u8;}+    };++    class invalid_utf16 : public exception {+        uint16_t u16;+    public:+        invalid_utf16 (uint16_t u) : u16(u) {}+        virtual const char* what() const throw() { return "Invalid UTF-16"; }+        uint16_t utf16_word() const {return u16;}+    };++    class not_enough_room : public exception {+    public:+        virtual const char* what() const throw() { return "Not enough space"; }+    };++    /// The library API - functions intended to be called by the users++    template <typename octet_iterator>+    octet_iterator append(uint32_t cp, octet_iterator result)+    {+        if (!utf8::internal::is_code_point_valid(cp))+            throw invalid_code_point(cp);++        if (cp < 0x80)                        // one octet+            *(result++) = static_cast<uint8_t>(cp);+        else if (cp < 0x800) {                // two octets+            *(result++) = static_cast<uint8_t>((cp >> 6)            | 0xc0);+            *(result++) = static_cast<uint8_t>((cp & 0x3f)          | 0x80);+        }+        else if (cp < 0x10000) {              // three octets+            *(result++) = static_cast<uint8_t>((cp >> 12)           | 0xe0);+            *(result++) = static_cast<uint8_t>(((cp >> 6) & 0x3f)   | 0x80);+            *(result++) = static_cast<uint8_t>((cp & 0x3f)          | 0x80);+        }+        else {                                // four octets+            *(result++) = static_cast<uint8_t>((cp >> 18)           | 0xf0);+            *(result++) = static_cast<uint8_t>(((cp >> 12) & 0x3f)  | 0x80);+            *(result++) = static_cast<uint8_t>(((cp >> 6) & 0x3f)   | 0x80);+            *(result++) = static_cast<uint8_t>((cp & 0x3f)          | 0x80);+        }+        return result;+    }++    template <typename octet_iterator, typename output_iterator>+    output_iterator replace_invalid(octet_iterator start, octet_iterator end, output_iterator out, uint32_t replacement)+    {+        while (start != end) {+            octet_iterator sequence_start = start;+            internal::utf_error err_code = utf8::internal::validate_next(start, end);+            switch (err_code) {+                case internal::UTF8_OK :+                    for (octet_iterator it = sequence_start; it != start; ++it)+                        *out++ = *it;+                    break;+                case internal::NOT_ENOUGH_ROOM:+                    throw not_enough_room();+                case internal::INVALID_LEAD:+                    out = utf8::append (replacement, out);+                    ++start;+                    break;+                case internal::INCOMPLETE_SEQUENCE:+                case internal::OVERLONG_SEQUENCE:+                case internal::INVALID_CODE_POINT:+                    out = utf8::append (replacement, out);+                    ++start;+                    // just one replacement mark for the sequence+                    while (start != end && utf8::internal::is_trail(*start))+                        ++start;+                    break;+            }+        }+        return out;+    }++    template <typename octet_iterator, typename output_iterator>+    inline output_iterator replace_invalid(octet_iterator start, octet_iterator end, output_iterator out)+    {+        static const uint32_t replacement_marker = utf8::internal::mask16(0xfffd);+        return utf8::replace_invalid(start, end, out, replacement_marker);+    }++    template <typename octet_iterator>+    uint32_t next(octet_iterator& it, octet_iterator end)+    {+        uint32_t cp = 0;+        internal::utf_error err_code = utf8::internal::validate_next(it, end, cp);+        switch (err_code) {+            case internal::UTF8_OK :+                break;+            case internal::NOT_ENOUGH_ROOM :+                throw not_enough_room();+            case internal::INVALID_LEAD :+            case internal::INCOMPLETE_SEQUENCE :+            case internal::OVERLONG_SEQUENCE :+                throw invalid_utf8(*it);+            case internal::INVALID_CODE_POINT :+                throw invalid_code_point(cp);+        }+        return cp;+    }++    template <typename octet_iterator>+    uint32_t peek_next(octet_iterator it, octet_iterator end)+    {+        return utf8::next(it, end);+    }++    template <typename octet_iterator>+    uint32_t prior(octet_iterator& it, octet_iterator start)+    {+        // can't do much if it == start+        if (it == start)+            throw not_enough_room();++        octet_iterator end = it;+        // Go back until we hit either a lead octet or start+        while (utf8::internal::is_trail(*(--it)))+            if (it == start)+                throw invalid_utf8(*it); // error - no lead byte in the sequence+        return utf8::peek_next(it, end);+    }++    /// Deprecated in versions that include "prior"+    template <typename octet_iterator>+    uint32_t previous(octet_iterator& it, octet_iterator pass_start)+    {+        octet_iterator end = it;+        while (utf8::internal::is_trail(*(--it)))+            if (it == pass_start)+                throw invalid_utf8(*it); // error - no lead byte in the sequence+        octet_iterator temp = it;+        return utf8::next(temp, end);+    }++    template <typename octet_iterator, typename distance_type>+    void advance (octet_iterator& it, distance_type n, octet_iterator end)+    {+        for (distance_type i = 0; i < n; ++i)+            utf8::next(it, end);+    }++    template <typename octet_iterator>+    typename std::iterator_traits<octet_iterator>::difference_type+    distance (octet_iterator first, octet_iterator last)+    {+        typename std::iterator_traits<octet_iterator>::difference_type dist;+        for (dist = 0; first < last; ++dist)+            utf8::next(first, last);+        return dist;+    }++    template <typename u16bit_iterator, typename octet_iterator>+    octet_iterator utf16to8 (u16bit_iterator start, u16bit_iterator end, octet_iterator result)+    {+        while (start != end) {+            uint32_t cp = utf8::internal::mask16(*start++);+            // Take care of surrogate pairs first+            if (utf8::internal::is_lead_surrogate(cp)) {+                if (start != end) {+                    uint32_t trail_surrogate = utf8::internal::mask16(*start++);+                    if (utf8::internal::is_trail_surrogate(trail_surrogate))+                        cp = (cp << 10) + trail_surrogate + internal::SURROGATE_OFFSET;+                    else+                        throw invalid_utf16(static_cast<uint16_t>(trail_surrogate));+                }+                else+                    throw invalid_utf16(static_cast<uint16_t>(cp));++            }+            // Lone trail surrogate+            else if (utf8::internal::is_trail_surrogate(cp))+                throw invalid_utf16(static_cast<uint16_t>(cp));++            result = utf8::append(cp, result);+        }+        return result;+    }++    template <typename u16bit_iterator, typename octet_iterator>+    u16bit_iterator utf8to16 (octet_iterator start, octet_iterator end, u16bit_iterator result)+    {+        while (start != end) {+            uint32_t cp = utf8::next(start, end);+            if (cp > 0xffff) { //make a surrogate pair+                *result++ = static_cast<uint16_t>((cp >> 10)   + internal::LEAD_OFFSET);+                *result++ = static_cast<uint16_t>((cp & 0x3ff) + internal::TRAIL_SURROGATE_MIN);+            }+            else+                *result++ = static_cast<uint16_t>(cp);+        }+        return result;+    }++    template <typename octet_iterator, typename u32bit_iterator>+    octet_iterator utf32to8 (u32bit_iterator start, u32bit_iterator end, octet_iterator result)+    {+        while (start != end)+            result = utf8::append(*(start++), result);++        return result;+    }++    template <typename octet_iterator, typename u32bit_iterator>+    u32bit_iterator utf8to32 (octet_iterator start, octet_iterator end, u32bit_iterator result)+    {+        while (start != end)+            (*result++) = utf8::next(start, end);++        return result;+    }++    // The iterator class+    template <typename octet_iterator>+    class iterator : public std::iterator <std::bidirectional_iterator_tag, uint32_t> {+      octet_iterator it;+      octet_iterator range_start;+      octet_iterator range_end;+      public:+      iterator () {}+      explicit iterator (const octet_iterator& octet_it,+                         const octet_iterator& range_start,+                         const octet_iterator& range_end) :+               it(octet_it), range_start(range_start), range_end(range_end)+      {+          if (it < range_start || it > range_end)+              throw std::out_of_range("Invalid utf-8 iterator position");+      }+      // the default "big three" are OK+      octet_iterator base () const { return it; }+      uint32_t operator * () const+      {+          octet_iterator temp = it;+          return utf8::next(temp, range_end);+      }+      bool operator == (const iterator& rhs) const+      {+          if (range_start != rhs.range_start || range_end != rhs.range_end)+              throw std::logic_error("Comparing utf-8 iterators defined with different ranges");+          return (it == rhs.it);+      }+      bool operator != (const iterator& rhs) const+      {+          return !(operator == (rhs));+      }+      iterator& operator ++ ()+      {+          utf8::next(it, range_end);+          return *this;+      }+      iterator operator ++ (int)+      {+          iterator temp = *this;+          utf8::next(it, range_end);+          return temp;+      }+      iterator& operator -- ()+      {+          utf8::prior(it, range_start);+          return *this;+      }+      iterator operator -- (int)+      {+          iterator temp = *this;+          utf8::prior(it, range_start);+          return temp;+      }+    }; // class iterator++} // namespace utf8++#endif //header guard++
+ libsass/utf8/core.h view
@@ -0,0 +1,329 @@+// Copyright 2006 Nemanja Trifunovic++/*+Permission is hereby granted, free of charge, to any person or organization+obtaining a copy of the software and accompanying documentation covered by+this license (the "Software") to use, reproduce, display, distribute,+execute, and transmit the Software, and to prepare derivative works of the+Software, and to permit third-parties to whom the Software is furnished to+do so, all subject to the following:++The copyright notices in the Software and this entire statement, including+the above license grant, this restriction and the following disclaimer,+must be included in all copies of the Software, in whole or in part, and+all derivative works of the Software, unless such copies or derivative+works are solely in the form of machine-executable object code generated by+a source language processor.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT+SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE+FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER+DEALINGS IN THE SOFTWARE.+*/+++#ifndef UTF8_FOR_CPP_CORE_H_2675DCD0_9480_4c0c_B92A_CC14C027B731+#define UTF8_FOR_CPP_CORE_H_2675DCD0_9480_4c0c_B92A_CC14C027B731++#include <iterator>++namespace utf8+{+    // The typedefs for 8-bit, 16-bit and 32-bit unsigned integers+    // You may need to change them to match your system.+    // These typedefs have the same names as ones from cstdint, or boost/cstdint+    typedef unsigned char   uint8_t;+    typedef unsigned short  uint16_t;+    typedef unsigned int    uint32_t;++// Helper code - not intended to be directly called by the library users. May be changed at any time+namespace internal+{+    // Unicode constants+    // Leading (high) surrogates: 0xd800 - 0xdbff+    // Trailing (low) surrogates: 0xdc00 - 0xdfff+    const uint16_t LEAD_SURROGATE_MIN  = 0xd800u;+    const uint16_t LEAD_SURROGATE_MAX  = 0xdbffu;+    const uint16_t TRAIL_SURROGATE_MIN = 0xdc00u;+    const uint16_t TRAIL_SURROGATE_MAX = 0xdfffu;+    const uint16_t LEAD_OFFSET         = LEAD_SURROGATE_MIN - (0x10000 >> 10);+    const uint32_t SURROGATE_OFFSET    = 0x10000u - (LEAD_SURROGATE_MIN << 10) - TRAIL_SURROGATE_MIN;++    // Maximum valid value for a Unicode code point+    const uint32_t CODE_POINT_MAX      = 0x0010ffffu;++    template<typename octet_type>+    inline uint8_t mask8(octet_type oc)+    {+        return static_cast<uint8_t>(0xff & oc);+    }+    template<typename u16_type>+    inline uint16_t mask16(u16_type oc)+    {+        return static_cast<uint16_t>(0xffff & oc);+    }+    template<typename octet_type>+    inline bool is_trail(octet_type oc)+    {+        return ((utf8::internal::mask8(oc) >> 6) == 0x2);+    }++    template <typename u16>+    inline bool is_lead_surrogate(u16 cp)+    {+        return (cp >= LEAD_SURROGATE_MIN && cp <= LEAD_SURROGATE_MAX);+    }++    template <typename u16>+    inline bool is_trail_surrogate(u16 cp)+    {+        return (cp >= TRAIL_SURROGATE_MIN && cp <= TRAIL_SURROGATE_MAX);+    }++    template <typename u16>+    inline bool is_surrogate(u16 cp)+    {+        return (cp >= LEAD_SURROGATE_MIN && cp <= TRAIL_SURROGATE_MAX);+    }++    template <typename u32>+    inline bool is_code_point_valid(u32 cp)+    {+        return (cp <= CODE_POINT_MAX && !utf8::internal::is_surrogate(cp));+    }++    template <typename octet_iterator>+    inline typename std::iterator_traits<octet_iterator>::difference_type+    sequence_length(octet_iterator lead_it)+    {+        uint8_t lead = utf8::internal::mask8(*lead_it);+        if (lead < 0x80)+            return 1;+        else if ((lead >> 5) == 0x6)+            return 2;+        else if ((lead >> 4) == 0xe)+            return 3;+        else if ((lead >> 3) == 0x1e)+            return 4;+        else+            return 0;+    }++    template <typename octet_difference_type>+    inline bool is_overlong_sequence(uint32_t cp, octet_difference_type length)+    {+        if (cp < 0x80) {+            if (length != 1) +                return true;+        }+        else if (cp < 0x800) {+            if (length != 2) +                return true;+        }+        else if (cp < 0x10000) {+            if (length != 3) +                return true;+        }++        return false;+    }++    enum utf_error {UTF8_OK, NOT_ENOUGH_ROOM, INVALID_LEAD, INCOMPLETE_SEQUENCE, OVERLONG_SEQUENCE, INVALID_CODE_POINT};++    /// Helper for get_sequence_x+    template <typename octet_iterator>+    utf_error increase_safely(octet_iterator& it, octet_iterator end)+    {+        if (++it == end)+            return NOT_ENOUGH_ROOM;++        if (!utf8::internal::is_trail(*it))+            return INCOMPLETE_SEQUENCE;+        +        return UTF8_OK;+    }++    #define UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(IT, END) {utf_error ret = increase_safely(IT, END); if (ret != UTF8_OK) return ret;}    ++    /// get_sequence_x functions decode utf-8 sequences of the length x+    template <typename octet_iterator>+    utf_error get_sequence_1(octet_iterator& it, octet_iterator end, uint32_t& code_point)+    {+        if (it == end)+            return NOT_ENOUGH_ROOM;++        code_point = utf8::internal::mask8(*it);++        return UTF8_OK;+    }++    template <typename octet_iterator>+    utf_error get_sequence_2(octet_iterator& it, octet_iterator end, uint32_t& code_point)+    {+        if (it == end) +            return NOT_ENOUGH_ROOM;+        +        code_point = utf8::internal::mask8(*it);++        UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end)++        code_point = ((code_point << 6) & 0x7ff) + ((*it) & 0x3f);++        return UTF8_OK;+    }++    template <typename octet_iterator>+    utf_error get_sequence_3(octet_iterator& it, octet_iterator end, uint32_t& code_point)+    {+        if (it == end)+            return NOT_ENOUGH_ROOM;+            +        code_point = utf8::internal::mask8(*it);++        UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end)++        code_point = ((code_point << 12) & 0xffff) + ((utf8::internal::mask8(*it) << 6) & 0xfff);++        UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end)++        code_point += (*it) & 0x3f;++        return UTF8_OK;+    }++    template <typename octet_iterator>+    utf_error get_sequence_4(octet_iterator& it, octet_iterator end, uint32_t& code_point)+    {+        if (it == end)+           return NOT_ENOUGH_ROOM;++        code_point = utf8::internal::mask8(*it);++        UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end)++        code_point = ((code_point << 18) & 0x1fffff) + ((utf8::internal::mask8(*it) << 12) & 0x3ffff);++        UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end)++        code_point += (utf8::internal::mask8(*it) << 6) & 0xfff;++        UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end)++        code_point += (*it) & 0x3f;++        return UTF8_OK;+    }++    #undef UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR++    template <typename octet_iterator>+    utf_error validate_next(octet_iterator& it, octet_iterator end, uint32_t& code_point)+    {+        // Save the original value of it so we can go back in case of failure+        // Of course, it does not make much sense with i.e. stream iterators+        octet_iterator original_it = it;++        uint32_t cp = 0;+        // Determine the sequence length based on the lead octet+        typedef typename std::iterator_traits<octet_iterator>::difference_type octet_difference_type;+        const octet_difference_type length = utf8::internal::sequence_length(it);++        // Get trail octets and calculate the code point+        utf_error err = UTF8_OK;+        switch (length) {+            case 0: +                return INVALID_LEAD;+            case 1:+                err = utf8::internal::get_sequence_1(it, end, cp);+                break;+            case 2:+                err = utf8::internal::get_sequence_2(it, end, cp);+            break;+            case 3:+                err = utf8::internal::get_sequence_3(it, end, cp);+            break;+            case 4:+                err = utf8::internal::get_sequence_4(it, end, cp);+            break;+        }++        if (err == UTF8_OK) {+            // Decoding succeeded. Now, security checks...+            if (utf8::internal::is_code_point_valid(cp)) {+                if (!utf8::internal::is_overlong_sequence(cp, length)){+                    // Passed! Return here.+                    code_point = cp;+                    ++it;+                    return UTF8_OK;+                }+                else+                    err = OVERLONG_SEQUENCE;+            }+            else +                err = INVALID_CODE_POINT;+        }++        // Failure branch - restore the original value of the iterator+        it = original_it;+        return err;+    }++    template <typename octet_iterator>+    inline utf_error validate_next(octet_iterator& it, octet_iterator end) {+        uint32_t ignored;+        return utf8::internal::validate_next(it, end, ignored);+    }++} // namespace internal++    /// The library API - functions intended to be called by the users++    // Byte order mark+    const uint8_t bom[] = {0xef, 0xbb, 0xbf};++    template <typename octet_iterator>+    octet_iterator find_invalid(octet_iterator start, octet_iterator end)+    {+        octet_iterator result = start;+        while (result != end) {+            utf8::internal::utf_error err_code = utf8::internal::validate_next(result, end);+            if (err_code != internal::UTF8_OK)+                return result;+        }+        return result;+    }++    template <typename octet_iterator>+    inline bool is_valid(octet_iterator start, octet_iterator end)+    {+        return (utf8::find_invalid(start, end) == end);+    }++    template <typename octet_iterator>+    inline bool starts_with_bom (octet_iterator it, octet_iterator end)+    {+        return (+            ((it != end) && (utf8::internal::mask8(*it++)) == bom[0]) &&+            ((it != end) && (utf8::internal::mask8(*it++)) == bom[1]) &&+            ((it != end) && (utf8::internal::mask8(*it))   == bom[2])+           );+    }+	+    //Deprecated in release 2.3 +    template <typename octet_iterator>+    inline bool is_bom (octet_iterator it)+    {+        return (+            (utf8::internal::mask8(*it++)) == bom[0] &&+            (utf8::internal::mask8(*it++)) == bom[1] &&+            (utf8::internal::mask8(*it))   == bom[2]+           );+    }+} // namespace utf8++#endif // header guard++
+ libsass/utf8/unchecked.h view
@@ -0,0 +1,228 @@+// Copyright 2006 Nemanja Trifunovic++/*+Permission is hereby granted, free of charge, to any person or organization+obtaining a copy of the software and accompanying documentation covered by+this license (the "Software") to use, reproduce, display, distribute,+execute, and transmit the Software, and to prepare derivative works of the+Software, and to permit third-parties to whom the Software is furnished to+do so, all subject to the following:++The copyright notices in the Software and this entire statement, including+the above license grant, this restriction and the following disclaimer,+must be included in all copies of the Software, in whole or in part, and+all derivative works of the Software, unless such copies or derivative+works are solely in the form of machine-executable object code generated by+a source language processor.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT+SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE+FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER+DEALINGS IN THE SOFTWARE.+*/+++#ifndef UTF8_FOR_CPP_UNCHECKED_H_2675DCD0_9480_4c0c_B92A_CC14C027B731+#define UTF8_FOR_CPP_UNCHECKED_H_2675DCD0_9480_4c0c_B92A_CC14C027B731++#include "core.h"++namespace utf8+{+    namespace unchecked +    {+        template <typename octet_iterator>+        octet_iterator append(uint32_t cp, octet_iterator result)+        {+            if (cp < 0x80)                        // one octet+                *(result++) = static_cast<uint8_t>(cp);  +            else if (cp < 0x800) {                // two octets+                *(result++) = static_cast<uint8_t>((cp >> 6)          | 0xc0);+                *(result++) = static_cast<uint8_t>((cp & 0x3f)        | 0x80);+            }+            else if (cp < 0x10000) {              // three octets+                *(result++) = static_cast<uint8_t>((cp >> 12)         | 0xe0);+                *(result++) = static_cast<uint8_t>(((cp >> 6) & 0x3f) | 0x80);+                *(result++) = static_cast<uint8_t>((cp & 0x3f)        | 0x80);+            }+            else {                                // four octets+                *(result++) = static_cast<uint8_t>((cp >> 18)         | 0xf0);+                *(result++) = static_cast<uint8_t>(((cp >> 12) & 0x3f)| 0x80);+                *(result++) = static_cast<uint8_t>(((cp >> 6) & 0x3f) | 0x80);+                *(result++) = static_cast<uint8_t>((cp & 0x3f)        | 0x80);+            }+            return result;+        }++        template <typename octet_iterator>+        uint32_t next(octet_iterator& it)+        {+            uint32_t cp = utf8::internal::mask8(*it);+            typename std::iterator_traits<octet_iterator>::difference_type length = utf8::internal::sequence_length(it);+            switch (length) {+                case 1:+                    break;+                case 2:+                    it++;+                    cp = ((cp << 6) & 0x7ff) + ((*it) & 0x3f);+                    break;+                case 3:+                    ++it; +                    cp = ((cp << 12) & 0xffff) + ((utf8::internal::mask8(*it) << 6) & 0xfff);+                    ++it;+                    cp += (*it) & 0x3f;+                    break;+                case 4:+                    ++it;+                    cp = ((cp << 18) & 0x1fffff) + ((utf8::internal::mask8(*it) << 12) & 0x3ffff);                +                    ++it;+                    cp += (utf8::internal::mask8(*it) << 6) & 0xfff;+                    ++it;+                    cp += (*it) & 0x3f; +                    break;+            }+            ++it;+            return cp;        +        }++        template <typename octet_iterator>+        uint32_t peek_next(octet_iterator it)+        {+            return utf8::unchecked::next(it);    +        }++        template <typename octet_iterator>+        uint32_t prior(octet_iterator& it)+        {+            while (utf8::internal::is_trail(*(--it))) ;+            octet_iterator temp = it;+            return utf8::unchecked::next(temp);+        }++        // Deprecated in versions that include prior, but only for the sake of consistency (see utf8::previous)+        template <typename octet_iterator>+        inline uint32_t previous(octet_iterator& it)+        {+            return utf8::unchecked::prior(it);+        }++        template <typename octet_iterator, typename distance_type>+        void advance (octet_iterator& it, distance_type n)+        {+            for (distance_type i = 0; i < n; ++i)+                utf8::unchecked::next(it);+        }++        template <typename octet_iterator>+        typename std::iterator_traits<octet_iterator>::difference_type+        distance (octet_iterator first, octet_iterator last)+        {+            typename std::iterator_traits<octet_iterator>::difference_type dist;+            for (dist = 0; first < last; ++dist) +                utf8::unchecked::next(first);+            return dist;+        }++        template <typename u16bit_iterator, typename octet_iterator>+        octet_iterator utf16to8 (u16bit_iterator start, u16bit_iterator end, octet_iterator result)+        {       +            while (start != end) {+                uint32_t cp = utf8::internal::mask16(*start++);+            // Take care of surrogate pairs first+                if (utf8::internal::is_lead_surrogate(cp)) {+                    uint32_t trail_surrogate = utf8::internal::mask16(*start++);+                    cp = (cp << 10) + trail_surrogate + internal::SURROGATE_OFFSET;+                }+                result = utf8::unchecked::append(cp, result);+            }+            return result;         +        }++        template <typename u16bit_iterator, typename octet_iterator>+        u16bit_iterator utf8to16 (octet_iterator start, octet_iterator end, u16bit_iterator result)+        {+            while (start < end) {+                uint32_t cp = utf8::unchecked::next(start);+                if (cp > 0xffff) { //make a surrogate pair+                    *result++ = static_cast<uint16_t>((cp >> 10)   + internal::LEAD_OFFSET);+                    *result++ = static_cast<uint16_t>((cp & 0x3ff) + internal::TRAIL_SURROGATE_MIN);+                }+                else+                    *result++ = static_cast<uint16_t>(cp);+            }+            return result;+        }++        template <typename octet_iterator, typename u32bit_iterator>+        octet_iterator utf32to8 (u32bit_iterator start, u32bit_iterator end, octet_iterator result)+        {+            while (start != end)+                result = utf8::unchecked::append(*(start++), result);++            return result;+        }++        template <typename octet_iterator, typename u32bit_iterator>+        u32bit_iterator utf8to32 (octet_iterator start, octet_iterator end, u32bit_iterator result)+        {+            while (start < end)+                (*result++) = utf8::unchecked::next(start);++            return result;+        }++        // The iterator class+        template <typename octet_iterator>+          class iterator : public std::iterator <std::bidirectional_iterator_tag, uint32_t> { +            octet_iterator it;+            public:+            iterator () {}+            explicit iterator (const octet_iterator& octet_it): it(octet_it) {}+            // the default "big three" are OK+            octet_iterator base () const { return it; }+            uint32_t operator * () const+            {+                octet_iterator temp = it;+                return utf8::unchecked::next(temp);+            }+            bool operator == (const iterator& rhs) const +            { +                return (it == rhs.it);+            }+            bool operator != (const iterator& rhs) const+            {+                return !(operator == (rhs));+            }+            iterator& operator ++ () +            {+                ::std::advance(it, utf8::internal::sequence_length(it));+                return *this;+            }+            iterator operator ++ (int)+            {+                iterator temp = *this;+                ::std::advance(it, utf8::internal::sequence_length(it));+                return temp;+            }  +            iterator& operator -- ()+            {+                utf8::unchecked::prior(it);+                return *this;+            }+            iterator operator -- (int)+            {+                iterator temp = *this;+                utf8::unchecked::prior(it);+                return temp;+            }+          }; // class iterator++    } // namespace utf8::unchecked+} // namespace utf8 +++#endif // header guard+
+ libsass/utf8_string.cpp view
@@ -0,0 +1,97 @@+#include <string>+#include <vector>+#include <cstdlib>+#include <cmath>++#include "utf8.h"++namespace Sass {+  namespace UTF_8 {+    using std::string;++    // naming conventions:+    // offset: raw byte offset (0 based)+    // position: code point offset (0 based)+    // index: code point offset (1 based or negative)++    // function that will count the number of code points (utf-8 characters) from the given beginning to the given end+    size_t code_point_count(const string& str, size_t start, size_t end) {+      return utf8::distance(str.begin() + start, str.begin() + end);+    }++    size_t code_point_count(const string& str) {+      return utf8::distance(str.begin(), str.end());+    }++    // function that will return the byte offset at a code point position+    size_t offset_at_position(const string& str, size_t position) {+      string::const_iterator it = str.begin();+      utf8::advance(it, position, str.end());+      return distance(str.begin(), it);+    }++    // function that returns number of bytes in a character at offset+    size_t code_point_size_at_offset(const string& str, size_t offset) {+      // get iterator from string and forward by offset+      string::const_iterator stop = str.begin() + offset;+      // check if beyond boundary+      if (stop == str.end()) return 0;+      // advance by one code point+      utf8::advance(stop, 1, str.end());+      // calculate offset for code point+      return  stop - str.begin() - offset;+    }++    // function that will return a normalized index, given a crazy one+    size_t normalize_index(int index, size_t len) {+      long signed_len = static_cast<long>(len);+      // assuming the index is 1-based+      // we are returning a 0-based index+      if (index > 0 && index <= signed_len) {+        // positive and within string length+        return index-1;+      }+      else if (index > signed_len) {+        // positive and past string length+        return len;+      }+      else if (index == 0) {+        return 0;+      }+      else if (std::abs((double)index) <= signed_len) {+        // negative and within string length+        return index + signed_len;+      }+      else {+        // negative and past string length+        return 0;+      }+    }++    // utf16 functions+    using std::wstring;++    // convert from utf16/wide string to utf8 string+    string convert_from_utf16(const wstring& utf16)+    {+      string utf8;+      // pre-allocate expected memory+      utf8.reserve(sizeof(utf16)/2);+      utf8::utf16to8(utf16.begin(), utf16.end(),+                     back_inserter(utf8));+      return utf8;+    }++    // convert from utf8 string to utf16/wide string+    wstring convert_to_utf16(const string& utf8)+    {+      wstring utf16;+      // pre-allocate expected memory+      utf16.reserve(code_point_count(utf8)*2);+      utf8::utf8to16(utf8.begin(), utf8.end(),+                     back_inserter(utf16));+      return utf16;+    }++  }+}
+ libsass/utf8_string.hpp view
@@ -0,0 +1,37 @@+#ifndef SASS_UTF8_STRING_H+#define SASS_UTF8_STRING_H++#include <string>+#include "utf8.h"++namespace Sass {+  namespace UTF_8 {++    // naming conventions:+    // offset: raw byte offset (0 based)+    // position: code point offset (0 based)+    // index: code point offset (1 based or negative)++    // function that will count the number of code points (utf-8 characters) from the beginning to the given end+    size_t code_point_count(const string& str, size_t start, size_t end);+    size_t code_point_count(const string& str);++    // function that will return the byte offset of a code point in a+    size_t offset_at_position(const string& str, size_t position);++    // function that returns number of bytes in a character in a string+    size_t code_point_size_at_offset(const string& str, size_t offset);++    // function that will return a normalized index, given a crazy one+    size_t normalize_index(int index, size_t len);++    #ifdef _WIN32+    // functions to handle unicode paths on windows+    string convert_from_utf16(const wstring& wstr);+    wstring convert_to_utf16(const string& str);+    #endif++  }+}++#endif
+ libsass/util.cpp view
@@ -0,0 +1,626 @@+#include<stdint.h>+#include "ast.hpp"+#include "util.hpp"+#include "prelexer.hpp"+#include "utf8/checked.h"++namespace Sass {++  #define out_of_memory() do {                    \+      fprintf(stderr, "Out of memory.\n");    \+      exit(EXIT_FAILURE);                     \+    } while (0)++  /* Sadly, sass_strdup is not portable. */+  char *sass_strdup(const char *str)+  {+    char *ret = (char*) malloc(strlen(str) + 1);+    if (ret == NULL)+      out_of_memory();+    strcpy(ret, str);+    return ret;+  }++  /* Locale unspecific atof function. */+  double sass_atof(const char *str)+  {+    char separator = *(localeconv()->decimal_point);+    if(separator != '.'){+      // The current locale specifies another+      // separator. convert the separator to the+      // one understood by the locale if needed+      const char *found = strchr(str, '.');+      if(found != NULL){+        // substitution is required. perform the substitution on a copy+        // of the string. This is slower but it is thread safe.+        char *copy = sass_strdup(str);+        *(copy + (found - str)) = separator;+        double res = atof(copy);+        free(copy);+        return res;+      }+    }++    return atof(str);+  }++  // double escape every escape sequences+  // escape unescaped quotes and backslashes+  string string_escape(const string& str)+  {+    string out("");+    for (auto i : str) {+      // escape some characters+      if (i == '"') out += '\\';+      if (i == '\'') out += '\\';+      if (i == '\\') out += '\\';+      out += i;+    }+    return out;+  }++  // unescape every escape sequence+  // only removes unescaped backslashes+  string string_unescape(const string& str)+  {+    string out("");+    bool esc = false;+    for (auto i : str) {+      if (esc || i != '\\') {+        esc = false;+        out += i;+      } else {+        esc = true;+      }+    }+    // open escape sequence at end+    // maybe it should thow an error+    if (esc) { out += '\\'; }+    return out;+  }++  // read css string (handle multiline DELIM)+  string read_css_string(const string& str)+  {+    string out("");+    bool esc = false;+    for (auto i : str) {+      if (i == '\\') {+        esc = ! esc;+      } else if (esc && i == '\r') {+        continue;+      } else if (esc && i == '\n') {+        out.resize (out.size () - 1);+        esc = false;+        continue;+      } else {+        esc = false;+      }+      out.push_back(i);+    }+    if (esc) out += '\\';+    return out;+  }++  // evacuate unescaped quoted+  // leave everything else untouched+  string evacuate_quotes(const string& str)+  {+    string out("");+    bool esc = false;+    for (auto i : str) {+      if (!esc) {+        // ignore next character+        if (i == '\\') esc = true;+        // evacuate unescaped quotes+        else if (i == '"') out += '\\';+        else if (i == '\'') out += '\\';+      }+      // get escaped char now+      else { esc = false; }+      // remove nothing+      out += i;+    }+    return out;+  }++  // double escape all escape sequences+  // keep unescaped quotes and backslashes+  string evacuate_escapes(const string& str)+  {+    string out("");+    bool esc = false;+    for (auto i : str) {+      if (i == '\\' && !esc) {+        out += '\\';+        out += '\\';+        esc = true;+      } else if (esc && i == '"') {+        out += '\\';+        out += i;+        esc = false;+      } else if (esc && i == '\'') {+        out += '\\';+        out += i;+        esc = false;+      } else if (esc && i == '\\') {+        out += '\\';+        out += i;+        esc = false;+      } else {+        esc = false;+        out += i;+      }+    }+    // happens when parsing does not correctly skip+    // over escaped sequences for ie. interpolations+    // one example: foo\#{interpolate}+    // if (esc) out += '\\';+    return out;+  }++  // bell character is replaces with space+  string string_to_output(const string& str)+  {+    string out("");+    for (auto i : str) {+      if (i == 10) {+        out += ' ';+      } else {+        out += i;+      }+    }+    return out;+  }++  string comment_to_string(const string& text)+  {+    string str = "";+    size_t has = 0;+    bool clean = false;+    for (auto i : text) {+      if (clean) {+        if (i == '\n') { has = 0; }+        else if (i == '\r') { has = 0; }+        else if (i == '\t') { ++ has; }+        else if (i == ' ') { ++ has; }+        else if (i == '*') {}+        else {+          clean = false;+          str += ' ';+          str += i;+        }+      } else if (i == '\n') {+        clean = true;+      } else if (i == '\r') {+        clean = true;+      } else {+        str += i;+      }+    }+    if (has) return str;+    else return text;+  }++   string normalize_wspace(const string& str)+  {+    bool ws = false;+    bool esc = false;+    char inside_str = 0;+    string text = "";+    for(auto i : str) {+      if (!esc && i == '\\') {+        esc = true;+        text += i;+      } else if (esc) {+        esc = false;+        text += i;+      } else if (!inside_str && (i == '"' || i == '\'')) {+        inside_str = i;+        text += i;+      } else if (inside_str) {+        if (i == inside_str)+          inside_str = false;+        text += i;+      } else if (+        i == ' ' ||+        i == '\r' ||+        i == '\n' ||+        i == '	'+      ) {+        // only add one space+        if (!ws) text += ' ';+        ws = true;+      } else {+        ws = false;+        text += i;+      }+    }+    if (esc) text += '\\';+    return text;+  }++  // find best quote_mark by detecting if the string contains any single+  // or double quotes. When a single quote is found, we not we want a double+  // quote as quote_mark. Otherwise we check if the string cotains any double+  // quotes, which will trigger the use of single quotes as best quote_mark.+  char detect_best_quotemark(const char* s, char qm)+  {+    // ensure valid fallback quote_mark+    char quote_mark = qm && qm != '*' ? qm : '"';+    while (*s) {+      // force double quotes as soon+      // as one single quote is found+      if (*s == '\'') { return '"'; }+      // a single does not force quote_mark+      // maybe we see a double quote later+      else if (*s == '"') { quote_mark = '\''; }+      ++ s;+    }+    return quote_mark;+  }++  string unquote(const string& s, char* qd)+  {++    // not enough room for quotes+    // no possibility to unquote+    if (s.length() < 2) return s;++    char q;+    bool skipped = false;++    // this is no guarantee that the unquoting will work+    // what about whitespace before/after the quote_mark?+    if      (*s.begin() == '"'  && *s.rbegin() == '"')  q = '"';+    else if (*s.begin() == '\'' && *s.rbegin() == '\'') q = '\'';+    else                                                return s;++    string unq;+    unq.reserve(s.length()-2);++    for (size_t i = 1, L = s.length() - 1; i < L; ++i) {++      // implement the same strange ruby sass behavior+      // an escape sequence can also mean a unicode char+      if (s[i] == '\\' && !skipped) {+        // remember+        skipped = true;++        // skip it+        // ++ i;++        // if (i == L) break;++        // escape length+        size_t len = 1;++        // parse as many sequence chars as possible+        // ToDo: Check if ruby aborts after possible max+        while (i + len < L && s[i + len] && isxdigit(s[i + len])) ++ len;++        // hex string?+        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(), nullptr, 16);++          // assert invalid code points+          if (cp == 0) cp = 0xFFFD;+          // replace bell character+          // if (cp == 10) 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; u[m] && m < 5; m++) unq.push_back(u[m]);++          // skip some more chars?+          i += len - 1; skipped = false;++        }+++      }+      // check for unexpected delimiter+      // be strict and throw error back+      else if (!skipped && q == s[i]) {+        // don't be that strict+        return s;+        // this basically always means an internal error and not users fault+        error("Unescaped delimiter in string to unquote found. [" + s + "]", ParserState("[UNQUOTE]"));+      }+      else {+        skipped = false;+        unq.push_back(s[i]);+      }++    }+    if (skipped) { return s; }+    if (qd) *qd = q;+    return unq;++  }++  string quote(const string& s, char q)+  {++    // autodetect with fallback to given quote+    q = detect_best_quotemark(s.c_str(), q);++    // return an empty quoted string+    if (s.empty()) return string(2, q ? q : '"');++    string quoted;+    quoted.reserve(s.length()+2);+    quoted.push_back(q);++    const char* it = s.c_str();+    const char* end = it + strlen(it) + 1;+    while (*it && it < end) {+      const char* now = it;++      if (*it == q) {+        quoted.push_back('\\');+      } else if (*it == '\\') {+        quoted.push_back('\\');+      }++      int cp = utf8::next(it, end);++      if (cp == 10) {+        quoted.push_back('\\');+        quoted.push_back('a');+      } else if (cp < 127) {+        quoted.push_back((char) cp);+      } else {+        while (now < it) {+          quoted.push_back(*now);+          ++ now;+        }+      }+    }++    quoted.push_back(q);+    return quoted;+  }++  bool is_hex_doublet(double n)+  {+    return n == 0x00 || n == 0x11 || n == 0x22 || n == 0x33 ||+           n == 0x44 || n == 0x55 || n == 0x66 || n == 0x77 ||+           n == 0x88 || n == 0x99 || n == 0xAA || n == 0xBB ||+           n == 0xCC || n == 0xDD || n == 0xEE || n == 0xFF ;+  }++  bool is_color_doublet(double r, double g, double b)+  {+    return is_hex_doublet(r) && is_hex_doublet(g) && is_hex_doublet(b);+  }++  bool peek_linefeed(const char* start)+  {+    while (*start) {+      if (*start == '\n' || *start == '\r') return true;+      if (*start != ' ' && *start != '\t') return false;+      ++ start;+    }+    return false;+  }++  namespace Util {+    using std::string;++    string normalize_underscores(const string& str) {+      string normalized = str;+      for(size_t i = 0, L = normalized.length(); i < L; ++i) {+        if(normalized[i] == '_') {+          normalized[i] = '-';+        }+      }+      return normalized;+    }++    string normalize_decimals(const string& str) {+      string prefix = "0";+      string normalized = str;++      return normalized[0] == '.' ? normalized.insert(0, prefix) : normalized;+    }++    // compress a color sixtuplet if possible+    // input: "#CC9900" -> output: "#C90"+    string normalize_sixtuplet(const string& col) {+      if(+        col.substr(1, 1) == col.substr(2, 1) &&+        col.substr(3, 1) == col.substr(4, 1) &&+        col.substr(5, 1) == col.substr(6, 1)+      ) {+        return string("#" + col.substr(1, 1)+                          + col.substr(3, 1)+                          + col.substr(5, 1));+      } else {+        return string(col);+      }+    }++    bool isPrintable(Ruleset* r, Output_Style style) {+      if (r == NULL) {+        return false;+      }++      Block* b = r->block();++      bool hasSelectors = static_cast<Selector_List*>(r->selector())->length() > 0;++      if (!hasSelectors) {+        return false;+      }++      bool hasDeclarations = false;+      bool hasPrintableChildBlocks = false;+      for (size_t i = 0, L = b->length(); i < L; ++i) {+        Statement* stm = (*b)[i];+        if (dynamic_cast<Has_Block*>(stm)) {+          Block* pChildBlock = ((Has_Block*)stm)->block();+          if (isPrintable(pChildBlock, style)) {+            hasPrintableChildBlocks = true;+          }+        } else if (Comment* c = dynamic_cast<Comment*>(stm)) {+          if (style == COMPRESSED) {+            hasDeclarations = c->is_important();+          } else {+            hasDeclarations = true;+          }+        } else {+          hasDeclarations = true;+        }++        if (hasDeclarations || hasPrintableChildBlocks) {+          return true;+        }+      }++      return false;+    }++    bool isPrintable(Feature_Block* f, Output_Style style) {+      if (f == NULL) {+        return false;+      }++      Block* b = f->block();++      bool hasSelectors = f->selector() && static_cast<Selector_List*>(f->selector())->length() > 0;++      bool hasDeclarations = false;+      bool hasPrintableChildBlocks = false;+      for (size_t i = 0, L = b->length(); i < L; ++i) {+        Statement* stm = (*b)[i];+        if (!stm->is_hoistable() && f->selector() != NULL && !hasSelectors) {+          // If a statement isn't hoistable, the selectors apply to it. If there are no selectors (a selector list of length 0),+          // then those statements aren't considered printable. That means there was a placeholder that was removed. If the selector+          // is NULL, then that means there was never a wrapping selector and it is printable (think of a top level media block with+          // a declaration in it).+        }+        else if (typeid(*stm) == typeid(Declaration) || typeid(*stm) == typeid(At_Rule)) {+          hasDeclarations = true;+        }+        else if (dynamic_cast<Has_Block*>(stm)) {+          Block* pChildBlock = ((Has_Block*)stm)->block();+          if (isPrintable(pChildBlock, style)) {+            hasPrintableChildBlocks = true;+          }+        }++        if (hasDeclarations || hasPrintableChildBlocks) {+          return true;+        }+      }++      return false;+    }++    bool isPrintable(Media_Block* m, Output_Style style) {+      if (m == NULL) {+        return false;+      }++      Block* b = m->block();++      bool hasSelectors = m->selector() && static_cast<Selector_List*>(m->selector())->length() > 0;++      bool hasDeclarations = false;+      bool hasPrintableChildBlocks = false;+      for (size_t i = 0, L = b->length(); i < L; ++i) {+        Statement* stm = (*b)[i];+        if (!stm->is_hoistable() && m->selector() != NULL && !hasSelectors) {+          // If a statement isn't hoistable, the selectors apply to it. If there are no selectors (a selector list of length 0),+          // then those statements aren't considered printable. That means there was a placeholder that was removed. If the selector+          // is NULL, then that means there was never a wrapping selector and it is printable (think of a top level media block with+          // a declaration in it).+        }+        else if (typeid(*stm) == typeid(Declaration) || typeid(*stm) == typeid(At_Rule)) {+          hasDeclarations = true;+        }+        else if (dynamic_cast<Has_Block*>(stm)) {+          Block* pChildBlock = ((Has_Block*)stm)->block();+          if (isPrintable(pChildBlock, style)) {+            hasPrintableChildBlocks = true;+          }+        }++        if (hasDeclarations || hasPrintableChildBlocks) {+          return true;+        }+      }++      return false;+    }++    bool isPrintable(Block* b, Output_Style style) {+      if (b == NULL) {+        return false;+      }++      for (size_t i = 0, L = b->length(); i < L; ++i) {+        Statement* stm = (*b)[i];+        if (typeid(*stm) == typeid(Declaration) || typeid(*stm) == typeid(At_Rule)) {+          return true;+        }+        else if (typeid(*stm) == typeid(Comment)) {++        }+        else if (typeid(*stm) == typeid(Ruleset)) {+          Ruleset* r = (Ruleset*) stm;+          if (isPrintable(r, style)) {+            return true;+          }+        }+        else if (typeid(*stm) == typeid(Feature_Block)) {+          Feature_Block* f = (Feature_Block*) stm;+          if (isPrintable(f, style)) {+            return true;+          }+        }+        else if (typeid(*stm) == typeid(Media_Block)) {+          Media_Block* m = (Media_Block*) stm;+          if (isPrintable(m, style)) {+            return true;+          }+        }+        else if (dynamic_cast<Has_Block*>(stm) && isPrintable(((Has_Block*)stm)->block(), style)) {+          return true;+        }+      }++      return false;+    }++    string vecJoin(const vector<string>& vec, const string& sep)+    {+      switch (vec.size())+      {+        case 0:+            return string("");+        case 1:+            return vec[0];+        default:+            std::ostringstream os;+            os << vec[0];+            for (size_t i = 1; i < vec.size(); i++) {+              os << sep << vec[i];+            }+            return os.str();+      }+    }++     bool isAscii(const char chr) {+       return unsigned(chr) < 128;+     }++  }+}
+ libsass/util.hpp view
@@ -0,0 +1,48 @@+#ifndef SASS_UTIL_H+#define SASS_UTIL_H++#include <vector>+#include <string>+#include "ast_fwd_decl.hpp"++namespace Sass {+  using namespace std;++  char* sass_strdup(const char* str);+  double sass_atof(const char* str);+  string string_escape(const string& str);+  string string_unescape(const string& str);+  string read_css_string(const string& str);+  string evacuate_quotes(const string& str);+  string evacuate_escapes(const string& str);+  string string_to_output(const string& str);+  string comment_to_string(const string& text);+  string normalize_wspace(const string& str);++  string quote(const string&, char q = 0);+  string unquote(const string&, char* q = 0);+  char detect_best_quotemark(const char* s, char qm = '"');++  bool is_hex_doublet(double n);+  bool is_color_doublet(double r, double g, double b);++  bool peek_linefeed(const char* start);++  namespace Util {++    string normalize_underscores(const string& str);+    string normalize_decimals(const string& str);+    string normalize_sixtuplet(const string& col);++    string vecJoin(const vector<string>& vec, const string& sep);+    bool containsAnyPrintableStatements(Block* b);++    bool isPrintable(Ruleset* r, Output_Style style = NESTED);+    bool isPrintable(Feature_Block* r, Output_Style style = NESTED);+    bool isPrintable(Media_Block* r, Output_Style style = NESTED);+    bool isPrintable(Block* b, Output_Style style = NESTED);+    bool isAscii(const char chr);++  }+}+#endif
+ tests/Main.hs view
@@ -0,0 +1,32 @@+{-# LANGUAGE BangPatterns #-}+import           Binding.Libsass+import           Foreign+import           Foreign.C+import           Test.Hspec++simpleCompile :: String -> IO String+simpleCompile str = do+    cstr <- newCString str+    ctx <- sass_make_data_context cstr+    status <- sass_compile_data_context ctx+    if status /= 0+        then do+            sass_delete_data_context ctx+            return ""+        else do+            cres <- sass_context_get_output_string (castPtr ctx)+            !res <- peekCString cres+            sass_delete_data_context ctx+            return res++sampleInput :: String+sampleInput = "foo { margin: 21px * 2; }"++sampleOutput :: String+sampleOutput = "foo {\n  margin: 42px; }\n"++main :: IO ()+main = hspec $ do+  describe "Libsass" $ do+    it "should correctly compile simple expression" $ do+      simpleCompile sampleInput `shouldReturn` sampleOutput