From b83e33c24fa2059e46d51cb1637c3a9735fb4329 Mon Sep 17 00:00:00 2001 From: xzyfer Date: Mon, 23 Apr 2018 23:07:47 +1000 Subject: [PATCH 1/2] Bump LibSass@3.5.3 See https://github.com/sass/libsass/releases/tag/3.5.3 --- package.json | 4 +- src/libsass/GNUmakefile.am | 56 +- src/libsass/Readme.md | 110 +- src/libsass/api-context-example.md | 45 + src/libsass/api-context-internal.md | 163 ++ src/libsass/api-context.md | 295 ++ src/libsass/api-doc.md | 215 ++ src/libsass/api-function-example.md | 67 + src/libsass/api-function-internal.md | 8 + src/libsass/api-function.md | 74 + src/libsass/api-importer-example.md | 112 + src/libsass/api-importer-internal.md | 20 + src/libsass/api-importer.md | 86 + src/libsass/api-value-example.md | 55 + src/libsass/api-value-internal.md | 76 + src/libsass/api-value.md | 154 ++ src/libsass/build-on-darwin.md | 27 + src/libsass/build-on-gentoo.md | 55 + src/libsass/build-on-windows.md | 139 + src/libsass/build-shared-library.md | 35 + src/libsass/build-with-autotools.md | 78 + src/libsass/build-with-makefiles.md | 68 + src/libsass/build-with-mingw.md | 107 + src/libsass/build-with-visual-studio.md | 90 + src/libsass/build.md | 97 + src/libsass/compatibility-plan.md | 48 + src/libsass/configure.ac | 20 +- src/libsass/context.cpp | 926 +++++++ src/libsass/context.h | 173 ++ src/libsass/context.hpp | 155 ++ src/libsass/contributing.md | 17 + src/libsass/cssize.cpp | 606 +++++ src/libsass/custom-functions-internal.md | 122 + src/libsass/debugger.hpp | 801 ++++++ src/libsass/dev-ast-memory.md | 223 ++ src/libsass/eval.cpp | 1663 ++++++++++++ src/libsass/expand.cpp | 817 ++++++ src/libsass/file.cpp | 485 ++++ src/libsass/file.hpp | 139 + src/libsass/functions.cpp | 2234 +++++++++++++++ src/libsass/implementations.md | 65 + src/libsass/inspect.cpp | 1138 ++++++++ src/libsass/operators.cpp | 240 ++ src/libsass/parser.cpp | 3137 ++++++++++++++++++++++ src/libsass/parser.hpp | 400 +++ src/libsass/plugins.md | 47 + src/libsass/prelexer.cpp | 1774 ++++++++++++ src/libsass/sass.cpp | 151 ++ src/libsass/sass2scss.cpp | 895 ++++++ src/libsass/sass_context.cpp | 799 ++++++ src/libsass/sass_context.hpp | 132 + src/libsass/setup-environment.md | 68 + src/libsass/source-map-internals.md | 51 + src/libsass/tap-runner | 1 + src/libsass/trace.md | 26 + src/libsass/triage.md | 17 + src/libsass/unicode.md | 45 + src/libsass/util.hpp | 56 + 58 files changed, 19561 insertions(+), 146 deletions(-) create mode 100644 src/libsass/api-context-example.md create mode 100644 src/libsass/api-context-internal.md create mode 100644 src/libsass/api-context.md create mode 100644 src/libsass/api-doc.md create mode 100644 src/libsass/api-function-example.md create mode 100644 src/libsass/api-function-internal.md create mode 100644 src/libsass/api-function.md create mode 100644 src/libsass/api-importer-example.md create mode 100644 src/libsass/api-importer-internal.md create mode 100644 src/libsass/api-importer.md create mode 100644 src/libsass/api-value-example.md create mode 100644 src/libsass/api-value-internal.md create mode 100644 src/libsass/api-value.md create mode 100644 src/libsass/build-on-darwin.md create mode 100644 src/libsass/build-on-gentoo.md create mode 100644 src/libsass/build-on-windows.md create mode 100644 src/libsass/build-shared-library.md create mode 100644 src/libsass/build-with-autotools.md create mode 100644 src/libsass/build-with-makefiles.md create mode 100644 src/libsass/build-with-mingw.md create mode 100644 src/libsass/build-with-visual-studio.md create mode 100644 src/libsass/build.md create mode 100644 src/libsass/compatibility-plan.md create mode 100644 src/libsass/context.cpp create mode 100644 src/libsass/context.h create mode 100644 src/libsass/context.hpp create mode 100644 src/libsass/contributing.md create mode 100644 src/libsass/cssize.cpp create mode 100644 src/libsass/custom-functions-internal.md create mode 100644 src/libsass/debugger.hpp create mode 100644 src/libsass/dev-ast-memory.md create mode 100644 src/libsass/eval.cpp create mode 100644 src/libsass/expand.cpp create mode 100644 src/libsass/file.cpp create mode 100644 src/libsass/file.hpp create mode 100644 src/libsass/functions.cpp create mode 100644 src/libsass/implementations.md create mode 100644 src/libsass/inspect.cpp create mode 100644 src/libsass/operators.cpp create mode 100644 src/libsass/parser.cpp create mode 100644 src/libsass/parser.hpp create mode 100644 src/libsass/plugins.md create mode 100644 src/libsass/prelexer.cpp create mode 100644 src/libsass/sass.cpp create mode 100644 src/libsass/sass2scss.cpp create mode 100644 src/libsass/sass_context.cpp create mode 100644 src/libsass/sass_context.hpp create mode 100644 src/libsass/setup-environment.md create mode 100644 src/libsass/source-map-internals.md create mode 100755 src/libsass/tap-runner create mode 100644 src/libsass/trace.md create mode 100644 src/libsass/triage.md create mode 100644 src/libsass/unicode.md create mode 100644 src/libsass/util.hpp diff --git a/package.json b/package.json index d61efb451..7a9e236ad 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "node-sass", "version": "4.8.3", - "libsass": "3.5.2", + "libsass": "3.5.3", "description": "Wrapper around libsass", "license": "MIT", "bugs": "https://github.com/sass/node-sass/issues", @@ -83,7 +83,7 @@ "object-merge": "^2.5.1", "read-yaml": "^1.0.0", "rimraf": "^2.5.2", - "sass-spec": "^3.5.1", + "sass-spec": "3.5.3", "unique-temp-dir": "^1.0.0" } } diff --git a/src/libsass/GNUmakefile.am b/src/libsass/GNUmakefile.am index 9e658a415..d197261e7 100644 --- a/src/libsass/GNUmakefile.am +++ b/src/libsass/GNUmakefile.am @@ -25,63 +25,49 @@ else AM_CXXFLAGS += -std=c++0x endif +TEST_EXTENSIONS = .rb + if ENABLE_TESTS -noinst_PROGRAMS = tester +SASS_SASSC_PATH ?= $(top_srcdir)/sassc +SASS_SPEC_PATH ?= $(top_srcdir)/sass-spec +noinst_PROGRAMS = tester tester_LDADD = src/libsass.la -tester_SOURCES = $(SASS_SASSC_PATH)/sassc.c -tester_VERSION ?= `cd "$(SASS_SASSC_PATH)" && ./version.sh` -tester_CFLAGS = $(AM_CFLAGS) -DSASSC_VERSION="\"$(tester_VERSION)\"" -tester_CXXFLAGS = $(AM_CXXFLAGS) -DSASSC_VERSION="\"$(tester_VERSION)\"" tester_LDFLAGS = $(AM_LDFLAGS) +nodist_tester_SOURCES = $(SASS_SASSC_PATH)/sassc.c +SASS_SASSC_VERSION ?= `cd "$(SASS_SASSC_PATH)" && ./version.sh` +tester_CFLAGS = $(AM_CFLAGS) -DSASSC_VERSION="\"$(SASS_SASSC_VERSION)\"" +tester_CXXFLAGS = $(AM_CXXFLAGS) -DSASSC_VERSION="\"$(SASS_SASSC_VERSION)\"" if ENABLE_COVERAGE nodist_EXTRA_tester_SOURCES = non-existent-file-to-force-CXX-linking.cxx endif -SASS_SASSC_PATH ?= $(top_srcdir)/sassc -SASS_SPEC_PATH ?= $(top_srcdir)/sass-spec - -TESTS = \ - $(SASS_SPEC_PATH)/spec/basic \ - $(SASS_SPEC_PATH)/spec/css \ - $(SASS_SPEC_PATH)/spec/extend-tests \ - $(SASS_SPEC_PATH)/spec/extends \ - $(SASS_SPEC_PATH)/spec/libsass \ - $(SASS_SPEC_PATH)/spec/libsass-closed-issues \ - $(SASS_SPEC_PATH)/spec/maps \ - $(SASS_SPEC_PATH)/spec/misc \ - $(SASS_SPEC_PATH)/spec/regressions \ - $(SASS_SPEC_PATH)/spec/scss \ - $(SASS_SPEC_PATH)/spec/scss-tests \ - $(SASS_SPEC_PATH)/spec/types +TESTS = $(SASS_SPEC_PATH)/sass-spec.rb +RB_LOG_COMPILER = ./script/tap-runner +AM_RB_LOG_FLAGS = $(RUBY) SASS_TEST_FLAGS = -V 3.5 --impl libsass -LOG_DRIVER = env AM_TAP_AWK='$(AWK)' $(SHELL) ./script/tap-driver -AM_LOG_FLAGS = -c ./tester $(LOG_FLAGS) -if USE_TAP - AM_LOG_FLAGS += -t - SASS_TEST_FLAGS += -t | tapout - LOG_COMPILER = ./script/tap-runner $(RUBY) $(SASS_SPEC_PATH)/sass-spec.rb -else - LOG_COMPILER = $(RUBY) $(SASS_SPEC_PATH)/sass-spec.rb -endif +SASS_TEST_FLAGS += -r $(SASS_SPEC_PATH) +SASS_TEST_FLAGS += -c $(top_srcdir)/tester$(EXEEXT) +AM_TESTS_ENVIRONMENT = TEST_FLAGS='$(SASS_TEST_FLAGS)' SASS_TESTER = $(RUBY) $(SASS_SPEC_PATH)/sass-spec.rb -SASS_TESTER += -c $(top_srcdir)/tester$(EXEEXT) test: - $(SASS_TESTER) $(LOG_FLAGS) $(SASS_SPEC_PATH) $(SASS_TEST_FLAGS) + $(SASS_TESTER) $(SASS_TEST_FLAGS) test_build: - $(SASS_TESTER) $(LOG_FLAGS) $(SASS_SPEC_PATH) $(SASS_TEST_FLAGS) + $(SASS_TESTER) $(SASS_TEST_FLAGS) test_full: - $(SASS_TESTER) --run-todo $(LOG_FLAGS) $(SASS_SPEC_PATH) $(SASS_TEST_FLAGS) + $(SASS_TESTER) --run-todo $(SASS_TEST_FLAGS) test_probe: - $(SASS_TESTER) --probe-todo $(LOG_FLAGS) $(SASS_SPEC_PATH) $(SASS_TEST_FLAGS) + $(SASS_TESTER) --probe-todo $(SASS_TEST_FLAGS) + +.PHONY: test test_build test_full test_probe endif diff --git a/src/libsass/Readme.md b/src/libsass/Readme.md index 908de2dc4..a233fae48 100644 --- a/src/libsass/Readme.md +++ b/src/libsass/Readme.md @@ -1,104 +1,20 @@ -LibSass - Sass compiler written in C++ -====================================== +Welcome to the LibSass documentation! -Currently maintained by Marcel Greter ([@mgreter]) and Michael Mifsud ([@xzyfer]) -Originally created by Aaron Leung ([@akhleung]) and Hampton Catlin ([@hcatlin]) +## First Off +LibSass is just a library. To run the code locally (i.e. to compile your stylesheets), you need an implementer. SassC (get it?) is an implementer written in C. There are a number of other implementations of LibSass - for example Node. We encourage you to write your own port - the whole point of LibSass is that we want to bring Sass to many other languages, not just Ruby! -[![Unix CI](https://travis-ci.org/sass/libsass.svg?branch=master)](https://travis-ci.org/sass/libsass "Travis CI") -[![Windows CI](https://ci.appveyor.com/api/projects/status/github/sass/libsass?svg=true)](https://ci.appveyor.com/project/sass/libsass/branch/master "Appveyor CI") -[![Coverage Status](https://img.shields.io/coveralls/sass/libsass.svg)](https://coveralls.io/r/sass/libsass?branch=feature%2Ftest-travis-ci-3 "Code coverage of spec tests") -[![Percentage of issues still open](http://isitmaintained.com/badge/open/sass/libsass.svg)](http://isitmaintained.com/project/sass/libsass "Percentage of issues still open") -[![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/sass/libsass.svg)](http://isitmaintained.com/project/sass/libsass "Average time to resolve an issue") -[![Bountysource](https://www.bountysource.com/badge/tracker?tracker_id=283068)](https://www.bountysource.com/trackers/283068-libsass?utm_source=283068&utm_medium=shield&utm_campaign=TRACKER_BADGE "Bountysource") -[![Join us](https://libsass-slack.herokuapp.com/badge.svg)](https://libsass-slack.herokuapp.com/ "Slack communication channels") +We're working hard on moving to full parity with Ruby Sass... learn more at the [The-LibSass-Compatibility-Plan](compatibility-plan.md)! +### Implementing LibSass -[LibSass](https://github.com/sass/libsass "LibSass GitHub Project") is just a library! -If you want to use LibSass to compile Sass, you need an implementer. Some -implementations are only bindings into other programming languages. But most also -ship with a command line interface (CLI) you can use directly. There is also -[SassC](https://github.com/sass/sassc), which is the official lightweight -CLI tool built by the same people as LibSass. +If you're interested in implementing LibSass in your own project see the [API Documentation](api-doc.md) which now includes implementing +your own [Sass functions](api-function.md). You may wish to [look at other implementations](implementations.md) for your language of choice. +Or make your own! -### Excerpt of "sanctioned" implementations: +### Contributing to LibSass -- https://github.com/sass/node-sass (Node.js) -- https://github.com/sass/perl-libsass (Perl) -- https://github.com/sass/libsass-python (Python) -- https://github.com/wellington/go-libsass (Go) -- https://github.com/sass/sassc-ruby (Ruby) -- https://github.com/sass/libsass-net (C#) -- https://github.com/medialize/sass.js (JS) -- https://github.com/bit3/jsass (Java) +| Issue Tracker | Issue Triage | Community Guidelines | +|-------------------|----------------------------------|-----------------------------| +| We're always needing help, so check out our issue tracker, help some people out, and read our article on [Contributing](contributing.md)! It's got all the details on what to do! | To help understand the process of triaging bugs, have a look at our [Issue-Triage](triage.md) document. | Oh, and don't forget we always follow [[Sass Community Guidelines|http://sass-lang.com/community-guidelines]]. Be nice and everyone else will be nice too! | -This list does not say anything about the quality of either the listed or not listed [implementations](docs/implementations.md)! -The authors of the listed projects above are just known to work regularly together with LibSass developers. - -About ------ - -LibSass is a C++ port of the original Ruby Sass CSS compiler with a [C API](docs/api-doc.md). -We coded LibSass with portability and efficiency in mind. You can expect LibSass to be a lot -faster than Ruby Sass and on par or faster than the best alternative CSS compilers around. - -Developing ----------- - -As noted above, the LibSass repository does not contain any binaries or other way to execute -LibSass. Therefore, you need an implementer to develop LibSass. Easiest is to start with -the official [SassC](http://github.com/sass/sassc) CLI wrapper. It is *guaranteed* to compile -with the latest code in LibSass master, since it is also used in the CI process. There is no -limitation here, as you may use any other LibSass implementer to test your LibSass branch! - -Testing -------- - -Since LibSass is a pure library, tests are run through the [Sass-Spec](https://github.com/sass/sass-spec) -project using the [SassC](http://github.com/sass/sassc) CLI wrapper. To run the tests against LibSass while -developing, you can run `./script/spec`. This will clone SassC and Sass-Spec under the project folder and -then run the Sass-Spec test suite. You may want to update the clones to ensure you have the latest version. -Note that the scripts in the `./script` folder are mainly intended for our CI needs. - -Building --------- - -To build LibSass you need GCC 4.6+ or Clang/LLVM. If your OS is older, you may need to upgrade -them first (or install clang as an alternative). On Windows, you need MinGW with GCC 4.6+ or VS 2013 -Update 4+. It is also possible to build LibSass with Clang/LLVM on Windows with various build chains -and/or command line interpreters. - -See the [build docs for further instructions](docs/build.md)! - -Compatibility -------------- - -Current LibSass 3.4 should be compatible with Sass 3.4. Please refer to the [sass compatibility -page](http://sass-compatibility.github.io/) for a more detailed comparison. But note that there -are still a few incomplete edges which we are aware of. Otherwise LibSass has reached a good level -of stability, thanks to our ever growing [Sass-Spec test suite](https://github.com/sass/sass-spec). - -About Sass ----------- - -Sass is a CSS pre-processor language to add on exciting, new, awesome features to CSS. Sass was -the first language of its kind and by far the most mature and up to date codebase. - -Sass was originally conceived of by the co-creator of this library, Hampton Catlin ([@hcatlin]). -Most of the language has been the result of years of work by Natalie Weizenbaum ([@nex3]) and -Chris Eppstein ([@chriseppstein]). - -For more information about Sass itself, please visit http://sass-lang.com - -Initial development of LibSass by Aaron Leung and Hampton Catlin was supported by [Moovweb](http://www.moovweb.com). - -Licensing ---------- - -Our [MIT license](LICENSE) is designed to be as simple and liberal as possible. - -[@hcatlin]: https://github.com/hcatlin -[@akhleung]: https://github.com/akhleung -[@chriseppstein]: https://github.com/chriseppstein -[@nex3]: https://github.com/nex3 -[@mgreter]: https://github.com/mgreter -[@xzyfer]: https://github.com/xzyfer +Please refer to the steps on [Building LibSass](build.md) diff --git a/src/libsass/api-context-example.md b/src/libsass/api-context-example.md new file mode 100644 index 000000000..4f2a2a0ce --- /dev/null +++ b/src/libsass/api-context-example.md @@ -0,0 +1,45 @@ +## Example main.c + +```C +#include +#include "sass/context.h" + +int main( int argc, const char* argv[] ) +{ + + // get the input file from first argument or use default + const char* input = argc > 1 ? argv[1] : "styles.scss"; + + // create the file context and get all related structs + struct Sass_File_Context* file_ctx = sass_make_file_context(input); + struct Sass_Context* ctx = sass_file_context_get_context(file_ctx); + struct Sass_Options* ctx_opt = sass_context_get_options(ctx); + + // configure some options ... + sass_option_set_precision(ctx_opt, 10); + + // context is set up, call the compile step now + int status = sass_compile_file_context(file_ctx); + + // print the result or the error to the stdout + if (status == 0) puts(sass_context_get_output_string(ctx)); + else puts(sass_context_get_error_message(ctx)); + + // release allocated memory + sass_delete_file_context(file_ctx); + + // exit status + return status; + +} +``` + +### Compile main.c + +```bash +gcc -c main.c -o main.o +gcc -o sample main.o -lsass +echo "foo { margin: 21px * 2; }" > foo.scss +./sample foo.scss => "foo { margin: 42px }" +``` + diff --git a/src/libsass/api-context-internal.md b/src/libsass/api-context-internal.md new file mode 100644 index 000000000..1a2818b34 --- /dev/null +++ b/src/libsass/api-context-internal.md @@ -0,0 +1,163 @@ +```C +// Input behaviours +enum Sass_Input_Style { + SASS_CONTEXT_NULL, + SASS_CONTEXT_FILE, + SASS_CONTEXT_DATA, + SASS_CONTEXT_FOLDER +}; + +// sass config options structure +struct Sass_Inspect_Options { + + // Output style for the generated css code + // A value from above SASS_STYLE_* constants + enum Sass_Output_Style output_style; + + // Precision for fractional numbers + int precision; + +}; + +// sass config options structure +struct Sass_Output_Options : Sass_Inspect_Options { + + // String to be used for indentation + const char* indent; + // String to be used to for line feeds + const char* linefeed; + + // Emit comments in the generated CSS indicating + // the corresponding source line. + bool source_comments; + +}; + +// sass config options structure +struct Sass_Options : Sass_Output_Options { + + // embed sourceMappingUrl as data uri + bool source_map_embed; + + // embed include contents in maps + bool source_map_contents; + + // create file urls for sources + bool source_map_file_urls; + + // Disable sourceMappingUrl in css output + bool omit_source_map_url; + + // Treat source_string as sass (as opposed to scss) + bool is_indented_syntax_src; + + // The input path is used for source map + // generation. It can be used to define + // something with string compilation or to + // overload the input file path. It is + // set to "stdin" for data contexts and + // to the input file on file contexts. + char* input_path; + + // The output path is used for source map + // generation. LibSass will not write to + // this file, it is just used to create + // information in source-maps etc. + char* output_path; + + // Colon-separated list of paths + // Semicolon-separated on Windows + // Maybe use array interface instead? + char* include_path; + char* plugin_path; + + // Include paths (linked string list) + struct string_list* include_paths; + // Plugin paths (linked string list) + struct string_list* plugin_paths; + + // Path to source map file + // Enables source map generation + // Used to create sourceMappingUrl + char* source_map_file; + + // Directly inserted in source maps + char* source_map_root; + + // Custom functions that can be called from sccs code + Sass_Function_List c_functions; + + // Callback to overload imports + Sass_Importer_List c_importers; + + // List of custom headers + Sass_Importer_List c_headers; + +}; + +// base for all contexts +struct Sass_Context : Sass_Options +{ + + // store context type info + enum Sass_Input_Style type; + + // generated output data + char* output_string; + + // generated source map json + char* source_map_string; + + // error status + int error_status; + char* error_json; + char* error_text; + char* error_message; + // error position + char* error_file; + size_t error_line; + size_t error_column; + 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; + char* srcmap_string; + +}; + +// Compiler states +enum Sass_Compiler_State { + SASS_COMPILER_CREATED, + SASS_COMPILER_PARSED, + SASS_COMPILER_EXECUTED +}; + +// link c and cpp context +struct Sass_Compiler { + // progress status + Sass_Compiler_State state; + // original c context + Sass_Context* c_ctx; + // Sass::Context + Sass::Context* cpp_ctx; + // Sass::Block + Sass::Block_Obj root; +}; +``` + diff --git a/src/libsass/api-context.md b/src/libsass/api-context.md new file mode 100644 index 000000000..dfd10c181 --- /dev/null +++ b/src/libsass/api-context.md @@ -0,0 +1,295 @@ +Sass Contexts come in two flavors: + +- `Sass_File_Context` +- `Sass_Data_Context` + +### Basic Usage + +```C +#include "sass/context.h" +``` + +***Sass_Options*** + +```C +// Precision for fractional numbers +int precision; +``` +```C +// Output style for the generated css code +// A value from above SASS_STYLE_* constants +int output_style; +``` +```C +// Emit comments in the generated CSS indicating +// the corresponding source line. +bool source_comments; +``` +```C +// embed sourceMappingUrl as data uri +bool source_map_embed; +``` +```C +// embed include contents in maps +bool source_map_contents; +``` +```C +// create file urls for sources +bool source_map_file_urls; +``` +```C +// Disable sourceMappingUrl in css output +bool omit_source_map_url; +``` +```C +// Treat source_string as sass (as opposed to scss) +bool is_indented_syntax_src; +``` +```C +// The input path is used for source map +// generating. It can be used to define +// something with string compilation or to +// overload the input file path. It is +// set to "stdin" for data contexts and +// to the input file on file contexts. +char* input_path; +``` +```C +// The output path is used for source map +// generating. LibSass will not write to +// this file, it is just used to create +// information in source-maps etc. +char* output_path; +``` +```C +// String to be used for indentation +const char* indent; +``` +```C +// String to be used to for line feeds +const char* linefeed; +``` +```C +// Colon-separated list of paths +// Semicolon-separated on Windows +char* include_path; +char* plugin_path; +``` +```C +// Additional include paths +// Must be null delimited +char** include_paths; +char** plugin_paths; +``` +```C +// Path to source map file +// Enables the source map generating +// Used to create sourceMappingUrl +char* source_map_file; +``` +```C +// Directly inserted in source maps +char* source_map_root; +``` +```C +// Custom functions that can be called from Sass code +Sass_C_Function_List c_functions; +``` +```C +// Callback to overload imports +Sass_C_Import_Callback importer; +``` + +***Sass_Context*** + +```C +// store context type info +enum Sass_Input_Style type; +```` +```C +// generated output data +char* output_string; +``` +```C +// generated source map json +char* source_map_string; +``` +```C +// error status +int error_status; +char* error_json; +char* error_text; +char* error_message; +// error position +char* error_file; +size_t error_line; +size_t error_column; +``` +```C +// report imported files +char** included_files; +``` + +***Sass_File_Context*** + +```C +// no additional fields required +// input_path is already on options +``` + +***Sass_Data_Context*** + +```C +// provided source string +char* source_string; +``` + +### Sass Context API + +```C +// Forward declaration +struct Sass_Compiler; + +// Forward declaration +struct Sass_Options; +struct Sass_Context; // : Sass_Options +struct Sass_File_Context; // : Sass_Context +struct Sass_Data_Context; // : Sass_Context + +// Create and initialize an option struct +struct Sass_Options* sass_make_options (void); +// Create and initialize a specific context +struct Sass_File_Context* sass_make_file_context (const char* input_path); +struct Sass_Data_Context* sass_make_data_context (char* source_string); + +// Call the compilation step for the specific context +int sass_compile_file_context (struct Sass_File_Context* ctx); +int sass_compile_data_context (struct Sass_Data_Context* ctx); + +// Create a sass compiler instance for more control +struct Sass_Compiler* sass_make_file_compiler (struct Sass_File_Context* file_ctx); +struct Sass_Compiler* sass_make_data_compiler (struct Sass_Data_Context* data_ctx); + +// Execute the different compilation steps individually +// Usefull if you only want to query the included files +int sass_compiler_parse (struct Sass_Compiler* compiler); +int sass_compiler_execute (struct Sass_Compiler* compiler); + +// Release all memory allocated with the compiler +// This does _not_ include any contexts or options +void sass_delete_compiler (struct Sass_Compiler* compiler); +void sass_delete_options(struct Sass_Options* options); + +// Release all memory allocated and also ourself +void sass_delete_file_context (struct Sass_File_Context* ctx); +void sass_delete_data_context (struct Sass_Data_Context* ctx); + +// Getters for Context from specific implementation +struct Sass_Context* sass_file_context_get_context (struct Sass_File_Context* file_ctx); +struct Sass_Context* sass_data_context_get_context (struct Sass_Data_Context* data_ctx); + +// Getters for Context_Options from Sass_Context +struct Sass_Options* sass_context_get_options (struct Sass_Context* ctx); +struct Sass_Options* sass_file_context_get_options (struct Sass_File_Context* file_ctx); +struct Sass_Options* sass_data_context_get_options (struct Sass_Data_Context* data_ctx); +void sass_file_context_set_options (struct Sass_File_Context* file_ctx, struct Sass_Options* opt); +void sass_data_context_set_options (struct Sass_Data_Context* data_ctx, struct Sass_Options* opt); + +// Getters for Sass_Context values +const char* sass_context_get_output_string (struct Sass_Context* ctx); +int sass_context_get_error_status (struct Sass_Context* ctx); +const char* sass_context_get_error_json (struct Sass_Context* ctx); +const char* sass_context_get_error_text (struct Sass_Context* ctx); +const char* sass_context_get_error_message (struct Sass_Context* ctx); +const char* sass_context_get_error_file (struct Sass_Context* ctx); +size_t sass_context_get_error_line (struct Sass_Context* ctx); +size_t sass_context_get_error_column (struct Sass_Context* ctx); +const char* sass_context_get_source_map_string (struct Sass_Context* ctx); +char** sass_context_get_included_files (struct Sass_Context* ctx); + +// Getters for Sass_Compiler options (query import stack) +size_t sass_compiler_get_import_stack_size(struct Sass_Compiler* compiler); +Sass_Import_Entry sass_compiler_get_last_import(struct Sass_Compiler* compiler); +Sass_Import_Entry sass_compiler_get_import_entry(struct Sass_Compiler* compiler, size_t idx); +// Getters for Sass_Compiler options (query function stack) +size_t sass_compiler_get_callee_stack_size(struct Sass_Compiler* compiler); +Sass_Callee_Entry sass_compiler_get_last_callee(struct Sass_Compiler* compiler); +Sass_Callee_Entry sass_compiler_get_callee_entry(struct Sass_Compiler* compiler, size_t idx); + +// Take ownership of memory (value on context is set to 0) +char* sass_context_take_error_json (struct Sass_Context* ctx); +char* sass_context_take_error_text (struct Sass_Context* ctx); +char* sass_context_take_error_message (struct Sass_Context* ctx); +char* sass_context_take_error_file (struct Sass_Context* ctx); +char* sass_context_take_output_string (struct Sass_Context* ctx); +char* sass_context_take_source_map_string (struct Sass_Context* ctx); +``` + +### Sass Options API + +```C +// Getters for Context_Option values +int sass_option_get_precision (struct Sass_Options* options); +enum Sass_Output_Style sass_option_get_output_style (struct Sass_Options* options); +bool sass_option_get_source_comments (struct Sass_Options* options); +bool sass_option_get_source_map_embed (struct Sass_Options* options); +bool sass_option_get_source_map_contents (struct Sass_Options* options); +bool sass_option_get_source_map_file_urls (struct Sass_Options* options); +bool sass_option_get_omit_source_map_url (struct Sass_Options* options); +bool sass_option_get_is_indented_syntax_src (struct Sass_Options* options); +const char* sass_option_get_indent (struct Sass_Options* options); +const char* sass_option_get_linefeed (struct Sass_Options* options); +const char* sass_option_get_input_path (struct Sass_Options* options); +const char* sass_option_get_output_path (struct Sass_Options* options); +const char* sass_option_get_source_map_file (struct Sass_Options* options); +const char* sass_option_get_source_map_root (struct Sass_Options* options); +Sass_C_Function_List sass_option_get_c_functions (struct Sass_Options* options); +Sass_C_Import_Callback sass_option_get_importer (struct Sass_Options* options); + +// Getters for Context_Option include path array +size_t sass_option_get_include_path_size(struct Sass_Options* options); +const char* sass_option_get_include_path(struct Sass_Options* options, size_t i); +// Plugin paths to load dynamic libraries work the same +size_t sass_option_get_plugin_path_size(struct Sass_Options* options); +const char* sass_option_get_plugin_path(struct Sass_Options* options, size_t i); + +// Setters for Context_Option values +void sass_option_set_precision (struct Sass_Options* options, int precision); +void sass_option_set_output_style (struct Sass_Options* options, enum Sass_Output_Style output_style); +void sass_option_set_source_comments (struct Sass_Options* options, bool source_comments); +void sass_option_set_source_map_embed (struct Sass_Options* options, bool source_map_embed); +void sass_option_set_source_map_contents (struct Sass_Options* options, bool source_map_contents); +void sass_option_set_source_map_file_urls (struct Sass_Options* options, bool source_map_file_urls); +void sass_option_set_omit_source_map_url (struct Sass_Options* options, bool omit_source_map_url); +void sass_option_set_is_indented_syntax_src (struct Sass_Options* options, bool is_indented_syntax_src); +void sass_option_set_indent (struct Sass_Options* options, const char* indent); +void sass_option_set_linefeed (struct Sass_Options* options, const char* linefeed); +void sass_option_set_input_path (struct Sass_Options* options, const char* input_path); +void sass_option_set_output_path (struct Sass_Options* options, const char* output_path); +void sass_option_set_plugin_path (struct Sass_Options* options, const char* plugin_path); +void sass_option_set_include_path (struct Sass_Options* options, const char* include_path); +void sass_option_set_source_map_file (struct Sass_Options* options, const char* source_map_file); +void sass_option_set_source_map_root (struct Sass_Options* options, const char* source_map_root); +void sass_option_set_c_functions (struct Sass_Options* options, Sass_C_Function_List c_functions); +void sass_option_set_importer (struct Sass_Options* options, Sass_C_Import_Callback importer); + +// Push function for paths (no manipulation support for now) +void sass_option_push_plugin_path (struct Sass_Options* options, const char* path); +void sass_option_push_include_path (struct Sass_Options* options, const char* path); + +// Resolve a file via the given include paths in the sass option struct +// find_file looks for the exact file name while find_include does a regular sass include +char* sass_find_file (const char* path, struct Sass_Options* opt); +char* sass_find_include (const char* path, struct Sass_Options* opt); + +// Resolve a file relative to last import or include paths in the sass option struct +// find_file looks for the exact file name while find_include does a regular sass include +char* sass_compiler_find_file (const char* path, struct Sass_Compiler* compiler); +char* sass_compiler_find_include (const char* path, struct Sass_Compiler* compiler); +``` + +### More links + +- [Sass Context Example](api-context-example.md) +- [Sass Context Internal](api-context-internal.md) + diff --git a/src/libsass/api-doc.md b/src/libsass/api-doc.md new file mode 100644 index 000000000..376561612 --- /dev/null +++ b/src/libsass/api-doc.md @@ -0,0 +1,215 @@ +## Introduction + +LibSass wouldn't be much good without a way to interface with it. These +interface documentations describe the various functions and data structures +available to implementers. They are split up over three major components, which +have all their own source files (plus some common functionality). + +- [Sass Context](api-context.md) - Trigger and handle the main Sass compilation +- [Sass Value](api-value.md) - Exchange values and its format with LibSass +- [Sass Function](api-function.md) - Get invoked by LibSass for function statments +- [Sass Importer](api-importer.md) - Get invoked by LibSass for @import statments + +### Basic usage + +First you will need to include the header file! +This will automatically load all other headers too! + +```C +#include "sass/context.h" +``` + +## Basic C Example + +```C +#include +#include "sass/context.h" + +int main() { + puts(libsass_version()); + return 0; +} +``` + +```bash +gcc -Wall version.c -lsass -o version && ./version +``` + +## More C Examples + +- [Sample code for Sass Context](api-context-example.md) +- [Sample code for Sass Value](api-value-example.md) +- [Sample code for Sass Function](api-function-example.md) +- [Sample code for Sass Importer](api-importer-example.md) + +## Compiling your code + +The most important is your sass file (or string of sass code). With this, you +will want to start a LibSass compiler. Here is some pseudocode describing the +process. The compiler has two different modes: direct input as a string with +`Sass_Data_Context` or LibSass will do file reading for you by using +`Sass_File_Context`. See the code for a list of options available +[Sass_Options](https://github.com/sass/libsass/blob/36feef0/include/sass/interface.h#L18) + +**Building a file compiler** + + context = sass_make_file_context("file.scss") + options = sass_file_context_get_options(context) + sass_option_set_precision(options, 1) + sass_option_set_source_comments(options, true) + + sass_file_context_set_options(context, options) + + compiler = sass_make_file_compiler(sass_context) + sass_compiler_parse(compiler) + sass_compiler_execute(compiler) + + output = sass_context_get_output_string(context) + // Retrieve errors during compilation + error_status = sass_context_get_error_status(context) + json_error = sass_context_get_error_json(context) + // Release memory dedicated to the C compiler + sass_delete_compiler(compiler) + +**Building a data compiler** + + context = sass_make_data_context("div { a { color: blue; } }") + options = sass_data_context_get_options(context) + sass_option_set_precision(options, 1) + sass_option_set_source_comments(options, true) + + sass_data_context_set_options(context, options) + + compiler = sass_make_data_compiler(context) + sass_compiler_parse(compiler) + sass_compiler_execute(compiler) + + output = sass_context_get_output_string(context) + // div a { color: blue; } + // Retrieve errors during compilation + error_status = sass_context_get_error_status(context) + json_error = sass_context_get_error_json(context) + // Release memory dedicated to the C compiler + sass_delete_compiler(compiler) + +## Sass Context Internals + +Everything is stored in structs: + +```C +struct Sass_Options; +struct Sass_Context : Sass_Options; +struct Sass_File_context : Sass_Context; +struct Sass_Data_context : Sass_Context; +``` + +This mirrors very well how `libsass` uses these structures. + +- `Sass_Options` holds everything you feed in before the compilation. It also hosts +`input_path` and `output_path` options, because they are used to generate/calculate +relative links in source-maps. The `input_path` is shared with `Sass_File_Context`. +- `Sass_Context` holds all the data returned by the compilation step. +- `Sass_File_Context` is a specific implementation that requires no additional fields +- `Sass_Data_Context` is a specific implementation that adds the `input_source` field + +Structs can be down-casted to access `context` or `options`! + +## Memory handling and life-cycles + +We keep memory around for as long as the main [context](api-context.md) object +is not destroyed (`sass_delete_context`). LibSass will create copies of most +inputs/options beside the main sass code. You need to allocate and fill that +buffer before passing it to LibSass. You may also overtake memory management +from libsass for certain return values (i.e. `sass_context_take_output_string`). + +```C +// to allocate buffer to be filled +void* sass_alloc_memory(size_t size); +// to allocate a buffer from existing string +char* sass_copy_c_string(const char* str); +// to free overtaken memory when done +void sass_free_memory(void* ptr); +``` + +## Miscellaneous API functions + +```C +// Some convenient string helper function +char* sass_string_unquote (const char* str); +char* sass_string_quote (const char* str, const char quote_mark); + +// Get compiled libsass version +const char* libsass_version(void); + +// Implemented sass language version +// Hardcoded version 3.4 for time being +const char* libsass_language_version(void); +``` + +## Common Pitfalls + +**input_path** + +The `input_path` is part of `Sass_Options`, but it also is the main option for +`Sass_File_Context`. It is also used to generate relative file links in source- +maps. Therefore it is pretty usefull to pass this information if you have a +`Sass_Data_Context` and know the original path. + +**output_path** + +Be aware that `libsass` does not write the output file itself. This option +merely exists to give `libsass` the proper information to generate links in +source-maps. The file has to be written to the disk by the +binding/implementation. If the `output_path` is omitted, `libsass` tries to +extrapolate one from the `input_path` by replacing (or adding) the file ending +with `.css`. + +## Error Codes + +The `error_code` is integer value which indicates the type of error that +occurred inside the LibSass process. Following is the list of error codes along +with the short description: + +* 1: normal errors like parsing or `eval` errors +* 2: bad allocation error (memory error) +* 3: "untranslated" C++ exception (`throw std::exception`) +* 4: legacy string exceptions ( `throw const char*` or `std::string` ) +* 5: Some other unknown exception + +Although for the API consumer, error codes do not offer much value except +indicating whether *any* error occurred during the compilation, it helps +debugging the LibSass internal code paths. + +## Real-World Implementations + +The proof is in the pudding, so we have highlighted a few implementations that +should be on par with the latest LibSass interface version. Some of them may not +have all features implemented! + +1. [Perl Example](https://github.com/sass/perl-libsass/blob/master/lib/CSS/Sass.xs) +2. [Go Example](https://godoc.org/github.com/wellington/go-libsass#example-Compiler--Stdin) +3. [Node Example](https://github.com/sass/node-sass/blob/master/src/binding.cpp) + +## ABI forward compatibility + +We use a functional API to make dynamic linking more robust and future +compatible. The API is not yet 100% stable, so we do not yet guarantee +[ABI](https://gcc.gnu.org/onlinedocs/libstdc++/manual/abi.html) forward +compatibility. + +## Plugins (experimental) + +LibSass can load plugins from directories. Just define `plugin_path` on context +options to load all plugins from the directories. To implement plugins, please +consult the following example implementations. + +- https://github.com/mgreter/libsass-glob +- https://github.com/mgreter/libsass-math +- https://github.com/mgreter/libsass-digest + +## Internal Structs + +- [Sass Context Internals](api-context-internal.md) +- [Sass Value Internals](api-value-internal.md) +- [Sass Function Internals](api-function-internal.md) +- [Sass Importer Internals](api-importer-internal.md) diff --git a/src/libsass/api-function-example.md b/src/libsass/api-function-example.md new file mode 100644 index 000000000..38608e1a2 --- /dev/null +++ b/src/libsass/api-function-example.md @@ -0,0 +1,67 @@ +## Example main.c + +```C +#include +#include +#include "sass/context.h" + +union Sass_Value* call_fn_foo(const union Sass_Value* s_args, Sass_Function_Entry cb, struct Sass_Compiler* comp) +{ + // get context/option struct associated with this compiler + struct Sass_Context* ctx = sass_compiler_get_context(comp); + struct Sass_Options* opts = sass_compiler_get_options(comp); + // get information about previous importer entry from the stack + Sass_Import_Entry import = sass_compiler_get_last_import(comp); + const char* prev_abs_path = sass_import_get_abs_path(import); + const char* prev_imp_path = sass_import_get_imp_path(import); + // get the cookie from function descriptor + void* cookie = sass_function_get_cookie(cb); + // we actually abuse the void* to store an "int" + return sass_make_number((intptr_t)cookie, "px"); +} + +int main( int argc, const char* argv[] ) +{ + + // get the input file from first argument or use default + const char* input = argc > 1 ? argv[1] : "styles.scss"; + + // create the file context and get all related structs + struct Sass_File_Context* file_ctx = sass_make_file_context(input); + struct Sass_Context* ctx = sass_file_context_get_context(file_ctx); + struct Sass_Options* ctx_opt = sass_context_get_options(ctx); + + // allocate a custom function caller + Sass_Function_Entry fn_foo = + sass_make_function("foo()", call_fn_foo, (void*)42); + + // create list of all custom functions + Sass_Function_List fn_list = sass_make_function_list(1); + sass_function_set_list_entry(fn_list, 0, fn_foo); + sass_option_set_c_functions(ctx_opt, fn_list); + + // context is set up, call the compile step now + int status = sass_compile_file_context(file_ctx); + + // print the result or the error to the stdout + if (status == 0) puts(sass_context_get_output_string(ctx)); + else puts(sass_context_get_error_message(ctx)); + + // release allocated memory + sass_delete_file_context(file_ctx); + + // exit status + return status; + +} +``` + +### Compile main.c + +```bash +gcc -c main.c -o main.o +gcc -o sample main.o -lsass +echo "foo { margin: foo(); }" > foo.scss +./sample foo.scss => "foo { margin: 42px }" +``` + diff --git a/src/libsass/api-function-internal.md b/src/libsass/api-function-internal.md new file mode 100644 index 000000000..69d81d04d --- /dev/null +++ b/src/libsass/api-function-internal.md @@ -0,0 +1,8 @@ +```C +// Struct to hold custom function callback +struct Sass_Function { + const char* signature; + Sass_Function_Fn function; + void* cookie; +}; +``` diff --git a/src/libsass/api-function.md b/src/libsass/api-function.md new file mode 100644 index 000000000..8d9d97ca4 --- /dev/null +++ b/src/libsass/api-function.md @@ -0,0 +1,74 @@ +Sass functions are used to define new custom functions callable by Sass code. They are also used to overload debug or error statements. You can also define a fallback function, which is called for every unknown function found in the Sass code. Functions get passed zero or more `Sass_Values` (a `Sass_List` value) and they must also return a `Sass_Value`. Return a `Sass_Error` if you want to signal an error. + +## Special signatures + +- `*` - Fallback implementation +- `@warn` - Overload warn statements +- `@error` - Overload error statements +- `@debug` - Overload debug statements + +Note: The fallback implementation will be given the name of the called function as the first argument, before all the original function arguments. These features are pretty new and should be considered experimental. + +### Basic Usage + +```C +#include "sass/functions.h" +``` + +## Sass Function API + +```C +// Forward declaration +struct Sass_Compiler; +struct Sass_Function; + +// Typedef helpers for custom functions lists +typedef struct Sass_Function (*Sass_Function_Entry); +typedef struct Sass_Function* (*Sass_Function_List); +// Typedef defining function signature and return type +typedef union Sass_Value* (*Sass_Function_Fn) + (const union Sass_Value*, Sass_Function_Entry cb, struct Sass_Compiler* compiler); + +// Creators for sass function list and function descriptors +Sass_Function_List sass_make_function_list (size_t length); +Sass_Function_Entry sass_make_function (const char* signature, Sass_Function_Fn cb, void* cookie); +// In case you need to free them yourself +void sass_delete_function (Sass_Function_Entry entry); +void sass_delete_function_list (Sass_Function_List list); + +// Setters and getters for callbacks on function lists +Sass_Function_Entry sass_function_get_list_entry(Sass_Function_List list, size_t pos); +void sass_function_set_list_entry(Sass_Function_List list, size_t pos, Sass_Function_Entry cb); + +// Setters to insert an entry into the import list (you may also use [] access directly) +// Since we are dealing with pointers they should have a guaranteed and fixed size +void sass_import_set_list_entry (Sass_Import_List list, size_t idx, Sass_Import_Entry entry); +Sass_Import_Entry sass_import_get_list_entry (Sass_Import_List list, size_t idx); + +// Getters for custom function descriptors +const char* sass_function_get_signature (Sass_Function_Entry cb); +Sass_Function_Fn sass_function_get_function (Sass_Function_Entry cb); +void* sass_function_get_cookie (Sass_Function_Entry cb); + +// Getters for callee entry +const char* sass_callee_get_name (Sass_Callee_Entry); +const char* sass_callee_get_path (Sass_Callee_Entry); +size_t sass_callee_get_line (Sass_Callee_Entry); +size_t sass_callee_get_column (Sass_Callee_Entry); +enum Sass_Callee_Type sass_callee_get_type (Sass_Callee_Entry); +Sass_Env_Frame sass_callee_get_env (Sass_Callee_Entry); + +// Getters and Setters for environments (lexical, local and global) +union Sass_Value* sass_env_get_lexical (Sass_Env_Frame, const char*); +void sass_env_set_lexical (Sass_Env_Frame, const char*, union Sass_Value*); +union Sass_Value* sass_env_get_local (Sass_Env_Frame, const char*); +void sass_env_set_local (Sass_Env_Frame, const char*, union Sass_Value*); +union Sass_Value* sass_env_get_global (Sass_Env_Frame, const char*); +void sass_env_set_global (Sass_Env_Frame, const char*, union Sass_Value*); +``` + +### More links + +- [Sass Function Example](api-function-example.md) +- [Sass Function Internal](api-function-internal.md) + diff --git a/src/libsass/api-importer-example.md b/src/libsass/api-importer-example.md new file mode 100644 index 000000000..d83bf2609 --- /dev/null +++ b/src/libsass/api-importer-example.md @@ -0,0 +1,112 @@ +## Example importer.c + +```C +#include +#include +#include "sass/context.h" + +Sass_Import_List sass_importer(const char* path, Sass_Importer_Entry cb, struct Sass_Compiler* comp) +{ + // get the cookie from importer descriptor + void* cookie = sass_importer_get_cookie(cb); + Sass_Import_List list = sass_make_import_list(2); + char* local = sass_copy_c_string("local { color: green; }"); + char* remote = sass_copy_c_string("remote { color: red; }"); + list[0] = sass_make_import_entry("/tmp/styles.scss", local, 0); + list[1] = sass_make_import_entry("http://www.example.com", remote, 0); + return list; +} + +int main( int argc, const char* argv[] ) +{ + + // get the input file from first argument or use default + const char* input = argc > 1 ? argv[1] : "styles.scss"; + + // create the file context and get all related structs + struct Sass_File_Context* file_ctx = sass_make_file_context(input); + struct Sass_Context* ctx = sass_file_context_get_context(file_ctx); + struct Sass_Options* ctx_opt = sass_context_get_options(ctx); + + // allocate custom importer + Sass_Importer_Entry c_imp = + sass_make_importer(sass_importer, 0, 0); + // create list for all custom importers + Sass_Importer_List imp_list = sass_make_importer_list(1); + // put only the importer on to the list + sass_importer_set_list_entry(imp_list, 0, c_imp); + // register list on to the context options + sass_option_set_c_importers(ctx_opt, imp_list); + // context is set up, call the compile step now + int status = sass_compile_file_context(file_ctx); + + // print the result or the error to the stdout + if (status == 0) puts(sass_context_get_output_string(ctx)); + else puts(sass_context_get_error_message(ctx)); + + // release allocated memory + sass_delete_file_context(file_ctx); + + // exit status + return status; + +} +``` + +Compile importer.c + +```bash +gcc -c importer.c -o importer.o +gcc -o importer importer.o -lsass +echo "@import 'foobar';" > importer.scss +./importer importer.scss +``` + +## Importer Behavior Examples + +```C +Sass_Import_List importer(const char* path, Sass_Importer_Entry cb, struct Sass_Compiler* comp) { + // let LibSass handle the import request + return NULL; +} + +Sass_Import_List importer(const char* path, Sass_Importer_Entry cb, struct Sass_Compiler* comp) { + // let LibSass handle the request + // swallows »@import "http://…"« pass-through + // (arguably a bug) + Sass_Import_List list = sass_make_import_list(1); + list[0] = sass_make_import_entry(path, 0, 0); + return list; +} + +Sass_Import_List importer(const char* path, Sass_Importer_Entry cb, struct Sass_Compiler* comp) { + // return an error to halt execution + Sass_Import_List list = sass_make_import_list(1); + const char* message = "some error message"; + list[0] = sass_make_import_entry(path, 0, 0); + sass_import_set_error(list[0], sass_copy_c_string(message), 0, 0); + return list; +} + +Sass_Import_List importer(const char* path, Sass_Importer_Entry cb, struct Sass_Compiler* comp) { + // let LibSass load the file identifed by the importer + Sass_Import_List list = sass_make_import_list(1); + list[0] = sass_make_import_entry("/tmp/file.scss", 0, 0); + return list; +} + +Sass_Import_List importer(const char* path, Sass_Importer_Entry cb, struct Sass_Compiler* comp) { + // completely hide the import + // (arguably a bug) + Sass_Import_List list = sass_make_import_list(0); + return list; +} + +Sass_Import_List importer(const char* path, Sass_Importer_Entry cb, struct Sass_Compiler* comp) { + // completely hide the import + // (arguably a bug) + Sass_Import_List list = sass_make_import_list(1); + list[0] = sass_make_import_entry(0, 0, 0); + return list; +} +``` diff --git a/src/libsass/api-importer-internal.md b/src/libsass/api-importer-internal.md new file mode 100644 index 000000000..63d70fe75 --- /dev/null +++ b/src/libsass/api-importer-internal.md @@ -0,0 +1,20 @@ +```C +// External import entry +struct Sass_Import { + char* imp_path; // path as found in the import statement + char *abs_path; // path after importer has resolved it + char* source; + char* srcmap; + // error handling + char* error; + size_t line; + size_t column; +}; + +// Struct to hold importer callback +struct Sass_Importer { + Sass_Importer_Fn importer; + double priority; + void* cookie; +}; +``` diff --git a/src/libsass/api-importer.md b/src/libsass/api-importer.md new file mode 100644 index 000000000..b6265002e --- /dev/null +++ b/src/libsass/api-importer.md @@ -0,0 +1,86 @@ +By using custom importers, Sass stylesheets can be implemented in any possible way, such as by being loaded via a remote server. Please note: this feature is experimental and is implemented differently than importers in Ruby Sass. Imports must be relative to the parent import context and therefore we need to pass this information to the importer callback. This is currently done by passing the complete import string/path of the previous import context. + +## Return Imports + +You actually have to return a list of imports, since some importers may want to import multiple files from one import statement (ie. a glob/star importer). The memory you pass with source and srcmap is taken over by LibSass and freed automatically when the import is done. You are also allowed to return `0` instead of a list, which will tell LibSass to handle the import by itself (as if no custom importer was in use). + +```C +Sass_Import_Entry* rv = sass_make_import_list(1); +rv[0] = sass_make_import(rel, abs, source, srcmap); +``` + +Every import will then be included in LibSass. You are allowed to only return a file path without any loaded source. This way you can ie. implement rewrite rules for import paths and leave the loading part for LibSass. + +Please note that LibSass doesn't use the srcmap parameter yet. It has been added to not deprecate the C-API once support has been implemented. It will be used to re-map the actual sourcemap with the provided ones. + +### Basic Usage + +```C +#include "sass/functions.h" +``` + +## Sass Importer API + +```C +// Forward declaration +struct Sass_Import; + +// Forward declaration +struct Sass_C_Import_Descriptor; + +// Typedef defining the custom importer callback +typedef struct Sass_C_Import_Descriptor (*Sass_C_Import_Callback); +// Typedef defining the importer c function prototype +typedef Sass_Import_Entry* (*Sass_C_Import_Fn) (const char* url, const char* prev, void* cookie); + +// Creators for custom importer callback (with some additional pointer) +// The pointer is mostly used to store the callback into the actual function +Sass_C_Import_Callback sass_make_importer (Sass_C_Import_Fn, void* cookie); + +// Getters for import function descriptors +Sass_C_Import_Fn sass_import_get_function (Sass_C_Import_Callback fn); +void* sass_import_get_cookie (Sass_C_Import_Callback fn); + +// Deallocator for associated memory +void sass_delete_importer (Sass_C_Import_Callback fn); + +// Creator for sass custom importer return argument list +Sass_Import_Entry* sass_make_import_list (size_t length); +// Creator for a single import entry returned by the custom importer inside the list +Sass_Import_Entry sass_make_import_entry (const char* path, char* source, char* srcmap); +Sass_Import_Entry sass_make_import (const char* rel, const char* abs, char* source, char* srcmap); + +// set error message to abort import and to print out a message (path from existing object is used in output) +Sass_Import_Entry sass_import_set_error(Sass_Import_Entry import, const char* message, size_t line, size_t col); + +// Setters to insert an entry into the import list (you may also use [] access directly) +// Since we are dealing with pointers they should have a guaranteed and fixed size +void sass_import_set_list_entry (Sass_Import_Entry* list, size_t idx, Sass_Import_Entry entry); +Sass_Import_Entry sass_import_get_list_entry (Sass_Import_Entry* list, size_t idx); + +// Getters for import entry +const char* sass_import_get_imp_path (Sass_Import_Entry); +const char* sass_import_get_abs_path (Sass_Import_Entry); +const char* sass_import_get_source (Sass_Import_Entry); +const char* sass_import_get_srcmap (Sass_Import_Entry); +// Explicit functions to take ownership of these items +// The property on our struct will be reset to NULL +char* sass_import_take_source (Sass_Import_Entry); +char* sass_import_take_srcmap (Sass_Import_Entry); + +// Getters for import error entries +size_t sass_import_get_error_line (Sass_Import_Entry); +size_t sass_import_get_error_column (Sass_Import_Entry); +const char* sass_import_get_error_message (Sass_Import_Entry); + +// Deallocator for associated memory (incl. entries) +void sass_delete_import_list (Sass_Import_Entry*); +// Just in case we have some stray import structs +void sass_delete_import (Sass_Import_Entry); +``` + +### More links + +- [Sass Importer Example](api-importer-example.md) +- [Sass Importer Internal](api-importer-internal.md) + diff --git a/src/libsass/api-value-example.md b/src/libsass/api-value-example.md new file mode 100644 index 000000000..690654eaf --- /dev/null +++ b/src/libsass/api-value-example.md @@ -0,0 +1,55 @@ +## Example operation.c + +```C +#include +#include +#include "sass/values.h" + +int main( int argc, const char* argv[] ) +{ + + // create two new sass values to be added + union Sass_Value* string = sass_make_string("String"); + union Sass_Value* number = sass_make_number(42, "nits"); + + // invoke the add operation which returns a new sass value + union Sass_Value* total = sass_value_op(ADD, string, number); + + // no further use for the two operands + sass_delete_value(string); + sass_delete_value(number); + + // this works since libsass will always return a + // string for add operations with a string as the + // left hand side. But you should never rely on it! + puts(sass_string_get_value(total)); + + // invoke stringification (uncompressed with precision of 5) + union Sass_Value* result = sass_value_stringify(total, false, 5); + + // no further use for the sum + sass_delete_value(total); + + // print the result - you may want to make + // sure result is indeed a string, altough + // stringify guarantees to return a string + // if (sass_value_is_string(result)) {} + // really depends on your level of paranoia + puts(sass_string_get_value(result)); + + // finally free result + sass_delete_value(result); + + // exit status + return 0; + +} +``` + +## Compile operation.c + +```bash +gcc -c operation.c -o operation.o +gcc -o operation operation.o -lsass +./operation # => String42nits +``` diff --git a/src/libsass/api-value-internal.md b/src/libsass/api-value-internal.md new file mode 100644 index 000000000..fed402256 --- /dev/null +++ b/src/libsass/api-value-internal.md @@ -0,0 +1,76 @@ +```C +struct Sass_Unknown { + enum Sass_Tag tag; +}; + +struct Sass_Boolean { + enum Sass_Tag tag; + bool value; +}; + +struct Sass_Number { + enum Sass_Tag tag; + double value; + char* unit; +}; + +struct Sass_Color { + enum Sass_Tag tag; + double r; + double g; + double b; + double a; +}; + +struct Sass_String { + enum Sass_Tag tag; + char* value; +}; + +struct Sass_List { + enum Sass_Tag tag; + enum Sass_Separator separator; + size_t length; + // null terminated "array" + union Sass_Value** values; +}; + +struct Sass_Map { + enum Sass_Tag tag; + size_t length; + struct Sass_MapPair* pairs; +}; + +struct Sass_Null { + enum Sass_Tag tag; +}; + +struct Sass_Error { + enum Sass_Tag tag; + char* message; +}; + +struct Sass_Warning { + enum Sass_Tag tag; + char* message; +}; + +union Sass_Value { + struct Sass_Unknown unknown; + struct Sass_Boolean boolean; + struct Sass_Number number; + struct Sass_Color color; + struct Sass_String string; + struct Sass_List list; + struct Sass_Map map; + struct Sass_Null null; + struct Sass_Error error; + struct Sass_Warning warning; +}; + +struct Sass_MapPair { + union Sass_Value* key; + union Sass_Value* value; +}; +``` + diff --git a/src/libsass/api-value.md b/src/libsass/api-value.md new file mode 100644 index 000000000..d78625875 --- /dev/null +++ b/src/libsass/api-value.md @@ -0,0 +1,154 @@ +`Sass_Values` are used to pass values and their types between the implementer +and LibSass. Sass knows various different value types (including nested arrays +and hash-maps). If you implement a binding to another programming language, you +have to find a way to [marshal][1] (convert) `Sass_Values` between the target +language and C. `Sass_Values` are currently only used by custom functions, but +it should also be possible to use them without a compiler context. + +[1]: https://en.wikipedia.org/wiki/Marshalling_%28computer_science%29 + +### Basic Usage + +```C +#include "sass/values.h" +``` + +```C +// Type for Sass values +enum Sass_Tag { + SASS_BOOLEAN, + SASS_NUMBER, + SASS_COLOR, + SASS_STRING, + SASS_LIST, + SASS_MAP, + SASS_NULL, + SASS_ERROR, + SASS_WARNING +}; + +// Tags for denoting Sass list separators +enum Sass_Separator { + SASS_COMMA, + SASS_SPACE, + // only used internally to represent a hash map before evaluation + // otherwise we would be too early to check for duplicate keys + SASS_HASH +}; + +// Value Operators +enum Sass_OP { + AND, OR, // logical connectives + EQ, NEQ, GT, GTE, LT, LTE, // arithmetic relations + ADD, SUB, MUL, DIV, MOD, // arithmetic functions + NUM_OPS // so we know how big to make the op table +}; +``` + +### Sass Value API + +```C +// Forward declaration +union Sass_Value; + +// Creator functions for all value types +union Sass_Value* sass_make_null (void); +union Sass_Value* sass_make_boolean (bool val); +union Sass_Value* sass_make_string (const char* val); +union Sass_Value* sass_make_qstring (const char* val); +union Sass_Value* sass_make_number (double val, const char* unit); +union Sass_Value* sass_make_color (double r, double g, double b, double a); +union Sass_Value* sass_make_list (size_t len, enum Sass_Separator sep, bool is_bracketed); +union Sass_Value* sass_make_map (size_t len); +union Sass_Value* sass_make_error (const char* msg); +union Sass_Value* sass_make_warning (const char* msg); + +// Generic destructor function for all types +// Will release memory of all associated Sass_Values +// Means we will delete recursively for lists and maps +void sass_delete_value (union Sass_Value* val); + +// Make a deep cloned copy of the given sass value +union Sass_Value* sass_clone_value (const union Sass_Value* val); + +// Stringify a Sass_Values and also return the result as a Sass_Value (of type STRING) +union Sass_Value* sass_value_stringify (const union Sass_Value* a, bool compressed, int precision); + +// Execute an operation for two Sass_Values and return the result as a Sass_Value too +union Sass_Value* sass_value_op (enum Sass_OP op, const union Sass_Value* a, const union Sass_Value* b); + +// Return the sass tag for a generic sass value +// Check is needed before accessing specific values! +enum Sass_Tag sass_value_get_tag (const union Sass_Value* v); + +// Check value to be of a specific type +// Can also be used before accessing properties! +bool sass_value_is_null (const union Sass_Value* v); +bool sass_value_is_number (const union Sass_Value* v); +bool sass_value_is_string (const union Sass_Value* v); +bool sass_value_is_boolean (const union Sass_Value* v); +bool sass_value_is_color (const union Sass_Value* v); +bool sass_value_is_list (const union Sass_Value* v); +bool sass_value_is_map (const union Sass_Value* v); +bool sass_value_is_error (const union Sass_Value* v); +bool sass_value_is_warning (const union Sass_Value* v); + +// Getters and setters for Sass_Number +double sass_number_get_value (const union Sass_Value* v); +void sass_number_set_value (union Sass_Value* v, double value); +const char* sass_number_get_unit (const union Sass_Value* v); +void sass_number_set_unit (union Sass_Value* v, char* unit); + +// Getters and setters for Sass_String +const char* sass_string_get_value (const union Sass_Value* v); +void sass_string_set_value (union Sass_Value* v, char* value); +bool sass_string_is_quoted(const union Sass_Value* v); +void sass_string_set_quoted(union Sass_Value* v, bool quoted); + +// Getters and setters for Sass_Boolean +bool sass_boolean_get_value (const union Sass_Value* v); +void sass_boolean_set_value (union Sass_Value* v, bool value); + +// Getters and setters for Sass_Color +double sass_color_get_r (const union Sass_Value* v); +void sass_color_set_r (union Sass_Value* v, double r); +double sass_color_get_g (const union Sass_Value* v); +void sass_color_set_g (union Sass_Value* v, double g); +double sass_color_get_b (const union Sass_Value* v); +void sass_color_set_b (union Sass_Value* v, double b); +double sass_color_get_a (const union Sass_Value* v); +void sass_color_set_a (union Sass_Value* v, double a); + +// Getter for the number of items in list +size_t sass_list_get_length (const union Sass_Value* v); +// Getters and setters for Sass_List +enum Sass_Separator sass_list_get_separator (const union Sass_Value* v); +void sass_list_set_separator (union Sass_Value* v, enum Sass_Separator value); +bool sass_list_get_is_bracketed (const union Sass_Value* v); +void sass_list_set_is_bracketed (union Sass_Value* v, bool value); +// Getters and setters for Sass_List values +union Sass_Value* sass_list_get_value (const union Sass_Value* v, size_t i); +void sass_list_set_value (union Sass_Value* v, size_t i, union Sass_Value* value); + +// Getter for the number of items in map +size_t sass_map_get_length (const union Sass_Value* v); +// Getters and setters for Sass_Map keys and values +union Sass_Value* sass_map_get_key (const union Sass_Value* v, size_t i); +void sass_map_set_key (union Sass_Value* v, size_t i, union Sass_Value*); +union Sass_Value* sass_map_get_value (const union Sass_Value* v, size_t i); +void sass_map_set_value (union Sass_Value* v, size_t i, union Sass_Value*); + +// Getters and setters for Sass_Error +char* sass_error_get_message (const union Sass_Value* v); +void sass_error_set_message (union Sass_Value* v, char* msg); + +// Getters and setters for Sass_Warning +char* sass_warning_get_message (const union Sass_Value* v); +void sass_warning_set_message (union Sass_Value* v, char* msg); +``` + +### More links + +- [Sass Value Example](api-value-example.md) +- [Sass Value Internal](api-value-internal.md) + diff --git a/src/libsass/build-on-darwin.md b/src/libsass/build-on-darwin.md new file mode 100644 index 000000000..119a5350e --- /dev/null +++ b/src/libsass/build-on-darwin.md @@ -0,0 +1,27 @@ +To install LibSass, make sure the OS X build tools are installed: + + xcode-select --install + +## Homebrew + +To install homebrew, see [http://brew.sh](http://brew.sh) + + ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" + +You can install the latest version of LibSass quite easily with brew. + + brew install --HEAD libsass + +To update this, do: + + brew reinstall --HEAD libsass + +Brew will build static and shared libraries, and a `libsass.pc` file in `/usr/local/lib/pkgconfig`. + +To use `libsass.pc`, make sure this path is in your `PKG_CONFIG_PATH` + + export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig + +## Manually + +See the linux instructions [Building-with-autotools](build-with-autotools.md) or [Building-with-makefiles](build-with-makefiles.md) diff --git a/src/libsass/build-on-gentoo.md b/src/libsass/build-on-gentoo.md new file mode 100644 index 000000000..601b1fe5e --- /dev/null +++ b/src/libsass/build-on-gentoo.md @@ -0,0 +1,55 @@ +Here are two ebuilds to compile LibSass and sassc on gentoo linux. If you do not know how to use these ebuilds, you should probably read the gentoo wiki page about [portage overlays](http://wiki.gentoo.org/wiki/Overlay). + +## www-misc/libsass/libsass-9999.ebuild +```ebuild +EAPI=4 + +inherit eutils git-2 autotools + +DESCRIPTION="A C/C++ implementation of a Sass compiler." +HOMEPAGE="http://libsass.org/" +EGIT_PROJECT='libsass' +EGIT_REPO_URI="https://github.com/sass/libsass.git" +LICENSE="MIT" +SLOT="0" +KEYWORDS="" +IUSE="" +DEPEND="" +RDEPEND="${DEPEND}" +DEPEND="${DEPEND}" + +pkg_pretend() { + # older gcc is not supported + local major=$(gcc-major-version) + local minor=$(gcc-minor-version) + [[ "${MERGE_TYPE}" != "binary" && ( $major > 4 || ( $major == 4 && $minor < 5 ) ) ]] && \ + die "Sorry, but gcc earlier than 4.5 will not work for LibSass." +} + +src_prepare() { + eautoreconf +} +``` + +## www-misc/sassc/sassc-9999.ebuild +```ebuild +EAPI=4 + +inherit eutils git-2 autotools + +DESCRIPTION="Command Line Tool for LibSass." +HOMEPAGE="http://libsass.org/" +EGIT_PROJECT='sassc' +EGIT_REPO_URI="https://github.com/sass/sassc.git" +LICENSE="MIT" +SLOT="0" +KEYWORDS="" +IUSE="" +DEPEND="www-misc/libsass" +RDEPEND="${DEPEND}" +DEPEND="${DEPEND}" + +src_prepare() { + eautoreconf +} +``` diff --git a/src/libsass/build-on-windows.md b/src/libsass/build-on-windows.md new file mode 100644 index 000000000..0afaa2e4c --- /dev/null +++ b/src/libsass/build-on-windows.md @@ -0,0 +1,139 @@ +We support builds via MingGW and via Visual Studio Community 2013. +Both should be considered experimental (MinGW was better tested)! + +## Building via MingGW (makefiles) + +First grab the latest [MinGW for windows][1] installer. Once it is installed, you can click on continue or open the Installation Manager via `bin\mingw-get.exe`. + +You need to have the following components installed: +![Visualization of components installed in the interface](https://cloud.githubusercontent.com/assets/282293/5525466/947bf396-89e6-11e4-841d-4aa916f14de1.png) + +Next we need to install [git for windows][2]. You probably want to check the option to add it to the global path, but you do not need to install the unix tools. + +If you want to run the spec test-suite you also need [ruby][3] and a few gems available. Grab the [latest installer][3] and make sure to add it the global path. Then install the missing gems: + +```bash +gem install minitest +``` + +### Mount the mingw root directory + +As mentioned in the [MinGW Getting Started](http://www.mingw.org/wiki/Getting_Started#toc5) guide, you should edit `C:\MinGW\msys\1.0\etc\fstab` to contain the following line: + +``` +C:\MinGW /mingw +``` + +### Starting a "MingGW" console + +Create a batch file with this content: +```bat +@echo off +set PATH=C:\MinGW\bin;%PATH% +REM only needed if not already available +set PATH=%PROGRAMFILES%\git\bin;%PATH% +REM C:\MinGW\msys\1.0\msys.bat +cmd +``` + +Execute it and make sure these commands can be called: `git`, `mingw32-make`, `rm` and `gcc`! Once this is all set, you should be ready to compile `libsass`! + +### Get the sources + +```bash +# using git is preferred +git clone https://github.com/sass/libsass.git +# only needed for sassc and/or testsuite +git clone https://github.com/sass/sassc.git libsass/sassc +git clone https://github.com/sass/sass-spec.git libsass/sass-spec +``` + +### Decide for static or shared library + +`libsass` can be built and linked as a `static` or as a `shared` library. The default is `static`. To change it you can set the `BUILD` environment variable: + +```bat +set BUILD="shared" +``` + +### Compile the library +```bash +mingw32-make -C libsass +``` + +### Results can be found in +```bash +$ ls libsass/lib +libsass.a libsass.dll libsass.so +``` + +### Run the spec test-suite +```bash +mingw32-make -C libsass test_build +``` + +## Building via MingGW 64bit (makefiles) +Building libass to dll on window 64bit. + ++ downloads [MinGW64 for windows7 64bit](http://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win64/Personal%20Builds/mingw-builds/4.9.2/threads-win32/seh/x86_64-4.9.2-release-win32-seh-rt_v3-rev0.7z/download) , and unzip to "C:\mingw64". + ++ Create a batch file with this content: + +```bat +@echo off +set PATH=C:\mingw64\bin;%PATH% +set CC=gcc +REM only needed if not already available +set PATH=%PROGRAMFILES%\Git\bin;%PATH% +REM C:\MinGW\msys\1.0\msys.bat +cmd +``` + ++ By default , mingw64 dll will depends on "​m​i​n​g​w​m​1​0​.​d​l​l​、​ ​l​i​b​g​c​c​_​s​_​d​w​2​-​1​.​d​l​l​" , we can modify Makefile to fix this:(add "-static") + +``` bash +lib/libsass.dll: $(COBJECTS) $(OBJECTS) $(RCOBJECTS) + $(MKDIR) lib + $(CXX) -shared $(LDFLAGS) -o $@ $(COBJECTS) $(OBJECTS) $(RCOBJECTS) $(LDLIBS) -s -static -Wl,--subsystem,windows,--out-implib,lib/libsass.a +``` + ++ Compile the library + +```bash +mingw32-make -C libsass +``` + +By the way , if you are using java jna , [JNAerator](http://jnaerator.googlecode.com/) is a good tool. + +## Building via Visual Studio Community 2013 + +Open a Visual Studio 2013 command prompt: +- `VS2013 x86 Native Tools Command Prompt` + +Note: When I installed the community edition, I only got the 2012 command prompts. I copied them from the Startmenu to the Desktop and adjusted the paths from `Visual Studio 11.0` to `Visual Studio 12.0`. Since `libsass` uses some `C++11` features, you need at least a MSVC 2013 compiler (v120). + +### Get the source +```bash +# using git is preferred +git clone https://github.com/sass/libsass.git +git clone https://github.com/sass/sassc.git libsass/sassc +# only needed if you want to run the testsuite +git clone https://github.com/sass/sass-spec.git libsass/sass-spec +``` + +### Compile sassc + +Sometimes `msbuild` seems not available from the command prompt. Just search for it and add it to the global path. It seems to be included in the .net folders too. + +```bat +cd libsass +REM set PATH=%PATH%;%PROGRAMFILES%\MSBuild\12.0\Bin +msbuild /m:4 /p:Configuration=Release win\libsass.sln +REM running the spec test-suite manually (needs ruby and minitest gem) +ruby sass-spec\sass-spec.rb -V 3.5 -c win\bin\sassc.exe -s --impl libsass sass-spec/spec +cd .. +``` + +[1]: http://sourceforge.net/projects/mingw/files/latest/download?source=files +[2]: https://msysgit.github.io/ +[3]: http://rubyinstaller.org/ diff --git a/src/libsass/build-shared-library.md b/src/libsass/build-shared-library.md new file mode 100644 index 000000000..3c143b46a --- /dev/null +++ b/src/libsass/build-shared-library.md @@ -0,0 +1,35 @@ +This page is mostly intended for people that want to build a system library that gets distributed via RPMs or other means. This is currently in a experimental phase, as we currently do not really guarantee any ABI forward compatibility. The C API was rewritten to make this possible in the future, but we want to wait some more time till we can call this final and stable. + +Building via autotools +-- + +You want to build a system library only via autotools, since it will create the proper `libtool` files to make it loadable on multiple systems. We hope this works correctly, but nobody of the `libsass` core team has much knowledge in this area. Therefore we are open for comments or improvements by people that have more experience in that matter (like package maintainers from various linux distributions). + +```bash +apt-get install autoconf libtool +git clone https://github.com/sass/libsass.git +cd libsass +autoreconf --force --install +./configure \ + --disable-tests \ + --disable-static \ + --enable-shared \ + --prefix=/usr +make -j5 install +cd .. +``` + +This should install these files +```bash +# $ ls -la /usr/lib/libsass.* +/usr/lib/libsass.la +/usr/lib/libsass.so -> libsass.so.0.0.9 +/usr/lib/libsass.so.0 -> libsass.so.0.0.9 +/usr/lib/libsass.so.0.0.9 +# $ ls -la /usr/include/sass* +/usr/include/sass.h +/usr/include/sass2scss.h +/usr/include/sass/context.h +/usr/include/sass/functions.h +/usr/include/sass/values.h +``` diff --git a/src/libsass/build-with-autotools.md b/src/libsass/build-with-autotools.md new file mode 100644 index 000000000..a48ed18aa --- /dev/null +++ b/src/libsass/build-with-autotools.md @@ -0,0 +1,78 @@ +### Get the sources +```bash +# using git is preferred +git clone https://github.com/sass/libsass.git +# only needed for sassc and/or testsuite +git clone https://github.com/sass/sassc.git libsass/sassc +git clone https://github.com/sass/sass-spec.git libsass/sass-spec +``` + +### Prerequisites + +In order to run autotools you need a few tools installed on your system. +```bash +yum install automake libtool # RedHat Linux +emerge -a automake libtool # Gentoo Linux +pkgin install automake libtool # SmartOS +``` + + +### Create configure script +```bash +cd libsass +autoreconf --force --install +cd .. +``` + +### Create custom makefiles +```bash +cd libsass +./configure \ + --disable-tests \ + --disable-shared \ + --prefix=/usr +cd .. +``` + +### Build the library +```bash +make -C libsass -j5 +``` + +### Install the library +The library will be installed to the location given as `prefix` to `configure`. This is standard behavior for autotools and not `libsass` specific. +```bash +make -C libsass -j5 install +``` + +### Configure options +The `configure` script is created by autotools. To get an overview of available options you can call `./configure --help`. When you execute this script, it will create specific makefiles, which you then use via the regular make command. + +There are some `libsass` specific options: + +``` +Optional Features: + --enable-tests enable testing the build + --enable-coverage enable coverage report for test suite + --enable-shared build shared libraries [default=yes] + --enable-static build static libraries [default=yes] + +Optional Packages: + --with-sassc-dir= specify directory of sassc sources for + testing (default: sassc) + --with-sass-spec-dir= specify directory of sass-spec for testing + (default: sass-spec) +``` + +### Build sassc and run spec test-suite + +```bash +cd libsass +autoreconf --force --install +./configure \ + --enable-tests \ + --enable-shared \ + --prefix=/usr +make -j5 test_build +cd .. +``` diff --git a/src/libsass/build-with-makefiles.md b/src/libsass/build-with-makefiles.md new file mode 100644 index 000000000..7ae2e33d6 --- /dev/null +++ b/src/libsass/build-with-makefiles.md @@ -0,0 +1,68 @@ +### Get the sources +```bash +# using git is preferred +git clone https://github.com/sass/libsass.git +# only needed for sassc and/or testsuite +git clone https://github.com/sass/sassc.git libsass/sassc +git clone https://github.com/sass/sass-spec.git libsass/sass-spec +``` + +### Decide for static or shared library + +`libsass` can be built and linked as a `static` or as a `shared` library. The default is `static`. To change it you can set the `BUILD` environment variable: + +```bash +export BUILD="shared" +``` + +Alternatively you can also define it directly when calling make: + +```bash +BUILD="shared" make ... +``` + +### Compile the library +```bash +make -C libsass -j5 +``` + +### Results can be found in +```bash +$ ls libsass/lib +libsass.a libsass.so +``` + +### Install onto the system + +We recommend to use [autotools to install](build-with-autotools.md) libsass onto the +system, since that brings all the benefits of using libtools as the main install method. +If you still want to install libsass via the makefile, you need to make sure that gnu +`install` utility (or compatible) is installed on your system. +```bash +yum install coreutils # RedHat Linux +emerge -a coreutils # Gentoo Linux +pkgin install coreutils # SmartOS +``` + +You can set the install location by setting `PREFIX` +```bash +PREFIX="/opt/local" make install +``` + + +### Compling sassc + +```bash +# Let build know library location +export SASS_LIBSASS_PATH="`pwd`/libsass" +# Invokes the sassc makefile +make -C libsass -j5 sassc +``` + +### Run the spec test-suite + +```bash +# needs ruby available +# also gem install minitest +make -C libsass -j5 test_build +``` diff --git a/src/libsass/build-with-mingw.md b/src/libsass/build-with-mingw.md new file mode 100644 index 000000000..416507f3c --- /dev/null +++ b/src/libsass/build-with-mingw.md @@ -0,0 +1,107 @@ +## Building LibSass with MingGW (makefiles) + +First grab the latest [MinGW for windows][1] installer. Once it is installed, you can click on continue or open the Installation Manager via `bin\mingw-get.exe`. + +You need to have the following components installed: +![](https://cloud.githubusercontent.com/assets/282293/5525466/947bf396-89e6-11e4-841d-4aa916f14de1.png) + +Next we need to install [git for windows][2]. You probably want to check the option to add it to the global path, but you do not need to install the unix tools. + +If you want to run the spec test-suite you also need [ruby][3] and a few gems available. Grab the [latest installer][3] and make sure to add it the global path. Then install the missing gems: + +```bash +gem install minitest +``` + +### Mount the mingw root directory + +As mentioned in the [MinGW Getting Started](http://www.mingw.org/wiki/Getting_Started#toc5) guide, you should edit `C:\MinGW\msys\1.0\etc\fstab` to contain the following line: + +``` +C:\MinGW /mingw +``` + +### Starting a "MingGW" console + +Create a batch file with this content: +```bat +@echo off +set PATH=C:\MinGW\bin;%PATH% +REM only needed if not already available +set PATH=%PROGRAMFILES%\git\bin;%PATH% +REM C:\MinGW\msys\1.0\msys.bat +cmd +``` + +Execute it and make sure these commands can be called: `git`, `mingw32-make`, `rm` and `gcc`! Once this is all set, you should be ready to compile `libsass`! + +### Get the sources + +```bash +# using git is preferred +git clone https://github.com/sass/libsass.git +# only needed for sassc and/or testsuite +git clone https://github.com/sass/sassc.git libsass/sassc +git clone https://github.com/sass/sass-spec.git libsass/sass-spec +``` + +### Decide for static or shared library + +`libsass` can be built and linked as a `static` or as a `shared` library. The default is `static`. To change it you can set the `BUILD` environment variable: + +```bat +set BUILD="shared" +``` + +### Compile the library +```bash +mingw32-make -C libsass +``` + +### Results can be found in +```bash +$ ls libsass/lib +libsass.a libsass.dll libsass.so +``` + +### Run the spec test-suite +```bash +mingw32-make -C libsass test_build +``` + +## Building via MingGW 64bit (makefiles) +Building libass to dll on window 64bit. + +Download [MinGW64 for windows7 64bit](http://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win64/Personal%20Builds/mingw-builds/4.9.2/threads-win32/seh/x86_64-4.9.2-release-win32-seh-rt_v3-rev0.7z/download) and unzip to "C:\mingw64". + +Create a batch file with this content: + +```bat +@echo off +set PATH=C:\mingw64\bin;%PATH% +set CC=gcc +REM only needed if not already available +set PATH=%PROGRAMFILES%\Git\bin;%PATH% +REM C:\MinGW\msys\1.0\msys.bat +cmd +``` + +By default, mingw64 dll will depends on "​m​i​n​g​w​m​1​0​.​d​l​l​、​ ​l​i​b​g​c​c​_​s​_​d​w​2​-​1​.​d​l​l​", we can modify Makefile to fix this:(add "-static") + +``` bash +lib/libsass.dll: $(COBJECTS) $(OBJECTS) $(RCOBJECTS) + $(MKDIR) lib + $(CXX) -shared $(LDFLAGS) -o $@ $(COBJECTS) $(OBJECTS) $(RCOBJECTS) $(LDLIBS) -s -static -Wl,--subsystem,windows,--out-implib,lib/libsass.a +``` + +Compile the library + +```bash +mingw32-make -C libsass +``` + +By the way, if you are using java jna, [JNAerator](http://jnaerator.googlecode.com/) is a good tool. + +[1]: http://sourceforge.net/projects/mingw/files/latest/download?source=files +[2]: https://msysgit.github.io/ +[3]: http://rubyinstaller.org/ diff --git a/src/libsass/build-with-visual-studio.md b/src/libsass/build-with-visual-studio.md new file mode 100644 index 000000000..275b917b8 --- /dev/null +++ b/src/libsass/build-with-visual-studio.md @@ -0,0 +1,90 @@ +## Building LibSass with Visual Studio + +### Requirements: + +The minimum requirement to build LibSass with Visual Studio is "Visual Studio 2013 Express for Desktop". + +Additionally, it is recommended to have `git` installed and available in `PATH`, so to deduce the `libsass` version information. For instance, if GitHub for Windows (https://windows.github.com/) is installed, the `PATH` will have an entry resembling: `X:\Users\\AppData\Local\GitHub\PortableGit_\cmd\` (where `X` is the drive letter of system drive). If `git` is not available, inquiring the LibSass version will result in `[NA]`. + +### Build Steps: + +#### From Visual Studio: + +On opening the `win\libsass.sln` solution and build (Ctrl+Shift+B) to build `libsass.dll`. + +To Build LibSass as a static Library, it is recommended to set an environment variable `LIBSASS_STATIC_LIB` before launching the project: + +```cmd +cd path\to\libsass +SET LIBSASS_STATIC_LIB=1 +:: +:: or in PowerShell: +:: $env:LIBSASS_STATIC_LIB=1 +:: +win\libsass.sln +``` + +Visual Studio will form the filtered source tree as shown below: + +![image](https://cloud.githubusercontent.com/assets/3840695/9298985/aae9e072-44bf-11e5-89eb-e7995c098085.png) + +`Header Files` contains the .h and .hpp files, while `Source Files` covers `.c` and `.cpp`. The other used headers/sources will appear under `External Dependencies`. + +If there is a LibSass code file appearing under External Dependencies, it can be changed by altering the `win\libsass.vcxproj.filters` file or dragging in Solution Explorer. + +#### From Command Prompt: + +Notice that in the following commands: + +* If the platform is 32-bit Windows, replace `ProgramFiles(x86)` with `ProgramFiles`. +* To build with Visual Studio 2015, replace `12.0` with `14.0` in the aforementioned command. + +Open a command prompt: + +To build dynamic/shared library (`libsass.dll`): + +```cmd +:: debug build: +"%ProgramFiles(x86)%\MSBuild\12.0\Bin\MSBuild" win\libsass.sln + +:: release build: +"%ProgramFiles(x86)%\MSBuild\12.0\Bin\MSBuild" win\libsass.sln ^ +/p:Configuration=Release +``` + +To build static library (`libsass.lib`): + +```cmd +:: debug build: +"%ProgramFiles(x86)%\MSBuild\12.0\Bin\MSBuild" win\libsass.sln ^ +/p:LIBSASS_STATIC_LIB=1 + +:: release build: +"%ProgramFiles(x86)%\MSBuild\12.0\Bin\MSBuild" win\libsass.sln ^ +/p:LIBSASS_STATIC_LIB=1 /p:Configuration=Release +``` + +#### From PowerShell: + +To build dynamic/shared library (`libsass.dll`): + +```powershell +# debug build: +&"${env:ProgramFiles(x86)}\MSBuild\12.0\Bin\MSBuild" win\libsass.sln + +# release build: +&"${env:ProgramFiles(x86)}\MSBuild\12.0\Bin\MSBuild" win\libsass.sln ` +/p:Configuration=Release +``` + +To build static library (`libsass.lib`): + +```powershell +# build: +&"${env:ProgramFiles(x86)}\MSBuild\12.0\Bin\MSBuild" win\libsass.sln ` +/p:LIBSASS_STATIC_LIB=1 + +# release build: +&"${env:ProgramFiles(x86)}\MSBuild\12.0\Bin\MSBuild" win\libsass.sln ` +/p:LIBSASS_STATIC_LIB=1 /p:Configuration=Release +``` diff --git a/src/libsass/build.md b/src/libsass/build.md new file mode 100644 index 000000000..c656d8839 --- /dev/null +++ b/src/libsass/build.md @@ -0,0 +1,97 @@ +`libsass` is only a library and does not do much on its own. You need an implementation that you can use from the [command line][6]. Or some [bindings|Implementations][9] to use it within your favorite programming language. You should be able to get [`sassc`][6] running by following the instructions in this guide. + +Before starting, see [setup dev environment](setup-environment.md). + +Building on different Operating Systems +-- + +We try to keep the code as OS independent and standard compliant as possible. Reading files from the file-system has some OS depending code, but will ultimately fall back to a posix compatible implementation. We do use some `C++11` features, but are so far only committed to use `unordered_map`. This means you will need a pretty recent compiler on most systems (gcc 4.5 seems to be the minimum). + +### Building on Linux (and other *nix flavors) + +Linux is the main target for `libsass` and we support two ways to build `libsass` here. The old plain makefiles should still work on most systems (including MinGW), while the autotools build is preferred if you want to create a [system library] (experimental). + +- [Building with makefiles][1] +- [Building with autotools][2] + +### Building on Windows (experimental) + +Windows build support was added very recently and should be considered experimental. Credits go to @darrenkopp and @am11 for their work on getting `libsass` and `sassc` to compile with visual studio! + +- [Building with MinGW][3] +- [Building with Visual Studio][11] + +### Building on Max OS X (untested) + +Works the same as on linux, but you can also install LibSass via `homebrew`. + +- [Building on Mac OS X][10] + +### Building a system library (experimental) + +Since `libsass` is a library, it makes sense to install it as a shared library on your system. On linux this means creating a `.so` library via autotools. This should work pretty well already, but we are not yet committed to keep the ABI 100% stable. This should be the case once we increase the version number for the library to 1.0.0 or higher. On Windows you should be able get a `dll` by creating a shared build with MinGW. There is currently no target in the MSVC project files to do this. + +- [Building shared system library][4] + +Compiling with clang instead of gcc +-- + +To use clang you just need to set the appropriate environment variables: + +```bash +export CC=/usr/bin/clang +export CXX=/usr/bin/clang++ +``` + +Running the spec test-suite +-- + +We constantly and automatically test `libsass` against the official [spec test-suite][5]. To do this we need to have a test-runner (which is written in ruby) and a command-line tool ([`sassc`][6]) to run the tests. Therefore we need to additionally compile `sassc`. To do this, the build files of all three projects need to work together. This may not have the same quality for all build flavors. You definitely need to have ruby (2.1?) installed (version 1.9 seems to cause problems at least on windows). You also need some gems installed: + +```bash +ruby -v +gem install minitest +# should be optional +gem install minitap +``` + +Including the LibSass version +-- + +There is a function in `libsass` to query the current version. This has to be defined at compile time. We use a C macro for this, which can be defined by calling `g++ -DLIBSASS_VERSION="\"x.y.z.\""`. The two quotes are necessary, since it needs to end up as a valid C string. Normally you do not need to do anything if you use the makefiles or autotools. They will try to fetch the version via git directly. If you only have the sources without the git repo, you can pass the version as an environment variable to `make` or `configure`: + +``` +export LIBSASS_VERSION="x.y.z." +``` + +Continuous Integration +-- + +We use two CI services to automatically test all commits against the latest [spec test-suite][5]. + +- [LibSass on Travis-CI (linux)][7] +[![Build Status](https://travis-ci.org/sass/libsass.png?branch=master)](https://travis-ci.org/sass/libsass) +- [LibSass on AppVeyor (windows)][8] +[![Build status](https://ci.appveyor.com/api/projects/status/github/sass/libsass?svg=true)](https://ci.appveyor.com/project/mgreter/libsass-513/branch/master) + +Why not using CMake? +-- + +There were some efforts to get `libsass` to compile with CMake, which should make it easier to create build files for linux and windows. Unfortunately this was not completed. But we are certainly open for PRs! + +Miscellaneous +-- + +- [Ebuilds for Gentoo Linux](build-on-gentoo.md) + +[1]: build-with-makefiles.md +[2]: build-with-autotools.md +[3]: build-with-mingw.md +[4]: build-shared-library.md +[5]: https://github.com/sass/sass-spec +[6]: https://github.com/sass/sassc +[7]: https://github.com/sass/libsass/blob/master/.travis.yml +[8]: https://github.com/sass/libsass/blob/master/appveyor.yml +[9]: implementations.md +[10]: build-on-darwin.md +[11]: build-with-visual-studio.md diff --git a/src/libsass/compatibility-plan.md b/src/libsass/compatibility-plan.md new file mode 100644 index 000000000..d8e538fa4 --- /dev/null +++ b/src/libsass/compatibility-plan.md @@ -0,0 +1,48 @@ +This document is to serve as a living, changing plan for getting LibSass caught up with Ruby Sass. + +_Note: an "s" preceeding a version number is specifying a Ruby Sass version. Without an s, it's a version of LibSass._ + +# Goal +**Our goal is to reach full s3.4 compatibility as soon as possible. LibSass version 3.4 will behave just like Ruby Sass 3.4** + +I highlight the goal, because there are some things that are *not* currently priorities. To be clear, they WILL be priorities, but they are not at the moment: + +* Performance Improvements +* Extensibility + +The overriding goal is correctness. + +## Verifying Correctness +LibSass uses the spec for its testing. The spec was originally based off s3.2 tests. Many things have changed in Ruby Sass since then and some of the tests need to be updated and changed in order to get them to match both LibSass and Ruby Sass. + +Until this project is complete, the spec will be primarily a place to test LibSass. By the time LibSass reaches 3.4, it is our goal that sass-spec will be fully usable as an official testing source for ALL implementations of Sass. + +## Version Naming +Until LibSass reaches parity with Ruby Sass, we will be aggressively bumping versions, and LibSass 3.4 will be the peer to Ruby Sass 3.4 in every way. + +# Release Plan + +## 3.0 +The goal of 3.0 is to introduce some of the most demanded features for LibSass. That is, we are focusing on issues and features that have kept adoption down. This is a mongrel release wrt which version of Sass it's targeting. It's often a mixture of 3.2 / 3.3 / 3.4 behaviours. This is not ideal, but it's favourable to not existing. Targeting 3.4 strictly during this release would mean we never actually release. + +# 3.1 +The goal of 3.1 is to update all the passing specs to agree with 3.4. This will not be a complete representation of s3.4 (aka, there will me missing features), but the goal is to change existing features and implemented features to match 3.4 behaviour. + +By the end of this, the sass-spec must pass against 3.4. + +Major issues: +* Variable Scoping +* Color Handling +* Precision + +# 3.2 +This version will focus on edge case fixes. There are a LOT of edge cases in the _todo_ tests and this is the release where we hunt those down like dogs (not that we want to hurt dogs, it's just a figure of speech in English). + +# 3.3 +Dress rehearsal. When we are 99% sure that we've fixed the main issues keeping us from saying we are compliant in s3.4 behaviour. + +# 3.4 +Compass Compatibility. We need to be able to work with Compass and all the other libraries out there. At this point, we are calling LibSass "mature" + +# Beyond 3.4 +Obviously, there is matching Sass 3.5 behaviour. But, beyond that, we'll want to focus on performance, stability, and error handling. These can always be improved upon and are the life's work of an open source project. We'll have to work closely with Sass in the future. diff --git a/src/libsass/configure.ac b/src/libsass/configure.ac index bf05dbfaa..b5a943217 100644 --- a/src/libsass/configure.ac +++ b/src/libsass/configure.ac @@ -9,6 +9,7 @@ AC_CONFIG_MACRO_DIR([m4]) AC_CONFIG_HEADERS([src/config.h]) AC_CONFIG_FILES([include/sass/version.h]) AC_CONFIG_AUX_DIR([script]) + # These are flags passed to automake # Though they look like gcc flags! AM_INIT_AUTOMAKE([foreign parallel-tests -Wall]) @@ -93,21 +94,16 @@ the --with-sass-spec-dir= argument. ;; esac AC_SUBST(SASS_SPEC_PATH) - - # TODO: Remove this when automake requirements are 1.12+ - AC_MSG_CHECKING([whether we can use TAP mode]) - tmp=`$AWK '/TEST_LOG_DRIVER/' $srcdir/GNUmakefile.in` - if test "x$tmp" != "x"; then - use_tap=yes - else - use_tap=no - fi - AC_MSG_RESULT([$use_tap]) - +else + # we do not really need these paths for non test build + # but automake may error if we do not define them here + SASS_SPEC_PATH=sass-spec + SASS_SASSC_PATH=sassc + AC_SUBST(SASS_SPEC_PATH) + AC_SUBST(SASS_SASSC_PATH) fi AM_CONDITIONAL(ENABLE_TESTS, test "x$enable_tests" = "xyes") -AM_CONDITIONAL(USE_TAP, test "x$use_tap" = "xyes") AC_ARG_ENABLE([coverage], [AS_HELP_STRING([--enable-coverage], diff --git a/src/libsass/context.cpp b/src/libsass/context.cpp new file mode 100644 index 000000000..b199412cd --- /dev/null +++ b/src/libsass/context.cpp @@ -0,0 +1,926 @@ +#include "sass.hpp" +#include +#include +#include +#include +#include +#include + +#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 "check_nesting.hpp" +#include "cssize.hpp" +#include "listize.hpp" +#include "extend.hpp" +#include "remove_placeholders.hpp" +#include "functions.hpp" +#include "sass_functions.hpp" +#include "backtrace.hpp" +#include "sass2scss.h" +#include "prelexer.hpp" +#include "emitter.hpp" + +namespace Sass { + using namespace Constants; + using namespace File; + using namespace Sass; + + inline bool sort_importers (const Sass_Importer_Entry& i, const Sass_Importer_Entry& j) + { return sass_importer_get_priority(i) > sass_importer_get_priority(j); } + + static std::string safe_input(const char* in_path) + { + // enforce some safe defaults + // used to create relative file links + std::string safe_path(in_path ? in_path : ""); + return safe_path == "" ? "stdin" : safe_path; + } + + static std::string safe_output(const char* out_path, const std::string& input_path = "") + { + std::string safe_path(out_path ? out_path : ""); + // maybe we can extract an output path from input path + if (safe_path == "" && input_path != "") { + int lastindex = static_cast(input_path.find_last_of(".")); + return (lastindex > -1 ? input_path.substr(0, lastindex) : input_path) + ".css"; + } + // enforce some safe defaults + // used to create relative file links + return safe_path == "" ? "stdout" : safe_path; + } + + Context::Context(struct Sass_Context& c_ctx) + : CWD(File::get_cwd()), + c_options(c_ctx), + entry_path(""), + head_imports(0), + plugins(), + emitter(c_options), + + ast_gc(), + strings(), + resources(), + sheets(), + subset_map(), + import_stack(), + callee_stack(), + traces(), + c_compiler(NULL), + + c_headers (std::vector()), + c_importers (std::vector()), + c_functions (std::vector()), + + indent (safe_str(c_options.indent, " ")), + linefeed (safe_str(c_options.linefeed, "\n")), + + input_path (make_canonical_path(safe_input(c_options.input_path))), + output_path (make_canonical_path(safe_output(c_options.output_path, input_path))), + source_map_file (make_canonical_path(safe_str(c_options.source_map_file, ""))), + source_map_root (make_canonical_path(safe_str(c_options.source_map_root, ""))) + + { + + // Sass 3.4: The current working directory will no longer be placed onto the Sass load path by default. + // If you need the current working directory to be available, set SASS_PATH=. in your shell's environment. + // include_paths.push_back(CWD); + + // collect more paths from different options + collect_extensions(c_options.extension); + collect_extensions(c_options.extensions); + collect_include_paths(c_options.include_path); + collect_include_paths(c_options.include_paths); + collect_plugin_paths(c_options.plugin_path); + collect_plugin_paths(c_options.plugin_paths); + + // load plugins and register custom behaviors + for(auto plug : plugin_paths) plugins.load_plugins(plug); + for(auto fn : plugins.get_headers()) c_headers.push_back(fn); + for(auto fn : plugins.get_importers()) c_importers.push_back(fn); + for(auto fn : plugins.get_functions()) c_functions.push_back(fn); + + // sort the items by priority (lowest first) + sort (c_headers.begin(), c_headers.end(), sort_importers); + sort (c_importers.begin(), c_importers.end(), sort_importers); + + emitter.set_filename(abs2rel(output_path, source_map_file, CWD)); + + } + + void Context::add_c_function(Sass_Function_Entry function) + { + c_functions.push_back(function); + } + void Context::add_c_header(Sass_Importer_Entry header) + { + c_headers.push_back(header); + // need to sort the array afterwards (no big deal) + sort (c_headers.begin(), c_headers.end(), sort_importers); + } + void Context::add_c_importer(Sass_Importer_Entry importer) + { + c_importers.push_back(importer); + // need to sort the array afterwards (no big deal) + sort (c_importers.begin(), c_importers.end(), sort_importers); + } + + Context::~Context() + { + // resources were allocated by malloc + for (size_t i = 0; i < resources.size(); ++i) { + free(resources[i].contents); + free(resources[i].srcmap); + } + // free all strings we kept alive during compiler execution + for (size_t n = 0; n < strings.size(); ++n) free(strings[n]); + // everything that gets put into sources will be freed by us + // this shouldn't have anything in it anyway!? + for (size_t m = 0; m < import_stack.size(); ++m) { + sass_import_take_source(import_stack[m]); + sass_import_take_srcmap(import_stack[m]); + sass_delete_import(import_stack[m]); + } + // clear inner structures (vectors) and input source + resources.clear(); import_stack.clear(); + subset_map.clear(), sheets.clear(); + } + + Data_Context::~Data_Context() + { + // --> this will be freed by resources + // make sure we free the source even if not processed! + // if (resources.size() == 0 && source_c_str) free(source_c_str); + // if (resources.size() == 0 && srcmap_c_str) free(srcmap_c_str); + // source_c_str = 0; srcmap_c_str = 0; + } + + File_Context::~File_Context() + { + } + + void Context::collect_extensions(const char* exts_str) + { + if (exts_str) { + const char* beg = exts_str; + const char* end = Prelexer::find_first(beg); + + while (end) { + std::string ext(beg, end - beg); + if (!ext.empty()) { + extensions.push_back(ext); + } + beg = end + 1; + end = Prelexer::find_first(beg); + } + + std::string ext(beg); + if (!ext.empty()) { + extensions.push_back(ext); + } + } + } + + void Context::collect_extensions(string_list* paths_array) + { + while (paths_array) + { + collect_extensions(paths_array->string); + paths_array = paths_array->next; + } + } + + void Context::collect_include_paths(const char* paths_str) + { + if (paths_str) { + const char* beg = paths_str; + const char* end = Prelexer::find_first(beg); + + while (end) { + std::string path(beg, end - beg); + if (!path.empty()) { + if (*path.rbegin() != '/') path += '/'; + include_paths.push_back(path); + } + beg = end + 1; + end = Prelexer::find_first(beg); + } + + std::string path(beg); + if (!path.empty()) { + if (*path.rbegin() != '/') path += '/'; + include_paths.push_back(path); + } + } + } + + void Context::collect_include_paths(string_list* paths_array) + { + while (paths_array) + { + collect_include_paths(paths_array->string); + paths_array = paths_array->next; + } + } + + void Context::collect_plugin_paths(const char* paths_str) + { + if (paths_str) { + const char* beg = paths_str; + const char* end = Prelexer::find_first(beg); + + while (end) { + std::string path(beg, end - beg); + if (!path.empty()) { + if (*path.rbegin() != '/') path += '/'; + plugin_paths.push_back(path); + } + beg = end + 1; + end = Prelexer::find_first(beg); + } + + std::string path(beg); + if (!path.empty()) { + if (*path.rbegin() != '/') path += '/'; + plugin_paths.push_back(path); + } + } + } + + void Context::collect_plugin_paths(string_list* paths_array) + { + while (paths_array) + { + collect_plugin_paths(paths_array->string); + paths_array = paths_array->next; + } + } + + // resolve the imp_path in base_path or include_paths + // looks for alternatives and returns a list from one directory + std::vector Context::find_includes(const Importer& import) + { + // include configured extensions + std::vector exts(File::defaultExtensions); + if (extensions.size() > 0) { + exts.insert(exts.end(), extensions.begin(), extensions.end()); + } + // make sure we resolve against an absolute path + std::string base_path(rel2abs(import.base_path)); + // first try to resolve the load path relative to the base path + std::vector vec(resolve_includes(base_path, import.imp_path, exts)); + // then search in every include path (but only if nothing found yet) + for (size_t i = 0, S = include_paths.size(); vec.size() == 0 && i < S; ++i) + { + // call resolve_includes and individual base path and append all results + std::vector resolved(resolve_includes(include_paths[i], import.imp_path, exts)); + if (resolved.size()) vec.insert(vec.end(), resolved.begin(), resolved.end()); + } + // return vector + return vec; + } + + // register include with resolved path and its content + // memory of the resources will be freed by us on exit + void Context::register_resource(const Include& inc, const Resource& res) + { + + // do not parse same resource twice + // maybe raise an error in this case + // if (sheets.count(inc.abs_path)) { + // free(res.contents); free(res.srcmap); + // throw std::runtime_error("duplicate resource registered"); + // return; + // } + + // get index for this resource + size_t idx = resources.size(); + + // tell emitter about new resource + emitter.add_source_index(idx); + + // put resources under our control + // the memory will be freed later + resources.push_back(res); + + // add a relative link to the working directory + included_files.push_back(inc.abs_path); + // add a relative link to the source map output file + srcmap_links.push_back(abs2rel(inc.abs_path, source_map_file, CWD)); + + // get pointer to the loaded content + Sass_Import_Entry import = sass_make_import( + inc.imp_path.c_str(), + inc.abs_path.c_str(), + res.contents, + res.srcmap + ); + // add the entry to the stack + import_stack.push_back(import); + + // get pointer to the loaded content + const char* contents = resources[idx].contents; + // keep a copy of the path around (for parserstates) + // ToDo: we clean it, but still not very elegant!? + strings.push_back(sass_copy_c_string(inc.abs_path.c_str())); + // create the initial parser state from resource + ParserState pstate(strings.back(), contents, idx); + + // check existing import stack for possible recursion + for (size_t i = 0; i < import_stack.size() - 2; ++i) { + auto parent = import_stack[i]; + if (std::strcmp(parent->abs_path, import->abs_path) == 0) { + std::string cwd(File::get_cwd()); + // make path relative to the current directory + std::string stack("An @import loop has been found:"); + for (size_t n = 1; n < i + 2; ++n) { + stack += "\n " + std::string(File::abs2rel(import_stack[n]->abs_path, cwd, cwd)) + + " imports " + std::string(File::abs2rel(import_stack[n+1]->abs_path, cwd, cwd)); + } + // implement error throw directly until we + // decided how to handle full stack traces + throw Exception::InvalidSyntax(pstate, traces, stack); + // error(stack, prstate ? *prstate : pstate, import_stack); + } + } + + // create a parser instance from the given c_str buffer + Parser p(Parser::from_c_str(contents, *this, traces, pstate)); + // do not yet dispose these buffers + sass_import_take_source(import); + sass_import_take_srcmap(import); + // then parse the root block + Block_Obj root = p.parse(); + // delete memory of current stack frame + sass_delete_import(import_stack.back()); + // remove current stack frame + import_stack.pop_back(); + // create key/value pair for ast node + std::pair + ast_pair(inc.abs_path, { res, root }); + // register resulting resource + sheets.insert(ast_pair); + } + + // register include with resolved path and its content + // memory of the resources will be freed by us on exit + void Context::register_resource(const Include& inc, const Resource& res, ParserState& prstate) + { + traces.push_back(Backtrace(prstate)); + register_resource(inc, res); + traces.pop_back(); + } + + // Add a new import to the context (called from `import_url`) + Include Context::load_import(const Importer& imp, ParserState pstate) + { + + // search for valid imports (ie. partials) on the filesystem + // this may return more than one valid result (ambiguous imp_path) + const std::vector resolved(find_includes(imp)); + + // error nicely on ambiguous imp_path + if (resolved.size() > 1) { + std::stringstream msg_stream; + msg_stream << "It's not clear which file to import for "; + msg_stream << "'@import \"" << imp.imp_path << "\"'." << "\n"; + msg_stream << "Candidates:" << "\n"; + for (size_t i = 0, L = resolved.size(); i < L; ++i) + { msg_stream << " " << resolved[i].imp_path << "\n"; } + msg_stream << "Please delete or rename all but one of these files." << "\n"; + error(msg_stream.str(), pstate, traces); + } + + // process the resolved entry + else if (resolved.size() == 1) { + bool use_cache = c_importers.size() == 0; + if (resolved[0].deprecated) { + // emit deprecation warning when import resolves to a .css file + deprecated( + "Including .css files with @import is non-standard behaviour which will be removed in future versions of LibSass.", + "Use a custom importer to maintain this behaviour. Check your implementations documentation on how to create a custom importer.", + true, pstate + ); + } + // use cache for the resource loading + if (use_cache && sheets.count(resolved[0].abs_path)) return resolved[0]; + // try to read the content of the resolved file entry + // the memory buffer returned must be freed by us! + if (char* contents = read_file(resolved[0].abs_path)) { + // register the newly resolved file resource + register_resource(resolved[0], { contents, 0 }, pstate); + // return resolved entry + return resolved[0]; + } + } + + // nothing found + return { imp, "" }; + + } + + void Context::import_url (Import_Ptr imp, std::string load_path, const std::string& ctx_path) { + + ParserState pstate(imp->pstate()); + std::string imp_path(unquote(load_path)); + std::string protocol("file"); + + using namespace Prelexer; + if (const char* proto = sequence< identifier, exactly<':'>, exactly<'/'>, exactly<'/'> >(imp_path.c_str())) { + + protocol = std::string(imp_path.c_str(), proto - 3); + // if (protocol.compare("file") && true) { } + } + + // add urls (protocol other than file) and urls without procotol to `urls` member + // ToDo: if ctx_path is already a file resource, we should not add it here? + if (imp->import_queries() || protocol != "file" || imp_path.substr(0, 2) == "//") { + imp->urls().push_back(SASS_MEMORY_NEW(String_Quoted, imp->pstate(), load_path)); + } + else if (imp_path.length() > 4 && imp_path.substr(imp_path.length() - 4, 4) == ".css") { + String_Constant_Ptr loc = SASS_MEMORY_NEW(String_Constant, pstate, unquote(load_path)); + Argument_Obj loc_arg = SASS_MEMORY_NEW(Argument, pstate, loc); + Arguments_Obj loc_args = SASS_MEMORY_NEW(Arguments, pstate); + loc_args->append(loc_arg); + Function_Call_Ptr new_url = SASS_MEMORY_NEW(Function_Call, pstate, "url", loc_args); + imp->urls().push_back(new_url); + } + else { + const Importer importer(imp_path, ctx_path); + Include include(load_import(importer, pstate)); + if (include.abs_path.empty()) { + error("File to import not found or unreadable: " + imp_path + ".", pstate, traces); + } + imp->incs().push_back(include); + } + + } + + + // call custom importers on the given (unquoted) load_path and eventually parse the resulting style_sheet + bool Context::call_loader(const std::string& load_path, const char* ctx_path, ParserState& pstate, Import_Ptr imp, std::vector importers, bool only_one) + { + // unique counter + size_t count = 0; + // need one correct import + bool has_import = false; + // process all custom importers (or custom headers) + for (Sass_Importer_Entry& importer_ent : importers) { + // int priority = sass_importer_get_priority(importer); + Sass_Importer_Fn fn = sass_importer_get_function(importer_ent); + // skip importer if it returns NULL + if (Sass_Import_List includes = + fn(load_path.c_str(), importer_ent, c_compiler) + ) { + // get c pointer copy to iterate over + Sass_Import_List it_includes = includes; + while (*it_includes) { ++count; + // create unique path to use as key + std::string uniq_path = load_path; + if (!only_one && count) { + std::stringstream path_strm; + path_strm << uniq_path << ":" << count; + uniq_path = path_strm.str(); + } + // create the importer struct + Importer importer(uniq_path, ctx_path); + // query data from the current include + Sass_Import_Entry include_ent = *it_includes; + char* source = sass_import_take_source(include_ent); + char* srcmap = sass_import_take_srcmap(include_ent); + size_t line = sass_import_get_error_line(include_ent); + size_t column = sass_import_get_error_column(include_ent); + const char *abs_path = sass_import_get_abs_path(include_ent); + // handle error message passed back from custom importer + // it may (or may not) override the line and column info + if (const char* err_message = sass_import_get_error_message(include_ent)) { + if (source || srcmap) register_resource({ importer, uniq_path }, { source, srcmap }, pstate); + if (line == std::string::npos && column == std::string::npos) error(err_message, pstate, traces); + else error(err_message, ParserState(ctx_path, source, Position(line, column)), traces); + } + // content for import was set + else if (source) { + // resolved abs_path should be set by custom importer + // use the created uniq_path as fallback (maybe enforce) + std::string path_key(abs_path ? abs_path : uniq_path); + // create the importer struct + Include include(importer, path_key); + // attach information to AST node + imp->incs().push_back(include); + // register the resource buffers + register_resource(include, { source, srcmap }, pstate); + } + // only a path was retuned + // try to load it like normal + else if(abs_path) { + // checks some urls to preserve + // `http://`, `https://` and `//` + // or dispatchs to `import_file` + // which will check for a `.css` extension + // or resolves the file on the filesystem + // added and resolved via `add_file` + // finally stores everything on `imp` + import_url(imp, abs_path, ctx_path); + } + // move to next + ++it_includes; + } + // deallocate the returned memory + sass_delete_import_list(includes); + // set success flag + has_import = true; + // break out of loop + if (only_one) break; + } + } + // return result + return has_import; + } + + void register_function(Context&, Signature sig, Native_Function f, Env* env); + void register_function(Context&, Signature sig, Native_Function f, size_t arity, Env* env); + void register_overload_stub(Context&, std::string name, Env* env); + void register_built_in_functions(Context&, Env* env); + void register_c_functions(Context&, Env* env, Sass_Function_List); + void register_c_function(Context&, Env* env, Sass_Function_Entry); + + char* Context::render(Block_Obj root) + { + // check for valid block + if (!root) return 0; + // start the render process + root->perform(&emitter); + // finish emitter stream + emitter.finalize(); + // get the resulting buffer from stream + OutputBuffer emitted = emitter.get_buffer(); + // should we append a source map url? + if (!c_options.omit_source_map_url) { + // generate an embeded source map + if (c_options.source_map_embed) { + emitted.buffer += linefeed; + emitted.buffer += format_embedded_source_map(); + } + // or just link the generated one + else if (source_map_file != "") { + emitted.buffer += linefeed; + emitted.buffer += format_source_mapping_url(source_map_file); + } + } + // create a copy of the resulting buffer string + // this must be freed or taken over by implementor + return sass_copy_c_string(emitted.buffer.c_str()); + } + + void Context::apply_custom_headers(Block_Obj root, const char* ctx_path, ParserState pstate) + { + // create a custom import to resolve headers + Import_Obj imp = SASS_MEMORY_NEW(Import, pstate); + // dispatch headers which will add custom functions + // custom headers are added to the import instance + call_headers(entry_path, ctx_path, pstate, imp); + // increase head count to skip later + head_imports += resources.size() - 1; + // add the statement if we have urls + if (!imp->urls().empty()) root->append(imp); + // process all other resources (add Import_Stub nodes) + for (size_t i = 0, S = imp->incs().size(); i < S; ++i) { + root->append(SASS_MEMORY_NEW(Import_Stub, pstate, imp->incs()[i])); + } + } + + Block_Obj File_Context::parse() + { + + // check if entry file is given + if (input_path.empty()) return 0; + + // create absolute path from input filename + // ToDo: this should be resolved via custom importers + std::string abs_path(rel2abs(input_path, CWD)); + + // try to load the entry file + char* contents = read_file(abs_path); + + // alternatively also look inside each include path folder + // I think this differs from ruby sass (IMO too late to remove) + for (size_t i = 0, S = include_paths.size(); contents == 0 && i < S; ++i) { + // build absolute path for this include path entry + abs_path = rel2abs(input_path, include_paths[i]); + // try to load the resulting path + contents = read_file(abs_path); + } + + // abort early if no content could be loaded (various reasons) + if (!contents) throw std::runtime_error("File to read not found or unreadable: " + input_path); + + // store entry path + entry_path = abs_path; + + // create entry only for import stack + Sass_Import_Entry import = sass_make_import( + input_path.c_str(), + entry_path.c_str(), + contents, + 0 + ); + // add the entry to the stack + import_stack.push_back(import); + + // create the source entry for file entry + register_resource({{ input_path, "." }, abs_path }, { contents, 0 }); + + // create root ast tree node + return compile(); + + } + + Block_Obj Data_Context::parse() + { + + // check if source string is given + if (!source_c_str) return 0; + + // convert indented sass syntax + if(c_options.is_indented_syntax_src) { + // call sass2scss to convert the string + char * converted = sass2scss(source_c_str, + // preserve the structure as much as possible + SASS2SCSS_PRETTIFY_1 | SASS2SCSS_KEEP_COMMENT); + // replace old source_c_str with converted + free(source_c_str); source_c_str = converted; + } + + // remember entry path (defaults to stdin for string) + entry_path = input_path.empty() ? "stdin" : input_path; + + // ToDo: this may be resolved via custom importers + std::string abs_path(rel2abs(entry_path)); + char* abs_path_c_str = sass_copy_c_string(abs_path.c_str()); + strings.push_back(abs_path_c_str); + + // create entry only for the import stack + Sass_Import_Entry import = sass_make_import( + entry_path.c_str(), + abs_path_c_str, + source_c_str, + srcmap_c_str + ); + // add the entry to the stack + import_stack.push_back(import); + + // register a synthetic resource (path does not really exist, skip in includes) + register_resource({{ input_path, "." }, input_path }, { source_c_str, srcmap_c_str }); + + // create root ast tree node + return compile(); + } + + + + // parse root block from includes + Block_Obj Context::compile() + { + // abort if there is no data + if (resources.size() == 0) return 0; + // get root block from the first style sheet + Block_Obj root = sheets.at(entry_path).root; + // abort on invalid root + if (root.isNull()) return 0; + Env global; // create root environment + // register built-in functions on env + register_built_in_functions(*this, &global); + // register custom functions (defined via C-API) + for (size_t i = 0, S = c_functions.size(); i < S; ++i) + { register_c_function(*this, &global, c_functions[i]); } + // create initial backtrace entry + // create crtp visitor objects + Expand expand(*this, &global); + Cssize cssize(*this); + CheckNesting check_nesting; + // check nesting in all files + for (auto sheet : sheets) { + auto styles = sheet.second; + check_nesting(styles.root); + } + // expand and eval the tree + root = expand(root); + // check nesting + check_nesting(root); + // merge and bubble certain rules + root = cssize(root); + // should we extend something? + if (!subset_map.empty()) { + // create crtp visitor object + Extend extend(subset_map); + extend.setEval(expand.eval); + // extend tree nodes + extend(root); + } + + // clean up by removing empty placeholders + // ToDo: maybe we can do this somewhere else? + Remove_Placeholders remove_placeholders; + root->perform(&remove_placeholders); + // return processed tree + return root; + } + // EO compile + + std::string Context::format_embedded_source_map() + { + std::string map = emitter.render_srcmap(*this); + std::istringstream is( map ); + std::ostringstream buffer; + base64::encoder E; + E.encode(is, buffer); + std::string url = "data:application/json;base64," + buffer.str(); + url.erase(url.size() - 1); + return "/*# sourceMappingURL=" + url + " */"; + } + + std::string Context::format_source_mapping_url(const std::string& file) + { + std::string url = abs2rel(file, output_path, CWD); + return "/*# sourceMappingURL=" + url + " */"; + } + + char* Context::render_srcmap() + { + if (source_map_file == "") return 0; + std::string map = emitter.render_srcmap(*this); + return sass_copy_c_string(map.c_str()); + } + + + // for data context we want to start after "stdin" + // we probably always want to skip the header includes? + std::vector Context::get_included_files(bool skip, size_t headers) + { + // create a copy of the vector for manipulations + std::vector includes = included_files; + if (includes.size() == 0) return includes; + if (skip) { includes.erase( includes.begin(), includes.begin() + 1 + headers); } + else { includes.erase( includes.begin() + 1, includes.begin() + 1 + headers); } + includes.erase( std::unique( includes.begin(), includes.end() ), includes.end() ); + std::sort( includes.begin() + (skip ? 0 : 1), includes.end() ); + return includes; + } + + void register_function(Context& ctx, Signature sig, Native_Function f, Env* env) + { + Definition_Ptr def = make_native_function(sig, f, ctx); + def->environment(env); + (*env)[def->name() + "[f]"] = def; + } + + void register_function(Context& ctx, Signature sig, Native_Function f, size_t arity, Env* env) + { + Definition_Ptr def = make_native_function(sig, f, ctx); + std::stringstream ss; + ss << def->name() << "[f]" << arity; + def->environment(env); + (*env)[ss.str()] = def; + } + + void register_overload_stub(Context& ctx, std::string name, Env* env) + { + Definition_Ptr stub = SASS_MEMORY_NEW(Definition, + ParserState("[built-in function]"), + 0, + name, + 0, + 0, + true); + (*env)[name + "[f]"] = stub; + } + + + void register_built_in_functions(Context& ctx, Env* env) + { + using namespace Functions; + // RGB Functions + register_function(ctx, rgb_sig, rgb, env); + register_overload_stub(ctx, "rgba", env); + register_function(ctx, rgba_4_sig, rgba_4, 4, env); + register_function(ctx, rgba_2_sig, rgba_2, 2, env); + register_function(ctx, red_sig, red, env); + register_function(ctx, green_sig, green, env); + register_function(ctx, blue_sig, blue, env); + register_function(ctx, mix_sig, mix, env); + // HSL Functions + register_function(ctx, hsl_sig, hsl, env); + register_function(ctx, hsla_sig, hsla, env); + register_function(ctx, hue_sig, hue, env); + register_function(ctx, saturation_sig, saturation, env); + register_function(ctx, lightness_sig, lightness, env); + register_function(ctx, adjust_hue_sig, adjust_hue, env); + register_function(ctx, lighten_sig, lighten, env); + register_function(ctx, darken_sig, darken, env); + register_function(ctx, saturate_sig, saturate, env); + register_function(ctx, desaturate_sig, desaturate, env); + register_function(ctx, grayscale_sig, grayscale, env); + register_function(ctx, complement_sig, complement, env); + register_function(ctx, invert_sig, invert, env); + // Opacity Functions + register_function(ctx, alpha_sig, alpha, env); + register_function(ctx, opacity_sig, alpha, env); + register_function(ctx, opacify_sig, opacify, env); + register_function(ctx, fade_in_sig, opacify, env); + register_function(ctx, transparentize_sig, transparentize, env); + register_function(ctx, fade_out_sig, transparentize, env); + // Other Color Functions + register_function(ctx, adjust_color_sig, adjust_color, env); + register_function(ctx, scale_color_sig, scale_color, env); + register_function(ctx, change_color_sig, change_color, env); + register_function(ctx, ie_hex_str_sig, ie_hex_str, env); + // String Functions + register_function(ctx, unquote_sig, sass_unquote, env); + register_function(ctx, quote_sig, sass_quote, env); + register_function(ctx, str_length_sig, str_length, env); + register_function(ctx, str_insert_sig, str_insert, env); + register_function(ctx, str_index_sig, str_index, env); + register_function(ctx, str_slice_sig, str_slice, env); + register_function(ctx, to_upper_case_sig, to_upper_case, env); + register_function(ctx, to_lower_case_sig, to_lower_case, env); + // Number Functions + register_function(ctx, percentage_sig, percentage, env); + register_function(ctx, round_sig, round, env); + register_function(ctx, ceil_sig, ceil, env); + register_function(ctx, floor_sig, floor, env); + register_function(ctx, abs_sig, abs, env); + register_function(ctx, min_sig, min, env); + register_function(ctx, max_sig, max, env); + register_function(ctx, random_sig, random, env); + // List Functions + register_function(ctx, length_sig, length, env); + register_function(ctx, nth_sig, nth, env); + register_function(ctx, set_nth_sig, set_nth, env); + register_function(ctx, index_sig, index, env); + register_function(ctx, join_sig, join, env); + register_function(ctx, append_sig, append, env); + register_function(ctx, zip_sig, zip, env); + register_function(ctx, list_separator_sig, list_separator, env); + register_function(ctx, is_bracketed_sig, is_bracketed, env); + // Map Functions + register_function(ctx, map_get_sig, map_get, env); + register_function(ctx, map_merge_sig, map_merge, env); + register_function(ctx, map_remove_sig, map_remove, env); + register_function(ctx, map_keys_sig, map_keys, env); + register_function(ctx, map_values_sig, map_values, env); + register_function(ctx, map_has_key_sig, map_has_key, env); + register_function(ctx, keywords_sig, keywords, env); + // Introspection Functions + register_function(ctx, type_of_sig, type_of, env); + register_function(ctx, unit_sig, unit, env); + register_function(ctx, unitless_sig, unitless, env); + register_function(ctx, comparable_sig, comparable, env); + register_function(ctx, variable_exists_sig, variable_exists, env); + register_function(ctx, global_variable_exists_sig, global_variable_exists, env); + register_function(ctx, function_exists_sig, function_exists, env); + register_function(ctx, mixin_exists_sig, mixin_exists, env); + register_function(ctx, feature_exists_sig, feature_exists, env); + register_function(ctx, call_sig, call, env); + register_function(ctx, content_exists_sig, content_exists, env); + register_function(ctx, get_function_sig, get_function, env); + // Boolean Functions + register_function(ctx, not_sig, sass_not, env); + register_function(ctx, if_sig, sass_if, env); + // Misc Functions + register_function(ctx, inspect_sig, inspect, env); + register_function(ctx, unique_id_sig, unique_id, env); + // Selector functions + register_function(ctx, selector_nest_sig, selector_nest, env); + register_function(ctx, selector_append_sig, selector_append, env); + register_function(ctx, selector_extend_sig, selector_extend, env); + register_function(ctx, selector_replace_sig, selector_replace, env); + register_function(ctx, selector_unify_sig, selector_unify, env); + register_function(ctx, is_superselector_sig, is_superselector, env); + register_function(ctx, simple_selectors_sig, simple_selectors, env); + register_function(ctx, selector_parse_sig, selector_parse, env); + } + + void register_c_functions(Context& ctx, Env* env, Sass_Function_List descrs) + { + while (descrs && *descrs) { + register_c_function(ctx, env, *descrs); + ++descrs; + } + } + void register_c_function(Context& ctx, Env* env, Sass_Function_Entry descr) + { + Definition_Ptr def = make_c_function(descr, ctx); + def->environment(env); + (*env)[def->name() + "[f]"] = def; + } + +} diff --git a/src/libsass/context.h b/src/libsass/context.h new file mode 100644 index 000000000..29754b75f --- /dev/null +++ b/src/libsass/context.h @@ -0,0 +1,173 @@ +#ifndef SASS_C_CONTEXT_H +#define SASS_C_CONTEXT_H + +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +// Forward declaration +struct Sass_Compiler; + +// Forward declaration +struct Sass_Options; // base struct +struct Sass_Context; // : Sass_Options +struct Sass_File_Context; // : Sass_Context +struct Sass_Data_Context; // : Sass_Context + +// Compiler states +enum Sass_Compiler_State { + SASS_COMPILER_CREATED, + SASS_COMPILER_PARSED, + SASS_COMPILER_EXECUTED +}; + +// Create and initialize an option struct +ADDAPI struct Sass_Options* ADDCALL sass_make_options (void); +// Create and initialize a specific context +ADDAPI struct Sass_File_Context* ADDCALL sass_make_file_context (const char* input_path); +ADDAPI struct Sass_Data_Context* ADDCALL sass_make_data_context (char* source_string); + +// Call the compilation step for the specific context +ADDAPI int ADDCALL sass_compile_file_context (struct Sass_File_Context* ctx); +ADDAPI int ADDCALL sass_compile_data_context (struct Sass_Data_Context* ctx); + +// Create a sass compiler instance for more control +ADDAPI struct Sass_Compiler* ADDCALL sass_make_file_compiler (struct Sass_File_Context* file_ctx); +ADDAPI struct Sass_Compiler* ADDCALL sass_make_data_compiler (struct Sass_Data_Context* data_ctx); + +// Execute the different compilation steps individually +// 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); +ADDAPI void ADDCALL sass_delete_options(struct Sass_Options* options); + +// Release all memory allocated and also ourself +ADDAPI void ADDCALL sass_delete_file_context (struct Sass_File_Context* ctx); +ADDAPI void ADDCALL sass_delete_data_context (struct Sass_Data_Context* ctx); + +// Getters for context from specific implementation +ADDAPI struct Sass_Context* ADDCALL sass_file_context_get_context (struct Sass_File_Context* file_ctx); +ADDAPI struct Sass_Context* ADDCALL sass_data_context_get_context (struct Sass_Data_Context* data_ctx); + +// Getters for Context_Options from Sass_Context +ADDAPI struct Sass_Options* ADDCALL sass_context_get_options (struct Sass_Context* ctx); +ADDAPI struct Sass_Options* ADDCALL sass_file_context_get_options (struct Sass_File_Context* file_ctx); +ADDAPI struct Sass_Options* ADDCALL sass_data_context_get_options (struct Sass_Data_Context* data_ctx); +ADDAPI void ADDCALL sass_file_context_set_options (struct Sass_File_Context* file_ctx, struct Sass_Options* opt); +ADDAPI void ADDCALL sass_data_context_set_options (struct Sass_Data_Context* data_ctx, struct Sass_Options* opt); + + +// Getters for Context_Option values +ADDAPI int ADDCALL sass_option_get_precision (struct Sass_Options* options); +ADDAPI enum Sass_Output_Style ADDCALL sass_option_get_output_style (struct Sass_Options* options); +ADDAPI bool ADDCALL sass_option_get_source_comments (struct Sass_Options* options); +ADDAPI bool ADDCALL sass_option_get_source_map_embed (struct Sass_Options* options); +ADDAPI bool ADDCALL sass_option_get_source_map_contents (struct Sass_Options* options); +ADDAPI bool ADDCALL sass_option_get_source_map_file_urls (struct Sass_Options* options); +ADDAPI bool ADDCALL sass_option_get_omit_source_map_url (struct Sass_Options* options); +ADDAPI bool ADDCALL sass_option_get_is_indented_syntax_src (struct Sass_Options* options); +ADDAPI const char* ADDCALL sass_option_get_indent (struct Sass_Options* options); +ADDAPI const char* ADDCALL sass_option_get_linefeed (struct Sass_Options* options); +ADDAPI const char* ADDCALL sass_option_get_input_path (struct Sass_Options* options); +ADDAPI const char* ADDCALL sass_option_get_output_path (struct Sass_Options* options); +ADDAPI const char* ADDCALL sass_option_get_source_map_file (struct Sass_Options* options); +ADDAPI const char* ADDCALL sass_option_get_source_map_root (struct Sass_Options* options); +ADDAPI Sass_Importer_List ADDCALL sass_option_get_c_headers (struct Sass_Options* options); +ADDAPI Sass_Importer_List ADDCALL sass_option_get_c_importers (struct Sass_Options* options); +ADDAPI Sass_Function_List ADDCALL sass_option_get_c_functions (struct Sass_Options* options); + +// Setters for Context_Option values +ADDAPI void ADDCALL sass_option_set_precision (struct Sass_Options* options, int precision); +ADDAPI void ADDCALL sass_option_set_output_style (struct Sass_Options* options, enum Sass_Output_Style output_style); +ADDAPI void ADDCALL sass_option_set_source_comments (struct Sass_Options* options, bool source_comments); +ADDAPI void ADDCALL sass_option_set_source_map_embed (struct Sass_Options* options, bool source_map_embed); +ADDAPI void ADDCALL sass_option_set_source_map_contents (struct Sass_Options* options, bool source_map_contents); +ADDAPI void ADDCALL sass_option_set_source_map_file_urls (struct Sass_Options* options, bool source_map_file_urls); +ADDAPI void ADDCALL sass_option_set_omit_source_map_url (struct Sass_Options* options, bool omit_source_map_url); +ADDAPI void ADDCALL sass_option_set_is_indented_syntax_src (struct Sass_Options* options, bool is_indented_syntax_src); +ADDAPI void ADDCALL sass_option_set_indent (struct Sass_Options* options, const char* indent); +ADDAPI void ADDCALL sass_option_set_linefeed (struct Sass_Options* options, const char* linefeed); +ADDAPI void ADDCALL sass_option_set_input_path (struct Sass_Options* options, const char* input_path); +ADDAPI void ADDCALL sass_option_set_output_path (struct Sass_Options* options, const char* output_path); +ADDAPI void ADDCALL sass_option_set_plugin_path (struct Sass_Options* options, const char* plugin_path); +ADDAPI void ADDCALL sass_option_set_include_path (struct Sass_Options* options, const char* include_path); +ADDAPI void ADDCALL sass_option_set_source_map_file (struct Sass_Options* options, const char* source_map_file); +ADDAPI void ADDCALL sass_option_set_source_map_root (struct Sass_Options* options, const char* source_map_root); +ADDAPI void ADDCALL sass_option_set_c_headers (struct Sass_Options* options, Sass_Importer_List c_headers); +ADDAPI void ADDCALL sass_option_set_c_importers (struct Sass_Options* options, Sass_Importer_List c_importers); +ADDAPI void ADDCALL sass_option_set_c_functions (struct Sass_Options* options, Sass_Function_List c_functions); + + +// Getters for Sass_Context values +ADDAPI const char* ADDCALL sass_context_get_output_string (struct Sass_Context* ctx); +ADDAPI int ADDCALL sass_context_get_error_status (struct Sass_Context* ctx); +ADDAPI const char* ADDCALL sass_context_get_error_json (struct Sass_Context* ctx); +ADDAPI const char* ADDCALL sass_context_get_error_text (struct Sass_Context* ctx); +ADDAPI const char* ADDCALL sass_context_get_error_message (struct Sass_Context* ctx); +ADDAPI const char* ADDCALL sass_context_get_error_file (struct Sass_Context* ctx); +ADDAPI const char* ADDCALL sass_context_get_error_src (struct Sass_Context* ctx); +ADDAPI size_t ADDCALL sass_context_get_error_line (struct Sass_Context* ctx); +ADDAPI size_t ADDCALL sass_context_get_error_column (struct Sass_Context* ctx); +ADDAPI const char* ADDCALL sass_context_get_source_map_string (struct Sass_Context* ctx); +ADDAPI char** ADDCALL sass_context_get_included_files (struct Sass_Context* ctx); + +// Getters for options include path array +ADDAPI size_t ADDCALL sass_option_get_include_path_size(struct Sass_Options* options); +ADDAPI const char* ADDCALL sass_option_get_include_path(struct Sass_Options* options, size_t i); + +// 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 struct Sass_Options* ADDCALL sass_compiler_get_options(struct Sass_Compiler* compiler); +ADDAPI size_t ADDCALL sass_compiler_get_import_stack_size(struct Sass_Compiler* compiler); +ADDAPI Sass_Import_Entry ADDCALL sass_compiler_get_last_import(struct Sass_Compiler* compiler); +ADDAPI Sass_Import_Entry ADDCALL sass_compiler_get_import_entry(struct Sass_Compiler* compiler, size_t idx); +ADDAPI size_t ADDCALL sass_compiler_get_callee_stack_size(struct Sass_Compiler* compiler); +ADDAPI Sass_Callee_Entry ADDCALL sass_compiler_get_last_callee(struct Sass_Compiler* compiler); +ADDAPI Sass_Callee_Entry ADDCALL sass_compiler_get_callee_entry(struct Sass_Compiler* compiler, size_t idx); + +// Push function for import extenions +ADDAPI void ADDCALL sass_option_push_import_extension (struct Sass_Options* options, const char* ext); + +// Push function for paths (no manipulation support for now) +ADDAPI void ADDCALL sass_option_push_plugin_path (struct Sass_Options* options, const char* path); +ADDAPI void ADDCALL sass_option_push_include_path (struct Sass_Options* options, const char* path); + +// Resolve a file via the given include paths in the sass option struct +// find_file looks for the exact file name while find_include does a regular sass include +ADDAPI char* ADDCALL sass_find_file (const char* path, struct Sass_Options* opt); +ADDAPI char* ADDCALL sass_find_include (const char* path, struct Sass_Options* opt); + +// Resolve a file relative to last import or include paths in the sass option struct +// find_file looks for the exact file name while find_include does a regular sass include +ADDAPI char* ADDCALL sass_compiler_find_file (const char* path, struct Sass_Compiler* compiler); +ADDAPI char* ADDCALL sass_compiler_find_include (const char* path, struct Sass_Compiler* compiler); + +#ifdef __cplusplus +} // __cplusplus defined. +#endif + +#endif diff --git a/src/libsass/context.hpp b/src/libsass/context.hpp new file mode 100644 index 000000000..f14e69f6d --- /dev/null +++ b/src/libsass/context.hpp @@ -0,0 +1,155 @@ +#ifndef SASS_CONTEXT_H +#define SASS_CONTEXT_H + +#include +#include +#include + +#define BUFFERSIZE 255 +#include "b64/encode.h" + +#include "ast_fwd_decl.hpp" +#include "kwd_arg_macros.hpp" +#include "ast_fwd_decl.hpp" +#include "sass_context.hpp" +#include "environment.hpp" +#include "source_map.hpp" +#include "subset_map.hpp" +#include "backtrace.hpp" +#include "output.hpp" +#include "plugins.hpp" +#include "file.hpp" + + +struct Sass_Function; + +namespace Sass { + + class Context { + public: + void import_url (Import_Ptr imp, std::string load_path, const std::string& ctx_path); + bool call_headers(const std::string& load_path, const char* ctx_path, ParserState& pstate, Import_Ptr imp) + { return call_loader(load_path, ctx_path, pstate, imp, c_headers, false); }; + bool call_importers(const std::string& load_path, const char* ctx_path, ParserState& pstate, Import_Ptr imp) + { return call_loader(load_path, ctx_path, pstate, imp, c_importers, true); }; + + private: + bool call_loader(const std::string& load_path, const char* ctx_path, ParserState& pstate, Import_Ptr imp, std::vector importers, bool only_one = true); + + public: + const std::string CWD; + struct Sass_Options& c_options; + std::string entry_path; + size_t head_imports; + Plugins plugins; + Output emitter; + + // generic ast node garbage container + // used to avoid possible circular refs + std::vector ast_gc; + // resources add under our control + // these are guaranteed to be freed + std::vector strings; + std::vector resources; + std::map sheets; + Subset_Map subset_map; + std::vector import_stack; + std::vector callee_stack; + std::vector traces; + + struct Sass_Compiler* c_compiler; + + // absolute paths to includes + std::vector included_files; + // relative includes for sourcemap + std::vector srcmap_links; + // vectors above have same size + + std::vector plugin_paths; // relative paths to load plugins + std::vector include_paths; // lookup paths for includes + std::vector extensions; // lookup extensions for imports` + + + + + + void apply_custom_headers(Block_Obj root, const char* path, ParserState pstate); + + std::vector c_headers; + std::vector c_importers; + std::vector c_functions; + + void add_c_header(Sass_Importer_Entry header); + void add_c_importer(Sass_Importer_Entry importer); + void add_c_function(Sass_Function_Entry function); + + const std::string indent; // String to be used for indentation + const std::string linefeed; // String to be used for line feeds + const std::string input_path; // for relative paths in src-map + const std::string output_path; // for relative paths to the output + const std::string source_map_file; // path to source map file (enables feature) + const std::string source_map_root; // path for sourceRoot property (pass-through) + + virtual ~Context(); + Context(struct Sass_Context&); + virtual Block_Obj parse() = 0; + virtual Block_Obj compile(); + virtual char* render(Block_Obj root); + virtual char* render_srcmap(); + + void register_resource(const Include&, const Resource&); + void register_resource(const Include&, const Resource&, ParserState&); + std::vector find_includes(const Importer& import); + Include load_import(const Importer&, ParserState pstate); + + Sass_Output_Style output_style() { return c_options.output_style; }; + std::vector get_included_files(bool skip = false, size_t headers = 0); + + private: + void collect_plugin_paths(const char* paths_str); + void collect_plugin_paths(string_list* paths_array); + void collect_include_paths(const char* paths_str); + void collect_include_paths(string_list* paths_array); + void collect_extensions(const char* extensions_str); + void collect_extensions(string_list* extensions_array); + std::string format_embedded_source_map(); + std::string format_source_mapping_url(const std::string& out_path); + + + // void register_built_in_functions(Env* env); + // void register_function(Signature sig, Native_Function f, Env* env); + // void register_function(Signature sig, Native_Function f, size_t arity, Env* env); + // void register_overload_stub(std::string name, Env* env); + + public: + const std::string& cwd() { return CWD; }; + }; + + class File_Context : public Context { + public: + File_Context(struct Sass_File_Context& ctx) + : Context(ctx) + { } + virtual ~File_Context(); + virtual Block_Obj parse(); + }; + + class Data_Context : public Context { + public: + char* source_c_str; + char* srcmap_c_str; + Data_Context(struct Sass_Data_Context& ctx) + : Context(ctx) + { + source_c_str = ctx.source_string; + srcmap_c_str = ctx.srcmap_string; + ctx.source_string = 0; // passed away + ctx.srcmap_string = 0; // passed away + } + virtual ~Data_Context(); + virtual Block_Obj parse(); + }; + +} + +#endif diff --git a/src/libsass/contributing.md b/src/libsass/contributing.md new file mode 100644 index 000000000..4a2d470ef --- /dev/null +++ b/src/libsass/contributing.md @@ -0,0 +1,17 @@ +First of all, welcome! Thanks for even reading this page. If you're here, you're probably wondering what you can do to help make the LibSass project even more awesome. And, even having that feeling means you are awesome! + +## I'm a programmer + +Awesome! We need your help. The best thing to do is go find issues that are tagged with both "bug" and "test written". We do spec driven development here and these issues have a test that's written already in the sass-spec project. Go find the test by going to sass-spec/spec/LibSass-todo-issues/issue_XXX/ where XXX is the issue number. Write the code, and compile, and then issue a pull request referencing the issue. We'll quickly verify it and get it merged in! + +To get your dev environment setup, check out our article on [Setup-Dev-Environment](setup-environment.md). + +## I'm not a backend programmer + +COOL! We also need your help. Doing [Issue-Triage](triage.md) is a big deal and something we need constant help with. That means helping to verify issues, write tests for them, and make sure they are getting fixed. It's being part of the smiling face of the project. + +Also, we need help with the Sass-Spec project itself. Just people to organize, refactor, and understand the tests in there. + +## I don't know what a computer is? + +Hmm.... well, it's the thing you are looking at right now. Ummm... check out training courses! Then, come back and join us! diff --git a/src/libsass/cssize.cpp b/src/libsass/cssize.cpp new file mode 100644 index 000000000..6a12fdf7b --- /dev/null +++ b/src/libsass/cssize.cpp @@ -0,0 +1,606 @@ +#include "sass.hpp" +#include +#include +#include + +#include "cssize.hpp" +#include "context.hpp" + +namespace Sass { + + Cssize::Cssize(Context& ctx) + : ctx(ctx), + traces(ctx.traces), + block_stack(std::vector()), + p_stack(std::vector()) + { } + + Statement_Ptr Cssize::parent() + { + return p_stack.size() ? p_stack.back() : block_stack.front(); + } + + Block_Ptr Cssize::operator()(Block_Ptr b) + { + Block_Obj bb = SASS_MEMORY_NEW(Block, b->pstate(), b->length(), b->is_root()); + // bb->tabs(b->tabs()); + block_stack.push_back(bb); + append_block(b, bb); + block_stack.pop_back(); + return bb.detach(); + } + + Statement_Ptr Cssize::operator()(Trace_Ptr t) + { + traces.push_back(Backtrace(t->pstate())); + auto result = t->block()->perform(this); + traces.pop_back(); + return result; + } + + Statement_Ptr Cssize::operator()(Declaration_Ptr d) + { + String_Obj property = Cast(d->property()); + + if (Declaration_Ptr dd = Cast(parent())) { + String_Obj parent_property = Cast(dd->property()); + property = SASS_MEMORY_NEW(String_Constant, + d->property()->pstate(), + parent_property->to_string() + "-" + property->to_string()); + if (!dd->value()) { + d->tabs(dd->tabs() + 1); + } + } + + Declaration_Obj dd = SASS_MEMORY_NEW(Declaration, + d->pstate(), + property, + d->value(), + d->is_important(), + d->is_custom_property()); + dd->is_indented(d->is_indented()); + dd->tabs(d->tabs()); + + p_stack.push_back(dd); + Block_Obj bb = d->block() ? operator()(d->block()) : NULL; + p_stack.pop_back(); + + if (bb && bb->length()) { + if (dd->value() && !dd->value()->is_invisible()) { + bb->unshift(dd); + } + return bb.detach(); + } + else if (dd->value() && !dd->value()->is_invisible()) { + return dd.detach(); + } + + return 0; + } + + Statement_Ptr Cssize::operator()(Directive_Ptr r) + { + if (!r->block() || !r->block()->length()) return r; + + if (parent()->statement_type() == Statement::RULESET) + { + return (r->is_keyframes()) ? SASS_MEMORY_NEW(Bubble, r->pstate(), r) : bubble(r); + } + + p_stack.push_back(r); + Directive_Obj rr = SASS_MEMORY_NEW(Directive, + r->pstate(), + r->keyword(), + r->selector(), + r->block() ? operator()(r->block()) : 0); + if (r->value()) rr->value(r->value()); + p_stack.pop_back(); + + bool directive_exists = false; + size_t L = rr->block() ? rr->block()->length() : 0; + for (size_t i = 0; i < L && !directive_exists; ++i) { + Statement_Obj s = r->block()->at(i); + if (s->statement_type() != Statement::BUBBLE) directive_exists = true; + else { + Bubble_Obj s_obj = Cast(s); + s = s_obj->node(); + if (s->statement_type() != Statement::DIRECTIVE) directive_exists = false; + else directive_exists = (Cast(s)->keyword() == rr->keyword()); + } + + } + + Block_Ptr result = SASS_MEMORY_NEW(Block, rr->pstate()); + if (!(directive_exists || rr->is_keyframes())) + { + Directive_Ptr empty_node = Cast(rr); + empty_node->block(SASS_MEMORY_NEW(Block, rr->block() ? rr->block()->pstate() : rr->pstate())); + result->append(empty_node); + } + + Block_Obj db = rr->block(); + if (db.isNull()) db = SASS_MEMORY_NEW(Block, rr->pstate()); + Block_Obj ss = debubble(db, rr); + for (size_t i = 0, L = ss->length(); i < L; ++i) { + result->append(ss->at(i)); + } + + return result; + } + + Statement_Ptr Cssize::operator()(Keyframe_Rule_Ptr r) + { + if (!r->block() || !r->block()->length()) return r; + + Keyframe_Rule_Obj rr = SASS_MEMORY_NEW(Keyframe_Rule, + r->pstate(), + operator()(r->block())); + if (!r->name().isNull()) rr->name(r->name()); + + return debubble(rr->block(), rr); + } + + Statement_Ptr Cssize::operator()(Ruleset_Ptr r) + { + p_stack.push_back(r); + // this can return a string schema + // string schema is not a statement! + // r->block() is already a string schema + // and that is comming from propset expand + Block_Ptr bb = operator()(r->block()); + // this should protect us (at least a bit) from our mess + // fixing this properly is harder that it should be ... + if (Cast(bb) == NULL) { + error("Illegal nesting: Only properties may be nested beneath properties.", r->block()->pstate(), traces); + } + Ruleset_Obj rr = SASS_MEMORY_NEW(Ruleset, + r->pstate(), + r->selector(), + bb); + + rr->is_root(r->is_root()); + // rr->tabs(r->block()->tabs()); + p_stack.pop_back(); + + if (!rr->block()) { + error("Illegal nesting: Only properties may be nested beneath properties.", r->block()->pstate(), traces); + } + + Block_Obj props = SASS_MEMORY_NEW(Block, rr->block()->pstate()); + Block_Ptr rules = SASS_MEMORY_NEW(Block, rr->block()->pstate()); + for (size_t i = 0, L = rr->block()->length(); i < L; i++) + { + Statement_Ptr s = rr->block()->at(i); + if (bubblable(s)) rules->append(s); + if (!bubblable(s)) props->append(s); + } + + if (props->length()) + { + Block_Obj pb = SASS_MEMORY_NEW(Block, rr->block()->pstate()); + pb->concat(props); + rr->block(pb); + + for (size_t i = 0, L = rules->length(); i < L; i++) + { + Statement_Ptr stm = rules->at(i); + stm->tabs(stm->tabs() + 1); + } + + rules->unshift(rr); + } + + Block_Ptr ptr = rules; + rules = debubble(rules); + void* lp = ptr; + void* rp = rules; + if (lp != rp) { + Block_Obj obj = ptr; + } + + if (!(!rules->length() || + !bubblable(rules->last()) || + parent()->statement_type() == Statement::RULESET)) + { + rules->last()->group_end(true); + } + return rules; + } + + Statement_Ptr Cssize::operator()(Null_Ptr m) + { + return 0; + } + + Statement_Ptr Cssize::operator()(Media_Block_Ptr m) + { + if (parent()->statement_type() == Statement::RULESET) + { return bubble(m); } + + if (parent()->statement_type() == Statement::MEDIA) + { return SASS_MEMORY_NEW(Bubble, m->pstate(), m); } + + p_stack.push_back(m); + + Media_Block_Obj mm = SASS_MEMORY_NEW(Media_Block, + m->pstate(), + m->media_queries(), + operator()(m->block())); + mm->tabs(m->tabs()); + + p_stack.pop_back(); + + return debubble(mm->block(), mm); + } + + Statement_Ptr Cssize::operator()(Supports_Block_Ptr m) + { + if (!m->block()->length()) + { return m; } + + if (parent()->statement_type() == Statement::RULESET) + { return bubble(m); } + + p_stack.push_back(m); + + Supports_Block_Obj mm = SASS_MEMORY_NEW(Supports_Block, + m->pstate(), + m->condition(), + operator()(m->block())); + mm->tabs(m->tabs()); + + p_stack.pop_back(); + + return debubble(mm->block(), mm); + } + + Statement_Ptr Cssize::operator()(At_Root_Block_Ptr m) + { + bool tmp = false; + for (size_t i = 0, L = p_stack.size(); i < L; ++i) { + Statement_Ptr s = p_stack[i]; + tmp |= m->exclude_node(s); + } + + if (!tmp && m->block()) + { + Block_Ptr bb = operator()(m->block()); + for (size_t i = 0, L = bb->length(); i < L; ++i) { + // (bb->elements())[i]->tabs(m->tabs()); + Statement_Obj stm = bb->at(i); + if (bubblable(stm)) stm->tabs(stm->tabs() + m->tabs()); + } + if (bb->length() && bubblable(bb->last())) bb->last()->group_end(m->group_end()); + return bb; + } + + if (m->exclude_node(parent())) + { + return SASS_MEMORY_NEW(Bubble, m->pstate(), m); + } + + return bubble(m); + } + + Statement_Ptr Cssize::bubble(Directive_Ptr m) + { + Block_Ptr bb = SASS_MEMORY_NEW(Block, this->parent()->pstate()); + Has_Block_Obj new_rule = Cast(SASS_MEMORY_COPY(this->parent())); + new_rule->block(bb); + new_rule->tabs(this->parent()->tabs()); + new_rule->block()->concat(m->block()); + + Block_Obj wrapper_block = SASS_MEMORY_NEW(Block, m->block() ? m->block()->pstate() : m->pstate()); + wrapper_block->append(new_rule); + Directive_Obj mm = SASS_MEMORY_NEW(Directive, + m->pstate(), + m->keyword(), + m->selector(), + wrapper_block); + if (m->value()) mm->value(m->value()); + + Bubble_Ptr bubble = SASS_MEMORY_NEW(Bubble, mm->pstate(), mm); + return bubble; + } + + Statement_Ptr Cssize::bubble(At_Root_Block_Ptr m) + { + if (!m || !m->block()) return NULL; + Block_Ptr bb = SASS_MEMORY_NEW(Block, this->parent()->pstate()); + Has_Block_Obj new_rule = Cast(SASS_MEMORY_COPY(this->parent())); + Block_Ptr wrapper_block = SASS_MEMORY_NEW(Block, m->block()->pstate()); + if (new_rule) { + new_rule->block(bb); + new_rule->tabs(this->parent()->tabs()); + new_rule->block()->concat(m->block()); + wrapper_block->append(new_rule); + } + + At_Root_Block_Ptr mm = SASS_MEMORY_NEW(At_Root_Block, + m->pstate(), + wrapper_block, + m->expression()); + Bubble_Ptr bubble = SASS_MEMORY_NEW(Bubble, mm->pstate(), mm); + return bubble; + } + + Statement_Ptr Cssize::bubble(Supports_Block_Ptr m) + { + Ruleset_Obj parent = Cast(SASS_MEMORY_COPY(this->parent())); + + Block_Ptr bb = SASS_MEMORY_NEW(Block, parent->block()->pstate()); + Ruleset_Ptr new_rule = SASS_MEMORY_NEW(Ruleset, + parent->pstate(), + parent->selector(), + bb); + new_rule->tabs(parent->tabs()); + new_rule->block()->concat(m->block()); + + Block_Ptr wrapper_block = SASS_MEMORY_NEW(Block, m->block()->pstate()); + wrapper_block->append(new_rule); + Supports_Block_Ptr mm = SASS_MEMORY_NEW(Supports_Block, + m->pstate(), + m->condition(), + wrapper_block); + + mm->tabs(m->tabs()); + + Bubble_Ptr bubble = SASS_MEMORY_NEW(Bubble, mm->pstate(), mm); + return bubble; + } + + Statement_Ptr Cssize::bubble(Media_Block_Ptr m) + { + Ruleset_Obj parent = Cast(SASS_MEMORY_COPY(this->parent())); + + Block_Ptr bb = SASS_MEMORY_NEW(Block, parent->block()->pstate()); + Ruleset_Ptr new_rule = SASS_MEMORY_NEW(Ruleset, + parent->pstate(), + parent->selector(), + bb); + new_rule->tabs(parent->tabs()); + new_rule->block()->concat(m->block()); + + Block_Ptr wrapper_block = SASS_MEMORY_NEW(Block, m->block()->pstate()); + wrapper_block->append(new_rule); + Media_Block_Obj mm = SASS_MEMORY_NEW(Media_Block, + m->pstate(), + m->media_queries(), + wrapper_block); + + mm->tabs(m->tabs()); + + return SASS_MEMORY_NEW(Bubble, mm->pstate(), mm); + } + + bool Cssize::bubblable(Statement_Ptr s) + { + return Cast(s) || s->bubbles(); + } + + Block_Ptr Cssize::flatten(Block_Ptr b) + { + Block_Ptr result = SASS_MEMORY_NEW(Block, b->pstate(), 0, b->is_root()); + for (size_t i = 0, L = b->length(); i < L; ++i) { + Statement_Ptr ss = b->at(i); + if (Block_Ptr bb = Cast(ss)) { + Block_Obj bs = flatten(bb); + for (size_t j = 0, K = bs->length(); j < K; ++j) { + result->append(bs->at(j)); + } + } + else { + result->append(ss); + } + } + return result; + } + + std::vector> Cssize::slice_by_bubble(Block_Ptr b) + { + std::vector> results; + + for (size_t i = 0, L = b->length(); i < L; ++i) { + Statement_Obj value = b->at(i); + bool key = Cast(value) != NULL; + + if (!results.empty() && results.back().first == key) + { + Block_Obj wrapper_block = results.back().second; + wrapper_block->append(value); + } + else + { + Block_Ptr wrapper_block = SASS_MEMORY_NEW(Block, value->pstate()); + wrapper_block->append(value); + results.push_back(std::make_pair(key, wrapper_block)); + } + } + return results; + } + + Block_Ptr Cssize::debubble(Block_Ptr children, Statement_Ptr parent) + { + Has_Block_Obj previous_parent = 0; + std::vector> baz = slice_by_bubble(children); + Block_Obj result = SASS_MEMORY_NEW(Block, children->pstate()); + + for (size_t i = 0, L = baz.size(); i < L; ++i) { + bool is_bubble = baz[i].first; + Block_Obj slice = baz[i].second; + + if (!is_bubble) { + if (!parent) { + result->append(slice); + } + else if (previous_parent) { + previous_parent->block()->concat(slice); + } + else { + previous_parent = Cast(SASS_MEMORY_COPY(parent)); + previous_parent->block(slice); + previous_parent->tabs(parent->tabs()); + + result->append(previous_parent); + } + continue; + } + + for (size_t j = 0, K = slice->length(); j < K; ++j) + { + Statement_Ptr ss; + Statement_Obj stm = slice->at(j); + // this has to go now here (too bad) + Bubble_Obj node = Cast(stm); + Media_Block_Ptr m1 = NULL; + Media_Block_Ptr m2 = NULL; + if (parent) m1 = Cast(parent); + if (node) m2 = Cast(node->node()); + if (!parent || + parent->statement_type() != Statement::MEDIA || + node->node()->statement_type() != Statement::MEDIA || + (m1 && m2 && *m1->media_queries() == *m2->media_queries()) + ) + { + ss = node->node(); + } + else + { + List_Obj mq = merge_media_queries( + Cast(node->node()), + Cast(parent) + ); + if (!mq->length()) continue; + if (Media_Block* b = Cast(node->node())) { + b->media_queries(mq); + } + ss = node->node(); + } + + if (!ss) continue; + + ss->tabs(ss->tabs() + node->tabs()); + ss->group_end(node->group_end()); + + Block_Obj bb = SASS_MEMORY_NEW(Block, + children->pstate(), + children->length(), + children->is_root()); + bb->append(ss->perform(this)); + + Block_Obj wrapper_block = SASS_MEMORY_NEW(Block, + children->pstate(), + children->length(), + children->is_root()); + + Block_Ptr wrapper = flatten(bb); + wrapper_block->append(wrapper); + + if (wrapper->length()) { + previous_parent = NULL; + } + + if (wrapper_block) { + result->append(wrapper_block); + } + } + } + + return flatten(result); + } + + Statement_Ptr Cssize::fallback_impl(AST_Node_Ptr n) + { + return static_cast(n); + } + + void Cssize::append_block(Block_Ptr b, Block_Ptr cur) + { + for (size_t i = 0, L = b->length(); i < L; ++i) { + Statement_Obj ith = b->at(i)->perform(this); + if (Block_Ptr bb = Cast(ith)) { + for (size_t j = 0, K = bb->length(); j < K; ++j) { + cur->append(bb->at(j)); + } + } + else if (ith) { + cur->append(ith); + } + } + } + + List_Ptr Cssize::merge_media_queries(Media_Block_Ptr m1, Media_Block_Ptr m2) + { + List_Ptr qq = SASS_MEMORY_NEW(List, + m1->media_queries()->pstate(), + m1->media_queries()->length(), + SASS_COMMA); + + for (size_t i = 0, L = m1->media_queries()->length(); i < L; i++) { + for (size_t j = 0, K = m2->media_queries()->length(); j < K; j++) { + Expression_Obj l1 = m1->media_queries()->at(i); + Expression_Obj l2 = m2->media_queries()->at(j); + Media_Query_Ptr mq1 = Cast(l1); + Media_Query_Ptr mq2 = Cast(l2); + Media_Query_Ptr mq = merge_media_query(mq1, mq2); + if (mq) qq->append(mq); + } + } + + return qq; + } + + + Media_Query_Ptr Cssize::merge_media_query(Media_Query_Ptr mq1, Media_Query_Ptr mq2) + { + + std::string type; + std::string mod; + + std::string m1 = std::string(mq1->is_restricted() ? "only" : mq1->is_negated() ? "not" : ""); + std::string t1 = mq1->media_type() ? mq1->media_type()->to_string(ctx.c_options) : ""; + std::string m2 = std::string(mq2->is_restricted() ? "only" : mq2->is_negated() ? "not" : ""); + std::string t2 = mq2->media_type() ? mq2->media_type()->to_string(ctx.c_options) : ""; + + + if (t1.empty()) t1 = t2; + if (t2.empty()) t2 = t1; + + if ((m1 == "not") ^ (m2 == "not")) { + if (t1 == t2) { + return 0; + } + type = m1 == "not" ? t2 : t1; + mod = m1 == "not" ? m2 : m1; + } + else if (m1 == "not" && m2 == "not") { + if (t1 != t2) { + return 0; + } + type = t1; + mod = "not"; + } + else if (t1 != t2) { + return 0; + } else { + type = t1; + mod = m1.empty() ? m2 : m1; + } + + Media_Query_Ptr mm = SASS_MEMORY_NEW(Media_Query, + mq1->pstate(), + 0, + mq1->length() + mq2->length(), + mod == "not", + mod == "only"); + + if (!type.empty()) { + mm->media_type(SASS_MEMORY_NEW(String_Quoted, mq1->pstate(), type)); + } + + mm->concat(mq2); + mm->concat(mq1); + + return mm; + } +} diff --git a/src/libsass/custom-functions-internal.md b/src/libsass/custom-functions-internal.md new file mode 100644 index 000000000..57fec82b8 --- /dev/null +++ b/src/libsass/custom-functions-internal.md @@ -0,0 +1,122 @@ +# Developer Documentation + +Custom functions are internally represented by `struct Sass_C_Function_Descriptor`. + +## Sass_C_Function_Descriptor + +```C +struct Sass_C_Function_Descriptor { + const char* signature; + Sass_C_Function function; + void* cookie; +}; +``` + +- `signature`: The function declaration, like `foo($bar, $baz:1)` +- `function`: Reference to the C function callback +- `cookie`: any pointer you want to attach + +### signature + +The signature defines how the function can be invoked. It also declares which arguments are required and which are optional. Required arguments will be enforced by LibSass and a Sass error is thrown in the event a call as missing an argument. Optional arguments only need to be present when you want to overwrite the default value. + + foo($bar, $baz: 2) + +In this example, `$bar` is required and will error if not passed. `$baz` is optional and the default value of it is 2. A call like `foo(10)` is therefore equal to `foo(10, 2)`, while `foo()` will produce an error. + +### function + +The callback function needs to be of the following form: + +```C +union Sass_Value* call_sass_function( + const union Sass_Value* s_args, + void* cookie +) { + return sass_clone_value(s_args); +} +``` + +### cookie + +The cookie can hold any pointer you want. In the `perl-libsass` implementation it holds the structure with the reference of the actual registered callback into the perl interpreter. Before that call `perl-libsass` will convert all `Sass_Values` to corresponding perl data types (so they can be used natively inside the perl interpretor). The callback can also return a `Sass_Value`. In `perl-libsass` the actual function returns a perl value, which has to be converted before `libsass` can work with it again! + +## Sass_Values + +```C +// allocate memory (copies passed strings) +union Sass_Value* sass_make_null (void); +union Sass_Value* sass_make_boolean (bool val); +union Sass_Value* sass_make_string (const char* val); +union Sass_Value* sass_make_qstring (const char* val); +union Sass_Value* sass_make_number (double val, const char* unit); +union Sass_Value* sass_make_color (double r, double g, double b, double a); +union Sass_Value* sass_make_list (size_t len, enum Sass_Separator sep, bool is_bracketed); +union Sass_Value* sass_make_map (size_t len); +union Sass_Value* sass_make_error (const char* msg); +union Sass_Value* sass_make_warning (const char* msg); + +// Make a deep cloned copy of the given sass value +union Sass_Value* sass_clone_value (const union Sass_Value* val); + +// deallocate memory (incl. all copied memory) +void sass_delete_value (const union Sass_Value* val); +``` + +## Example main.c + +```C +#include +#include +#include "sass/context.h" + +union Sass_Value* call_fn_foo(const union Sass_Value* s_args, void* cookie) +{ + // we actually abuse the void* to store an "int" + return sass_make_number((size_t)cookie, "px"); +} + +int main( int argc, const char* argv[] ) +{ + + // get the input file from first argument or use default + const char* input = argc > 1 ? argv[1] : "styles.scss"; + + // create the file context and get all related structs + struct Sass_File_Context* file_ctx = sass_make_file_context(input); + struct Sass_Context* ctx = sass_file_context_get_context(file_ctx); + struct Sass_Options* ctx_opt = sass_context_get_options(ctx); + + // allocate a custom function caller + Sass_C_Function_Callback fn_foo = + sass_make_function("foo()", call_fn_foo, (void*)42); + + // create list of all custom functions + Sass_C_Function_List fn_list = sass_make_function_list(1); + sass_function_set_list_entry(fn_list, 0, fn_foo); + sass_option_set_c_functions(ctx_opt, fn_list); + + // context is set up, call the compile step now + int status = sass_compile_file_context(file_ctx); + + // print the result or the error to the stdout + if (status == 0) puts(sass_context_get_output_string(ctx)); + else puts(sass_context_get_error_message(ctx)); + + // release allocated memory + sass_delete_file_context(file_ctx); + + // exit status + return status; + +} +``` + +## Compile main.c + +```bash +gcc -c main.c -o main.o +gcc -o sample main.o -lsass +echo "foo { margin: foo(); }" > foo.scss +./sample foo.scss => "foo { margin: 42px }" +``` diff --git a/src/libsass/debugger.hpp b/src/libsass/debugger.hpp new file mode 100644 index 000000000..f1ceabd9a --- /dev/null +++ b/src/libsass/debugger.hpp @@ -0,0 +1,801 @@ +#ifndef SASS_DEBUGGER_H +#define SASS_DEBUGGER_H + +#include +#include +#include "node.hpp" +#include "ast_fwd_decl.hpp" + +using namespace Sass; + +inline void debug_ast(AST_Node_Ptr node, std::string ind = "", Env* env = 0); + +inline void debug_ast(const AST_Node* node, std::string ind = "", Env* env = 0) { + debug_ast(const_cast(node), ind, env); +} + +inline void debug_sources_set(ComplexSelectorSet& set, std::string ind = "") +{ + if (ind == "") std::cerr << "#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; + for(auto const &pair : set) { + debug_ast(pair, ind + ""); + // debug_ast(set[pair], ind + "first: "); + } + if (ind == "") std::cerr << "#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; +} + +inline std::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 std::string prettyprint(const std::string& str) { + std::string clean = str_replace(str, "\n", "\\n"); + clean = str_replace(clean, " ", "\\t"); + clean = str_replace(clean, "\r", "\\r"); + return clean; +} + +inline std::string longToHex(long long t) { + std::stringstream is; + is << std::hex << t; + return is.str(); +} + +inline std::string pstate_source_position(AST_Node_Ptr node) +{ + std::stringstream str; + Position start(node->pstate()); + Position end(start + node->pstate().offset); + str << (start.file == std::string::npos ? -1 : start.file) + << "@[" << start.line << ":" << start.column << "]" + << "-[" << end.line << ":" << end.column << "]"; +#ifdef DEBUG_SHARED_PTR + str << "x" << node->getRefCount() << "" + << " " << node->getDbgFile() + << "@" << node->getDbgLine(); +#endif + return str.str(); +} + +inline void debug_ast(AST_Node_Ptr node, std::string ind, Env* env) +{ + if (node == 0) return; + if (ind == "") std::cerr << "####################################################################\n"; + if (Cast(node)) { + Bubble_Ptr bubble = Cast(node); + std::cerr << ind << "Bubble " << bubble; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << bubble->tabs(); + std::cerr << std::endl; + debug_ast(bubble->node(), ind + " ", env); + } else if (Cast(node)) { + Trace_Ptr trace = Cast(node); + std::cerr << ind << "Trace " << trace; + std::cerr << " (" << pstate_source_position(node) << ")" + << " [name:" << trace->name() << ", type: " << trace->type() << "]" + << std::endl; + debug_ast(trace->block(), ind + " ", env); + } else if (Cast(node)) { + At_Root_Block_Ptr root_block = Cast(node); + std::cerr << ind << "At_Root_Block " << root_block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << root_block->tabs(); + std::cerr << std::endl; + debug_ast(root_block->expression(), ind + ":", env); + debug_ast(root_block->block(), ind + " ", env); + } else if (Cast(node)) { + Selector_List_Ptr selector = Cast(node); + std::cerr << ind << "Selector_List " << selector; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " <" << selector->hash() << ">"; + std::cerr << " [@media:" << selector->media_block() << "]"; + std::cerr << (selector->is_invisible() ? " [INVISIBLE]": " -"); + std::cerr << (selector->has_placeholder() ? " [PLACEHOLDER]": " -"); + std::cerr << (selector->is_optional() ? " [is_optional]": " -"); + std::cerr << (selector->has_parent_ref() ? " [has-parent]": " -"); + std::cerr << (selector->has_line_break() ? " [line-break]": " -"); + std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); + std::cerr << std::endl; + debug_ast(selector->schema(), ind + "#{} "); + + for(const Complex_Selector_Obj& i : selector->elements()) { debug_ast(i, ind + " ", env); } + +// } else if (Cast(node)) { +// Expression_Ptr expression = Cast(node); +// std::cerr << ind << "Expression " << expression << " " << expression->concrete_type() << std::endl; + + } else if (Cast(node)) { + Parent_Selector_Ptr selector = Cast(node); + std::cerr << ind << "Parent_Selector " << selector; +// if (selector->not_selector()) cerr << " [in_declaration]"; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " <" << selector->hash() << ">"; + std::cerr << " [" << (selector->is_real_parent_ref() ? "REAL" : "FAKE") << "]"; + std::cerr << " <" << prettyprint(selector->pstate().token.ws_before()) << ">" << std::endl; +// debug_ast(selector->selector(), ind + "->", env); + + } else if (Cast(node)) { + Complex_Selector_Ptr selector = Cast(node); + std::cerr << ind << "Complex_Selector " << selector + << " (" << pstate_source_position(node) << ")" + << " <" << selector->hash() << ">" + << " [length:" << longToHex(selector->length()) << "]" + << " [weight:" << longToHex(selector->specificity()) << "]" + << " [@media:" << selector->media_block() << "]" + << (selector->is_invisible() ? " [INVISIBLE]": " -") + << (selector->has_placeholder() ? " [PLACEHOLDER]": " -") + << (selector->is_optional() ? " [is_optional]": " -") + << (selector->has_parent_ref() ? " [has parent]": " -") + << (selector->has_line_feed() ? " [line-feed]": " -") + << (selector->has_line_break() ? " [line-break]": " -") + << " -- "; + std::string del; + switch (selector->combinator()) { + case Complex_Selector::PARENT_OF: del = ">"; break; + case Complex_Selector::PRECEDES: del = "~"; break; + case Complex_Selector::ADJACENT_TO: del = "+"; break; + case Complex_Selector::ANCESTOR_OF: del = " "; break; + case Complex_Selector::REFERENCE: del = "//"; break; + } + // if (del = "/") del += selector->reference()->perform(&to_string) + "/"; + std::cerr << " <" << prettyprint(selector->pstate().token.ws_before()) << ">" << std::endl; + debug_ast(selector->head(), ind + " " /* + "[" + del + "]" */, env); + if (selector->tail()) { + debug_ast(selector->tail(), ind + "{" + del + "}", env); + } else if(del != " ") { + std::cerr << ind << " |" << del << "| {trailing op}" << std::endl; + } + ComplexSelectorSet set = selector->sources(); + // debug_sources_set(set, ind + " @--> "); + } else if (Cast(node)) { + Compound_Selector_Ptr selector = Cast(node); + std::cerr << ind << "Compound_Selector " << selector; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " <" << selector->hash() << ">"; + std::cerr << " [weight:" << longToHex(selector->specificity()) << "]"; + std::cerr << " [@media:" << selector->media_block() << "]"; + std::cerr << (selector->extended() ? " [extended]": " -"); + std::cerr << (selector->is_optional() ? " [is_optional]": " -"); + std::cerr << (selector->has_parent_ref() ? " [has-parent]": " -"); + std::cerr << (selector->has_line_break() ? " [line-break]": " -"); + std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); + std::cerr << " <" << prettyprint(selector->pstate().token.ws_before()) << ">" << std::endl; + for(const Simple_Selector_Obj& i : selector->elements()) { debug_ast(i, ind + " ", env); } + } else if (Cast(node)) { + Wrapped_Selector_Ptr selector = Cast(node); + std::cerr << ind << "Wrapped_Selector " << selector; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " <" << selector->hash() << ">"; + std::cerr << " <<" << selector->ns_name() << ">>"; + std::cerr << (selector->is_optional() ? " [is_optional]": " -"); + std::cerr << (selector->has_parent_ref() ? " [has-parent]": " -"); + std::cerr << (selector->has_line_break() ? " [line-break]": " -"); + std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); + std::cerr << std::endl; + debug_ast(selector->selector(), ind + " () ", env); + } else if (Cast(node)) { + Pseudo_Selector_Ptr selector = Cast(node); + std::cerr << ind << "Pseudo_Selector " << selector; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " <" << selector->hash() << ">"; + std::cerr << " <<" << selector->ns_name() << ">>"; + std::cerr << (selector->is_optional() ? " [is_optional]": " -"); + std::cerr << (selector->has_parent_ref() ? " [has-parent]": " -"); + std::cerr << (selector->has_line_break() ? " [line-break]": " -"); + std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); + std::cerr << std::endl; + debug_ast(selector->expression(), ind + " <= ", env); + } else if (Cast(node)) { + Attribute_Selector_Ptr selector = Cast(node); + std::cerr << ind << "Attribute_Selector " << selector; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " <" << selector->hash() << ">"; + std::cerr << " <<" << selector->ns_name() << ">>"; + std::cerr << (selector->is_optional() ? " [is_optional]": " -"); + std::cerr << (selector->has_parent_ref() ? " [has-parent]": " -"); + std::cerr << (selector->has_line_break() ? " [line-break]": " -"); + std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); + std::cerr << std::endl; + debug_ast(selector->value(), ind + "[" + selector->matcher() + "] ", env); + } else if (Cast(node)) { + Class_Selector_Ptr selector = Cast(node); + std::cerr << ind << "Class_Selector " << selector; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " <" << selector->hash() << ">"; + std::cerr << " <<" << selector->ns_name() << ">>"; + std::cerr << (selector->is_optional() ? " [is_optional]": " -"); + std::cerr << (selector->has_parent_ref() ? " [has-parent]": " -"); + std::cerr << (selector->has_line_break() ? " [line-break]": " -"); + std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); + std::cerr << std::endl; + } else if (Cast(node)) { + Id_Selector_Ptr selector = Cast(node); + std::cerr << ind << "Id_Selector " << selector; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " <" << selector->hash() << ">"; + std::cerr << " <<" << selector->ns_name() << ">>"; + std::cerr << (selector->is_optional() ? " [is_optional]": " -"); + std::cerr << (selector->has_parent_ref() ? " [has-parent]": " -"); + std::cerr << (selector->has_line_break() ? " [line-break]": " -"); + std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); + std::cerr << std::endl; + } else if (Cast(node)) { + Element_Selector_Ptr selector = Cast(node); + std::cerr << ind << "Element_Selector " << selector; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " <" << selector->hash() << ">"; + std::cerr << " <<" << selector->ns_name() << ">>"; + std::cerr << (selector->is_optional() ? " [is_optional]": " -"); + std::cerr << (selector->has_parent_ref() ? " [has-parent]": " -"); + std::cerr << (selector->has_line_break() ? " [line-break]": " -"); + std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); + std::cerr << " <" << prettyprint(selector->pstate().token.ws_before()) << ">"; + std::cerr << std::endl; + } else if (Cast(node)) { + + Placeholder_Selector_Ptr selector = Cast(node); + std::cerr << ind << "Placeholder_Selector [" << selector->ns_name() << "] " << selector; + std::cerr << " (" << pstate_source_position(selector) << ")" + << " <" << selector->hash() << ">" + << " [@media:" << selector->media_block() << "]" + << (selector->is_optional() ? " [is_optional]": " -") + << (selector->has_line_break() ? " [line-break]": " -") + << (selector->has_line_feed() ? " [line-feed]": " -") + << std::endl; + + } else if (Cast(node)) { + Simple_Selector* selector = Cast(node); + std::cerr << ind << "Simple_Selector " << selector; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << (selector->has_line_break() ? " [line-break]": " -") << (selector->has_line_feed() ? " [line-feed]": " -") << std::endl; + + } else if (Cast(node)) { + Selector_Schema_Ptr selector = Cast(node); + std::cerr << ind << "Selector_Schema " << selector; + std::cerr << " (" << pstate_source_position(node) << ")" + << " [@media:" << selector->media_block() << "]" + << (selector->connect_parent() ? " [connect-parent]": " -") + << std::endl; + + debug_ast(selector->contents(), ind + " "); + // for(auto i : selector->elements()) { debug_ast(i, ind + " ", env); } + + } else if (Cast(node)) { + Selector_Ptr selector = Cast(node); + std::cerr << ind << "Selector " << selector; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << (selector->has_line_break() ? " [line-break]": " -") + << (selector->has_line_feed() ? " [line-feed]": " -") + << std::endl; + + } else if (Cast(node)) { + Media_Query_Expression_Ptr block = Cast(node); + std::cerr << ind << "Media_Query_Expression " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << (block->is_interpolated() ? " [is_interpolated]": " -") + << std::endl; + debug_ast(block->feature(), ind + " feature) "); + debug_ast(block->value(), ind + " value) "); + + } else if (Cast(node)) { + Media_Query_Ptr block = Cast(node); + std::cerr << ind << "Media_Query " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << (block->is_negated() ? " [is_negated]": " -") + << (block->is_restricted() ? " [is_restricted]": " -") + << std::endl; + debug_ast(block->media_type(), ind + " "); + for(const auto& i : block->elements()) { debug_ast(i, ind + " ", env); } + + } else if (Cast(node)) { + Media_Block_Ptr block = Cast(node); + std::cerr << ind << "Media_Block " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << block->tabs() << std::endl; + debug_ast(block->media_queries(), ind + " =@ "); + if (block->block()) for(const Statement_Obj& i : block->block()->elements()) { debug_ast(i, ind + " ", env); } + } else if (Cast(node)) { + Supports_Block_Ptr block = Cast(node); + std::cerr << ind << "Supports_Block " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << block->tabs() << std::endl; + debug_ast(block->condition(), ind + " =@ "); + debug_ast(block->block(), ind + " <>"); + } else if (Cast(node)) { + Supports_Operator_Ptr block = Cast(node); + std::cerr << ind << "Supports_Operator " << block; + std::cerr << " (" << pstate_source_position(node) << ")" + << std::endl; + debug_ast(block->left(), ind + " left) "); + debug_ast(block->right(), ind + " right) "); + } else if (Cast(node)) { + Supports_Negation_Ptr block = Cast(node); + std::cerr << ind << "Supports_Negation " << block; + std::cerr << " (" << pstate_source_position(node) << ")" + << std::endl; + debug_ast(block->condition(), ind + " condition) "); + } else if (Cast(node)) { + At_Root_Query_Ptr block = Cast(node); + std::cerr << ind << "At_Root_Query " << block; + std::cerr << " (" << pstate_source_position(node) << ")" + << std::endl; + debug_ast(block->feature(), ind + " feature) "); + debug_ast(block->value(), ind + " value) "); + } else if (Cast(node)) { + Supports_Declaration_Ptr block = Cast(node); + std::cerr << ind << "Supports_Declaration " << block; + std::cerr << " (" << pstate_source_position(node) << ")" + << std::endl; + debug_ast(block->feature(), ind + " feature) "); + debug_ast(block->value(), ind + " value) "); + } else if (Cast(node)) { + Block_Ptr root_block = Cast(node); + std::cerr << ind << "Block " << root_block; + std::cerr << " (" << pstate_source_position(node) << ")"; + if (root_block->is_root()) std::cerr << " [root]"; + std::cerr << " " << root_block->tabs() << std::endl; + for(const Statement_Obj& i : root_block->elements()) { debug_ast(i, ind + " ", env); } + } else if (Cast(node)) { + Warning_Ptr block = Cast(node); + std::cerr << ind << "Warning " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << block->tabs() << std::endl; + debug_ast(block->message(), ind + " : "); + } else if (Cast(node)) { + Error_Ptr block = Cast(node); + std::cerr << ind << "Error " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << block->tabs() << std::endl; + } else if (Cast(node)) { + Debug_Ptr block = Cast(node); + std::cerr << ind << "Debug " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << block->tabs() << std::endl; + debug_ast(block->value(), ind + " "); + } else if (Cast(node)) { + Comment_Ptr block = Cast(node); + std::cerr << ind << "Comment " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << block->tabs() << + " <" << prettyprint(block->pstate().token.ws_before()) << ">" << std::endl; + debug_ast(block->text(), ind + "// ", env); + } else if (Cast(node)) { + If_Ptr block = Cast(node); + std::cerr << ind << "If " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << block->tabs() << std::endl; + debug_ast(block->predicate(), ind + " = "); + debug_ast(block->block(), ind + " <>"); + debug_ast(block->alternative(), ind + " ><"); + } else if (Cast(node)) { + Return_Ptr block = Cast(node); + std::cerr << ind << "Return " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << block->tabs() << std::endl; + } else if (Cast(node)) { + Extension_Ptr block = Cast(node); + std::cerr << ind << "Extension " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << block->tabs() << std::endl; + debug_ast(block->selector(), ind + "-> ", env); + } else if (Cast(node)) { + Content_Ptr block = Cast(node); + std::cerr << ind << "Content " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [@media:" << block->media_block() << "]"; + std::cerr << " " << block->tabs() << std::endl; + } else if (Cast(node)) { + Import_Stub_Ptr block = Cast(node); + std::cerr << ind << "Import_Stub " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [" << block->imp_path() << "] "; + std::cerr << " " << block->tabs() << std::endl; + } else if (Cast(node)) { + Import_Ptr block = Cast(node); + std::cerr << ind << "Import " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << block->tabs() << std::endl; + // std::vector files_; + for (auto imp : block->urls()) debug_ast(imp, ind + "@: ", env); + debug_ast(block->import_queries(), ind + "@@ "); + } else if (Cast(node)) { + Assignment_Ptr block = Cast(node); + std::cerr << ind << "Assignment " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " <<" << block->variable() << ">> " << block->tabs() << std::endl; + debug_ast(block->value(), ind + "=", env); + } else if (Cast(node)) { + Declaration_Ptr block = Cast(node); + std::cerr << ind << "Declaration " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [is_custom_property: " << block->is_custom_property() << "] "; + std::cerr << " " << block->tabs() << std::endl; + debug_ast(block->property(), ind + " prop: ", env); + debug_ast(block->value(), ind + " value: ", env); + debug_ast(block->block(), ind + " ", env); + } else if (Cast(node)) { + Keyframe_Rule_Ptr has_block = Cast(node); + std::cerr << ind << "Keyframe_Rule " << has_block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << has_block->tabs() << std::endl; + if (has_block->name()) debug_ast(has_block->name(), ind + "@"); + if (has_block->block()) for(const Statement_Obj& i : has_block->block()->elements()) { debug_ast(i, ind + " ", env); } + } else if (Cast(node)) { + Directive_Ptr block = Cast(node); + std::cerr << ind << "Directive " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [" << block->keyword() << "] " << block->tabs() << std::endl; + debug_ast(block->selector(), ind + "~", env); + debug_ast(block->value(), ind + "+", env); + if (block->block()) for(const Statement_Obj& i : block->block()->elements()) { debug_ast(i, ind + " ", env); } + } else if (Cast(node)) { + Each_Ptr block = Cast(node); + std::cerr << ind << "Each " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << block->tabs() << std::endl; + if (block->block()) for(const Statement_Obj& i : block->block()->elements()) { debug_ast(i, ind + " ", env); } + } else if (Cast(node)) { + For_Ptr block = Cast(node); + std::cerr << ind << "For " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << block->tabs() << std::endl; + if (block->block()) for(const Statement_Obj& i : block->block()->elements()) { debug_ast(i, ind + " ", env); } + } else if (Cast(node)) { + While_Ptr block = Cast(node); + std::cerr << ind << "While " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << block->tabs() << std::endl; + if (block->block()) for(const Statement_Obj& i : block->block()->elements()) { debug_ast(i, ind + " ", env); } + } else if (Cast(node)) { + Definition_Ptr block = Cast(node); + std::cerr << ind << "Definition " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [name: " << block->name() << "] "; + std::cerr << " [type: " << (block->type() == Sass::Definition::Type::MIXIN ? "Mixin " : "Function ") << "] "; + // this seems to lead to segfaults some times? + // std::cerr << " [signature: " << block->signature() << "] "; + std::cerr << " [native: " << block->native_function() << "] "; + std::cerr << " " << block->tabs() << std::endl; + debug_ast(block->parameters(), ind + " params: ", env); + if (block->block()) debug_ast(block->block(), ind + " ", env); + } else if (Cast(node)) { + Mixin_Call_Ptr block = Cast(node); + std::cerr << ind << "Mixin_Call " << block << " " << block->tabs(); + std::cerr << " (" << pstate_source_position(block) << ")"; + std::cerr << " [" << block->name() << "]"; + std::cerr << " [has_content: " << block->has_content() << "] " << std::endl; + debug_ast(block->arguments(), ind + " args: "); + if (block->block()) debug_ast(block->block(), ind + " ", env); + } else if (Ruleset_Ptr ruleset = Cast(node)) { + std::cerr << ind << "Ruleset " << ruleset; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [indent: " << ruleset->tabs() << "]"; + std::cerr << (ruleset->is_invisible() ? " [INVISIBLE]" : ""); + std::cerr << (ruleset->is_root() ? " [root]" : ""); + std::cerr << std::endl; + debug_ast(ruleset->selector(), ind + ">"); + debug_ast(ruleset->block(), ind + " "); + } else if (Cast(node)) { + Block_Ptr block = Cast(node); + std::cerr << ind << "Block " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << (block->is_invisible() ? " [INVISIBLE]" : ""); + std::cerr << " [indent: " << block->tabs() << "]" << std::endl; + for(const Statement_Obj& i : block->elements()) { debug_ast(i, ind + " ", env); } + } else if (Cast(node)) { + Variable_Ptr expression = Cast(node); + std::cerr << ind << "Variable " << expression; + std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [" << expression->name() << "]" << std::endl; + std::string name(expression->name()); + if (env && env->has(name)) debug_ast(Cast((*env)[name]), ind + " -> ", env); + } else if (Cast(node)) { + Function_Call_Schema_Ptr expression = Cast(node); + std::cerr << ind << "Function_Call_Schema " << expression; + std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << "" << std::endl; + debug_ast(expression->name(), ind + "name: ", env); + debug_ast(expression->arguments(), ind + " args: ", env); + } else if (Cast(node)) { + Function_Call_Ptr expression = Cast(node); + std::cerr << ind << "Function_Call " << expression; + std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [" << expression->name() << "]"; + if (expression->is_delayed()) std::cerr << " [delayed]"; + if (expression->is_interpolant()) std::cerr << " [interpolant]"; + if (expression->is_css()) std::cerr << " [css]"; + std::cerr << std::endl; + debug_ast(expression->arguments(), ind + " args: ", env); + debug_ast(expression->func(), ind + " func: ", env); + } else if (Cast(node)) { + Function_Ptr expression = Cast(node); + std::cerr << ind << "Function " << expression; + std::cerr << " (" << pstate_source_position(node) << ")"; + if (expression->is_css()) std::cerr << " [css]"; + std::cerr << std::endl; + debug_ast(expression->definition(), ind + " definition: ", env); + } else if (Cast(node)) { + Arguments_Ptr expression = Cast(node); + std::cerr << ind << "Arguments " << expression; + if (expression->is_delayed()) std::cerr << " [delayed]"; + std::cerr << " (" << pstate_source_position(node) << ")"; + if (expression->has_named_arguments()) std::cerr << " [has_named_arguments]"; + if (expression->has_rest_argument()) std::cerr << " [has_rest_argument]"; + if (expression->has_keyword_argument()) std::cerr << " [has_keyword_argument]"; + std::cerr << std::endl; + for(const Argument_Obj& i : expression->elements()) { debug_ast(i, ind + " ", env); } + } else if (Cast(node)) { + Argument_Ptr expression = Cast(node); + std::cerr << ind << "Argument " << expression; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [" << expression->value().ptr() << "]"; + std::cerr << " [name: " << expression->name() << "] "; + std::cerr << " [rest: " << expression->is_rest_argument() << "] "; + std::cerr << " [keyword: " << expression->is_keyword_argument() << "] " << std::endl; + debug_ast(expression->value(), ind + " value: ", env); + } else if (Cast(node)) { + Parameters_Ptr expression = Cast(node); + std::cerr << ind << "Parameters " << expression; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [has_optional: " << expression->has_optional_parameters() << "] "; + std::cerr << " [has_rest: " << expression->has_rest_parameter() << "] "; + std::cerr << std::endl; + for(const Parameter_Obj& i : expression->elements()) { debug_ast(i, ind + " ", env); } + } else if (Cast(node)) { + Parameter_Ptr expression = Cast(node); + std::cerr << ind << "Parameter " << expression; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [name: " << expression->name() << "] "; + std::cerr << " [default: " << expression->default_value().ptr() << "] "; + std::cerr << " [rest: " << expression->is_rest_parameter() << "] " << std::endl; + } else if (Cast(node)) { + Unary_Expression_Ptr expression = Cast(node); + std::cerr << ind << "Unary_Expression " << expression; + std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; + std::cerr << " [delayed: " << expression->is_delayed() << "] "; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [" << expression->type() << "]" << std::endl; + debug_ast(expression->operand(), ind + " operand: ", env); + } else if (Cast(node)) { + Binary_Expression_Ptr expression = Cast(node); + std::cerr << ind << "Binary_Expression " << expression; + if (expression->is_interpolant()) std::cerr << " [is interpolant] "; + if (expression->is_left_interpolant()) std::cerr << " [left interpolant] "; + if (expression->is_right_interpolant()) std::cerr << " [right interpolant] "; + std::cerr << " [delayed: " << expression->is_delayed() << "] "; + std::cerr << " [ws_before: " << expression->op().ws_before << "] "; + std::cerr << " [ws_after: " << expression->op().ws_after << "] "; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [" << expression->type_name() << "]" << std::endl; + debug_ast(expression->left(), ind + " left: ", env); + debug_ast(expression->right(), ind + " right: ", env); + } else if (Cast(node)) { + Map_Ptr expression = Cast(node); + std::cerr << ind << "Map " << expression; + std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [Hashed]" << std::endl; + for (const auto& i : expression->elements()) { + debug_ast(i.first, ind + " key: "); + debug_ast(i.second, ind + " val: "); + } + } else if (Cast(node)) { + List_Ptr expression = Cast(node); + std::cerr << ind << "List " << expression; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " (" << expression->length() << ") " << + (expression->separator() == SASS_COMMA ? "Comma " : expression->separator() == SASS_HASH ? "Map " : "Space ") << + " [delayed: " << expression->is_delayed() << "] " << + " [interpolant: " << expression->is_interpolant() << "] " << + " [listized: " << expression->from_selector() << "] " << + " [arglist: " << expression->is_arglist() << "] " << + " [bracketed: " << expression->is_bracketed() << "] " << + " [expanded: " << expression->is_expanded() << "] " << + " [hash: " << expression->hash() << "] " << + std::endl; + for(const auto& i : expression->elements()) { debug_ast(i, ind + " ", env); } + } else if (Cast(node)) { + Content_Ptr expression = Cast(node); + std::cerr << ind << "Content " << expression; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [@media:" << expression->media_block() << "]"; + std::cerr << " [Statement]" << std::endl; + } else if (Cast(node)) { + Boolean_Ptr expression = Cast(node); + std::cerr << ind << "Boolean " << expression; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; + std::cerr << " [" << expression->value() << "]" << std::endl; + } else if (Cast(node)) { + Color_Ptr expression = Cast(node); + std::cerr << ind << "Color " << expression; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [delayed: " << expression->is_delayed() << "] "; + std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; + std::cerr << " [" << expression->r() << ":" << expression->g() << ":" << expression->b() << "@" << expression->a() << "]" << std::endl; + } else if (Cast(node)) { + Number_Ptr expression = Cast(node); + std::cerr << ind << "Number " << expression; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [delayed: " << expression->is_delayed() << "] "; + std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; + std::cerr << " [" << expression->value() << expression->unit() << "]" << + " [hash: " << expression->hash() << "] " << + std::endl; + } else if (Cast(node)) { + Null_Ptr expression = Cast(node); + std::cerr << ind << "Null " << expression; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [interpolant: " << expression->is_interpolant() << "] " + // " [hash: " << expression->hash() << "] " + << std::endl; + } else if (Cast(node)) { + String_Quoted_Ptr expression = Cast(node); + std::cerr << ind << "String_Quoted " << expression; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [" << prettyprint(expression->value()) << "]"; + if (expression->is_delayed()) std::cerr << " [delayed]"; + if (expression->is_interpolant()) std::cerr << " [interpolant]"; + if (expression->quote_mark()) std::cerr << " [quote_mark: " << expression->quote_mark() << "]"; + std::cerr << " <" << prettyprint(expression->pstate().token.ws_before()) << ">" << std::endl; + } else if (Cast(node)) { + String_Constant_Ptr expression = Cast(node); + std::cerr << ind << "String_Constant " << expression; + if (expression->concrete_type()) { + std::cerr << " " << expression->concrete_type(); + } + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [" << prettyprint(expression->value()) << "]"; + if (expression->is_delayed()) std::cerr << " [delayed]"; + if (expression->is_interpolant()) std::cerr << " [interpolant]"; + std::cerr << " <" << prettyprint(expression->pstate().token.ws_before()) << ">" << std::endl; + } else if (Cast(node)) { + String_Schema_Ptr expression = Cast(node); + std::cerr << ind << "String_Schema " << expression; + std::cerr << " (" << pstate_source_position(expression) << ")"; + std::cerr << " " << expression->concrete_type(); + std::cerr << " (" << pstate_source_position(node) << ")"; + if (expression->css()) std::cerr << " [css]"; + if (expression->is_delayed()) std::cerr << " [delayed]"; + if (expression->is_interpolant()) std::cerr << " [is interpolant]"; + if (expression->has_interpolant()) std::cerr << " [has interpolant]"; + if (expression->is_left_interpolant()) std::cerr << " [left interpolant] "; + if (expression->is_right_interpolant()) std::cerr << " [right interpolant] "; + std::cerr << " <" << prettyprint(expression->pstate().token.ws_before()) << ">" << std::endl; + for(const auto& i : expression->elements()) { debug_ast(i, ind + " ", env); } + } else if (Cast(node)) { + String_Ptr expression = Cast(node); + std::cerr << ind << "String " << expression; + std::cerr << " " << expression->concrete_type(); + std::cerr << " (" << pstate_source_position(node) << ")"; + if (expression->is_interpolant()) std::cerr << " [interpolant]"; + std::cerr << " <" << prettyprint(expression->pstate().token.ws_before()) << ">" << std::endl; + } else if (Cast(node)) { + Expression_Ptr expression = Cast(node); + std::cerr << ind << "Expression " << expression; + std::cerr << " (" << pstate_source_position(node) << ")"; + switch (expression->concrete_type()) { + case Expression::Concrete_Type::NONE: std::cerr << " [NONE]"; break; + case Expression::Concrete_Type::BOOLEAN: std::cerr << " [BOOLEAN]"; break; + case Expression::Concrete_Type::NUMBER: std::cerr << " [NUMBER]"; break; + case Expression::Concrete_Type::COLOR: std::cerr << " [COLOR]"; break; + case Expression::Concrete_Type::STRING: std::cerr << " [STRING]"; break; + case Expression::Concrete_Type::LIST: std::cerr << " [LIST]"; break; + case Expression::Concrete_Type::MAP: std::cerr << " [MAP]"; break; + case Expression::Concrete_Type::SELECTOR: std::cerr << " [SELECTOR]"; break; + case Expression::Concrete_Type::NULL_VAL: std::cerr << " [NULL_VAL]"; break; + case Expression::Concrete_Type::C_WARNING: std::cerr << " [C_WARNING]"; break; + case Expression::Concrete_Type::C_ERROR: std::cerr << " [C_ERROR]"; break; + case Expression::Concrete_Type::FUNCTION: std::cerr << " [FUNCTION]"; break; + case Expression::Concrete_Type::NUM_TYPES: std::cerr << " [NUM_TYPES]"; break; + case Expression::Concrete_Type::VARIABLE: std::cerr << " [VARIABLE]"; break; + case Expression::Concrete_Type::FUNCTION_VAL: std::cerr << " [FUNCTION_VAL]"; break; + } + std::cerr << std::endl; + } else if (Cast(node)) { + Has_Block_Ptr has_block = Cast(node); + std::cerr << ind << "Has_Block " << has_block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << has_block->tabs() << std::endl; + if (has_block->block()) for(const Statement_Obj& i : has_block->block()->elements()) { debug_ast(i, ind + " ", env); } + } else if (Cast(node)) { + Statement_Ptr statement = Cast(node); + std::cerr << ind << "Statement " << statement; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << statement->tabs() << std::endl; + } + + if (ind == "") std::cerr << "####################################################################\n"; +} + +inline void debug_node(Node* node, std::string ind = "") +{ + if (ind == "") std::cerr << "#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n"; + if (node->isCombinator()) { + std::cerr << ind; + std::cerr << "Combinator "; + std::cerr << node << " "; + if (node->got_line_feed) std::cerr << "[LF] "; + switch (node->combinator()) { + case Complex_Selector::ADJACENT_TO: std::cerr << "{+} "; break; + case Complex_Selector::PARENT_OF: std::cerr << "{>} "; break; + case Complex_Selector::PRECEDES: std::cerr << "{~} "; break; + case Complex_Selector::REFERENCE: std::cerr << "{@} "; break; + case Complex_Selector::ANCESTOR_OF: std::cerr << "{ } "; break; + } + std::cerr << std::endl; + // debug_ast(node->combinator(), ind + " "); + } else if (node->isSelector()) { + std::cerr << ind; + std::cerr << "Selector "; + std::cerr << node << " "; + if (node->got_line_feed) std::cerr << "[LF] "; + std::cerr << std::endl; + debug_ast(node->selector(), ind + " "); + } else if (node->isCollection()) { + std::cerr << ind; + std::cerr << "Collection "; + std::cerr << node << " "; + if (node->got_line_feed) std::cerr << "[LF] "; + std::cerr << std::endl; + for(auto n : (*node->collection())) { + debug_node(&n, ind + " "); + } + } else if (node->isNil()) { + std::cerr << ind; + std::cerr << "Nil "; + std::cerr << node << " "; + if (node->got_line_feed) std::cerr << "[LF] "; + std::cerr << std::endl; + } else { + std::cerr << ind; + std::cerr << "OTHER "; + std::cerr << node << " "; + if (node->got_line_feed) std::cerr << "[LF] "; + std::cerr << std::endl; + } + if (ind == "") std::cerr << "#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n"; +} + +/* +inline void debug_ast(const AST_Node_Ptr node, std::string ind = "", Env* env = 0) +{ + debug_ast(const_cast(node), ind, env); +} +*/ +inline void debug_node(const Node* node, std::string ind = "") +{ + debug_node(const_cast(node), ind); +} + +inline void debug_subset_map(Sass::Subset_Map& map, std::string ind = "") +{ + if (ind == "") std::cerr << "#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; + for(auto const &it : map.values()) { + debug_ast(it.first, ind + "first: "); + debug_ast(it.second, ind + "second: "); + } + if (ind == "") std::cerr << "#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; +} + +inline void debug_subset_entries(SubSetMapPairs* entries, std::string ind = "") +{ + if (ind == "") std::cerr << "#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; + for(auto const &pair : *entries) { + debug_ast(pair.first, ind + "first: "); + debug_ast(pair.second, ind + "second: "); + } + if (ind == "") std::cerr << "#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; +} + +#endif // SASS_DEBUGGER diff --git a/src/libsass/dev-ast-memory.md b/src/libsass/dev-ast-memory.md new file mode 100644 index 000000000..31004bcf2 --- /dev/null +++ b/src/libsass/dev-ast-memory.md @@ -0,0 +1,223 @@ +# LibSass smart pointer implementation + +LibSass uses smart pointers very similar to `shared_ptr` known +by Boost or C++11. Implementation is a bit less modular since +it was not needed. Various compile time debug options are +available if you need to debug memory life-cycles. + + +## Memory Classes + +### SharedObj + +Base class for the actual node implementations. This ensures +that every object has a reference counter and other values. + +```c++ +class AST_Node : public SharedObj { ... }; +``` + +### SharedPtr (base class for SharedImpl) + +Base class that holds on to the pointer. The reference counter +is stored inside the pointer object directly (`SharedObj`). + +### SharedImpl (inherits from SharedPtr) + +This is the main base class for objects you use in your code. It +will make sure that the memory it points at will be deleted once +all copies to the same object/memory go out of scope. + +```c++ +Class* pointer = new Class(...); +SharedImpl obj(pointer); +``` + +To spare the developer of typing the templated class every time, +we created typedefs for each available AST Node specialization. + +```c++ +typedef SharedImpl Number_Obj; +Number_Obj number = SASS_MEMORY_NEW(...); +``` + + +## Memory life-cycles + +### Pointer pickups + +I often use the terminology of "pickup". This means the moment when +a raw pointer not under any control is assigned to a reference counted +object (`XYZ_Obj = XYZ_Ptr`). From that point on memory will be +automatically released once the object goes out of scope (but only +if the reference counter reaches zero). Main point beeing, you don't +have to worry about memory management yourself. + +### Object detach + +Sometimes we can't return reference counted objects directly (see +invalid covariant return types problems below). But we often still +need to use reference objects inside a function to avoid leaks when +something throws. For this you can use `detach`, which basically +detaches the pointer memory from the reference counted object. So +when the reference counted object goes out of scope, it will not +free the attached memory. You are now again in charge of freeing +the memory (just assign it to a reference counted object again). + + +## Circular references + +Reference counted memory implementations are prone to circular references. +This can be addressed by using a multi generation garbage collector. But +for our use-case that seems overkill. There is no way so far for users +(sass code) to create circular references. Therefore we can code around +this possible issue. But developers should be aware of this limitation. + +There are AFAIR two places where circular references could happen. One is +the `sources` member on every `Selector`. The other one can happen in the +extend code (Node handling). The easy way to avoid this is to only assign +complete object clones to these members. If you know the objects lifetime +is longer than the reference you create, you can also just store the raw +pointer. Once needed this could be solved with weak pointers. + + +## Addressing the invalid covariant return types problems + +If you are not familiar with the mentioned problem, you may want +to read up on covariant return types and virtual functions, i.e. + +- http://stackoverflow.com/questions/6924754/return-type-covariance-with-smart-pointers +- http://stackoverflow.com/questions/196733/how-can-i-use-covariant-return-types-with-smart-pointers +- http://stackoverflow.com/questions/2687790/how-to-accomplish-covariant-return-types-when-returning-a-shared-ptr + +We hit this issue at least with the CRTP visitor pattern (eval, expand, +listize and so forth). This means we cannot return reference counted +objects directly. We are forced to return raw pointers or we would need +to have a lot of explicit and expensive upcasts by callers/consumers. + +### Simple functions that allocate new AST Nodes + +In the parser step we often create new objects and can just return a +unique pointer (meaning ownership clearly shifts back to the caller). +The caller/consumer is responsible that the memory is freed. + +```c++ +typedef Number* Number_Ptr; +int parse_integer() { + ... // do the parsing + return 42; +} +Number_Ptr parse_number() { + Number_Ptr p_nr = SASS_MEMORY_NEW(...); + p_nr->value(parse_integer()); + return p_nr; +} +Number_Obj nr = parse_number(); +``` + +The above would be the encouraged pattern for such simple cases. + +### Allocate new AST Nodes in functions that can throw + +There is a major caveat with the previous example, considering this +more real-life implementation that throws an error. The throw may +happen deep down in another function. Holding raw pointers that +we need to free would leak in this case. + +```c++ +int parse_integer() { + ... // do the parsing + if (error) throw(error); + return 42; +} +``` + +With this `parse_integer` function the previous example would leak memory. +I guess it is pretty obvious, as the allocated memory will not be freed, +as it was never assigned to a SharedObj value. Therefore the above code +would better be written as: + +```c++ +typedef Number* Number_Ptr; +int parse_integer() { + ... // do the parsing + if (error) throw(error); + return 42; +} +// this leaks due to pointer return +// should return Number_Obj instead +// though not possible for virtuals! +Number_Ptr parse_number() { + Number_Obj nr = SASS_MEMORY_NEW(...); + nr->value(parse_integer()); // throws + return &nr; // Ptr from Obj +} +Number_Obj nr = parse_number(); +// will now be freed automatically +``` + +The example above unfortunately will not work as is, since we return a +`Number_Ptr` from that function. Therefore the object allocated inside +the function is already gone when it is picked up again by the caller. +The easy fix for the given simplified use case would be to change the +return type of `parse_number` to `Number_Obj`. Indeed we do it exactly +this way in the parser. But as stated above, this will not work for +virtual functions due to invalid covariant return types! + +### Return managed objects from virtual functions + +The easy fix would be to just create a new copy on the heap and return +that. But this seems like a very inelegant solution to this problem. I +mean why can't we just tell the object to treat it like a newly allocated +object? And indeed we can. I've added a `detach` method that will tell +the object to survive deallocation until the next pickup. This means +that it will leak if it is not picked up by consumer. + +```c++ +typedef Number* Number_Ptr; +int parse_integer() { + ... // do the parsing + if (error) throw(error); + return 42; +} +Number_Ptr parse_number() { + Number_Obj nr = SASS_MEMORY_NEW(...); + nr->value(parse_integer()); // throws + return nr.detach(); +} +Number_Obj nr = parse_number(); +// will now be freed automatically +``` + + +## Compile time debug options + +To enable memory debugging you need to define `DEBUG_SHARED_PTR`. +This can i.e. be done in `include/sass/base.h` + +```c++ +define DEBUG_SHARED_PTR +``` + +This will print lost memory on exit to stderr. You can also use +`setDbg(true)` on sepecific variables to emit reference counter +increase, decrease and other events. + + +## Why reinvent the wheel when there is `shared_ptr` from C++11 + +First, implementing a smart pointer class is not really that hard. It +was indeed also a learning experience for myself. But there are more +profound advantages: + +- Better GCC 4.4 compatibility (which most code still has OOTB) +- Not thread safe (give us some free performance on some compiler) +- Beeing able to track memory allocations for debugging purposes +- Adding additional features if needed (as seen in `detach`) +- Optional: optimized weak pointer implementation possible + +### Thread Safety + +As said above, this is not thread safe currently. But we don't need +this ATM anyway. And I guess we probably never will share AST Nodes +across different threads. \ No newline at end of file diff --git a/src/libsass/eval.cpp b/src/libsass/eval.cpp new file mode 100644 index 000000000..841f7277b --- /dev/null +++ b/src/libsass/eval.cpp @@ -0,0 +1,1663 @@ +#include "sass.hpp" +#include +#include +#include +#include +#include +#include + +#include "file.hpp" +#include "eval.hpp" +#include "ast.hpp" +#include "bind.hpp" +#include "util.hpp" +#include "inspect.hpp" +#include "operators.hpp" +#include "environment.hpp" +#include "position.hpp" +#include "sass/values.h" +#include "to_value.hpp" +#include "to_c.hpp" +#include "context.hpp" +#include "backtrace.hpp" +#include "lexer.hpp" +#include "prelexer.hpp" +#include "parser.hpp" +#include "expand.hpp" +#include "color_maps.hpp" +#include "sass_functions.hpp" + +namespace Sass { + + Eval::Eval(Expand& exp) + : exp(exp), + ctx(exp.ctx), + traces(exp.traces), + force(false), + is_in_comment(false), + is_in_selector_schema(false) + { + bool_true = SASS_MEMORY_NEW(Boolean, "[NA]", true); + bool_false = SASS_MEMORY_NEW(Boolean, "[NA]", false); + } + Eval::~Eval() { } + + Env* Eval::environment() + { + return exp.environment(); + } + + Selector_List_Obj Eval::selector() + { + return exp.selector(); + } + + Expression_Ptr Eval::operator()(Block_Ptr b) + { + Expression_Ptr val = 0; + for (size_t i = 0, L = b->length(); i < L; ++i) { + val = b->at(i)->perform(this); + if (val) return val; + } + return val; + } + + Expression_Ptr Eval::operator()(Assignment_Ptr a) + { + Env* env = exp.environment(); + std::string var(a->variable()); + if (a->is_global()) { + if (a->is_default()) { + if (env->has_global(var)) { + Expression_Ptr e = Cast(env->get_global(var)); + if (!e || e->concrete_type() == Expression::NULL_VAL) { + env->set_global(var, a->value()->perform(this)); + } + } + else { + env->set_global(var, a->value()->perform(this)); + } + } + else { + env->set_global(var, a->value()->perform(this)); + } + } + else if (a->is_default()) { + if (env->has_lexical(var)) { + auto cur = env; + while (cur && cur->is_lexical()) { + if (cur->has_local(var)) { + if (AST_Node_Obj node = cur->get_local(var)) { + Expression_Ptr e = Cast(node); + if (!e || e->concrete_type() == Expression::NULL_VAL) { + cur->set_local(var, a->value()->perform(this)); + } + } + else { + throw std::runtime_error("Env not in sync"); + } + return 0; + } + cur = cur->parent(); + } + throw std::runtime_error("Env not in sync"); + } + else if (env->has_global(var)) { + if (AST_Node_Obj node = env->get_global(var)) { + Expression_Ptr e = Cast(node); + if (!e || e->concrete_type() == Expression::NULL_VAL) { + env->set_global(var, a->value()->perform(this)); + } + } + } + else if (env->is_lexical()) { + env->set_local(var, a->value()->perform(this)); + } + else { + env->set_local(var, a->value()->perform(this)); + } + } + else { + env->set_lexical(var, a->value()->perform(this)); + } + return 0; + } + + Expression_Ptr Eval::operator()(If_Ptr i) + { + Expression_Obj rv = 0; + Env env(exp.environment()); + exp.env_stack.push_back(&env); + Expression_Obj cond = i->predicate()->perform(this); + if (!cond->is_false()) { + rv = i->block()->perform(this); + } + else { + Block_Obj alt = i->alternative(); + if (alt) rv = alt->perform(this); + } + exp.env_stack.pop_back(); + return rv.detach(); + } + + // For does not create a new env scope + // But iteration vars are reset afterwards + Expression_Ptr Eval::operator()(For_Ptr f) + { + std::string variable(f->variable()); + Expression_Obj low = f->lower_bound()->perform(this); + if (low->concrete_type() != Expression::NUMBER) { + traces.push_back(Backtrace(low->pstate())); + throw Exception::TypeMismatch(traces, *low, "integer"); + } + Expression_Obj high = f->upper_bound()->perform(this); + if (high->concrete_type() != Expression::NUMBER) { + traces.push_back(Backtrace(high->pstate())); + throw Exception::TypeMismatch(traces, *high, "integer"); + } + Number_Obj sass_start = Cast(low); + Number_Obj sass_end = Cast(high); + // check if units are valid for sequence + if (sass_start->unit() != sass_end->unit()) { + std::stringstream msg; msg << "Incompatible units: '" + << sass_end->unit() << "' and '" + << sass_start->unit() << "'."; + error(msg.str(), low->pstate(), traces); + } + double start = sass_start->value(); + double end = sass_end->value(); + // only create iterator once in this environment + Env env(environment(), true); + exp.env_stack.push_back(&env); + Block_Obj body = f->block(); + Expression_Ptr val = 0; + if (start < end) { + if (f->is_inclusive()) ++end; + for (double i = start; + i < end; + ++i) { + Number_Obj it = SASS_MEMORY_NEW(Number, low->pstate(), i, sass_end->unit()); + env.set_local(variable, it); + val = body->perform(this); + if (val) break; + } + } else { + if (f->is_inclusive()) --end; + for (double i = start; + i > end; + --i) { + Number_Obj it = SASS_MEMORY_NEW(Number, low->pstate(), i, sass_end->unit()); + env.set_local(variable, it); + val = body->perform(this); + if (val) break; + } + } + exp.env_stack.pop_back(); + return val; + } + + // Eval does not create a new env scope + // But iteration vars are reset afterwards + Expression_Ptr Eval::operator()(Each_Ptr e) + { + std::vector variables(e->variables()); + Expression_Obj expr = e->list()->perform(this); + Env env(environment(), true); + exp.env_stack.push_back(&env); + List_Obj list = 0; + Map_Ptr map = 0; + if (expr->concrete_type() == Expression::MAP) { + map = Cast(expr); + } + else if (Selector_List_Ptr ls = Cast(expr)) { + Listize listize; + Expression_Obj rv = ls->perform(&listize); + list = Cast(rv); + } + else if (expr->concrete_type() != Expression::LIST) { + list = SASS_MEMORY_NEW(List, expr->pstate(), 1, SASS_COMMA); + list->append(expr); + } + else { + list = Cast(expr); + } + + Block_Obj body = e->block(); + Expression_Obj val = 0; + + if (map) { + for (Expression_Obj key : map->keys()) { + Expression_Obj value = map->at(key); + + if (variables.size() == 1) { + List_Ptr variable = SASS_MEMORY_NEW(List, map->pstate(), 2, SASS_SPACE); + variable->append(key); + variable->append(value); + env.set_local(variables[0], variable); + } else { + env.set_local(variables[0], key); + env.set_local(variables[1], value); + } + + val = body->perform(this); + if (val) break; + } + } + else { + if (list->length() == 1 && Cast(list)) { + list = Cast(list); + } + for (size_t i = 0, L = list->length(); i < L; ++i) { + Expression_Ptr item = list->at(i); + // unwrap value if the expression is an argument + if (Argument_Ptr arg = Cast(item)) item = arg->value(); + // check if we got passed a list of args (investigate) + if (List_Ptr scalars = Cast(item)) { + if (variables.size() == 1) { + Expression_Ptr var = scalars; + env.set_local(variables[0], var); + } else { + // XXX: this is never hit via spec tests + for (size_t j = 0, K = variables.size(); j < K; ++j) { + Expression_Ptr res = j >= scalars->length() + ? SASS_MEMORY_NEW(Null, expr->pstate()) + : scalars->at(j); + env.set_local(variables[j], res); + } + } + } else { + if (variables.size() > 0) { + env.set_local(variables.at(0), item); + for (size_t j = 1, K = variables.size(); j < K; ++j) { + // XXX: this is never hit via spec tests + Expression_Ptr res = SASS_MEMORY_NEW(Null, expr->pstate()); + env.set_local(variables[j], res); + } + } + } + val = body->perform(this); + if (val) break; + } + } + exp.env_stack.pop_back(); + return val.detach(); + } + + Expression_Ptr Eval::operator()(While_Ptr w) + { + Expression_Obj pred = w->predicate(); + Block_Obj body = w->block(); + Env env(environment(), true); + exp.env_stack.push_back(&env); + Expression_Obj cond = pred->perform(this); + while (!cond->is_false()) { + Expression_Obj val = body->perform(this); + if (val) { + exp.env_stack.pop_back(); + return val.detach(); + } + cond = pred->perform(this); + } + exp.env_stack.pop_back(); + return 0; + } + + Expression_Ptr Eval::operator()(Return_Ptr r) + { + return r->value()->perform(this); + } + + Expression_Ptr Eval::operator()(Warning_Ptr w) + { + Sass_Output_Style outstyle = ctx.c_options.output_style; + ctx.c_options.output_style = NESTED; + Expression_Obj message = w->message()->perform(this); + Env* env = exp.environment(); + + // try to use generic function + if (env->has("@warn[f]")) { + + // add call stack entry + ctx.callee_stack.push_back({ + "@warn", + w->pstate().path, + w->pstate().line + 1, + w->pstate().column + 1, + SASS_CALLEE_FUNCTION, + { env } + }); + + Definition_Ptr def = Cast((*env)["@warn[f]"]); + // Block_Obj body = def->block(); + // Native_Function func = def->native_function(); + Sass_Function_Entry c_function = def->c_function(); + Sass_Function_Fn c_func = sass_function_get_function(c_function); + + To_C to_c; + union Sass_Value* c_args = sass_make_list(1, SASS_COMMA, false); + sass_list_set_value(c_args, 0, message->perform(&to_c)); + union Sass_Value* c_val = c_func(c_args, c_function, ctx.c_compiler); + ctx.c_options.output_style = outstyle; + ctx.callee_stack.pop_back(); + sass_delete_value(c_args); + sass_delete_value(c_val); + return 0; + + } + + std::string result(unquote(message->to_sass())); + std::cerr << "WARNING: " << result << std::endl; + traces.push_back(Backtrace(w->pstate())); + std::cerr << traces_to_string(traces, " "); + std::cerr << std::endl; + ctx.c_options.output_style = outstyle; + traces.pop_back(); + return 0; + } + + Expression_Ptr Eval::operator()(Error_Ptr e) + { + Sass_Output_Style outstyle = ctx.c_options.output_style; + ctx.c_options.output_style = NESTED; + Expression_Obj message = e->message()->perform(this); + Env* env = exp.environment(); + + // try to use generic function + if (env->has("@error[f]")) { + + // add call stack entry + ctx.callee_stack.push_back({ + "@error", + e->pstate().path, + e->pstate().line + 1, + e->pstate().column + 1, + SASS_CALLEE_FUNCTION, + { env } + }); + + Definition_Ptr def = Cast((*env)["@error[f]"]); + // Block_Obj body = def->block(); + // Native_Function func = def->native_function(); + Sass_Function_Entry c_function = def->c_function(); + Sass_Function_Fn c_func = sass_function_get_function(c_function); + + To_C to_c; + union Sass_Value* c_args = sass_make_list(1, SASS_COMMA, false); + sass_list_set_value(c_args, 0, message->perform(&to_c)); + union Sass_Value* c_val = c_func(c_args, c_function, ctx.c_compiler); + ctx.c_options.output_style = outstyle; + ctx.callee_stack.pop_back(); + sass_delete_value(c_args); + sass_delete_value(c_val); + return 0; + + } + + std::string result(unquote(message->to_sass())); + ctx.c_options.output_style = outstyle; + error(result, e->pstate(), traces); + return 0; + } + + Expression_Ptr Eval::operator()(Debug_Ptr d) + { + Sass_Output_Style outstyle = ctx.c_options.output_style; + ctx.c_options.output_style = NESTED; + Expression_Obj message = d->value()->perform(this); + Env* env = exp.environment(); + + // try to use generic function + if (env->has("@debug[f]")) { + + // add call stack entry + ctx.callee_stack.push_back({ + "@debug", + d->pstate().path, + d->pstate().line + 1, + d->pstate().column + 1, + SASS_CALLEE_FUNCTION, + { env } + }); + + Definition_Ptr def = Cast((*env)["@debug[f]"]); + // Block_Obj body = def->block(); + // Native_Function func = def->native_function(); + Sass_Function_Entry c_function = def->c_function(); + Sass_Function_Fn c_func = sass_function_get_function(c_function); + + To_C to_c; + union Sass_Value* c_args = sass_make_list(1, SASS_COMMA, false); + sass_list_set_value(c_args, 0, message->perform(&to_c)); + union Sass_Value* c_val = c_func(c_args, c_function, ctx.c_compiler); + ctx.c_options.output_style = outstyle; + ctx.callee_stack.pop_back(); + sass_delete_value(c_args); + sass_delete_value(c_val); + return 0; + + } + + std::string cwd(ctx.cwd()); + std::string result(unquote(message->to_sass())); + std::string abs_path(Sass::File::rel2abs(d->pstate().path, cwd, cwd)); + std::string rel_path(Sass::File::abs2rel(d->pstate().path, cwd, cwd)); + std::string output_path(Sass::File::path_for_console(rel_path, abs_path, d->pstate().path)); + ctx.c_options.output_style = outstyle; + + std::cerr << output_path << ":" << d->pstate().line+1 << " DEBUG: " << result; + std::cerr << std::endl; + return 0; + } + + Expression_Ptr Eval::operator()(List_Ptr l) + { + // special case for unevaluated map + if (l->separator() == SASS_HASH) { + Map_Obj lm = SASS_MEMORY_NEW(Map, + l->pstate(), + l->length() / 2); + for (size_t i = 0, L = l->length(); i < L; i += 2) + { + Expression_Obj key = (*l)[i+0]->perform(this); + Expression_Obj val = (*l)[i+1]->perform(this); + // make sure the color key never displays its real name + key->is_delayed(true); // verified + *lm << std::make_pair(key, val); + } + if (lm->has_duplicate_key()) { + traces.push_back(Backtrace(l->pstate())); + throw Exception::DuplicateKeyError(traces, *lm, *l); + } + + lm->is_interpolant(l->is_interpolant()); + return lm->perform(this); + } + // check if we should expand it + if (l->is_expanded()) return l; + // regular case for unevaluated lists + List_Obj ll = SASS_MEMORY_NEW(List, + l->pstate(), + l->length(), + l->separator(), + l->is_arglist(), + l->is_bracketed()); + for (size_t i = 0, L = l->length(); i < L; ++i) { + ll->append((*l)[i]->perform(this)); + } + ll->is_interpolant(l->is_interpolant()); + ll->from_selector(l->from_selector()); + ll->is_expanded(true); + return ll.detach(); + } + + Expression_Ptr Eval::operator()(Map_Ptr m) + { + if (m->is_expanded()) return m; + + // make sure we're not starting with duplicate keys. + // the duplicate key state will have been set in the parser phase. + if (m->has_duplicate_key()) { + traces.push_back(Backtrace(m->pstate())); + throw Exception::DuplicateKeyError(traces, *m, *m); + } + + Map_Obj mm = SASS_MEMORY_NEW(Map, + m->pstate(), + m->length()); + for (auto key : m->keys()) { + Expression_Ptr ex_key = key->perform(this); + Expression_Ptr ex_val = m->at(key); + if (ex_val == NULL) continue; + ex_val = ex_val->perform(this); + *mm << std::make_pair(ex_key, ex_val); + } + + // check the evaluated keys aren't duplicates. + if (mm->has_duplicate_key()) { + traces.push_back(Backtrace(m->pstate())); + throw Exception::DuplicateKeyError(traces, *mm, *m); + } + + mm->is_expanded(true); + return mm.detach(); + } + + Expression_Ptr Eval::operator()(Binary_Expression_Ptr b_in) + { + + Expression_Obj lhs = b_in->left(); + Expression_Obj rhs = b_in->right(); + enum Sass_OP op_type = b_in->optype(); + + if (op_type == Sass_OP::AND) { + // LOCAL_FLAG(force, true); + lhs = lhs->perform(this); + if (!*lhs) return lhs.detach(); + return rhs->perform(this); + } + else if (op_type == Sass_OP::OR) { + // LOCAL_FLAG(force, true); + lhs = lhs->perform(this); + if (*lhs) return lhs.detach(); + return rhs->perform(this); + } + + // Evaluate variables as early o + while (Variable_Ptr l_v = Cast(lhs)) { + lhs = operator()(l_v); + } + while (Variable_Ptr r_v = Cast(rhs)) { + rhs = operator()(r_v); + } + + Binary_Expression_Obj b = b_in; + + // Evaluate sub-expressions early on + while (Binary_Expression_Ptr l_b = Cast(lhs)) { + if (!force && l_b->is_delayed()) break; + lhs = operator()(l_b); + } + while (Binary_Expression_Ptr r_b = Cast(rhs)) { + if (!force && r_b->is_delayed()) break; + rhs = operator()(r_b); + } + + // don't eval delayed expressions (the '/' when used as a separator) + if (!force && op_type == Sass_OP::DIV && b->is_delayed()) { + b->right(b->right()->perform(this)); + b->left(b->left()->perform(this)); + return b.detach(); + } + + // specific types we know are final + // handle them early to avoid overhead + if (Number_Ptr l_n = Cast(lhs)) { + // lhs is number and rhs is number + if (Number_Ptr r_n = Cast(rhs)) { + try { + switch (op_type) { + case Sass_OP::EQ: return *l_n == *r_n ? bool_true : bool_false; + case Sass_OP::NEQ: return *l_n == *r_n ? bool_false : bool_true; + case Sass_OP::LT: return *l_n < *r_n ? bool_true : bool_false; + case Sass_OP::GTE: return *l_n < *r_n ? bool_false : bool_true; + case Sass_OP::LTE: return *l_n < *r_n || *l_n == *r_n ? bool_true : bool_false; + case Sass_OP::GT: return *l_n < *r_n || *l_n == *r_n ? bool_false : bool_true; + case Sass_OP::ADD: case Sass_OP::SUB: case Sass_OP::MUL: case Sass_OP::DIV: case Sass_OP::MOD: + return Operators::op_numbers(op_type, *l_n, *r_n, ctx.c_options, b_in->pstate()); + default: break; + } + } + catch (Exception::OperationError& err) + { + traces.push_back(Backtrace(b_in->pstate())); + throw Exception::SassValueError(traces, b_in->pstate(), err); + } + } + // lhs is number and rhs is color + else if (Color_Ptr r_c = Cast(rhs)) { + try { + switch (op_type) { + case Sass_OP::EQ: return *l_n == *r_c ? bool_true : bool_false; + case Sass_OP::NEQ: return *l_n == *r_c ? bool_false : bool_true; + case Sass_OP::ADD: case Sass_OP::SUB: case Sass_OP::MUL: case Sass_OP::DIV: case Sass_OP::MOD: + return Operators::op_number_color(op_type, *l_n, *r_c, ctx.c_options, b_in->pstate()); + default: break; + } + } + catch (Exception::OperationError& err) + { + traces.push_back(Backtrace(b_in->pstate())); + throw Exception::SassValueError(traces, b_in->pstate(), err); + } + } + } + else if (Color_Ptr l_c = Cast(lhs)) { + // lhs is color and rhs is color + if (Color_Ptr r_c = Cast(rhs)) { + try { + switch (op_type) { + case Sass_OP::EQ: return *l_c == *r_c ? bool_true : bool_false; + case Sass_OP::NEQ: return *l_c == *r_c ? bool_false : bool_true; + case Sass_OP::LT: return *l_c < *r_c ? bool_true : bool_false; + case Sass_OP::GTE: return *l_c < *r_c ? bool_false : bool_true; + case Sass_OP::LTE: return *l_c < *r_c || *l_c == *r_c ? bool_true : bool_false; + case Sass_OP::GT: return *l_c < *r_c || *l_c == *r_c ? bool_false : bool_true; + case Sass_OP::ADD: case Sass_OP::SUB: case Sass_OP::MUL: case Sass_OP::DIV: case Sass_OP::MOD: + return Operators::op_colors(op_type, *l_c, *r_c, ctx.c_options, b_in->pstate()); + default: break; + } + } + catch (Exception::OperationError& err) + { + traces.push_back(Backtrace(b_in->pstate())); + throw Exception::SassValueError(traces, b_in->pstate(), err); + } + } + // lhs is color and rhs is number + else if (Number_Ptr r_n = Cast(rhs)) { + try { + switch (op_type) { + case Sass_OP::EQ: return *l_c == *r_n ? bool_true : bool_false; + case Sass_OP::NEQ: return *l_c == *r_n ? bool_false : bool_true; + case Sass_OP::ADD: case Sass_OP::SUB: case Sass_OP::MUL: case Sass_OP::DIV: case Sass_OP::MOD: + return Operators::op_color_number(op_type, *l_c, *r_n, ctx.c_options, b_in->pstate()); + default: break; + } + } + catch (Exception::OperationError& err) + { + traces.push_back(Backtrace(b_in->pstate())); + throw Exception::SassValueError(traces, b_in->pstate(), err); + } + } + } + + String_Schema_Obj ret_schema; + + // only the last item will be used to eval the binary expression + if (String_Schema_Ptr s_l = Cast(b->left())) { + if (!s_l->has_interpolant() && (!s_l->is_right_interpolant())) { + ret_schema = SASS_MEMORY_NEW(String_Schema, b->pstate()); + Binary_Expression_Obj bin_ex = SASS_MEMORY_NEW(Binary_Expression, b->pstate(), + b->op(), s_l->last(), b->right()); + bin_ex->is_delayed(b->left()->is_delayed() || b->right()->is_delayed()); // unverified + for (size_t i = 0; i < s_l->length() - 1; ++i) { + ret_schema->append(s_l->at(i)->perform(this)); + } + ret_schema->append(bin_ex->perform(this)); + return ret_schema->perform(this); + } + } + if (String_Schema_Ptr s_r = Cast(b->right())) { + + if (!s_r->has_interpolant() && (!s_r->is_left_interpolant() || op_type == Sass_OP::DIV)) { + ret_schema = SASS_MEMORY_NEW(String_Schema, b->pstate()); + Binary_Expression_Obj bin_ex = SASS_MEMORY_NEW(Binary_Expression, b->pstate(), + b->op(), b->left(), s_r->first()); + bin_ex->is_delayed(b->left()->is_delayed() || b->right()->is_delayed()); // verified + ret_schema->append(bin_ex->perform(this)); + for (size_t i = 1; i < s_r->length(); ++i) { + ret_schema->append(s_r->at(i)->perform(this)); + } + return ret_schema->perform(this); + } + } + + // fully evaluate their values + if (op_type == Sass_OP::EQ || + op_type == Sass_OP::NEQ || + op_type == Sass_OP::GT || + op_type == Sass_OP::GTE || + op_type == Sass_OP::LT || + op_type == Sass_OP::LTE) + { + LOCAL_FLAG(force, true); + lhs->is_expanded(false); + lhs->set_delayed(false); + lhs = lhs->perform(this); + rhs->is_expanded(false); + rhs->set_delayed(false); + rhs = rhs->perform(this); + } + else { + lhs = lhs->perform(this); + } + + // not a logical connective, so go ahead and eval the rhs + rhs = rhs->perform(this); + AST_Node_Obj lu = lhs; + AST_Node_Obj ru = rhs; + + Expression::Concrete_Type l_type; + Expression::Concrete_Type r_type; + + // Is one of the operands an interpolant? + String_Schema_Obj s1 = Cast(b->left()); + String_Schema_Obj s2 = Cast(b->right()); + Binary_Expression_Obj b1 = Cast(b->left()); + Binary_Expression_Obj b2 = Cast(b->right()); + + bool schema_op = false; + + bool force_delay = (s2 && s2->is_left_interpolant()) || + (s1 && s1->is_right_interpolant()) || + (b1 && b1->is_right_interpolant()) || + (b2 && b2->is_left_interpolant()); + + if ((s1 && s1->has_interpolants()) || (s2 && s2->has_interpolants()) || force_delay) + { + if (op_type == Sass_OP::DIV || op_type == Sass_OP::MUL || op_type == Sass_OP::MOD || op_type == Sass_OP::ADD || op_type == Sass_OP::SUB || + op_type == Sass_OP::EQ) { + // If possible upgrade LHS to a number (for number to string compare) + if (String_Constant_Ptr str = Cast(lhs)) { + std::string value(str->value()); + const char* start = value.c_str(); + if (Prelexer::sequence < Prelexer::dimension, Prelexer::end_of_file >(start) != 0) { + lhs = Parser::lexed_dimension(b->pstate(), str->value()); + } + } + // If possible upgrade RHS to a number (for string to number compare) + if (String_Constant_Ptr str = Cast(rhs)) { + std::string value(str->value()); + const char* start = value.c_str(); + if (Prelexer::sequence < Prelexer::dimension, Prelexer::number >(start) != 0) { + rhs = Parser::lexed_dimension(b->pstate(), str->value()); + } + } + } + + To_Value to_value(ctx); + Value_Obj v_l = Cast(lhs->perform(&to_value)); + Value_Obj v_r = Cast(rhs->perform(&to_value)); + + if (force_delay) { + std::string str(""); + str += v_l->to_string(ctx.c_options); + if (b->op().ws_before) str += " "; + str += b->separator(); + if (b->op().ws_after) str += " "; + str += v_r->to_string(ctx.c_options); + String_Constant_Ptr val = SASS_MEMORY_NEW(String_Constant, b->pstate(), str); + val->is_interpolant(b->left()->has_interpolant()); + return val; + } + } + + // see if it's a relational expression + try { + switch(op_type) { + case Sass_OP::EQ: return SASS_MEMORY_NEW(Boolean, b->pstate(), Operators::eq(lhs, rhs)); + case Sass_OP::NEQ: return SASS_MEMORY_NEW(Boolean, b->pstate(), Operators::neq(lhs, rhs)); + case Sass_OP::GT: return SASS_MEMORY_NEW(Boolean, b->pstate(), Operators::gt(lhs, rhs)); + case Sass_OP::GTE: return SASS_MEMORY_NEW(Boolean, b->pstate(), Operators::gte(lhs, rhs)); + case Sass_OP::LT: return SASS_MEMORY_NEW(Boolean, b->pstate(), Operators::lt(lhs, rhs)); + case Sass_OP::LTE: return SASS_MEMORY_NEW(Boolean, b->pstate(), Operators::lte(lhs, rhs)); + default: break; + } + } + catch (Exception::OperationError& err) + { + // throw Exception::Base(b->pstate(), err.what()); + traces.push_back(Backtrace(b->pstate())); + throw Exception::SassValueError(traces, b->pstate(), err); + } + + l_type = lhs->concrete_type(); + r_type = rhs->concrete_type(); + + // ToDo: throw error in op functions + // ToDo: then catch and re-throw them + Expression_Obj rv; + try { + ParserState pstate(b->pstate()); + if (l_type == Expression::NUMBER && r_type == Expression::NUMBER) { + Number_Ptr l_n = Cast(lhs); + Number_Ptr r_n = Cast(rhs); + l_n->reduce(); r_n->reduce(); + rv = Operators::op_numbers(op_type, *l_n, *r_n, ctx.c_options, pstate); + } + else if (l_type == Expression::NUMBER && r_type == Expression::COLOR) { + Number_Ptr l_n = Cast(lhs); + Color_Ptr r_c = Cast(rhs); + rv = Operators::op_number_color(op_type, *l_n, *r_c, ctx.c_options, pstate); + } + else if (l_type == Expression::COLOR && r_type == Expression::NUMBER) { + Color_Ptr l_c = Cast(lhs); + Number_Ptr r_n = Cast(rhs); + rv = Operators::op_color_number(op_type, *l_c, *r_n, ctx.c_options, pstate); + } + else if (l_type == Expression::COLOR && r_type == Expression::COLOR) { + Color_Ptr l_c = Cast(lhs); + Color_Ptr r_c = Cast(rhs); + rv = Operators::op_colors(op_type, *l_c, *r_c, ctx.c_options, pstate); + } + else { + To_Value to_value(ctx); + // this will leak if perform does not return a value! + Value_Obj v_l = Cast(lhs->perform(&to_value)); + Value_Obj v_r = Cast(rhs->perform(&to_value)); + bool interpolant = b->is_right_interpolant() || + b->is_left_interpolant() || + b->is_interpolant(); + if (op_type == Sass_OP::SUB) interpolant = false; + // if (op_type == Sass_OP::DIV) interpolant = true; + // check for type violations + if (l_type == Expression::MAP || l_type == Expression::FUNCTION_VAL) { + traces.push_back(Backtrace(v_l->pstate())); + throw Exception::InvalidValue(traces, *v_l); + } + if (r_type == Expression::MAP || l_type == Expression::FUNCTION_VAL) { + traces.push_back(Backtrace(v_r->pstate())); + throw Exception::InvalidValue(traces, *v_r); + } + Value_Ptr ex = Operators::op_strings(b->op(), *v_l, *v_r, ctx.c_options, pstate, !interpolant); // pass true to compress + if (String_Constant_Ptr str = Cast(ex)) + { + if (str->concrete_type() == Expression::STRING) + { + String_Constant_Ptr lstr = Cast(lhs); + String_Constant_Ptr rstr = Cast(rhs); + if (op_type != Sass_OP::SUB) { + if (String_Constant_Ptr org = lstr ? lstr : rstr) + { str->quote_mark(org->quote_mark()); } + } + } + } + ex->is_interpolant(b->is_interpolant()); + rv = ex; + } + } + catch (Exception::OperationError& err) + { + traces.push_back(Backtrace(b->pstate())); + // throw Exception::Base(b->pstate(), err.what()); + throw Exception::SassValueError(traces, b->pstate(), err); + } + + if (rv) { + if (schema_op) { + // XXX: this is never hit via spec tests + (*s2)[0] = rv; + rv = s2->perform(this); + } + } + + return rv.detach(); + + } + + Expression_Ptr Eval::operator()(Unary_Expression_Ptr u) + { + Expression_Obj operand = u->operand()->perform(this); + if (u->optype() == Unary_Expression::NOT) { + Boolean_Ptr result = SASS_MEMORY_NEW(Boolean, u->pstate(), (bool)*operand); + result->value(!result->value()); + return result; + } + else if (Number_Obj nr = Cast(operand)) { + // negate value for minus unary expression + if (u->optype() == Unary_Expression::MINUS) { + Number_Obj cpy = SASS_MEMORY_COPY(nr); + cpy->value( - cpy->value() ); // negate value + return cpy.detach(); // return the copy + } + else if (u->optype() == Unary_Expression::SLASH) { + std::string str = '/' + nr->to_string(ctx.c_options); + return SASS_MEMORY_NEW(String_Constant, u->pstate(), str); + } + // nothing for positive + return nr.detach(); + } + else { + // Special cases: +/- variables which evaluate to null ouput just +/-, + // but +/- null itself outputs the string + if (operand->concrete_type() == Expression::NULL_VAL && Cast(u->operand())) { + u->operand(SASS_MEMORY_NEW(String_Quoted, u->pstate(), "")); + } + // Never apply unary opertions on colors @see #2140 + else if (Color_Ptr color = Cast(operand)) { + // Use the color name if this was eval with one + if (color->disp().length() > 0) { + operand = SASS_MEMORY_NEW(String_Constant, operand->pstate(), color->disp()); + u->operand(operand); + } + } + else { + u->operand(operand); + } + + return SASS_MEMORY_NEW(String_Quoted, + u->pstate(), + u->inspect()); + } + // unreachable + return u; + } + + Expression_Ptr Eval::operator()(Function_Call_Ptr c) + { + if (traces.size() > Constants::MaxCallStack) { + // XXX: this is never hit via spec tests + std::ostringstream stm; + stm << "Stack depth exceeded max of " << Constants::MaxCallStack; + error(stm.str(), c->pstate(), traces); + } + std::string name(Util::normalize_underscores(c->name())); + std::string full_name(name + "[f]"); + // we make a clone here, need to implement that further + Arguments_Obj args = c->arguments(); + + Env* env = environment(); + if (!env->has(full_name) || (!c->via_call() && Prelexer::re_special_fun(name.c_str()))) { + if (!env->has("*[f]")) { + for (Argument_Obj arg : args->elements()) { + if (List_Obj ls = Cast(arg->value())) { + if (ls->size() == 0) error("() isn't a valid CSS value.", c->pstate(), traces); + } + } + args = Cast(args->perform(this)); + Function_Call_Obj lit = SASS_MEMORY_NEW(Function_Call, + c->pstate(), + c->name(), + args); + if (args->has_named_arguments()) { + error("Function " + c->name() + " doesn't support keyword arguments", c->pstate(), traces); + } + String_Quoted_Ptr str = SASS_MEMORY_NEW(String_Quoted, + c->pstate(), + lit->to_string(ctx.c_options)); + str->is_interpolant(c->is_interpolant()); + return str; + } else { + // call generic function + full_name = "*[f]"; + } + } + + // further delay for calls + if (full_name != "call[f]") { + args->set_delayed(false); // verified + } + if (full_name != "if[f]") { + args = Cast(args->perform(this)); + } + Definition_Ptr def = Cast((*env)[full_name]); + + if (c->func()) def = c->func()->definition(); + + if (def->is_overload_stub()) { + std::stringstream ss; + size_t L = args->length(); + // account for rest arguments + if (args->has_rest_argument() && args->length() > 0) { + // get the rest arguments list + List_Ptr rest = Cast(args->last()->value()); + // arguments before rest argument plus rest + if (rest) L += rest->length() - 1; + } + ss << full_name << L; + full_name = ss.str(); + std::string resolved_name(full_name); + if (!env->has(resolved_name)) error("overloaded function `" + std::string(c->name()) + "` given wrong number of arguments", c->pstate(), traces); + def = Cast((*env)[resolved_name]); + } + + Expression_Obj result = c; + Block_Obj body = def->block(); + Native_Function func = def->native_function(); + Sass_Function_Entry c_function = def->c_function(); + + if (c->is_css()) return result.detach(); + + Parameters_Obj params = def->parameters(); + Env fn_env(def->environment()); + exp.env_stack.push_back(&fn_env); + + if (func || body) { + bind(std::string("Function"), c->name(), params, args, &ctx, &fn_env, this); + std::string msg(", in function `" + c->name() + "`"); + traces.push_back(Backtrace(c->pstate(), msg)); + ctx.callee_stack.push_back({ + c->name().c_str(), + c->pstate().path, + c->pstate().line + 1, + c->pstate().column + 1, + SASS_CALLEE_FUNCTION, + { env } + }); + + // eval the body if user-defined or special, invoke underlying CPP function if native + if (body /* && !Prelexer::re_special_fun(name.c_str()) */) { + result = body->perform(this); + } + else if (func) { + result = func(fn_env, *env, ctx, def->signature(), c->pstate(), traces, exp.selector_stack); + } + if (!result) { + error(std::string("Function ") + c->name() + " finished without @return", c->pstate(), traces); + } + ctx.callee_stack.pop_back(); + traces.pop_back(); + } + + // else if it's a user-defined c function + // convert call into C-API compatible form + else if (c_function) { + Sass_Function_Fn c_func = sass_function_get_function(c_function); + if (full_name == "*[f]") { + String_Quoted_Obj str = SASS_MEMORY_NEW(String_Quoted, c->pstate(), c->name()); + Arguments_Obj new_args = SASS_MEMORY_NEW(Arguments, c->pstate()); + new_args->append(SASS_MEMORY_NEW(Argument, c->pstate(), str)); + new_args->concat(args); + args = new_args; + } + + // populates env with default values for params + std::string ff(c->name()); + bind(std::string("Function"), c->name(), params, args, &ctx, &fn_env, this); + std::string msg(", in function `" + c->name() + "`"); + traces.push_back(Backtrace(c->pstate(), msg)); + ctx.callee_stack.push_back({ + c->name().c_str(), + c->pstate().path, + c->pstate().line + 1, + c->pstate().column + 1, + SASS_CALLEE_C_FUNCTION, + { env } + }); + + To_C to_c; + union Sass_Value* c_args = sass_make_list(params->length(), SASS_COMMA, false); + for(size_t i = 0; i < params->length(); i++) { + Parameter_Obj param = params->at(i); + std::string key = param->name(); + AST_Node_Obj node = fn_env.get_local(key); + Expression_Obj arg = Cast(node); + sass_list_set_value(c_args, i, arg->perform(&to_c)); + } + union Sass_Value* c_val = c_func(c_args, c_function, ctx.c_compiler); + if (sass_value_get_tag(c_val) == SASS_ERROR) { + error("error in C function " + c->name() + ": " + sass_error_get_message(c_val), c->pstate(), traces); + } else if (sass_value_get_tag(c_val) == SASS_WARNING) { + error("warning in C function " + c->name() + ": " + sass_warning_get_message(c_val), c->pstate(), traces); + } + result = cval_to_astnode(c_val, traces, c->pstate()); + + ctx.callee_stack.pop_back(); + traces.pop_back(); + sass_delete_value(c_args); + if (c_val != c_args) + sass_delete_value(c_val); + } + + // link back to function definition + // only do this for custom functions + if (result->pstate().file == std::string::npos) + result->pstate(c->pstate()); + + result = result->perform(this); + result->is_interpolant(c->is_interpolant()); + exp.env_stack.pop_back(); + return result.detach(); + } + + Expression_Ptr Eval::operator()(Function_Call_Schema_Ptr s) + { + Expression_Ptr evaluated_name = s->name()->perform(this); + Expression_Ptr evaluated_args = s->arguments()->perform(this); + String_Schema_Obj ss = SASS_MEMORY_NEW(String_Schema, s->pstate(), 2); + ss->append(evaluated_name); + ss->append(evaluated_args); + return ss->perform(this); + } + + Expression_Ptr Eval::operator()(Variable_Ptr v) + { + Expression_Obj value = 0; + Env* env = environment(); + const std::string& name(v->name()); + EnvResult rv(env->find(name)); + if (rv.found) value = static_cast(rv.it->second.ptr()); + else error("Undefined variable: \"" + v->name() + "\".", v->pstate(), traces); + if (Argument_Ptr arg = Cast(value)) value = arg->value(); + if (Number_Ptr nr = Cast(value)) nr->zero(true); // force flag + value->is_interpolant(v->is_interpolant()); + if (force) value->is_expanded(false); + value->set_delayed(false); // verified + value = value->perform(this); + if(!force) rv.it->second = value; + return value.detach(); + } + + Expression_Ptr Eval::operator()(Color_Ptr c) + { + return c; + } + + Expression_Ptr Eval::operator()(Number_Ptr n) + { + return n; + } + + Expression_Ptr Eval::operator()(Boolean_Ptr b) + { + return b; + } + + void Eval::interpolation(Context& ctx, std::string& res, Expression_Obj ex, bool into_quotes, bool was_itpl) { + + bool needs_closing_brace = false; + + if (Arguments_Ptr args = Cast(ex)) { + List_Ptr ll = SASS_MEMORY_NEW(List, args->pstate(), 0, SASS_COMMA); + for(auto arg : args->elements()) { + ll->append(arg->value()); + } + ll->is_interpolant(args->is_interpolant()); + needs_closing_brace = true; + res += "("; + ex = ll; + } + if (Number_Ptr nr = Cast(ex)) { + Number reduced(nr); + reduced.reduce(); + if (!reduced.is_valid_css_unit()) { + traces.push_back(Backtrace(nr->pstate())); + throw Exception::InvalidValue(traces, *nr); + } + } + if (Argument_Ptr arg = Cast(ex)) { + ex = arg->value(); + } + if (String_Quoted_Ptr sq = Cast(ex)) { + if (was_itpl) { + bool was_interpolant = ex->is_interpolant(); + ex = SASS_MEMORY_NEW(String_Constant, sq->pstate(), sq->value()); + ex->is_interpolant(was_interpolant); + } + } + + if (Cast(ex)) { return; } + + // parent selector needs another go + if (Cast(ex)) { + // XXX: this is never hit via spec tests + ex = ex->perform(this); + } + + if (List_Ptr l = Cast(ex)) { + List_Obj ll = SASS_MEMORY_NEW(List, l->pstate(), 0, l->separator()); + // this fixes an issue with bourbon sample, not really sure why + // if (l->size() && Cast((*l)[0])) { res += ""; } + for(Expression_Obj item : *l) { + item->is_interpolant(l->is_interpolant()); + std::string rl(""); interpolation(ctx, rl, item, into_quotes, l->is_interpolant()); + bool is_null = Cast(item) != 0; // rl != "" + if (!is_null) ll->append(SASS_MEMORY_NEW(String_Quoted, item->pstate(), rl)); + } + // Check indicates that we probably should not get a list + // here. Normally single list items are already unwrapped. + if (l->size() > 1) { + // string_to_output would fail "#{'_\a' '_\a'}"; + std::string str(ll->to_string(ctx.c_options)); + str = read_hex_escapes(str); // read escapes + newline_to_space(str); // replace directly + res += str; // append to result string + } else { + res += (ll->to_string(ctx.c_options)); + } + ll->is_interpolant(l->is_interpolant()); + } + + // Value + // Function_Call + // Selector_List + // String_Quoted + // String_Constant + // Parent_Selector + // Binary_Expression + else { + // ex = ex->perform(this); + if (into_quotes && ex->is_interpolant()) { + res += evacuate_escapes(ex ? ex->to_string(ctx.c_options) : ""); + } else { + std::string str(ex ? ex->to_string(ctx.c_options) : ""); + if (into_quotes) str = read_hex_escapes(str); + res += str; // append to result string + } + } + + if (needs_closing_brace) res += ")"; + + } + + Expression_Ptr Eval::operator()(String_Schema_Ptr s) + { + size_t L = s->length(); + bool into_quotes = false; + if (L > 1) { + if (!Cast((*s)[0]) && !Cast((*s)[L - 1])) { + if (String_Constant_Ptr l = Cast((*s)[0])) { + if (String_Constant_Ptr r = Cast((*s)[L - 1])) { + if (r->value().size() > 0) { + if (l->value()[0] == '"' && r->value()[r->value().size() - 1] == '"') into_quotes = true; + if (l->value()[0] == '\'' && r->value()[r->value().size() - 1] == '\'') into_quotes = true; + } + } + } + } + } + bool was_quoted = false; + bool was_interpolant = false; + std::string res(""); + for (size_t i = 0; i < L; ++i) { + bool is_quoted = Cast((*s)[i]) != NULL; + if (was_quoted && !(*s)[i]->is_interpolant() && !was_interpolant) { res += " "; } + else if (i > 0 && is_quoted && !(*s)[i]->is_interpolant() && !was_interpolant) { res += " "; } + Expression_Obj ex = (*s)[i]->perform(this); + interpolation(ctx, res, ex, into_quotes, ex->is_interpolant()); + was_quoted = Cast((*s)[i]) != NULL; + was_interpolant = (*s)[i]->is_interpolant(); + + } + if (!s->is_interpolant()) { + if (s->length() > 1 && res == "") return SASS_MEMORY_NEW(Null, s->pstate()); + return SASS_MEMORY_NEW(String_Constant, s->pstate(), res, s->css()); + } + // string schema seems to have a special unquoting behavior (also handles "nested" quotes) + String_Quoted_Obj str = SASS_MEMORY_NEW(String_Quoted, s->pstate(), res, 0, false, false, false, s->css()); + // if (s->is_interpolant()) str->quote_mark(0); + // String_Constant_Ptr str = SASS_MEMORY_NEW(String_Constant, s->pstate(), res); + if (str->quote_mark()) str->quote_mark('*'); + else if (!is_in_comment) str->value(string_to_output(str->value())); + str->is_interpolant(s->is_interpolant()); + return str.detach(); + } + + + Expression_Ptr Eval::operator()(String_Constant_Ptr s) + { + return s; + } + + Expression_Ptr Eval::operator()(String_Quoted_Ptr s) + { + String_Quoted_Ptr str = SASS_MEMORY_NEW(String_Quoted, s->pstate(), ""); + str->value(s->value()); + str->quote_mark(s->quote_mark()); + str->is_interpolant(s->is_interpolant()); + return str; + } + + Expression_Ptr Eval::operator()(Supports_Operator_Ptr c) + { + Expression_Ptr left = c->left()->perform(this); + Expression_Ptr right = c->right()->perform(this); + Supports_Operator_Ptr cc = SASS_MEMORY_NEW(Supports_Operator, + c->pstate(), + Cast(left), + Cast(right), + c->operand()); + return cc; + } + + Expression_Ptr Eval::operator()(Supports_Negation_Ptr c) + { + Expression_Ptr condition = c->condition()->perform(this); + Supports_Negation_Ptr cc = SASS_MEMORY_NEW(Supports_Negation, + c->pstate(), + Cast(condition)); + return cc; + } + + Expression_Ptr Eval::operator()(Supports_Declaration_Ptr c) + { + Expression_Ptr feature = c->feature()->perform(this); + Expression_Ptr value = c->value()->perform(this); + Supports_Declaration_Ptr cc = SASS_MEMORY_NEW(Supports_Declaration, + c->pstate(), + feature, + value); + return cc; + } + + Expression_Ptr Eval::operator()(Supports_Interpolation_Ptr c) + { + Expression_Ptr value = c->value()->perform(this); + Supports_Interpolation_Ptr cc = SASS_MEMORY_NEW(Supports_Interpolation, + c->pstate(), + value); + return cc; + } + + Expression_Ptr Eval::operator()(At_Root_Query_Ptr e) + { + Expression_Obj feature = e->feature(); + feature = (feature ? feature->perform(this) : 0); + Expression_Obj value = e->value(); + value = (value ? value->perform(this) : 0); + Expression_Ptr ee = SASS_MEMORY_NEW(At_Root_Query, + e->pstate(), + Cast(feature), + value); + return ee; + } + + Media_Query_Ptr Eval::operator()(Media_Query_Ptr q) + { + String_Obj t = q->media_type(); + t = static_cast(t.isNull() ? 0 : t->perform(this)); + Media_Query_Obj qq = SASS_MEMORY_NEW(Media_Query, + q->pstate(), + t, + q->length(), + q->is_negated(), + q->is_restricted()); + for (size_t i = 0, L = q->length(); i < L; ++i) { + qq->append(static_cast((*q)[i]->perform(this))); + } + return qq.detach(); + } + + Expression_Ptr Eval::operator()(Media_Query_Expression_Ptr e) + { + Expression_Obj feature = e->feature(); + feature = (feature ? feature->perform(this) : 0); + if (feature && Cast(feature)) { + feature = SASS_MEMORY_NEW(String_Quoted, + feature->pstate(), + Cast(feature)->value()); + } + Expression_Obj value = e->value(); + value = (value ? value->perform(this) : 0); + if (value && Cast(value)) { + // XXX: this is never hit via spec tests + value = SASS_MEMORY_NEW(String_Quoted, + value->pstate(), + Cast(value)->value()); + } + return SASS_MEMORY_NEW(Media_Query_Expression, + e->pstate(), + feature, + value, + e->is_interpolated()); + } + + Expression_Ptr Eval::operator()(Null_Ptr n) + { + return n; + } + + Expression_Ptr Eval::operator()(Argument_Ptr a) + { + Expression_Obj val = a->value()->perform(this); + bool is_rest_argument = a->is_rest_argument(); + bool is_keyword_argument = a->is_keyword_argument(); + + if (a->is_rest_argument()) { + if (val->concrete_type() == Expression::MAP) { + is_rest_argument = false; + is_keyword_argument = true; + } + else if(val->concrete_type() != Expression::LIST) { + List_Obj wrapper = SASS_MEMORY_NEW(List, + val->pstate(), + 0, + SASS_COMMA, + true); + wrapper->append(val); + val = wrapper; + } + } + return SASS_MEMORY_NEW(Argument, + a->pstate(), + val, + a->name(), + is_rest_argument, + is_keyword_argument); + } + + Expression_Ptr Eval::operator()(Arguments_Ptr a) + { + Arguments_Obj aa = SASS_MEMORY_NEW(Arguments, a->pstate()); + if (a->length() == 0) return aa.detach(); + for (size_t i = 0, L = a->length(); i < L; ++i) { + Expression_Obj rv = (*a)[i]->perform(this); + Argument_Ptr arg = Cast(rv); + if (!(arg->is_rest_argument() || arg->is_keyword_argument())) { + aa->append(arg); + } + } + + if (a->has_rest_argument()) { + Expression_Obj rest = a->get_rest_argument()->perform(this); + Expression_Obj splat = Cast(rest)->value()->perform(this); + + Sass_Separator separator = SASS_COMMA; + List_Ptr ls = Cast(splat); + Map_Ptr ms = Cast(splat); + + List_Obj arglist = SASS_MEMORY_NEW(List, + splat->pstate(), + 0, + ls ? ls->separator() : separator, + true); + + if (ls && ls->is_arglist()) { + arglist->concat(ls); + } else if (ms) { + aa->append(SASS_MEMORY_NEW(Argument, splat->pstate(), ms, "", false, true)); + } else if (ls) { + arglist->concat(ls); + } else { + arglist->append(splat); + } + if (arglist->length()) { + aa->append(SASS_MEMORY_NEW(Argument, splat->pstate(), arglist, "", true)); + } + } + + if (a->has_keyword_argument()) { + Expression_Obj rv = a->get_keyword_argument()->perform(this); + Argument_Ptr rvarg = Cast(rv); + Expression_Obj kwarg = rvarg->value()->perform(this); + + aa->append(SASS_MEMORY_NEW(Argument, kwarg->pstate(), kwarg, "", false, true)); + } + return aa.detach(); + } + + Expression_Ptr Eval::operator()(Comment_Ptr c) + { + return 0; + } + + inline Expression_Ptr Eval::fallback_impl(AST_Node_Ptr n) + { + return static_cast(n); + } + + // All the binary helpers. + + Expression_Ptr cval_to_astnode(union Sass_Value* v, Backtraces traces, ParserState pstate) + { + using std::strlen; + using std::strcpy; + Expression_Ptr e = NULL; + switch (sass_value_get_tag(v)) { + case SASS_BOOLEAN: { + e = SASS_MEMORY_NEW(Boolean, pstate, !!sass_boolean_get_value(v)); + } break; + case SASS_NUMBER: { + e = SASS_MEMORY_NEW(Number, pstate, sass_number_get_value(v), sass_number_get_unit(v)); + } break; + case SASS_COLOR: { + e = SASS_MEMORY_NEW(Color, pstate, sass_color_get_r(v), sass_color_get_g(v), sass_color_get_b(v), sass_color_get_a(v)); + } break; + case SASS_STRING: { + if (sass_string_is_quoted(v)) + e = SASS_MEMORY_NEW(String_Quoted, pstate, sass_string_get_value(v)); + else { + e = SASS_MEMORY_NEW(String_Constant, pstate, sass_string_get_value(v)); + } + } break; + case SASS_LIST: { + List_Ptr l = SASS_MEMORY_NEW(List, pstate, sass_list_get_length(v), sass_list_get_separator(v)); + for (size_t i = 0, L = sass_list_get_length(v); i < L; ++i) { + l->append(cval_to_astnode(sass_list_get_value(v, i), traces, pstate)); + } + l->is_bracketed(sass_list_get_is_bracketed(v)); + e = l; + } break; + case SASS_MAP: { + Map_Ptr m = SASS_MEMORY_NEW(Map, pstate); + for (size_t i = 0, L = sass_map_get_length(v); i < L; ++i) { + *m << std::make_pair( + cval_to_astnode(sass_map_get_key(v, i), traces, pstate), + cval_to_astnode(sass_map_get_value(v, i), traces, pstate)); + } + e = m; + } break; + case SASS_NULL: { + e = SASS_MEMORY_NEW(Null, pstate); + } break; + case SASS_ERROR: { + error("Error in C function: " + std::string(sass_error_get_message(v)), pstate, traces); + } break; + case SASS_WARNING: { + error("Warning in C function: " + std::string(sass_warning_get_message(v)), pstate, traces); + } break; + default: break; + } + return e; + } + + Selector_List_Ptr Eval::operator()(Selector_List_Ptr s) + { + std::vector rv; + Selector_List_Obj sl = SASS_MEMORY_NEW(Selector_List, s->pstate()); + sl->is_optional(s->is_optional()); + sl->media_block(s->media_block()); + sl->is_optional(s->is_optional()); + for (size_t i = 0, iL = s->length(); i < iL; ++i) { + rv.push_back(operator()((*s)[i])); + } + + // we should actually permutate parent first + // but here we have permutated the selector first + size_t round = 0; + while (round != std::string::npos) { + bool abort = true; + for (size_t i = 0, iL = rv.size(); i < iL; ++i) { + if (rv[i]->length() > round) { + sl->append((*rv[i])[round]); + abort = false; + } + } + if (abort) { + round = std::string::npos; + } else { + ++ round; + } + + } + return sl.detach(); + } + + + Selector_List_Ptr Eval::operator()(Complex_Selector_Ptr s) + { + bool implicit_parent = !exp.old_at_root_without_rule; + if (is_in_selector_schema) exp.selector_stack.push_back(0); + Selector_List_Obj resolved = s->resolve_parent_refs(exp.selector_stack, traces, implicit_parent); + if (is_in_selector_schema) exp.selector_stack.pop_back(); + for (size_t i = 0; i < resolved->length(); i++) { + Complex_Selector_Ptr is = resolved->at(i)->first(); + while (is) { + if (is->head()) { + is->head(operator()(is->head())); + } + is = is->tail(); + } + } + return resolved.detach(); + } + + Compound_Selector_Ptr Eval::operator()(Compound_Selector_Ptr s) + { + for (size_t i = 0; i < s->length(); i++) { + Simple_Selector_Ptr ss = s->at(i); + // skip parents here (called via resolve_parent_refs) + if (ss == NULL || Cast(ss)) continue; + s->at(i) = Cast(ss->perform(this)); + } + return s; + } + + Selector_List_Ptr Eval::operator()(Selector_Schema_Ptr s) + { + LOCAL_FLAG(is_in_selector_schema, true); + // the parser will look for a brace to end the selector + ctx.c_options.in_selector = true; // do not compress colors + Expression_Obj sel = s->contents()->perform(this); + std::string result_str(sel->to_string(ctx.c_options)); + ctx.c_options.in_selector = false; // flag temporary only + result_str = unquote(Util::rtrim(result_str)); + char* temp_cstr = sass_copy_c_string(result_str.c_str()); + ctx.strings.push_back(temp_cstr); // attach to context + Parser p = Parser::from_c_str(temp_cstr, ctx, traces, s->pstate()); + p.last_media_block = s->media_block(); + // a selector schema may or may not connect to parent? + bool chroot = s->connect_parent() == false; + Selector_List_Obj sl = p.parse_selector_list(chroot); + auto vec_str_rend = ctx.strings.rend(); + auto vec_str_rbegin = ctx.strings.rbegin(); + // remove the first item searching from the back + // we cannot assume our item is still the last one + // order is not important, so we can optimize this + auto it = std::find(vec_str_rbegin, vec_str_rend, temp_cstr); + // undefined behavior if not found! + if (it != vec_str_rend) { + // overwrite with last item + *it = ctx.strings.back(); + // remove last one from vector + ctx.strings.pop_back(); + // free temporary copy + free(temp_cstr); + } + flag_is_in_selector_schema.reset(); + return operator()(sl); + } + + Expression_Ptr Eval::operator()(Parent_Selector_Ptr p) + { + if (Selector_List_Obj pr = selector()) { + exp.selector_stack.pop_back(); + Selector_List_Obj rv = operator()(pr); + exp.selector_stack.push_back(rv); + return rv.detach(); + } else { + return SASS_MEMORY_NEW(Null, p->pstate()); + } + } + + Simple_Selector_Ptr Eval::operator()(Simple_Selector_Ptr s) + { + return s; + } + + // hotfix to avoid invalid nested `:not` selectors + // probably the wrong place, but this should ultimately + // be fixed by implement superselector correctly for `:not` + // first use of "find" (ATM only implemented for selectors) + bool hasNotSelector(AST_Node_Obj obj) { + if (Wrapped_Selector_Ptr w = Cast(obj)) { + return w->name() == ":not"; + } + return false; + } + + Wrapped_Selector_Ptr Eval::operator()(Wrapped_Selector_Ptr s) + { + + if (s->name() == ":not") { + if (exp.selector_stack.back()) { + if (s->selector()->find(hasNotSelector)) { + s->selector()->clear(); + s->name(" "); + } else if (s->selector()->length() == 1) { + Complex_Selector_Ptr cs = s->selector()->at(0); + if (cs->tail()) { + s->selector()->clear(); + s->name(" "); + } + } else if (s->selector()->length() > 1) { + s->selector()->clear(); + s->name(" "); + } + } + } + return s; + }; + +} diff --git a/src/libsass/expand.cpp b/src/libsass/expand.cpp new file mode 100644 index 000000000..d8dc03f14 --- /dev/null +++ b/src/libsass/expand.cpp @@ -0,0 +1,817 @@ +#include "sass.hpp" +#include +#include + +#include "ast.hpp" +#include "expand.hpp" +#include "bind.hpp" +#include "eval.hpp" +#include "backtrace.hpp" +#include "context.hpp" +#include "parser.hpp" +#include "sass_functions.hpp" + +namespace Sass { + + // simple endless recursion protection + const size_t maxRecursion = 500; + + Expand::Expand(Context& ctx, Env* env, std::vector* stack) + : ctx(ctx), + traces(ctx.traces), + eval(Eval(*this)), + recursions(0), + in_keyframes(false), + at_root_without_rule(false), + old_at_root_without_rule(false), + env_stack(std::vector()), + block_stack(std::vector()), + call_stack(std::vector()), + selector_stack(std::vector()), + media_block_stack(std::vector()) + { + env_stack.push_back(0); + env_stack.push_back(env); + block_stack.push_back(0); + call_stack.push_back(0); + if (stack == NULL) { selector_stack.push_back(0); } + else { selector_stack.insert(selector_stack.end(), stack->begin(), stack->end()); } + media_block_stack.push_back(0); + } + + Env* Expand::environment() + { + if (env_stack.size() > 0) + return env_stack.back(); + return 0; + } + + Selector_List_Obj Expand::selector() + { + if (selector_stack.size() > 0) + return selector_stack.back(); + return 0; + } + + // blocks create new variable scopes + Block_Ptr Expand::operator()(Block_Ptr b) + { + // create new local environment + // set the current env as parent + Env env(environment()); + // copy the block object (add items later) + Block_Obj bb = SASS_MEMORY_NEW(Block, + b->pstate(), + b->length(), + b->is_root()); + // setup block and env stack + this->block_stack.push_back(bb); + this->env_stack.push_back(&env); + // operate on block + // this may throw up! + this->append_block(b); + // revert block and env stack + this->block_stack.pop_back(); + this->env_stack.pop_back(); + // return copy + return bb.detach(); + } + + Statement_Ptr Expand::operator()(Ruleset_Ptr r) + { + LOCAL_FLAG(old_at_root_without_rule, at_root_without_rule); + + if (in_keyframes) { + Block_Ptr bb = operator()(r->block()); + Keyframe_Rule_Obj k = SASS_MEMORY_NEW(Keyframe_Rule, r->pstate(), bb); + if (r->selector()) { + if (Selector_List_Ptr s = r->selector()) { + selector_stack.push_back(0); + k->name(s->eval(eval)); + selector_stack.pop_back(); + } + } + return k.detach(); + } + + // reset when leaving scope + LOCAL_FLAG(at_root_without_rule, false); + + // `&` is allowed in `@at-root`! + bool has_parent_selector = false; + for (size_t i = 0, L = selector_stack.size(); i < L && !has_parent_selector; i++) { + Selector_List_Obj ll = selector_stack.at(i); + has_parent_selector = ll != 0 && ll->length() > 0; + } + + Selector_List_Obj sel = r->selector(); + if (sel) sel = sel->eval(eval); + + // check for parent selectors in base level rules + if (r->is_root() || (block_stack.back() && block_stack.back()->is_root())) { + if (Selector_List_Ptr selector_list = Cast(r->selector())) { + for (Complex_Selector_Obj complex_selector : selector_list->elements()) { + Complex_Selector_Ptr tail = complex_selector; + while (tail) { + if (tail->head()) for (Simple_Selector_Obj header : tail->head()->elements()) { + Parent_Selector_Ptr ptr = Cast(header); + if (ptr == NULL || (!ptr->real() || has_parent_selector)) continue; + std::string sel_str(complex_selector->to_string(ctx.c_options)); + error("Base-level rules cannot contain the parent-selector-referencing character '&'.", header->pstate(), traces); + } + tail = tail->tail(); + } + } + } + } + else { + if (sel->length() == 0 || sel->has_parent_ref()) { + if (sel->has_real_parent_ref() && !has_parent_selector) { + error("Base-level rules cannot contain the parent-selector-referencing character '&'.", sel->pstate(), traces); + } + } + } + + // do not connect parent again + sel->remove_parent_selectors(); + selector_stack.push_back(sel); + Env env(environment()); + if (block_stack.back()->is_root()) { + env_stack.push_back(&env); + } + sel->set_media_block(media_block_stack.back()); + Block_Obj blk = 0; + if (r->block()) blk = operator()(r->block()); + Ruleset_Ptr rr = SASS_MEMORY_NEW(Ruleset, + r->pstate(), + sel, + blk); + selector_stack.pop_back(); + if (block_stack.back()->is_root()) { + env_stack.pop_back(); + } + + rr->is_root(r->is_root()); + rr->tabs(r->tabs()); + + return rr; + } + + Statement_Ptr Expand::operator()(Supports_Block_Ptr f) + { + Expression_Obj condition = f->condition()->perform(&eval); + Supports_Block_Obj ff = SASS_MEMORY_NEW(Supports_Block, + f->pstate(), + Cast(condition), + operator()(f->block())); + return ff.detach(); + } + + Statement_Ptr Expand::operator()(Media_Block_Ptr m) + { + Media_Block_Obj cpy = SASS_MEMORY_COPY(m); + // Media_Blocks are prone to have circular references + // Copy could leak memory if it does not get picked up + // Looks like we are able to reset block reference for copy + // Good as it will ensure a low memory overhead for this fix + // So this is a cheap solution with a minimal price + ctx.ast_gc.push_back(cpy); cpy->block(0); + Expression_Obj mq = eval(m->media_queries()); + std::string str_mq(mq->to_string(ctx.c_options)); + char* str = sass_copy_c_string(str_mq.c_str()); + ctx.strings.push_back(str); + Parser p(Parser::from_c_str(str, ctx, traces, mq->pstate())); + mq = p.parse_media_queries(); // re-assign now + cpy->media_queries(mq); + media_block_stack.push_back(cpy); + Block_Obj blk = operator()(m->block()); + Media_Block_Ptr mm = SASS_MEMORY_NEW(Media_Block, + m->pstate(), + mq, + blk); + media_block_stack.pop_back(); + mm->tabs(m->tabs()); + return mm; + } + + Statement_Ptr Expand::operator()(At_Root_Block_Ptr a) + { + Block_Obj ab = a->block(); + Expression_Obj ae = a->expression(); + + if (ae) ae = ae->perform(&eval); + else ae = SASS_MEMORY_NEW(At_Root_Query, a->pstate()); + + LOCAL_FLAG(at_root_without_rule, true); + LOCAL_FLAG(in_keyframes, false); + + ; + + Block_Obj bb = ab ? operator()(ab) : NULL; + At_Root_Block_Obj aa = SASS_MEMORY_NEW(At_Root_Block, + a->pstate(), + bb, + Cast(ae)); + return aa.detach(); + } + + Statement_Ptr Expand::operator()(Directive_Ptr a) + { + LOCAL_FLAG(in_keyframes, a->is_keyframes()); + Block_Ptr ab = a->block(); + Selector_List_Ptr as = a->selector(); + Expression_Ptr av = a->value(); + selector_stack.push_back(0); + if (av) av = av->perform(&eval); + if (as) as = eval(as); + selector_stack.pop_back(); + Block_Ptr bb = ab ? operator()(ab) : NULL; + Directive_Ptr aa = SASS_MEMORY_NEW(Directive, + a->pstate(), + a->keyword(), + as, + bb, + av); + return aa; + } + + Statement_Ptr Expand::operator()(Declaration_Ptr d) + { + Block_Obj ab = d->block(); + String_Obj old_p = d->property(); + Expression_Obj prop = old_p->perform(&eval); + String_Obj new_p = Cast(prop); + // we might get a color back + if (!new_p) { + std::string str(prop->to_string(ctx.c_options)); + new_p = SASS_MEMORY_NEW(String_Constant, old_p->pstate(), str); + } + Expression_Obj value = d->value(); + if (value) value = value->perform(&eval); + Block_Obj bb = ab ? operator()(ab) : NULL; + if (!bb) { + if (!value || (value->is_invisible() && !d->is_important())) return 0; + } + Declaration_Ptr decl = SASS_MEMORY_NEW(Declaration, + d->pstate(), + new_p, + value, + d->is_important(), + d->is_custom_property(), + bb); + decl->tabs(d->tabs()); + return decl; + } + + Statement_Ptr Expand::operator()(Assignment_Ptr a) + { + Env* env = environment(); + const std::string& var(a->variable()); + if (a->is_global()) { + if (a->is_default()) { + if (env->has_global(var)) { + Expression_Obj e = Cast(env->get_global(var)); + if (!e || e->concrete_type() == Expression::NULL_VAL) { + env->set_global(var, a->value()->perform(&eval)); + } + } + else { + env->set_global(var, a->value()->perform(&eval)); + } + } + else { + env->set_global(var, a->value()->perform(&eval)); + } + } + else if (a->is_default()) { + if (env->has_lexical(var)) { + auto cur = env; + while (cur && cur->is_lexical()) { + if (cur->has_local(var)) { + if (AST_Node_Obj node = cur->get_local(var)) { + Expression_Obj e = Cast(node); + if (!e || e->concrete_type() == Expression::NULL_VAL) { + cur->set_local(var, a->value()->perform(&eval)); + } + } + else { + throw std::runtime_error("Env not in sync"); + } + return 0; + } + cur = cur->parent(); + } + throw std::runtime_error("Env not in sync"); + } + else if (env->has_global(var)) { + if (AST_Node_Obj node = env->get_global(var)) { + Expression_Obj e = Cast(node); + if (!e || e->concrete_type() == Expression::NULL_VAL) { + env->set_global(var, a->value()->perform(&eval)); + } + } + } + else if (env->is_lexical()) { + env->set_local(var, a->value()->perform(&eval)); + } + else { + env->set_local(var, a->value()->perform(&eval)); + } + } + else { + env->set_lexical(var, a->value()->perform(&eval)); + } + return 0; + } + + Statement_Ptr Expand::operator()(Import_Ptr imp) + { + Import_Obj result = SASS_MEMORY_NEW(Import, imp->pstate()); + if (imp->import_queries() && imp->import_queries()->size()) { + Expression_Obj ex = imp->import_queries()->perform(&eval); + result->import_queries(Cast(ex)); + } + for ( size_t i = 0, S = imp->urls().size(); i < S; ++i) { + result->urls().push_back(imp->urls()[i]->perform(&eval)); + } + // all resources have been dropped for Input_Stubs + // for ( size_t i = 0, S = imp->incs().size(); i < S; ++i) {} + return result.detach(); + } + + Statement_Ptr Expand::operator()(Import_Stub_Ptr i) + { + traces.push_back(Backtrace(i->pstate())); + // get parent node from call stack + AST_Node_Obj parent = call_stack.back(); + if (Cast(parent) == NULL) { + error("Import directives may not be used within control directives or mixins.", i->pstate(), traces); + } + // we don't seem to need that actually afterall + Sass_Import_Entry import = sass_make_import( + i->imp_path().c_str(), + i->abs_path().c_str(), + 0, 0 + ); + ctx.import_stack.push_back(import); + + Block_Obj trace_block = SASS_MEMORY_NEW(Block, i->pstate()); + Trace_Obj trace = SASS_MEMORY_NEW(Trace, i->pstate(), i->imp_path(), trace_block, 'i'); + block_stack.back()->append(trace); + block_stack.push_back(trace_block); + + const std::string& abs_path(i->resource().abs_path); + append_block(ctx.sheets.at(abs_path).root); + sass_delete_import(ctx.import_stack.back()); + ctx.import_stack.pop_back(); + block_stack.pop_back(); + traces.pop_back(); + return 0; + } + + Statement_Ptr Expand::operator()(Warning_Ptr w) + { + // eval handles this too, because warnings may occur in functions + w->perform(&eval); + return 0; + } + + Statement_Ptr Expand::operator()(Error_Ptr e) + { + // eval handles this too, because errors may occur in functions + e->perform(&eval); + return 0; + } + + Statement_Ptr Expand::operator()(Debug_Ptr d) + { + // eval handles this too, because warnings may occur in functions + d->perform(&eval); + return 0; + } + + Statement_Ptr Expand::operator()(Comment_Ptr c) + { + if (ctx.output_style() == COMPRESSED) { + // comments should not be evaluated in compact + // https://github.com/sass/libsass/issues/2359 + if (!c->is_important()) return NULL; + } + eval.is_in_comment = true; + Comment_Ptr rv = SASS_MEMORY_NEW(Comment, c->pstate(), Cast(c->text()->perform(&eval)), c->is_important()); + eval.is_in_comment = false; + // TODO: eval the text, once we're parsing/storing it as a String_Schema + return rv; + } + + Statement_Ptr Expand::operator()(If_Ptr i) + { + Env env(environment(), true); + env_stack.push_back(&env); + call_stack.push_back(i); + Expression_Obj rv = i->predicate()->perform(&eval); + if (*rv) { + append_block(i->block()); + } + else { + Block_Ptr alt = i->alternative(); + if (alt) append_block(alt); + } + call_stack.pop_back(); + env_stack.pop_back(); + return 0; + } + + // For does not create a new env scope + // But iteration vars are reset afterwards + Statement_Ptr Expand::operator()(For_Ptr f) + { + std::string variable(f->variable()); + Expression_Obj low = f->lower_bound()->perform(&eval); + if (low->concrete_type() != Expression::NUMBER) { + traces.push_back(Backtrace(low->pstate())); + throw Exception::TypeMismatch(traces, *low, "integer"); + } + Expression_Obj high = f->upper_bound()->perform(&eval); + if (high->concrete_type() != Expression::NUMBER) { + traces.push_back(Backtrace(high->pstate())); + throw Exception::TypeMismatch(traces, *high, "integer"); + } + Number_Obj sass_start = Cast(low); + Number_Obj sass_end = Cast(high); + // check if units are valid for sequence + if (sass_start->unit() != sass_end->unit()) { + std::stringstream msg; msg << "Incompatible units: '" + << sass_start->unit() << "' and '" + << sass_end->unit() << "'."; + error(msg.str(), low->pstate(), traces); + } + double start = sass_start->value(); + double end = sass_end->value(); + // only create iterator once in this environment + Env env(environment(), true); + env_stack.push_back(&env); + call_stack.push_back(f); + Block_Ptr body = f->block(); + if (start < end) { + if (f->is_inclusive()) ++end; + for (double i = start; + i < end; + ++i) { + Number_Obj it = SASS_MEMORY_NEW(Number, low->pstate(), i, sass_end->unit()); + env.set_local(variable, it); + append_block(body); + } + } else { + if (f->is_inclusive()) --end; + for (double i = start; + i > end; + --i) { + Number_Obj it = SASS_MEMORY_NEW(Number, low->pstate(), i, sass_end->unit()); + env.set_local(variable, it); + append_block(body); + } + } + call_stack.pop_back(); + env_stack.pop_back(); + return 0; + } + + // Eval does not create a new env scope + // But iteration vars are reset afterwards + Statement_Ptr Expand::operator()(Each_Ptr e) + { + std::vector variables(e->variables()); + Expression_Obj expr = e->list()->perform(&eval); + List_Obj list = 0; + Map_Obj map; + if (expr->concrete_type() == Expression::MAP) { + map = Cast(expr); + } + else if (Selector_List_Ptr ls = Cast(expr)) { + Listize listize; + Expression_Obj rv = ls->perform(&listize); + list = Cast(rv); + } + else if (expr->concrete_type() != Expression::LIST) { + list = SASS_MEMORY_NEW(List, expr->pstate(), 1, SASS_COMMA); + list->append(expr); + } + else { + list = Cast(expr); + } + // remember variables and then reset them + Env env(environment(), true); + env_stack.push_back(&env); + call_stack.push_back(e); + Block_Ptr body = e->block(); + + if (map) { + for (auto key : map->keys()) { + Expression_Obj k = key->perform(&eval); + Expression_Obj v = map->at(key)->perform(&eval); + + if (variables.size() == 1) { + List_Obj variable = SASS_MEMORY_NEW(List, map->pstate(), 2, SASS_SPACE); + variable->append(k); + variable->append(v); + env.set_local(variables[0], variable); + } else { + env.set_local(variables[0], k); + env.set_local(variables[1], v); + } + append_block(body); + } + } + else { + // bool arglist = list->is_arglist(); + if (list->length() == 1 && Cast(list)) { + list = Cast(list); + } + for (size_t i = 0, L = list->length(); i < L; ++i) { + Expression_Obj item = list->at(i); + // unwrap value if the expression is an argument + if (Argument_Obj arg = Cast(item)) item = arg->value(); + // check if we got passed a list of args (investigate) + if (List_Obj scalars = Cast(item)) { + if (variables.size() == 1) { + List_Obj var = scalars; + // if (arglist) var = (*scalars)[0]; + env.set_local(variables[0], var); + } else { + for (size_t j = 0, K = variables.size(); j < K; ++j) { + Expression_Obj res = j >= scalars->length() + ? SASS_MEMORY_NEW(Null, expr->pstate()) + : (*scalars)[j]->perform(&eval); + env.set_local(variables[j], res); + } + } + } else { + if (variables.size() > 0) { + env.set_local(variables.at(0), item); + for (size_t j = 1, K = variables.size(); j < K; ++j) { + Expression_Obj res = SASS_MEMORY_NEW(Null, expr->pstate()); + env.set_local(variables[j], res); + } + } + } + append_block(body); + } + } + call_stack.pop_back(); + env_stack.pop_back(); + return 0; + } + + Statement_Ptr Expand::operator()(While_Ptr w) + { + Expression_Obj pred = w->predicate(); + Block_Ptr body = w->block(); + Env env(environment(), true); + env_stack.push_back(&env); + call_stack.push_back(w); + Expression_Obj cond = pred->perform(&eval); + while (!cond->is_false()) { + append_block(body); + cond = pred->perform(&eval); + } + call_stack.pop_back(); + env_stack.pop_back(); + return 0; + } + + Statement_Ptr Expand::operator()(Return_Ptr r) + { + error("@return may only be used within a function", r->pstate(), traces); + return 0; + } + + + void Expand::expand_selector_list(Selector_Obj s, Selector_List_Obj extender) { + + if (Selector_List_Obj sl = Cast(s)) { + for (Complex_Selector_Obj complex_selector : sl->elements()) { + Complex_Selector_Obj tail = complex_selector; + while (tail) { + if (tail->head()) for (Simple_Selector_Obj header : tail->head()->elements()) { + if (Cast(header) == NULL) continue; // skip all others + std::string sel_str(complex_selector->to_string(ctx.c_options)); + error("Can't extend " + sel_str + ": can't extend parent selectors", header->pstate(), traces); + } + tail = tail->tail(); + } + } + } + + + Selector_List_Obj contextualized = Cast(s->perform(&eval)); + if (contextualized == false) return; + for (auto complex_sel : contextualized->elements()) { + Complex_Selector_Obj c = complex_sel; + if (!c->head() || c->tail()) { + std::string sel_str(contextualized->to_string(ctx.c_options)); + error("Can't extend " + sel_str + ": can't extend nested selectors", c->pstate(), traces); + } + Compound_Selector_Obj target = c->head(); + if (contextualized->is_optional()) target->is_optional(true); + for (size_t i = 0, L = extender->length(); i < L; ++i) { + Complex_Selector_Obj sel = (*extender)[i]; + if (!(sel->head() && sel->head()->length() > 0 && + Cast((*sel->head())[0]))) + { + Compound_Selector_Obj hh = SASS_MEMORY_NEW(Compound_Selector, (*extender)[i]->pstate()); + hh->media_block((*extender)[i]->media_block()); + Complex_Selector_Obj ssel = SASS_MEMORY_NEW(Complex_Selector, (*extender)[i]->pstate()); + ssel->media_block((*extender)[i]->media_block()); + if (sel->has_line_feed()) ssel->has_line_feed(true); + Parent_Selector_Obj ps = SASS_MEMORY_NEW(Parent_Selector, (*extender)[i]->pstate()); + ps->media_block((*extender)[i]->media_block()); + hh->append(ps); + ssel->tail(sel); + ssel->head(hh); + sel = ssel; + } + // if (c->has_line_feed()) sel->has_line_feed(true); + ctx.subset_map.put(target, std::make_pair(sel, target)); + } + } + + } + + Statement* Expand::operator()(Extension_Ptr e) + { + if (Selector_List_Ptr extender = selector()) { + Selector_List_Ptr sl = e->selector(); + // abort on invalid selector + if (sl == NULL) return NULL; + if (Selector_Schema_Ptr schema = sl->schema()) { + if (schema->has_real_parent_ref()) { + // put root block on stack again (ignore parents) + // selector schema must not connect in eval! + block_stack.push_back(block_stack.at(1)); + sl = eval(sl->schema()); + block_stack.pop_back(); + } else { + selector_stack.push_back(0); + sl = eval(sl->schema()); + selector_stack.pop_back(); + } + } + for (Complex_Selector_Obj cs : sl->elements()) { + if (!cs.isNull() && !cs->head().isNull()) { + cs->head()->media_block(media_block_stack.back()); + } + } + selector_stack.push_back(0); + expand_selector_list(sl, extender); + selector_stack.pop_back(); + } + return 0; + } + + Statement_Ptr Expand::operator()(Definition_Ptr d) + { + Env* env = environment(); + Definition_Obj dd = SASS_MEMORY_COPY(d); + env->local_frame()[d->name() + + (d->type() == Definition::MIXIN ? "[m]" : "[f]")] = dd; + + if (d->type() == Definition::FUNCTION && ( + Prelexer::calc_fn_call(d->name().c_str()) || + d->name() == "element" || + d->name() == "expression" || + d->name() == "url" + )) { + deprecated( + "Naming a function \"" + d->name() + "\" is disallowed and will be an error in future versions of Sass.", + "This name conflicts with an existing CSS function with special parse rules.", + false, d->pstate() + ); + } + + // set the static link so we can have lexical scoping + dd->environment(env); + return 0; + } + + Statement_Ptr Expand::operator()(Mixin_Call_Ptr c) + { + if (recursions > maxRecursion) { + throw Exception::StackError(traces, *c); + } + + recursions ++; + + Env* env = environment(); + std::string full_name(c->name() + "[m]"); + if (!env->has(full_name)) { + error("no mixin named " + c->name(), c->pstate(), traces); + } + Definition_Obj def = Cast((*env)[full_name]); + Block_Obj body = def->block(); + Parameters_Obj params = def->parameters(); + + if (c->block() && c->name() != "@content" && !body->has_content()) { + error("Mixin \"" + c->name() + "\" does not accept a content block.", c->pstate(), traces); + } + Expression_Obj rv = c->arguments()->perform(&eval); + Arguments_Obj args = Cast(rv); + std::string msg(", in mixin `" + c->name() + "`"); + traces.push_back(Backtrace(c->pstate(), msg)); + ctx.callee_stack.push_back({ + c->name().c_str(), + c->pstate().path, + c->pstate().line + 1, + c->pstate().column + 1, + SASS_CALLEE_MIXIN, + { env } + }); + + Env new_env(def->environment()); + env_stack.push_back(&new_env); + if (c->block()) { + // represent mixin content blocks as thunks/closures + Definition_Obj thunk = SASS_MEMORY_NEW(Definition, + c->pstate(), + "@content", + SASS_MEMORY_NEW(Parameters, c->pstate()), + c->block(), + Definition::MIXIN); + thunk->environment(env); + new_env.local_frame()["@content[m]"] = thunk; + } + + bind(std::string("Mixin"), c->name(), params, args, &ctx, &new_env, &eval); + + Block_Obj trace_block = SASS_MEMORY_NEW(Block, c->pstate()); + Trace_Obj trace = SASS_MEMORY_NEW(Trace, c->pstate(), c->name(), trace_block); + + env->set_global("is_in_mixin", bool_true); + if (Block_Ptr pr = block_stack.back()) { + trace_block->is_root(pr->is_root()); + } + block_stack.push_back(trace_block); + for (auto bb : body->elements()) { + if (Ruleset_Ptr r = Cast(bb)) { + r->is_root(trace_block->is_root()); + } + Statement_Obj ith = bb->perform(this); + if (ith) trace->block()->append(ith); + } + block_stack.pop_back(); + env->del_global("is_in_mixin"); + + ctx.callee_stack.pop_back(); + env_stack.pop_back(); + traces.pop_back(); + + recursions --; + return trace.detach(); + } + + Statement_Ptr Expand::operator()(Content_Ptr c) + { + Env* env = environment(); + // convert @content directives into mixin calls to the underlying thunk + if (!env->has("@content[m]")) return 0; + + if (block_stack.back()->is_root()) { + selector_stack.push_back(0); + } + + Mixin_Call_Obj call = SASS_MEMORY_NEW(Mixin_Call, + c->pstate(), + "@content", + SASS_MEMORY_NEW(Arguments, c->pstate())); + + Trace_Obj trace = Cast(call->perform(this)); + + if (block_stack.back()->is_root()) { + selector_stack.pop_back(); + } + + return trace.detach(); + } + + // produce an error if something is not implemented + inline Statement_Ptr Expand::fallback_impl(AST_Node_Ptr n) + { + std::string err =std:: string("`Expand` doesn't handle ") + typeid(*n).name(); + String_Quoted_Obj msg = SASS_MEMORY_NEW(String_Quoted, ParserState("[WARN]"), err); + error("unknown internal error; please contact the LibSass maintainers", n->pstate(), traces); + return SASS_MEMORY_NEW(Warning, ParserState("[WARN]"), msg); + } + + // process and add to last block on stack + inline void Expand::append_block(Block_Ptr b) + { + if (b->is_root()) call_stack.push_back(b); + for (size_t i = 0, L = b->length(); i < L; ++i) { + Statement_Ptr stm = b->at(i); + Statement_Obj ith = stm->perform(this); + if (ith) block_stack.back()->append(ith); + } + if (b->is_root()) call_stack.pop_back(); + } + +} diff --git a/src/libsass/file.cpp b/src/libsass/file.cpp new file mode 100644 index 000000000..ab2065194 --- /dev/null +++ b/src/libsass/file.cpp @@ -0,0 +1,485 @@ +#include "sass.hpp" +#ifdef _WIN32 +# ifdef __MINGW32__ +# ifndef off64_t +# define off64_t _off64_t /* Workaround for http://sourceforge.net/p/mingw/bugs/2024/ */ +# endif +# endif +# include +# define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) +#else +# include +#endif +#include +#include +#include +#include +#include +#include +#include "file.hpp" +#include "context.hpp" +#include "prelexer.hpp" +#include "utf8_string.hpp" +#include "sass_functions.hpp" +#include "sass2scss.h" + +#ifdef _WIN32 +# include + +# ifdef _MSC_VER +# include +inline static std::string wstring_to_string(const std::wstring& wstr) +{ + std::wstring_convert, wchar_t> wchar_converter; + return wchar_converter.to_bytes(wstr); +} +# else // mingw(/gcc) does not support C++11's codecvt yet. +inline static std::string wstring_to_string(const std::wstring &wstr) +{ + int size_needed = WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), NULL, 0, NULL, NULL); + std::string strTo(size_needed, 0); + WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), &strTo[0], size_needed, NULL, NULL); + return strTo; +} +# endif +#endif + +namespace Sass { + namespace File { + + // return the current directory + // always with forward slashes + // always with trailing slash + std::string get_cwd() + { + const size_t wd_len = 4096; + #ifndef _WIN32 + char wd[wd_len]; + char* pwd = getcwd(wd, wd_len); + // we should check error for more detailed info (e.g. ENOENT) + // http://man7.org/linux/man-pages/man2/getcwd.2.html#ERRORS + if (pwd == NULL) throw Exception::OperationError("cwd gone missing"); + std::string cwd = pwd; + #else + wchar_t wd[wd_len]; + wchar_t* pwd = _wgetcwd(wd, wd_len); + if (pwd == NULL) throw Exception::OperationError("cwd gone missing"); + std::string cwd = wstring_to_string(pwd); + //convert backslashes to forward slashes + replace(cwd.begin(), cwd.end(), '\\', '/'); + #endif + if (cwd[cwd.length() - 1] != '/') cwd += '/'; + return cwd; + } + + // test if path exists and is a file + bool file_exists(const std::string& path) + { + #ifdef _WIN32 + wchar_t resolved[32768]; + // windows unicode filepaths are encoded in utf16 + std::string abspath(join_paths(get_cwd(), path)); + std::wstring wpath(UTF_8::convert_to_utf16("\\\\?\\" + abspath)); + std::replace(wpath.begin(), wpath.end(), '/', '\\'); + DWORD rv = GetFullPathNameW(wpath.c_str(), 32767, resolved, NULL); + if (rv > 32767) throw Exception::OperationError("Path is too long"); + if (rv == 0) throw Exception::OperationError("Path could not be resolved"); + DWORD dwAttrib = GetFileAttributesW(resolved); + return (dwAttrib != INVALID_FILE_ATTRIBUTES && + (!(dwAttrib & FILE_ATTRIBUTE_DIRECTORY))); + #else + 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 std::string& path) + { + #ifdef _WIN32 + if (path.length() >= 2 && isalpha(path[0]) && path[1] == ':') return true; + #endif + size_t i = 0; + // check if we have a protocol + if (path[i] && Prelexer::is_alpha(path[i])) { + // skip over all alphanumeric characters + while (path[i] && Prelexer::is_alnum(path[i])) ++i; + i = i && path[i] == ':' ? i + 1 : 0; + } + return path[i] == '/'; + } + + // helper function to find the last directory seperator + inline size_t find_last_folder_separator(const std::string& path, size_t limit = std::string::npos) + { + size_t pos; + 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 = std::string::npos; + #endif + if (pos_p != std::string::npos && pos_w != std::string::npos) { + pos = std::max(pos_p, pos_w); + } + else if (pos_p != std::string::npos) { + pos = pos_p; + } + else { + pos = pos_w; + } + return pos; + } + + // return only the directory part of path + std::string dir_name(const std::string& path) + { + size_t pos = find_last_folder_separator(path); + if (pos == std::string::npos) return ""; + else return path.substr(0, pos+1); + } + + // return only the filename part of path + std::string base_name(const std::string& path) + { + size_t pos = find_last_folder_separator(path); + if (pos == std::string::npos) return path; + else return path.substr(pos+1); + } + + // do a logical clean up of the path + // no physical check on the filesystem + std::string make_canonical_path (std::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)) != std::string::npos) path.erase(pos, 2); + + // remove all leading and trailing self references + while(path.length() > 1 && path.substr(0, 2) == "./") path.erase(0, 2); + while((pos = path.length()) > 1 && path.substr(pos - 2) == "/.") path.erase(pos - 2); + + + size_t proto = 0; + // check if we have a protocol + if (path[proto] && Prelexer::is_alpha(path[proto])) { + // skip over all alphanumeric characters + while (path[proto] && Prelexer::is_alnum(path[proto++])) {} + // then skip over the mandatory colon + if (proto && path[proto] == ':') ++ proto; + } + + // then skip over start slashes + while (path[proto++] == '/') {} + + pos = proto; // collapse multiple delimiters into a single one + while((pos = path.find("//", pos)) != std::string::npos) path.erase(pos, 1); + + return path; + + } + + // join two path segments cleanly together + // but only if right side is not absolute yet + std::string join_paths(std::string l, std::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 += '/'; + + // this does a logical cleanup of the right hand path + // Note that this does collapse x/../y sections into y. + // This is by design. If /foo on your system is a symlink + // to /bar/baz, then /foo/../cd is actually /bar/cd, + // not /cd as a naive ../ removal would give you. + // will only work on leading double dot dirs on rhs + // therefore it is safe if lhs is already resolved cwd + while ((r.length() > 3) && ((r.substr(0, 3) == "../") || (r.substr(0, 3)) == "..\\")) { + size_t L = l.length(), pos = find_last_folder_separator(l, L - 2); + bool is_slash = pos + 2 == L && (l[pos+1] == '/' || l[pos+1] == '\\'); + bool is_self = pos + 3 == L && (l[pos+1] == '.'); + if (!is_self && !is_slash) r = r.substr(3); + else if (pos == std::string::npos) break; + l = l.substr(0, pos == std::string::npos ? pos : pos + 1); + } + + return l + r; + } + + std::string path_for_console(const std::string& rel_path, const std::string& abs_path, const std::string& orig_path) + { + // magic algorith goes here!! + + // if the file is outside this directory show the absolute path + if (rel_path.substr(0, 3) == "../") { + return orig_path; + } + // this seems to work most of the time + return abs_path == orig_path ? abs_path : rel_path; + } + + // create an absolute path by resolving relative paths with cwd + std::string rel2abs(const std::string& path, const std::string& base, const std::string& cwd) + { + return make_canonical_path(join_paths(join_paths(cwd + "/", base + "/"), 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 + std::string abs2rel(const std::string& path, const std::string& base, const std::string& cwd) + { + + std::string abs_path = rel2abs(path, cwd); + std::string abs_base = rel2abs(base, cwd); + + size_t proto = 0; + // check if we have a protocol + if (path[proto] && Prelexer::is_alpha(path[proto])) { + // skip over all alphanumeric characters + while (path[proto] && Prelexer::is_alnum(path[proto++])) {} + // then skip over the mandatory colon + if (proto && path[proto] == ':') ++ proto; + } + + // distinguish between windows absolute paths and valid protocols + // we assume that protocols must at least have two chars to be valid + if (proto && path[proto++] == '/' && proto > 3) return path; + + #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 (abs_base[0] != abs_path[0]) return abs_path; + #endif + + std::string stripped_uri = ""; + std::string stripped_base = ""; + + size_t index = 0; + size_t minSize = std::min(abs_path.size(), abs_base.size()); + for (size_t i = 0; i < minSize; ++i) { + #ifdef FS_CASE_SENSITIVE + if (abs_path[i] != abs_base[i]) break; + #else + // compare the charactes in a case insensitive manner + // windows fs is only case insensitive in ascii ranges + if (tolower(abs_path[i]) != tolower(abs_base[i])) break; + #endif + if (abs_path[i] == '/') index = i + 1; + } + for (size_t i = index; i < abs_path.size(); ++i) { + stripped_uri += abs_path[i]; + } + for (size_t i = index; i < abs_base.size(); ++i) { + stripped_base += abs_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; + } + } + + std::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 + std::vector resolve_includes(const std::string& root, const std::string& file, const std::vector& exts) + { + std::string filename = join_paths(root, file); + // split the filename + std::string base(dir_name(file)); + std::string name(base_name(file)); + std::vector includes; + // create full path (maybe relative) + std::string rel_path(join_paths(base, name)); + std::string abs_path(join_paths(root, rel_path)); + if (file_exists(abs_path)) includes.push_back({{ rel_path, root }, abs_path }); + // next test variation with underscore + rel_path = join_paths(base, "_" + name); + abs_path = join_paths(root, rel_path); + if (file_exists(abs_path)) includes.push_back({{ rel_path, root }, abs_path }); + // next test exts plus underscore + for(auto ext : exts) { + rel_path = join_paths(base, "_" + name + ext); + abs_path = join_paths(root, rel_path); + if (file_exists(abs_path)) includes.push_back({{ rel_path, root }, abs_path, ext == ".css" }); + } + // next test plain name with exts + for(auto ext : exts) { + rel_path = join_paths(base, name + ext); + abs_path = join_paths(root, rel_path); + if (file_exists(abs_path)) includes.push_back({{ rel_path, root }, abs_path, ext == ".css" }); + } + // nothing found + return includes; + } + + std::vector find_files(const std::string& file, const std::vector paths) + { + std::vector includes; + for (std::string path : paths) { + std::string abs_path(join_paths(path, file)); + if (file_exists(abs_path)) includes.push_back(abs_path); + } + return includes; + } + + std::vector find_files(const std::string& file, struct Sass_Compiler* compiler) + { + // get the last import entry to get current base directory + // struct Sass_Options* options = sass_compiler_get_options(compiler); + Sass_Import_Entry import = sass_compiler_get_last_import(compiler); + const std::vector& incs = compiler->cpp_ctx->include_paths; + // create the vector with paths to lookup + std::vector paths(1 + incs.size()); + paths.push_back(dir_name(import->abs_path)); + paths.insert(paths.end(), incs.begin(), incs.end()); + // dispatch to find files in paths + return find_files(file, paths); + } + + // helper function to search one file in all include paths + // this is normally not used internally by libsass (C-API sugar) + std::string find_file(const std::string& file, const std::vector paths) + { + if (file.empty()) return file; + auto res = find_files(file, paths); + return res.empty() ? "" : res.front(); + } + + // helper function to resolve a filename + std::string find_include(const std::string& file, const std::vector paths) + { + // search in every include path for a match + for (size_t i = 0, S = paths.size(); i < S; ++i) + { + std::vector resolved(resolve_includes(paths[i], file)); + if (resolved.size()) return resolved[0].abs_path; + } + // nothing found + return std::string(""); + } + + // try to load the given filename + // returned memory must be freed + // will auto convert .sass files + char* read_file(const std::string& path) + { + #ifdef _WIN32 + BYTE* pBuffer; + DWORD dwBytes; + wchar_t resolved[32768]; + // windows unicode filepaths are encoded in utf16 + std::string abspath(join_paths(get_cwd(), path)); + std::wstring wpath(UTF_8::convert_to_utf16("\\\\?\\" + abspath)); + std::replace(wpath.begin(), wpath.end(), '/', '\\'); + DWORD rv = GetFullPathNameW(wpath.c_str(), 32767, resolved, NULL); + if (rv > 32767) throw Exception::OperationError("Path is too long"); + if (rv == 0) throw Exception::OperationError("Path could not be resolved"); + HANDLE hFile = CreateFileW(resolved, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); + if (hFile == INVALID_HANDLE_VALUE) return 0; + DWORD dwFileLength = GetFileSize(hFile, NULL); + if (dwFileLength == INVALID_FILE_SIZE) return 0; + // allocate an extra byte for the null char + // and another one for edge-cases in lexer + pBuffer = (BYTE*)malloc((dwFileLength+2)*sizeof(BYTE)); + ReadFile(hFile, pBuffer, dwFileLength, &dwBytes, NULL); + pBuffer[dwFileLength+0] = '\0'; + pBuffer[dwFileLength+1] = '\0'; + 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; + std::ifstream file(path.c_str(), std::ios::in | std::ios::binary | std::ios::ate); + char* contents = 0; + if (file.is_open()) { + size_t size = file.tellg(); + // allocate an extra byte for the null char + // and another one for edge-cases in lexer + contents = (char*) malloc((size+2)*sizeof(char)); + file.seekg(0, std::ios::beg); + file.read(contents, size); + contents[size+0] = '\0'; + contents[size+1] = '\0'; + file.close(); + } + #endif + std::string extension; + if (path.length() > 5) { + extension = path.substr(path.length() - 5, 5); + } + for(size_t i=0; i split_path_list(const char* str) + { + std::vector paths; + if (str == NULL) return paths; + // find delimiter via prelexer (return zero at end) + const char* end = Prelexer::find_first(str); + // search until null delimiter + while (end) { + // add path from current position to delimiter + paths.push_back(std::string(str, end - str)); + str = end + 1; // skip delimiter + end = Prelexer::find_first(str); + } + // add path from current position to end + paths.push_back(std::string(str)); + // return back + return paths; + } + + } +} diff --git a/src/libsass/file.hpp b/src/libsass/file.hpp new file mode 100644 index 000000000..a043bea7a --- /dev/null +++ b/src/libsass/file.hpp @@ -0,0 +1,139 @@ +#ifndef SASS_FILE_H +#define SASS_FILE_H + +#include +#include + +#include "sass/context.h" +#include "ast_fwd_decl.hpp" + +namespace Sass { + + namespace File { + + // return the current directory + // always with forward slashes + std::string get_cwd(); + + // test if path exists and is a file + bool file_exists(const std::string& file); + + // return if given path is absolute + // works with *nix and windows paths + bool is_absolute_path(const std::string& path); + + // return only the directory part of path + std::string dir_name(const std::string& path); + + // return only the filename part of path + std::string base_name(const std::string&); + + // do a locigal clean up of the path + // no physical check on the filesystem + std::string make_canonical_path (std::string path); + + // join two path segments cleanly together + // but only if right side is not absolute yet + std::string join_paths(std::string root, std::string name); + + // if the relative path is outside of the cwd we want want to + // show the absolute path in console messages + std::string path_for_console(const std::string& rel_path, const std::string& abs_path, const std::string& orig_path); + + // create an absolute path by resolving relative paths with cwd + std::string rel2abs(const std::string& path, const std::string& base = ".", const std::string& cwd = get_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 + std::string abs2rel(const std::string& path, const std::string& base = ".", const std::string& cwd = get_cwd()); + + // helper function to resolve a filename + // searching without variations in all paths + std::string find_file(const std::string& file, struct Sass_Compiler* options); + std::string find_file(const std::string& file, const std::vector paths); + + // helper function to resolve a include filename + // this has the original resolve logic for sass include + std::string find_include(const std::string& file, const std::vector paths); + + // split a path string delimited by semicolons or colons (OS dependent) + std::vector split_path_list(const char* paths); + + // try to load the given filename + // returned memory must be freed + // will auto convert .sass files + char* read_file(const std::string& file); + + } + + // requested import + class Importer { + public: + // requested import path + std::string imp_path; + // parent context path + std::string ctx_path; + // base derived from context path + // this really just acts as a cache + std::string base_path; + public: + Importer(std::string imp_path, std::string ctx_path) + : imp_path(File::make_canonical_path(imp_path)), + ctx_path(File::make_canonical_path(ctx_path)), + base_path(File::dir_name(ctx_path)) + { } + }; + + // a resolved include (final import) + class Include : public Importer { + public: + // resolved absolute path + std::string abs_path; + // is a deprecated file type + bool deprecated; + public: + Include(const Importer& imp, std::string abs_path, bool deprecated) + : Importer(imp), abs_path(abs_path), deprecated(deprecated) + { } + Include(const Importer& imp, std::string abs_path) + : Importer(imp), abs_path(abs_path), deprecated(false) + { } + }; + + // a loaded resource + class Resource { + public: + // the file contents + char* contents; + // conected sourcemap + char* srcmap; + public: + Resource(char* contents, char* srcmap) + : contents(contents), srcmap(srcmap) + { } + }; + + // parsed stylesheet from loaded resource + class StyleSheet : public Resource { + public: + // parsed root block + Block_Obj root; + public: + StyleSheet(const Resource& res, Block_Obj root) + : Resource(res), root(root) + { } + }; + + namespace File { + + static std::vector defaultExtensions = { ".scss", ".sass" }; + + std::vector resolve_includes(const std::string& root, const std::string& file, + const std::vector& exts = defaultExtensions); + + + } + +} + +#endif diff --git a/src/libsass/functions.cpp b/src/libsass/functions.cpp new file mode 100644 index 000000000..c9999fc3a --- /dev/null +++ b/src/libsass/functions.cpp @@ -0,0 +1,2234 @@ +#include "sass.hpp" +#include "functions.hpp" +#include "ast.hpp" +#include "context.hpp" +#include "backtrace.hpp" +#include "parser.hpp" +#include "constants.hpp" +#include "inspect.hpp" +#include "extend.hpp" +#include "eval.hpp" +#include "util.hpp" +#include "expand.hpp" +#include "operators.hpp" +#include "utf8_string.hpp" +#include "sass/base.h" +#include "utf8.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __MINGW32__ +#include "windows.h" +#include "wincrypt.h" +#endif + +#define ARG(argname, argtype) get_arg(argname, env, sig, pstate, traces) +#define ARGM(argname, argtype, ctx) get_arg_m(argname, env, sig, pstate, traces, ctx) + +// return a number object (copied since we want to have reduced units) +#define ARGN(argname) get_arg_n(argname, env, sig, pstate, traces) // Number copy + +// special function for weird hsla percent (10px == 10% == 10 != 0.1) +#define ARGVAL(argname) get_arg_val(argname, env, sig, pstate, traces) // double + +// macros for common ranges (u mean unsigned or upper, r for full range) +#define DARG_U_FACT(argname) get_arg_r(argname, env, sig, pstate, traces, - 0.0, 1.0) // double +#define DARG_R_FACT(argname) get_arg_r(argname, env, sig, pstate, traces, - 1.0, 1.0) // double +#define DARG_U_BYTE(argname) get_arg_r(argname, env, sig, pstate, traces, - 0.0, 255.0) // double +#define DARG_R_BYTE(argname) get_arg_r(argname, env, sig, pstate, traces, - 255.0, 255.0) // double +#define DARG_U_PRCT(argname) get_arg_r(argname, env, sig, pstate, traces, - 0.0, 100.0) // double +#define DARG_R_PRCT(argname) get_arg_r(argname, env, sig, pstate, traces, - 100.0, 100.0) // double + +// macros for color related inputs (rbg and alpha/opacity values) +#define COLOR_NUM(argname) color_num(argname, env, sig, pstate, traces) // double +#define ALPHA_NUM(argname) alpha_num(argname, env, sig, pstate, traces) // double + +namespace Sass { + using std::stringstream; + using std::endl; + + Definition_Ptr make_native_function(Signature sig, Native_Function func, Context& ctx) + { + Parser sig_parser = Parser::from_c_str(sig, ctx, ctx.traces, ParserState("[built-in function]")); + sig_parser.lex(); + std::string name(Util::normalize_underscores(sig_parser.lexed)); + Parameters_Obj params = sig_parser.parse_parameters(); + return SASS_MEMORY_NEW(Definition, + ParserState("[built-in function]"), + sig, + name, + params, + func, + false); + } + + Definition_Ptr make_c_function(Sass_Function_Entry c_func, Context& ctx) + { + using namespace Prelexer; + + const char* sig = sass_function_get_signature(c_func); + Parser sig_parser = Parser::from_c_str(sig, ctx, ctx.traces, ParserState("[c function]")); + // allow to overload generic callback plus @warn, @error and @debug with custom functions + sig_parser.lex < alternatives < identifier, exactly <'*'>, + exactly < Constants::warn_kwd >, + exactly < Constants::error_kwd >, + exactly < Constants::debug_kwd > + > >(); + std::string name(Util::normalize_underscores(sig_parser.lexed)); + Parameters_Obj params = sig_parser.parse_parameters(); + return SASS_MEMORY_NEW(Definition, + ParserState("[c function]"), + sig, + name, + params, + c_func, + false, true); + } + + std::string function_name(Signature sig) + { + std::string str(sig); + return str.substr(0, str.find('(')); + } + + namespace Functions { + + inline void handle_utf8_error (const ParserState& pstate, Backtraces traces) + { + try { + throw; + } + catch (utf8::invalid_code_point) { + std::string msg("utf8::invalid_code_point"); + error(msg, pstate, traces); + } + catch (utf8::not_enough_room) { + std::string msg("utf8::not_enough_room"); + error(msg, pstate, traces); + } + catch (utf8::invalid_utf8) { + std::string msg("utf8::invalid_utf8"); + error(msg, pstate, traces); + } + catch (...) { throw; } + } + + template + T* get_arg(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces) + { + // Minimal error handling -- the expectation is that built-ins will be written correctly! + T* val = Cast(env[argname]); + if (!val) { + std::string msg("argument `"); + msg += argname; + msg += "` of `"; + msg += sig; + msg += "` must be a "; + msg += T::type_name(); + error(msg, pstate, traces); + } + return val; + } + + Map_Ptr get_arg_m(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces, Context& ctx) + { + // Minimal error handling -- the expectation is that built-ins will be written correctly! + Map_Ptr val = Cast(env[argname]); + if (val) return val; + + List_Ptr lval = Cast(env[argname]); + if (lval && lval->length() == 0) return SASS_MEMORY_NEW(Map, pstate, 0); + + // fallback on get_arg for error handling + val = get_arg(argname, env, sig, pstate, traces); + return val; + } + + double get_arg_r(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces, double lo, double hi) + { + // Minimal error handling -- the expectation is that built-ins will be written correctly! + Number_Ptr val = get_arg(argname, env, sig, pstate, traces); + Number tmpnr(val); + tmpnr.reduce(); + double v = tmpnr.value(); + if (!(lo <= v && v <= hi)) { + std::stringstream msg; + msg << "argument `" << argname << "` of `" << sig << "` must be between "; + msg << lo << " and " << hi; + error(msg.str(), pstate, traces); + } + return v; + } + + Number_Ptr get_arg_n(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces) + { + // Minimal error handling -- the expectation is that built-ins will be written correctly! + Number_Ptr val = get_arg(argname, env, sig, pstate, traces); + val = SASS_MEMORY_COPY(val); + val->reduce(); + return val; + } + + double get_arg_v(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces) + { + // Minimal error handling -- the expectation is that built-ins will be written correctly! + Number_Ptr val = get_arg(argname, env, sig, pstate, traces); + Number tmpnr(val); + tmpnr.reduce(); + /* + if (tmpnr.unit() == "%") { + tmpnr.value(tmpnr.value() / 100); + tmpnr.numerators.clear(); + } else { + if (!tmpnr.is_unitless()) error("argument " + argname + " of `" + std::string(sig) + "` must be unitless", pstate); + } + */ + return tmpnr.value(); + } + + double get_arg_val(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces) + { + // Minimal error handling -- the expectation is that built-ins will be written correctly! + Number_Ptr val = get_arg(argname, env, sig, pstate, traces); + Number tmpnr(val); + tmpnr.reduce(); + return tmpnr.value(); + } + + double color_num(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces) + { + Number_Ptr val = get_arg(argname, env, sig, pstate, traces); + Number tmpnr(val); + tmpnr.reduce(); + if (tmpnr.unit() == "%") { + return std::min(std::max(tmpnr.value() * 255 / 100.0, 0.0), 255.0); + } else { + return std::min(std::max(tmpnr.value(), 0.0), 255.0); + } + } + + + inline double alpha_num(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces) { + Number_Ptr val = get_arg(argname, env, sig, pstate, traces); + Number tmpnr(val); + tmpnr.reduce(); + if (tmpnr.unit() == "%") { + return std::min(std::max(tmpnr.value(), 0.0), 100.0); + } else { + return std::min(std::max(tmpnr.value(), 0.0), 1.0); + } + } + + #define ARGSEL(argname, seltype, contextualize) get_arg_sel(argname, env, sig, pstate, traces, ctx) + + template + T get_arg_sel(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces, Context& ctx); + + template <> + Selector_List_Obj get_arg_sel(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces, Context& ctx) { + Expression_Obj exp = ARG(argname, Expression); + if (exp->concrete_type() == Expression::NULL_VAL) { + std::stringstream msg; + msg << argname << ": null is not a valid selector: it must be a string,\n"; + msg << "a list of strings, or a list of lists of strings for `" << function_name(sig) << "'"; + error(msg.str(), pstate, traces); + } + if (String_Constant_Ptr str = Cast(exp)) { + str->quote_mark(0); + } + std::string exp_src = exp->to_string(ctx.c_options); + return Parser::parse_selector(exp_src.c_str(), ctx, traces); + } + + template <> + Compound_Selector_Obj get_arg_sel(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces, Context& ctx) { + Expression_Obj exp = ARG(argname, Expression); + if (exp->concrete_type() == Expression::NULL_VAL) { + std::stringstream msg; + msg << argname << ": null is not a string for `" << function_name(sig) << "'"; + error(msg.str(), pstate, traces); + } + if (String_Constant_Ptr str = Cast(exp)) { + str->quote_mark(0); + } + std::string exp_src = exp->to_string(ctx.c_options); + Selector_List_Obj sel_list = Parser::parse_selector(exp_src.c_str(), ctx, traces); + if (sel_list->length() == 0) return NULL; + Complex_Selector_Obj first = sel_list->first(); + if (!first->tail()) return first->head(); + return first->tail()->head(); + } + + #ifdef __MINGW32__ + uint64_t GetSeed() + { + HCRYPTPROV hp = 0; + BYTE rb[8]; + CryptAcquireContext(&hp, 0, 0, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT); + CryptGenRandom(hp, sizeof(rb), rb); + CryptReleaseContext(hp, 0); + + uint64_t seed; + memcpy(&seed, &rb[0], sizeof(seed)); + + return seed; + } + #else + uint64_t GetSeed() + { + std::random_device rd; + return rd(); + } + #endif + + // note: the performance of many implementations of + // random_device degrades sharply once the entropy pool + // is exhausted. For practical use, random_device is + // generally only used to seed a PRNG such as mt19937. + static std::mt19937 rand(static_cast(GetSeed())); + + // features + static std::set features { + "global-variable-shadowing", + "extend-selector-pseudoclass", + "at-error", + "units-level-3", + "custom-property" + }; + + //////////////// + // RGB FUNCTIONS + //////////////// + + inline bool special_number(String_Constant_Ptr s) { + if (s) { + std::string calc("calc("); + std::string var("var("); + std::string ss(s->value()); + return std::equal(calc.begin(), calc.end(), ss.begin()) || + std::equal(var.begin(), var.end(), ss.begin()); + } + return false; + } + + Signature rgb_sig = "rgb($red, $green, $blue)"; + BUILT_IN(rgb) + { + if ( + special_number(Cast(env["$red"])) || + special_number(Cast(env["$green"])) || + special_number(Cast(env["$blue"])) + ) { + return SASS_MEMORY_NEW(String_Constant, pstate, "rgb(" + + env["$red"]->to_string() + + ", " + + env["$green"]->to_string() + + ", " + + env["$blue"]->to_string() + + ")" + ); + } + + return SASS_MEMORY_NEW(Color, + pstate, + COLOR_NUM("$red"), + COLOR_NUM("$green"), + COLOR_NUM("$blue")); + } + + Signature rgba_4_sig = "rgba($red, $green, $blue, $alpha)"; + BUILT_IN(rgba_4) + { + if ( + special_number(Cast(env["$red"])) || + special_number(Cast(env["$green"])) || + special_number(Cast(env["$blue"])) || + special_number(Cast(env["$alpha"])) + ) { + return SASS_MEMORY_NEW(String_Constant, pstate, "rgba(" + + env["$red"]->to_string() + + ", " + + env["$green"]->to_string() + + ", " + + env["$blue"]->to_string() + + ", " + + env["$alpha"]->to_string() + + ")" + ); + } + + return SASS_MEMORY_NEW(Color, + pstate, + COLOR_NUM("$red"), + COLOR_NUM("$green"), + COLOR_NUM("$blue"), + ALPHA_NUM("$alpha")); + } + + Signature rgba_2_sig = "rgba($color, $alpha)"; + BUILT_IN(rgba_2) + { + if ( + special_number(Cast(env["$color"])) + ) { + return SASS_MEMORY_NEW(String_Constant, pstate, "rgba(" + + env["$color"]->to_string() + + ", " + + env["$alpha"]->to_string() + + ")" + ); + } + + Color_Ptr c_arg = ARG("$color", Color); + + if ( + special_number(Cast(env["$alpha"])) + ) { + std::stringstream strm; + strm << "rgba(" + << (int)c_arg->r() << ", " + << (int)c_arg->g() << ", " + << (int)c_arg->b() << ", " + << env["$alpha"]->to_string() + << ")"; + return SASS_MEMORY_NEW(String_Constant, pstate, strm.str()); + } + + Color_Ptr new_c = SASS_MEMORY_COPY(c_arg); + new_c->a(ALPHA_NUM("$alpha")); + new_c->disp(""); + return new_c; + } + + Signature red_sig = "red($color)"; + BUILT_IN(red) + { return SASS_MEMORY_NEW(Number, pstate, ARG("$color", Color)->r()); } + + Signature green_sig = "green($color)"; + BUILT_IN(green) + { return SASS_MEMORY_NEW(Number, pstate, ARG("$color", Color)->g()); } + + Signature blue_sig = "blue($color)"; + BUILT_IN(blue) + { return SASS_MEMORY_NEW(Number, pstate, ARG("$color", Color)->b()); } + + Color* colormix(Context& ctx, ParserState& pstate, Color* color1, Color* color2, double weight) { + double p = weight/100; + double w = 2*p - 1; + double a = color1->a() - color2->a(); + + double w1 = (((w * a == -1) ? w : (w + a)/(1 + w*a)) + 1)/2.0; + double w2 = 1 - w1; + + return SASS_MEMORY_NEW(Color, + pstate, + Sass::round(w1*color1->r() + w2*color2->r(), ctx.c_options.precision), + Sass::round(w1*color1->g() + w2*color2->g(), ctx.c_options.precision), + Sass::round(w1*color1->b() + w2*color2->b(), ctx.c_options.precision), + color1->a()*p + color2->a()*(1-p)); + } + + Signature mix_sig = "mix($color-1, $color-2, $weight: 50%)"; + BUILT_IN(mix) + { + Color_Obj color1 = ARG("$color-1", Color); + Color_Obj color2 = ARG("$color-2", Color); + double weight = DARG_U_PRCT("$weight"); + return colormix(ctx, pstate, color1, color2, weight); + + } + + //////////////// + // HSL FUNCTIONS + //////////////// + + // RGB to HSL helper function + struct HSL { double h; double s; double l; }; + HSL rgb_to_hsl(double r, double g, double b) + { + + // Algorithm from http://en.wikipedia.org/wiki/wHSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV + r /= 255.0; g /= 255.0; b /= 255.0; + + double max = std::max(r, std::max(g, b)); + double min = std::min(r, std::min(g, b)); + double delta = max - min; + + double h = 0; + double s; + double l = (max + min) / 2.0; + + if (NEAR_EQUAL(max, min)) { + h = s = 0; // achromatic + } + else { + if (l < 0.5) s = delta / (max + min); + else s = delta / (2.0 - max - min); + + if (r == max) h = (g - b) / delta + (g < b ? 6 : 0); + else if (g == max) h = (b - r) / delta + 2; + else if (b == max) h = (r - g) / delta + 4; + } + + HSL hsl_struct; + hsl_struct.h = h / 6 * 360; + hsl_struct.s = s * 100; + hsl_struct.l = l * 100; + + return hsl_struct; + } + + // hue to RGB helper function + double h_to_rgb(double m1, double m2, double h) { + while (h < 0) h += 1; + while (h > 1) h -= 1; + if (h*6.0 < 1) return m1 + (m2 - m1)*h*6; + if (h*2.0 < 1) return m2; + if (h*3.0 < 2) return m1 + (m2 - m1) * (2.0/3.0 - h)*6; + return m1; + } + + Color_Ptr hsla_impl(double h, double s, double l, double a, Context& ctx, ParserState pstate) + { + h /= 360.0; + s /= 100.0; + l /= 100.0; + + if (l < 0) l = 0; + if (s < 0) s = 0; + if (l > 1) l = 1; + if (s > 1) s = 1; + while (h < 0) h += 1; + while (h > 1) h -= 1; + + // if saturation is exacly zero, we loose + // information for hue, since it will evaluate + // to zero if converted back from rgb. Setting + // saturation to a very tiny number solves this. + if (s == 0) s = 1e-10; + + // Algorithm from the CSS3 spec: http://www.w3.org/TR/css3-color/#hsl-color. + double m2; + if (l <= 0.5) m2 = l*(s+1.0); + else m2 = (l+s)-(l*s); + double m1 = (l*2.0)-m2; + // round the results -- consider moving this into the Color constructor + double r = (h_to_rgb(m1, m2, h + 1.0/3.0) * 255.0); + double g = (h_to_rgb(m1, m2, h) * 255.0); + double b = (h_to_rgb(m1, m2, h - 1.0/3.0) * 255.0); + + return SASS_MEMORY_NEW(Color, pstate, r, g, b, a); + } + + Signature hsl_sig = "hsl($hue, $saturation, $lightness)"; + BUILT_IN(hsl) + { + if ( + special_number(Cast(env["$hue"])) || + special_number(Cast(env["$saturation"])) || + special_number(Cast(env["$lightness"])) + ) { + return SASS_MEMORY_NEW(String_Constant, pstate, "hsl(" + + env["$hue"]->to_string() + + ", " + + env["$saturation"]->to_string() + + ", " + + env["$lightness"]->to_string() + + ")" + ); + } + + return hsla_impl(ARGVAL("$hue"), + ARGVAL("$saturation"), + ARGVAL("$lightness"), + 1.0, + ctx, + pstate); + } + + Signature hsla_sig = "hsla($hue, $saturation, $lightness, $alpha)"; + BUILT_IN(hsla) + { + if ( + special_number(Cast(env["$hue"])) || + special_number(Cast(env["$saturation"])) || + special_number(Cast(env["$lightness"])) || + special_number(Cast(env["$alpha"])) + ) { + return SASS_MEMORY_NEW(String_Constant, pstate, "hsla(" + + env["$hue"]->to_string() + + ", " + + env["$saturation"]->to_string() + + ", " + + env["$lightness"]->to_string() + + ", " + + env["$alpha"]->to_string() + + ")" + ); + } + + return hsla_impl(ARGVAL("$hue"), + ARGVAL("$saturation"), + ARGVAL("$lightness"), + ARGVAL("$alpha"), + ctx, + pstate); + } + + Signature hue_sig = "hue($color)"; + BUILT_IN(hue) + { + Color_Ptr rgb_color = ARG("$color", Color); + HSL hsl_color = rgb_to_hsl(rgb_color->r(), + rgb_color->g(), + rgb_color->b()); + return SASS_MEMORY_NEW(Number, pstate, hsl_color.h, "deg"); + } + + Signature saturation_sig = "saturation($color)"; + BUILT_IN(saturation) + { + Color_Ptr rgb_color = ARG("$color", Color); + HSL hsl_color = rgb_to_hsl(rgb_color->r(), + rgb_color->g(), + rgb_color->b()); + return SASS_MEMORY_NEW(Number, pstate, hsl_color.s, "%"); + } + + Signature lightness_sig = "lightness($color)"; + BUILT_IN(lightness) + { + Color_Ptr rgb_color = ARG("$color", Color); + HSL hsl_color = rgb_to_hsl(rgb_color->r(), + rgb_color->g(), + rgb_color->b()); + return SASS_MEMORY_NEW(Number, pstate, hsl_color.l, "%"); + } + + Signature adjust_hue_sig = "adjust-hue($color, $degrees)"; + BUILT_IN(adjust_hue) + { + Color_Ptr rgb_color = ARG("$color", Color); + double degrees = ARGVAL("$degrees"); + HSL hsl_color = rgb_to_hsl(rgb_color->r(), + rgb_color->g(), + rgb_color->b()); + return hsla_impl(hsl_color.h + degrees, + hsl_color.s, + hsl_color.l, + rgb_color->a(), + ctx, + pstate); + } + + Signature lighten_sig = "lighten($color, $amount)"; + BUILT_IN(lighten) + { + Color_Ptr rgb_color = ARG("$color", Color); + double amount = DARG_U_PRCT("$amount"); + HSL hsl_color = rgb_to_hsl(rgb_color->r(), + rgb_color->g(), + rgb_color->b()); + //Check lightness is not negative before lighten it + double hslcolorL = hsl_color.l; + if (hslcolorL < 0) { + hslcolorL = 0; + } + + return hsla_impl(hsl_color.h, + hsl_color.s, + hslcolorL + amount, + rgb_color->a(), + ctx, + pstate); + } + + Signature darken_sig = "darken($color, $amount)"; + BUILT_IN(darken) + { + Color_Ptr rgb_color = ARG("$color", Color); + double amount = DARG_U_PRCT("$amount"); + HSL hsl_color = rgb_to_hsl(rgb_color->r(), + rgb_color->g(), + rgb_color->b()); + + //Check lightness if not over 100, before darken it + double hslcolorL = hsl_color.l; + if (hslcolorL > 100) { + hslcolorL = 100; + } + + return hsla_impl(hsl_color.h, + hsl_color.s, + hslcolorL - amount, + rgb_color->a(), + ctx, + pstate); + } + + Signature saturate_sig = "saturate($color, $amount: false)"; + BUILT_IN(saturate) + { + // CSS3 filter function overload: pass literal through directly + if (!Cast(env["$amount"])) { + return SASS_MEMORY_NEW(String_Quoted, pstate, "saturate(" + env["$color"]->to_string(ctx.c_options) + ")"); + } + + double amount = DARG_U_PRCT("$amount"); + Color_Ptr rgb_color = ARG("$color", Color); + HSL hsl_color = rgb_to_hsl(rgb_color->r(), + rgb_color->g(), + rgb_color->b()); + + double hslcolorS = hsl_color.s + amount; + + // Saturation cannot be below 0 or above 100 + if (hslcolorS < 0) { + hslcolorS = 0; + } + if (hslcolorS > 100) { + hslcolorS = 100; + } + + return hsla_impl(hsl_color.h, + hslcolorS, + hsl_color.l, + rgb_color->a(), + ctx, + pstate); + } + + Signature desaturate_sig = "desaturate($color, $amount)"; + BUILT_IN(desaturate) + { + Color_Ptr rgb_color = ARG("$color", Color); + double amount = DARG_U_PRCT("$amount"); + HSL hsl_color = rgb_to_hsl(rgb_color->r(), + rgb_color->g(), + rgb_color->b()); + + double hslcolorS = hsl_color.s - amount; + + // Saturation cannot be below 0 or above 100 + if (hslcolorS <= 0) { + hslcolorS = 0; + } + if (hslcolorS > 100) { + hslcolorS = 100; + } + + return hsla_impl(hsl_color.h, + hslcolorS, + hsl_color.l, + rgb_color->a(), + ctx, + pstate); + } + + Signature grayscale_sig = "grayscale($color)"; + BUILT_IN(grayscale) + { + // CSS3 filter function overload: pass literal through directly + Number_Ptr amount = Cast(env["$color"]); + if (amount) { + return SASS_MEMORY_NEW(String_Quoted, pstate, "grayscale(" + amount->to_string(ctx.c_options) + ")"); + } + + Color_Ptr rgb_color = ARG("$color", Color); + HSL hsl_color = rgb_to_hsl(rgb_color->r(), + rgb_color->g(), + rgb_color->b()); + return hsla_impl(hsl_color.h, + 0.0, + hsl_color.l, + rgb_color->a(), + ctx, + pstate); + } + + Signature complement_sig = "complement($color)"; + BUILT_IN(complement) + { + Color_Ptr rgb_color = ARG("$color", Color); + HSL hsl_color = rgb_to_hsl(rgb_color->r(), + rgb_color->g(), + rgb_color->b()); + return hsla_impl(hsl_color.h - 180.0, + hsl_color.s, + hsl_color.l, + rgb_color->a(), + ctx, + pstate); + } + + Signature invert_sig = "invert($color, $weight: 100%)"; + BUILT_IN(invert) + { + // CSS3 filter function overload: pass literal through directly + Number_Ptr amount = Cast(env["$color"]); + if (amount) { + return SASS_MEMORY_NEW(String_Quoted, pstate, "invert(" + amount->to_string(ctx.c_options) + ")"); + } + + double weight = DARG_U_PRCT("$weight"); + Color_Ptr rgb_color = ARG("$color", Color); + Color_Obj inv = SASS_MEMORY_NEW(Color, + pstate, + 255 - rgb_color->r(), + 255 - rgb_color->g(), + 255 - rgb_color->b(), + rgb_color->a()); + return colormix(ctx, pstate, inv, rgb_color, weight); + } + + //////////////////// + // OPACITY FUNCTIONS + //////////////////// + Signature alpha_sig = "alpha($color)"; + Signature opacity_sig = "opacity($color)"; + BUILT_IN(alpha) + { + String_Constant_Ptr ie_kwd = Cast(env["$color"]); + if (ie_kwd) { + return SASS_MEMORY_NEW(String_Quoted, pstate, "alpha(" + ie_kwd->value() + ")"); + } + + // CSS3 filter function overload: pass literal through directly + Number_Ptr amount = Cast(env["$color"]); + if (amount) { + return SASS_MEMORY_NEW(String_Quoted, pstate, "opacity(" + amount->to_string(ctx.c_options) + ")"); + } + + return SASS_MEMORY_NEW(Number, pstate, ARG("$color", Color)->a()); + } + + Signature opacify_sig = "opacify($color, $amount)"; + Signature fade_in_sig = "fade-in($color, $amount)"; + BUILT_IN(opacify) + { + Color_Ptr color = ARG("$color", Color); + double amount = DARG_U_FACT("$amount"); + double alpha = std::min(color->a() + amount, 1.0); + return SASS_MEMORY_NEW(Color, + pstate, + color->r(), + color->g(), + color->b(), + alpha); + } + + Signature transparentize_sig = "transparentize($color, $amount)"; + Signature fade_out_sig = "fade-out($color, $amount)"; + BUILT_IN(transparentize) + { + Color_Ptr color = ARG("$color", Color); + double amount = DARG_U_FACT("$amount"); + double alpha = std::max(color->a() - amount, 0.0); + return SASS_MEMORY_NEW(Color, + pstate, + color->r(), + color->g(), + color->b(), + alpha); + } + + //////////////////////// + // OTHER COLOR FUNCTIONS + //////////////////////// + + Signature adjust_color_sig = "adjust-color($color, $red: false, $green: false, $blue: false, $hue: false, $saturation: false, $lightness: false, $alpha: false)"; + BUILT_IN(adjust_color) + { + Color_Ptr color = ARG("$color", Color); + Number_Ptr r = Cast(env["$red"]); + Number_Ptr g = Cast(env["$green"]); + Number_Ptr b = Cast(env["$blue"]); + Number_Ptr h = Cast(env["$hue"]); + Number_Ptr s = Cast(env["$saturation"]); + Number_Ptr l = Cast(env["$lightness"]); + Number_Ptr a = Cast(env["$alpha"]); + + bool rgb = r || g || b; + bool hsl = h || s || l; + + if (rgb && hsl) { + error("Cannot specify HSL and RGB values for a color at the same time for `adjust-color'", pstate, traces); + } + if (rgb) { + double rr = r ? DARG_R_BYTE("$red") : 0; + double gg = g ? DARG_R_BYTE("$green") : 0; + double bb = b ? DARG_R_BYTE("$blue") : 0; + double aa = a ? DARG_R_FACT("$alpha") : 0; + return SASS_MEMORY_NEW(Color, + pstate, + color->r() + rr, + color->g() + gg, + color->b() + bb, + color->a() + aa); + } + if (hsl) { + HSL hsl_struct = rgb_to_hsl(color->r(), color->g(), color->b()); + double ss = s ? DARG_R_PRCT("$saturation") : 0; + double ll = l ? DARG_R_PRCT("$lightness") : 0; + double aa = a ? DARG_R_FACT("$alpha") : 0; + return hsla_impl(hsl_struct.h + (h ? h->value() : 0), + hsl_struct.s + ss, + hsl_struct.l + ll, + color->a() + aa, + ctx, + pstate); + } + if (a) { + return SASS_MEMORY_NEW(Color, + pstate, + color->r(), + color->g(), + color->b(), + color->a() + (a ? a->value() : 0)); + } + error("not enough arguments for `adjust-color'", pstate, traces); + // unreachable + return color; + } + + Signature scale_color_sig = "scale-color($color, $red: false, $green: false, $blue: false, $hue: false, $saturation: false, $lightness: false, $alpha: false)"; + BUILT_IN(scale_color) + { + Color_Ptr color = ARG("$color", Color); + Number_Ptr r = Cast(env["$red"]); + Number_Ptr g = Cast(env["$green"]); + Number_Ptr b = Cast(env["$blue"]); + Number_Ptr h = Cast(env["$hue"]); + Number_Ptr s = Cast(env["$saturation"]); + Number_Ptr l = Cast(env["$lightness"]); + Number_Ptr a = Cast(env["$alpha"]); + + bool rgb = r || g || b; + bool hsl = h || s || l; + + if (rgb && hsl) { + error("Cannot specify HSL and RGB values for a color at the same time for `scale-color'", pstate, traces); + } + if (rgb) { + double rscale = (r ? DARG_R_PRCT("$red") : 0.0) / 100.0; + double gscale = (g ? DARG_R_PRCT("$green") : 0.0) / 100.0; + double bscale = (b ? DARG_R_PRCT("$blue") : 0.0) / 100.0; + double ascale = (a ? DARG_R_PRCT("$alpha") : 0.0) / 100.0; + return SASS_MEMORY_NEW(Color, + pstate, + color->r() + rscale * (rscale > 0.0 ? 255 - color->r() : color->r()), + color->g() + gscale * (gscale > 0.0 ? 255 - color->g() : color->g()), + color->b() + bscale * (bscale > 0.0 ? 255 - color->b() : color->b()), + color->a() + ascale * (ascale > 0.0 ? 1.0 - color->a() : color->a())); + } + if (hsl) { + double hscale = (h ? DARG_R_PRCT("$hue") : 0.0) / 100.0; + double sscale = (s ? DARG_R_PRCT("$saturation") : 0.0) / 100.0; + double lscale = (l ? DARG_R_PRCT("$lightness") : 0.0) / 100.0; + double ascale = (a ? DARG_R_PRCT("$alpha") : 0.0) / 100.0; + HSL hsl_struct = rgb_to_hsl(color->r(), color->g(), color->b()); + hsl_struct.h += hscale * (hscale > 0.0 ? 360.0 - hsl_struct.h : hsl_struct.h); + hsl_struct.s += sscale * (sscale > 0.0 ? 100.0 - hsl_struct.s : hsl_struct.s); + hsl_struct.l += lscale * (lscale > 0.0 ? 100.0 - hsl_struct.l : hsl_struct.l); + double alpha = color->a() + ascale * (ascale > 0.0 ? 1.0 - color->a() : color->a()); + return hsla_impl(hsl_struct.h, hsl_struct.s, hsl_struct.l, alpha, ctx, pstate); + } + if (a) { + double ascale = (DARG_R_PRCT("$alpha")) / 100.0; + return SASS_MEMORY_NEW(Color, + pstate, + color->r(), + color->g(), + color->b(), + color->a() + ascale * (ascale > 0.0 ? 1.0 - color->a() : color->a())); + } + error("not enough arguments for `scale-color'", pstate, traces); + // unreachable + return color; + } + + Signature change_color_sig = "change-color($color, $red: false, $green: false, $blue: false, $hue: false, $saturation: false, $lightness: false, $alpha: false)"; + BUILT_IN(change_color) + { + Color_Ptr color = ARG("$color", Color); + Number_Ptr r = Cast(env["$red"]); + Number_Ptr g = Cast(env["$green"]); + Number_Ptr b = Cast(env["$blue"]); + Number_Ptr h = Cast(env["$hue"]); + Number_Ptr s = Cast(env["$saturation"]); + Number_Ptr l = Cast(env["$lightness"]); + Number_Ptr a = Cast(env["$alpha"]); + + bool rgb = r || g || b; + bool hsl = h || s || l; + + if (rgb && hsl) { + error("Cannot specify HSL and RGB values for a color at the same time for `change-color'", pstate, traces); + } + if (rgb) { + return SASS_MEMORY_NEW(Color, + pstate, + r ? DARG_U_BYTE("$red") : color->r(), + g ? DARG_U_BYTE("$green") : color->g(), + b ? DARG_U_BYTE("$blue") : color->b(), + a ? DARG_U_BYTE("$alpha") : color->a()); + } + if (hsl) { + HSL hsl_struct = rgb_to_hsl(color->r(), color->g(), color->b()); + if (h) hsl_struct.h = std::fmod(h->value(), 360.0); + if (s) hsl_struct.s = DARG_U_PRCT("$saturation"); + if (l) hsl_struct.l = DARG_U_PRCT("$lightness"); + double alpha = a ? DARG_U_FACT("$alpha") : color->a(); + return hsla_impl(hsl_struct.h, hsl_struct.s, hsl_struct.l, alpha, ctx, pstate); + } + if (a) { + double alpha = DARG_U_FACT("$alpha"); + return SASS_MEMORY_NEW(Color, + pstate, + color->r(), + color->g(), + color->b(), + alpha); + } + error("not enough arguments for `change-color'", pstate, traces); + // unreachable + return color; + } + + template + static double cap_channel(double c) { + if (c > range) return range; + else if (c < 0) return 0; + else return c; + } + + Signature ie_hex_str_sig = "ie-hex-str($color)"; + BUILT_IN(ie_hex_str) + { + Color_Ptr c = ARG("$color", Color); + double r = cap_channel<0xff>(c->r()); + double g = cap_channel<0xff>(c->g()); + double b = cap_channel<0xff>(c->b()); + double a = cap_channel<1> (c->a()) * 255; + + std::stringstream ss; + ss << '#' << std::setw(2) << std::setfill('0'); + ss << std::hex << std::setw(2) << static_cast(Sass::round(a, ctx.c_options.precision)); + ss << std::hex << std::setw(2) << static_cast(Sass::round(r, ctx.c_options.precision)); + ss << std::hex << std::setw(2) << static_cast(Sass::round(g, ctx.c_options.precision)); + ss << std::hex << std::setw(2) << static_cast(Sass::round(b, ctx.c_options.precision)); + + std::string result(ss.str()); + for (size_t i = 0, L = result.length(); i < L; ++i) { + result[i] = std::toupper(result[i]); + } + return SASS_MEMORY_NEW(String_Quoted, pstate, result); + } + + /////////////////// + // STRING FUNCTIONS + /////////////////// + + Signature unquote_sig = "unquote($string)"; + BUILT_IN(sass_unquote) + { + AST_Node_Obj arg = env["$string"]; + if (String_Quoted_Ptr string_quoted = Cast(arg)) { + String_Constant_Ptr result = SASS_MEMORY_NEW(String_Constant, pstate, string_quoted->value()); + // remember if the string was quoted (color tokens) + result->is_delayed(true); // delay colors + return result; + } + else if (String_Constant_Ptr str = Cast(arg)) { + return str; + } + else if (Expression_Ptr ex = Cast(arg)) { + Sass_Output_Style oldstyle = ctx.c_options.output_style; + ctx.c_options.output_style = SASS_STYLE_NESTED; + std::string val(arg->to_string(ctx.c_options)); + val = Cast(arg) ? "null" : val; + ctx.c_options.output_style = oldstyle; + + deprecated_function("Passing " + val + ", a non-string value, to unquote()", pstate); + return ex; + } + throw std::runtime_error("Invalid Data Type for unquote"); + } + + Signature quote_sig = "quote($string)"; + BUILT_IN(sass_quote) + { + AST_Node_Obj arg = env["$string"]; + // only set quote mark to true if already a string + if (String_Quoted_Ptr qstr = Cast(arg)) { + qstr->quote_mark('*'); + return qstr; + } + // all other nodes must be converted to a string node + std::string str(quote(arg->to_string(ctx.c_options), String_Constant::double_quote())); + String_Quoted_Ptr result = SASS_MEMORY_NEW(String_Quoted, pstate, str); + result->quote_mark('*'); + return result; + } + + + Signature str_length_sig = "str-length($string)"; + BUILT_IN(str_length) + { + size_t len = std::string::npos; + try { + String_Constant_Ptr s = ARG("$string", String_Constant); + len = UTF_8::code_point_count(s->value(), 0, s->value().size()); + + } + // handle any invalid utf8 errors + // other errors will be re-thrown + catch (...) { handle_utf8_error(pstate, traces); } + // return something even if we had an error (-1) + return SASS_MEMORY_NEW(Number, pstate, (double)len); + } + + Signature str_insert_sig = "str-insert($string, $insert, $index)"; + BUILT_IN(str_insert) + { + std::string str; + try { + String_Constant_Ptr s = ARG("$string", String_Constant); + str = s->value(); + str = unquote(str); + String_Constant_Ptr i = ARG("$insert", String_Constant); + std::string ins = i->value(); + ins = unquote(ins); + double index = ARGVAL("$index"); + size_t len = UTF_8::code_point_count(str, 0, str.size()); + + if (index > 0 && index <= len) { + // positive and within string length + str.insert(UTF_8::offset_at_position(str, static_cast(index) - 1), ins); + } + else if (index > len) { + // positive and past string length + str += ins; + } + else if (index == 0) { + str = ins + str; + } + else if (std::abs(index) <= len) { + // negative and within string length + index += len + 1; + str.insert(UTF_8::offset_at_position(str, static_cast(index)), ins); + } + else { + // negative and past string length + str = ins + str; + } + + if (String_Quoted_Ptr ss = Cast(s)) { + if (ss->quote_mark()) str = quote(str); + } + } + // handle any invalid utf8 errors + // other errors will be re-thrown + catch (...) { handle_utf8_error(pstate, traces); } + return SASS_MEMORY_NEW(String_Quoted, pstate, str); + } + + Signature str_index_sig = "str-index($string, $substring)"; + BUILT_IN(str_index) + { + size_t index = std::string::npos; + try { + String_Constant_Ptr s = ARG("$string", String_Constant); + String_Constant_Ptr t = ARG("$substring", String_Constant); + std::string str = s->value(); + str = unquote(str); + std::string substr = t->value(); + substr = unquote(substr); + + size_t c_index = str.find(substr); + if(c_index == std::string::npos) { + return SASS_MEMORY_NEW(Null, pstate); + } + index = UTF_8::code_point_count(str, 0, c_index) + 1; + } + // handle any invalid utf8 errors + // other errors will be re-thrown + catch (...) { handle_utf8_error(pstate, traces); } + // return something even if we had an error (-1) + return SASS_MEMORY_NEW(Number, pstate, (double)index); + } + + Signature str_slice_sig = "str-slice($string, $start-at, $end-at:-1)"; + BUILT_IN(str_slice) + { + std::string newstr; + try { + String_Constant_Ptr s = ARG("$string", String_Constant); + double start_at = ARGVAL("$start-at"); + double end_at = ARGVAL("$end-at"); + String_Quoted_Ptr ss = Cast(s); + + std::string str = unquote(s->value()); + + size_t size = utf8::distance(str.begin(), str.end()); + + if (!Cast(env["$end-at"])) { + end_at = -1; + } + + if (end_at == 0 || (end_at + size) < 0) { + if (ss && ss->quote_mark()) newstr = quote(""); + return SASS_MEMORY_NEW(String_Quoted, pstate, newstr); + } + + if (end_at < 0) { + end_at += size + 1; + if (end_at == 0) end_at = 1; + } + if (end_at > size) { end_at = (double)size; } + if (start_at < 0) { + start_at += size + 1; + if (start_at < 0) start_at = 0; + } + else if (start_at == 0) { ++ start_at; } + + if (start_at <= end_at) + { + std::string::iterator start = str.begin(); + utf8::advance(start, start_at - 1, str.end()); + std::string::iterator end = start; + utf8::advance(end, end_at - start_at + 1, str.end()); + newstr = std::string(start, end); + } + if (ss) { + if(ss->quote_mark()) newstr = quote(newstr); + } + } + // handle any invalid utf8 errors + // other errors will be re-thrown + catch (...) { handle_utf8_error(pstate, traces); } + return SASS_MEMORY_NEW(String_Quoted, pstate, newstr); + } + + Signature to_upper_case_sig = "to-upper-case($string)"; + BUILT_IN(to_upper_case) + { + String_Constant_Ptr s = ARG("$string", String_Constant); + std::string str = s->value(); + + for (size_t i = 0, L = str.length(); i < L; ++i) { + if (Sass::Util::isAscii(str[i])) { + str[i] = std::toupper(str[i]); + } + } + + if (String_Quoted_Ptr ss = Cast(s)) { + String_Quoted_Ptr cpy = SASS_MEMORY_COPY(ss); + cpy->value(str); + return cpy; + } else { + return SASS_MEMORY_NEW(String_Quoted, pstate, str); + } + } + + Signature to_lower_case_sig = "to-lower-case($string)"; + BUILT_IN(to_lower_case) + { + String_Constant_Ptr s = ARG("$string", String_Constant); + std::string str = s->value(); + + for (size_t i = 0, L = str.length(); i < L; ++i) { + if (Sass::Util::isAscii(str[i])) { + str[i] = std::tolower(str[i]); + } + } + + if (String_Quoted_Ptr ss = Cast(s)) { + String_Quoted_Ptr cpy = SASS_MEMORY_COPY(ss); + cpy->value(str); + return cpy; + } else { + return SASS_MEMORY_NEW(String_Quoted, pstate, str); + } + } + + /////////////////// + // NUMBER FUNCTIONS + /////////////////// + + Signature percentage_sig = "percentage($number)"; + BUILT_IN(percentage) + { + Number_Obj n = ARGN("$number"); + if (!n->is_unitless()) error("argument $number of `" + std::string(sig) + "` must be unitless", pstate, traces); + return SASS_MEMORY_NEW(Number, pstate, n->value() * 100, "%"); + } + + Signature round_sig = "round($number)"; + BUILT_IN(round) + { + Number_Obj r = ARGN("$number"); + r->value(Sass::round(r->value(), ctx.c_options.precision)); + r->pstate(pstate); + return r.detach(); + } + + Signature ceil_sig = "ceil($number)"; + BUILT_IN(ceil) + { + Number_Obj r = ARGN("$number"); + r->value(std::ceil(r->value())); + r->pstate(pstate); + return r.detach(); + } + + Signature floor_sig = "floor($number)"; + BUILT_IN(floor) + { + Number_Obj r = ARGN("$number"); + r->value(std::floor(r->value())); + r->pstate(pstate); + return r.detach(); + } + + Signature abs_sig = "abs($number)"; + BUILT_IN(abs) + { + Number_Obj r = ARGN("$number"); + r->value(std::abs(r->value())); + r->pstate(pstate); + return r.detach(); + } + + Signature min_sig = "min($numbers...)"; + BUILT_IN(min) + { + List_Ptr arglist = ARG("$numbers", List); + Number_Obj least = NULL; + for (size_t i = 0, L = arglist->length(); i < L; ++i) { + Expression_Obj val = arglist->value_at_index(i); + Number_Obj xi = Cast(val); + if (!xi) { + error("\"" + val->to_string(ctx.c_options) + "\" is not a number for `min'", pstate, traces); + } + if (least) { + if (*xi < *least) least = xi; + } else least = xi; + } + return least.detach(); + } + + Signature max_sig = "max($numbers...)"; + BUILT_IN(max) + { + List_Ptr arglist = ARG("$numbers", List); + Number_Obj greatest = NULL; + for (size_t i = 0, L = arglist->length(); i < L; ++i) { + Expression_Obj val = arglist->value_at_index(i); + Number_Obj xi = Cast(val); + if (!xi) { + error("\"" + val->to_string(ctx.c_options) + "\" is not a number for `max'", pstate, traces); + } + if (greatest) { + if (*greatest < *xi) greatest = xi; + } else greatest = xi; + } + return greatest.detach(); + } + + Signature random_sig = "random($limit:false)"; + BUILT_IN(random) + { + AST_Node_Obj arg = env["$limit"]; + Value_Ptr v = Cast(arg); + Number_Ptr l = Cast(arg); + Boolean_Ptr b = Cast(arg); + if (l) { + double lv = l->value(); + if (lv < 1) { + stringstream err; + err << "$limit " << lv << " must be greater than or equal to 1 for `random'"; + error(err.str(), pstate, traces); + } + bool eq_int = std::fabs(trunc(lv) - lv) < NUMBER_EPSILON; + if (!eq_int) { + stringstream err; + err << "Expected $limit to be an integer but got " << lv << " for `random'"; + error(err.str(), pstate, traces); + } + std::uniform_real_distribution<> distributor(1, lv + 1); + uint_fast32_t distributed = static_cast(distributor(rand)); + return SASS_MEMORY_NEW(Number, pstate, (double)distributed); + } + else if (b) { + std::uniform_real_distribution<> distributor(0, 1); + double distributed = static_cast(distributor(rand)); + return SASS_MEMORY_NEW(Number, pstate, distributed); + } else if (v) { + traces.push_back(Backtrace(pstate)); + throw Exception::InvalidArgumentType(pstate, traces, "random", "$limit", "number", v); + } else { + traces.push_back(Backtrace(pstate)); + throw Exception::InvalidArgumentType(pstate, traces, "random", "$limit", "number"); + } + } + + ///////////////// + // LIST FUNCTIONS + ///////////////// + + Signature length_sig = "length($list)"; + BUILT_IN(length) + { + if (Selector_List_Ptr sl = Cast(env["$list"])) { + return SASS_MEMORY_NEW(Number, pstate, (double)sl->length()); + } + Expression_Ptr v = ARG("$list", Expression); + if (v->concrete_type() == Expression::MAP) { + Map_Ptr map = Cast(env["$list"]); + return SASS_MEMORY_NEW(Number, pstate, (double)(map ? map->length() : 1)); + } + if (v->concrete_type() == Expression::SELECTOR) { + if (Compound_Selector_Ptr h = Cast(v)) { + return SASS_MEMORY_NEW(Number, pstate, (double)h->length()); + } else if (Selector_List_Ptr ls = Cast(v)) { + return SASS_MEMORY_NEW(Number, pstate, (double)ls->length()); + } else { + return SASS_MEMORY_NEW(Number, pstate, 1); + } + } + + List_Ptr list = Cast(env["$list"]); + return SASS_MEMORY_NEW(Number, + pstate, + (double)(list ? list->size() : 1)); + } + + Signature nth_sig = "nth($list, $n)"; + BUILT_IN(nth) + { + double nr = ARGVAL("$n"); + Map_Ptr m = Cast(env["$list"]); + if (Selector_List_Ptr sl = Cast(env["$list"])) { + size_t len = m ? m->length() : sl->length(); + bool empty = m ? m->empty() : sl->empty(); + if (empty) error("argument `$list` of `" + std::string(sig) + "` must not be empty", pstate, traces); + double index = std::floor(nr < 0 ? len + nr : nr - 1); + if (index < 0 || index > len - 1) error("index out of bounds for `" + std::string(sig) + "`", pstate, traces); + // return (*sl)[static_cast(index)]; + Listize listize; + return (*sl)[static_cast(index)]->perform(&listize); + } + List_Obj l = Cast(env["$list"]); + if (nr == 0) error("argument `$n` of `" + std::string(sig) + "` must be non-zero", pstate, traces); + // if the argument isn't a list, then wrap it in a singleton list + if (!m && !l) { + l = SASS_MEMORY_NEW(List, pstate, 1); + l->append(ARG("$list", Expression)); + } + size_t len = m ? m->length() : l->length(); + bool empty = m ? m->empty() : l->empty(); + if (empty) error("argument `$list` of `" + std::string(sig) + "` must not be empty", pstate, traces); + double index = std::floor(nr < 0 ? len + nr : nr - 1); + if (index < 0 || index > len - 1) error("index out of bounds for `" + std::string(sig) + "`", pstate, traces); + + if (m) { + l = SASS_MEMORY_NEW(List, pstate, 1); + l->append(m->keys()[static_cast(index)]); + l->append(m->at(m->keys()[static_cast(index)])); + return l.detach(); + } + else { + Expression_Obj rv = l->value_at_index(static_cast(index)); + rv->set_delayed(false); + return rv.detach(); + } + } + + Signature set_nth_sig = "set-nth($list, $n, $value)"; + BUILT_IN(set_nth) + { + Map_Obj m = Cast(env["$list"]); + List_Obj l = Cast(env["$list"]); + Number_Obj n = ARG("$n", Number); + Expression_Obj v = ARG("$value", Expression); + if (!l) { + l = SASS_MEMORY_NEW(List, pstate, 1); + l->append(ARG("$list", Expression)); + } + if (m) { + l = m->to_list(pstate); + } + if (l->empty()) error("argument `$list` of `" + std::string(sig) + "` must not be empty", pstate, traces); + double index = std::floor(n->value() < 0 ? l->length() + n->value() : n->value() - 1); + if (index < 0 || index > l->length() - 1) error("index out of bounds for `" + std::string(sig) + "`", pstate, traces); + List_Ptr result = SASS_MEMORY_NEW(List, pstate, l->length(), l->separator(), false, l->is_bracketed()); + for (size_t i = 0, L = l->length(); i < L; ++i) { + result->append(((i == index) ? v : (*l)[i])); + } + return result; + } + + Signature index_sig = "index($list, $value)"; + BUILT_IN(index) + { + Map_Obj m = Cast(env["$list"]); + List_Obj l = Cast(env["$list"]); + Expression_Obj v = ARG("$value", Expression); + if (!l) { + l = SASS_MEMORY_NEW(List, pstate, 1); + l->append(ARG("$list", Expression)); + } + if (m) { + l = m->to_list(pstate); + } + for (size_t i = 0, L = l->length(); i < L; ++i) { + if (Operators::eq(l->value_at_index(i), v)) return SASS_MEMORY_NEW(Number, pstate, (double)(i+1)); + } + return SASS_MEMORY_NEW(Null, pstate); + } + + Signature join_sig = "join($list1, $list2, $separator: auto, $bracketed: auto)"; + BUILT_IN(join) + { + Map_Obj m1 = Cast(env["$list1"]); + Map_Obj m2 = Cast(env["$list2"]); + List_Obj l1 = Cast(env["$list1"]); + List_Obj l2 = Cast(env["$list2"]); + String_Constant_Obj sep = ARG("$separator", String_Constant); + enum Sass_Separator sep_val = (l1 ? l1->separator() : SASS_SPACE); + Value* bracketed = ARG("$bracketed", Value); + bool is_bracketed = (l1 ? l1->is_bracketed() : false); + if (!l1) { + l1 = SASS_MEMORY_NEW(List, pstate, 1); + l1->append(ARG("$list1", Expression)); + sep_val = (l2 ? l2->separator() : SASS_SPACE); + is_bracketed = (l2 ? l2->is_bracketed() : false); + } + if (!l2) { + l2 = SASS_MEMORY_NEW(List, pstate, 1); + l2->append(ARG("$list2", Expression)); + } + if (m1) { + l1 = m1->to_list(pstate); + sep_val = SASS_COMMA; + } + if (m2) { + l2 = m2->to_list(pstate); + } + size_t len = l1->length() + l2->length(); + std::string sep_str = unquote(sep->value()); + if (sep_str == "space") sep_val = SASS_SPACE; + else if (sep_str == "comma") sep_val = SASS_COMMA; + else if (sep_str != "auto") error("argument `$separator` of `" + std::string(sig) + "` must be `space`, `comma`, or `auto`", pstate, traces); + String_Constant_Obj bracketed_as_str = Cast(bracketed); + bool bracketed_is_auto = bracketed_as_str && unquote(bracketed_as_str->value()) == "auto"; + if (!bracketed_is_auto) { + is_bracketed = !bracketed->is_false(); + } + List_Obj result = SASS_MEMORY_NEW(List, pstate, len, sep_val, false, is_bracketed); + result->concat(l1); + result->concat(l2); + return result.detach(); + } + + Signature append_sig = "append($list, $val, $separator: auto)"; + BUILT_IN(append) + { + Map_Obj m = Cast(env["$list"]); + List_Obj l = Cast(env["$list"]); + Expression_Obj v = ARG("$val", Expression); + if (Selector_List_Ptr sl = Cast(env["$list"])) { + Listize listize; + l = Cast(sl->perform(&listize)); + } + String_Constant_Obj sep = ARG("$separator", String_Constant); + if (!l) { + l = SASS_MEMORY_NEW(List, pstate, 1); + l->append(ARG("$list", Expression)); + } + if (m) { + l = m->to_list(pstate); + } + List_Ptr result = SASS_MEMORY_COPY(l); + std::string sep_str(unquote(sep->value())); + if (sep_str != "auto") { // check default first + if (sep_str == "space") result->separator(SASS_SPACE); + else if (sep_str == "comma") result->separator(SASS_COMMA); + else error("argument `$separator` of `" + std::string(sig) + "` must be `space`, `comma`, or `auto`", pstate, traces); + } + if (l->is_arglist()) { + result->append(SASS_MEMORY_NEW(Argument, + v->pstate(), + v, + "", + false, + false)); + + } else { + result->append(v); + } + return result; + } + + Signature zip_sig = "zip($lists...)"; + BUILT_IN(zip) + { + List_Obj arglist = SASS_MEMORY_COPY(ARG("$lists", List)); + size_t shortest = 0; + for (size_t i = 0, L = arglist->length(); i < L; ++i) { + List_Obj ith = Cast(arglist->value_at_index(i)); + Map_Obj mith = Cast(arglist->value_at_index(i)); + if (!ith) { + if (mith) { + ith = mith->to_list(pstate); + } else { + ith = SASS_MEMORY_NEW(List, pstate, 1); + ith->append(arglist->value_at_index(i)); + } + if (arglist->is_arglist()) { + Argument_Obj arg = (Argument_Ptr)(arglist->at(i).ptr()); // XXX + arg->value(ith); + } else { + (*arglist)[i] = ith; + } + } + shortest = (i ? std::min(shortest, ith->length()) : ith->length()); + } + List_Ptr zippers = SASS_MEMORY_NEW(List, pstate, shortest, SASS_COMMA); + size_t L = arglist->length(); + for (size_t i = 0; i < shortest; ++i) { + List_Ptr zipper = SASS_MEMORY_NEW(List, pstate, L); + for (size_t j = 0; j < L; ++j) { + zipper->append(Cast(arglist->value_at_index(j))->at(i)); + } + zippers->append(zipper); + } + return zippers; + } + + Signature list_separator_sig = "list_separator($list)"; + BUILT_IN(list_separator) + { + List_Obj l = Cast(env["$list"]); + if (!l) { + l = SASS_MEMORY_NEW(List, pstate, 1); + l->append(ARG("$list", Expression)); + } + return SASS_MEMORY_NEW(String_Quoted, + pstate, + l->separator() == SASS_COMMA ? "comma" : "space"); + } + + ///////////////// + // MAP FUNCTIONS + ///////////////// + + Signature map_get_sig = "map-get($map, $key)"; + BUILT_IN(map_get) + { + // leaks for "map-get((), foo)" if not Obj + // investigate why this is (unexpected) + Map_Obj m = ARGM("$map", Map, ctx); + Expression_Obj v = ARG("$key", Expression); + try { + Expression_Obj val = m->at(v); + if (!val) return SASS_MEMORY_NEW(Null, pstate); + val->set_delayed(false); + return val.detach(); + } catch (const std::out_of_range&) { + return SASS_MEMORY_NEW(Null, pstate); + } + catch (...) { throw; } + } + + Signature map_has_key_sig = "map-has-key($map, $key)"; + BUILT_IN(map_has_key) + { + Map_Obj m = ARGM("$map", Map, ctx); + Expression_Obj v = ARG("$key", Expression); + return SASS_MEMORY_NEW(Boolean, pstate, m->has(v)); + } + + Signature map_keys_sig = "map-keys($map)"; + BUILT_IN(map_keys) + { + Map_Obj m = ARGM("$map", Map, ctx); + List_Ptr result = SASS_MEMORY_NEW(List, pstate, m->length(), SASS_COMMA); + for ( auto key : m->keys()) { + result->append(key); + } + return result; + } + + Signature map_values_sig = "map-values($map)"; + BUILT_IN(map_values) + { + Map_Obj m = ARGM("$map", Map, ctx); + List_Ptr result = SASS_MEMORY_NEW(List, pstate, m->length(), SASS_COMMA); + for ( auto key : m->keys()) { + result->append(m->at(key)); + } + return result; + } + + Signature map_merge_sig = "map-merge($map1, $map2)"; + BUILT_IN(map_merge) + { + Map_Obj m1 = ARGM("$map1", Map, ctx); + Map_Obj m2 = ARGM("$map2", Map, ctx); + + size_t len = m1->length() + m2->length(); + Map_Ptr result = SASS_MEMORY_NEW(Map, pstate, len); + // concat not implemented for maps + *result += m1; + *result += m2; + return result; + } + + Signature map_remove_sig = "map-remove($map, $keys...)"; + BUILT_IN(map_remove) + { + bool remove; + Map_Obj m = ARGM("$map", Map, ctx); + List_Obj arglist = ARG("$keys", List); + Map_Ptr result = SASS_MEMORY_NEW(Map, pstate, 1); + for (auto key : m->keys()) { + remove = false; + for (size_t j = 0, K = arglist->length(); j < K && !remove; ++j) { + remove = Operators::eq(key, arglist->value_at_index(j)); + } + if (!remove) *result << std::make_pair(key, m->at(key)); + } + return result; + } + + Signature keywords_sig = "keywords($args)"; + BUILT_IN(keywords) + { + List_Obj arglist = SASS_MEMORY_COPY(ARG("$args", List)); // copy + Map_Obj result = SASS_MEMORY_NEW(Map, pstate, 1); + for (size_t i = arglist->size(), L = arglist->length(); i < L; ++i) { + Expression_Obj obj = arglist->at(i); + Argument_Obj arg = (Argument_Ptr) obj.ptr(); // XXX + std::string name = std::string(arg->name()); + name = name.erase(0, 1); // sanitize name (remove dollar sign) + *result << std::make_pair(SASS_MEMORY_NEW(String_Quoted, + pstate, name), + arg->value()); + } + return result.detach(); + } + + ////////////////////////// + // INTROSPECTION FUNCTIONS + ////////////////////////// + + Signature type_of_sig = "type-of($value)"; + BUILT_IN(type_of) + { + Expression_Ptr v = ARG("$value", Expression); + return SASS_MEMORY_NEW(String_Quoted, pstate, v->type()); + } + + Signature unit_sig = "unit($number)"; + BUILT_IN(unit) + { + Number_Obj arg = ARGN("$number"); + std::string str(quote(arg->unit(), '"')); + return SASS_MEMORY_NEW(String_Quoted, pstate, str); + } + + Signature unitless_sig = "unitless($number)"; + BUILT_IN(unitless) + { + Number_Obj arg = ARGN("$number"); + bool unitless = arg->is_unitless(); + return SASS_MEMORY_NEW(Boolean, pstate, unitless); + } + + Signature comparable_sig = "comparable($number-1, $number-2)"; + BUILT_IN(comparable) + { + Number_Obj n1 = ARGN("$number-1"); + Number_Obj n2 = ARGN("$number-2"); + if (n1->is_unitless() || n2->is_unitless()) { + return SASS_MEMORY_NEW(Boolean, pstate, true); + } + // normalize into main units + n1->normalize(); n2->normalize(); + Units &lhs_unit = *n1, &rhs_unit = *n2; + bool is_comparable = (lhs_unit == rhs_unit); + return SASS_MEMORY_NEW(Boolean, pstate, is_comparable); + } + + Signature variable_exists_sig = "variable-exists($name)"; + BUILT_IN(variable_exists) + { + std::string s = Util::normalize_underscores(unquote(ARG("$name", String_Constant)->value())); + + if(d_env.has("$"+s)) { + return SASS_MEMORY_NEW(Boolean, pstate, true); + } + else { + return SASS_MEMORY_NEW(Boolean, pstate, false); + } + } + + Signature global_variable_exists_sig = "global-variable-exists($name)"; + BUILT_IN(global_variable_exists) + { + std::string s = Util::normalize_underscores(unquote(ARG("$name", String_Constant)->value())); + + if(d_env.has_global("$"+s)) { + return SASS_MEMORY_NEW(Boolean, pstate, true); + } + else { + return SASS_MEMORY_NEW(Boolean, pstate, false); + } + } + + Signature function_exists_sig = "function-exists($name)"; + BUILT_IN(function_exists) + { + String_Constant_Ptr ss = Cast(env["$name"]); + if (!ss) { + error("$name: " + (env["$name"]->to_string()) + " is not a string for `function-exists'", pstate, traces); + } + + std::string name = Util::normalize_underscores(unquote(ss->value())); + + if(d_env.has_global(name+"[f]")) { + return SASS_MEMORY_NEW(Boolean, pstate, true); + } + else { + return SASS_MEMORY_NEW(Boolean, pstate, false); + } + } + + Signature mixin_exists_sig = "mixin-exists($name)"; + BUILT_IN(mixin_exists) + { + std::string s = Util::normalize_underscores(unquote(ARG("$name", String_Constant)->value())); + + if(d_env.has_global(s+"[m]")) { + return SASS_MEMORY_NEW(Boolean, pstate, true); + } + else { + return SASS_MEMORY_NEW(Boolean, pstate, false); + } + } + + Signature feature_exists_sig = "feature-exists($name)"; + BUILT_IN(feature_exists) + { + std::string s = unquote(ARG("$name", String_Constant)->value()); + + if(features.find(s) == features.end()) { + return SASS_MEMORY_NEW(Boolean, pstate, false); + } + else { + return SASS_MEMORY_NEW(Boolean, pstate, true); + } + } + + Signature call_sig = "call($name, $args...)"; + BUILT_IN(call) + { + std::string name; + Function_Ptr ff = Cast(env["$name"]); + String_Constant_Ptr ss = Cast(env["$name"]); + + if (ss) { + name = Util::normalize_underscores(unquote(ss->value())); + std::cerr << "DEPRECATION WARNING: "; + std::cerr << "Passing a string to call() is deprecated and will be illegal" << std::endl; + std::cerr << "in Sass 4.0. Use call(get-function(" + quote(name) + ")) instead." << std::endl; + std::cerr << std::endl; + } else if (ff) { + name = ff->name(); + } + + List_Obj arglist = SASS_MEMORY_COPY(ARG("$args", List)); + + Arguments_Obj args = SASS_MEMORY_NEW(Arguments, pstate); + // std::string full_name(name + "[f]"); + // Definition_Ptr def = d_env.has(full_name) ? Cast((d_env)[full_name]) : 0; + // Parameters_Ptr params = def ? def->parameters() : 0; + // size_t param_size = params ? params->length() : 0; + for (size_t i = 0, L = arglist->length(); i < L; ++i) { + Expression_Obj expr = arglist->value_at_index(i); + // if (params && params->has_rest_parameter()) { + // Parameter_Obj p = param_size > i ? (*params)[i] : 0; + // List_Ptr list = Cast(expr); + // if (list && p && !p->is_rest_parameter()) expr = (*list)[0]; + // } + if (arglist->is_arglist()) { + Expression_Obj obj = arglist->at(i); + Argument_Obj arg = (Argument_Ptr) obj.ptr(); // XXX + args->append(SASS_MEMORY_NEW(Argument, + pstate, + expr, + arg ? arg->name() : "", + arg ? arg->is_rest_argument() : false, + arg ? arg->is_keyword_argument() : false)); + } else { + args->append(SASS_MEMORY_NEW(Argument, pstate, expr)); + } + } + Function_Call_Obj func = SASS_MEMORY_NEW(Function_Call, pstate, name, args); + Expand expand(ctx, &d_env, &selector_stack); + func->via_call(true); // calc invoke is allowed + if (ff) func->func(ff); + return func->perform(&expand.eval); + } + + //////////////////// + // BOOLEAN FUNCTIONS + //////////////////// + + Signature not_sig = "not($value)"; + BUILT_IN(sass_not) + { + return SASS_MEMORY_NEW(Boolean, pstate, ARG("$value", Expression)->is_false()); + } + + Signature if_sig = "if($condition, $if-true, $if-false)"; + // BUILT_IN(sass_if) + // { return ARG("$condition", Expression)->is_false() ? ARG("$if-false", Expression) : ARG("$if-true", Expression); } + BUILT_IN(sass_if) + { + Expand expand(ctx, &d_env, &selector_stack); + Expression_Obj cond = ARG("$condition", Expression)->perform(&expand.eval); + bool is_true = !cond->is_false(); + Expression_Obj res = ARG(is_true ? "$if-true" : "$if-false", Expression); + res = res->perform(&expand.eval); + res->set_delayed(false); // clone? + return res.detach(); + } + + ////////////////////////// + // MISCELLANEOUS FUNCTIONS + ////////////////////////// + + // value.check_deprecated_interp if value.is_a?(Sass::Script::Value::String) + // unquoted_string(value.to_sass) + + Signature inspect_sig = "inspect($value)"; + BUILT_IN(inspect) + { + Expression_Ptr v = ARG("$value", Expression); + if (v->concrete_type() == Expression::NULL_VAL) { + return SASS_MEMORY_NEW(String_Quoted, pstate, "null"); + } else if (v->concrete_type() == Expression::BOOLEAN && v->is_false()) { + return SASS_MEMORY_NEW(String_Quoted, pstate, "false"); + } else if (v->concrete_type() == Expression::STRING) { + return v; + } else { + // ToDo: fix to_sass for nested parentheses + Sass_Output_Style old_style; + old_style = ctx.c_options.output_style; + ctx.c_options.output_style = TO_SASS; + Emitter emitter(ctx.c_options); + Inspect i(emitter); + i.in_declaration = false; + v->perform(&i); + ctx.c_options.output_style = old_style; + return SASS_MEMORY_NEW(String_Quoted, pstate, i.get_buffer()); + } + // return v; + } + Signature selector_nest_sig = "selector-nest($selectors...)"; + BUILT_IN(selector_nest) + { + List_Ptr arglist = ARG("$selectors", List); + + // Not enough parameters + if( arglist->length() == 0 ) + error("$selectors: At least one selector must be passed for `selector-nest'", pstate, traces); + + // Parse args into vector of selectors + std::vector parsedSelectors; + for (size_t i = 0, L = arglist->length(); i < L; ++i) { + Expression_Obj exp = Cast(arglist->value_at_index(i)); + if (exp->concrete_type() == Expression::NULL_VAL) { + std::stringstream msg; + msg << "$selectors: null is not a valid selector: it must be a string,\n"; + msg << "a list of strings, or a list of lists of strings for 'selector-nest'"; + error(msg.str(), pstate, traces); + } + if (String_Constant_Obj str = Cast(exp)) { + str->quote_mark(0); + } + std::string exp_src = exp->to_string(ctx.c_options); + Selector_List_Obj sel = Parser::parse_selector(exp_src.c_str(), ctx, traces); + parsedSelectors.push_back(sel); + } + + // Nothing to do + if( parsedSelectors.empty() ) { + return SASS_MEMORY_NEW(Null, pstate); + } + + // Set the first element as the `result`, keep appending to as we go down the parsedSelector vector. + std::vector::iterator itr = parsedSelectors.begin(); + Selector_List_Obj result = *itr; + ++itr; + + for(;itr != parsedSelectors.end(); ++itr) { + Selector_List_Obj child = *itr; + std::vector exploded; + selector_stack.push_back(result); + Selector_List_Obj rv = child->resolve_parent_refs(selector_stack, traces); + selector_stack.pop_back(); + for (size_t m = 0, mLen = rv->length(); m < mLen; ++m) { + exploded.push_back((*rv)[m]); + } + result->elements(exploded); + } + + Listize listize; + return result->perform(&listize); + } + + Signature selector_append_sig = "selector-append($selectors...)"; + BUILT_IN(selector_append) + { + List_Ptr arglist = ARG("$selectors", List); + + // Not enough parameters + if( arglist->length() == 0 ) + error("$selectors: At least one selector must be passed for `selector-append'", pstate, traces); + + // Parse args into vector of selectors + std::vector parsedSelectors; + for (size_t i = 0, L = arglist->length(); i < L; ++i) { + Expression_Obj exp = Cast(arglist->value_at_index(i)); + if (exp->concrete_type() == Expression::NULL_VAL) { + std::stringstream msg; + msg << "$selectors: null is not a valid selector: it must be a string,\n"; + msg << "a list of strings, or a list of lists of strings for 'selector-append'"; + error(msg.str(), pstate, traces); + } + if (String_Constant_Ptr str = Cast(exp)) { + str->quote_mark(0); + } + std::string exp_src = exp->to_string(); + Selector_List_Obj sel = Parser::parse_selector(exp_src.c_str(), ctx, traces); + parsedSelectors.push_back(sel); + } + + // Nothing to do + if( parsedSelectors.empty() ) { + return SASS_MEMORY_NEW(Null, pstate); + } + + // Set the first element as the `result`, keep appending to as we go down the parsedSelector vector. + std::vector::iterator itr = parsedSelectors.begin(); + Selector_List_Obj result = *itr; + ++itr; + + for(;itr != parsedSelectors.end(); ++itr) { + Selector_List_Obj child = *itr; + std::vector newElements; + + // For every COMPLEX_SELECTOR in `result` + // For every COMPLEX_SELECTOR in `child` + // let parentSeqClone equal a copy of result->elements[i] + // let childSeq equal child->elements[j] + // Append all of childSeq head elements into parentSeqClone + // Set the innermost tail of parentSeqClone, to childSeq's tail + // Replace result->elements with newElements + for (size_t i = 0, resultLen = result->length(); i < resultLen; ++i) { + for (size_t j = 0, childLen = child->length(); j < childLen; ++j) { + Complex_Selector_Obj parentSeqClone = SASS_MEMORY_CLONE((*result)[i]); + Complex_Selector_Obj childSeq = (*child)[j]; + Complex_Selector_Obj base = childSeq->tail(); + + // Must be a simple sequence + if( childSeq->combinator() != Complex_Selector::Combinator::ANCESTOR_OF ) { + std::string msg("Can't append \""); + msg += childSeq->to_string(); + msg += "\" to \""; + msg += parentSeqClone->to_string(); + msg += "\" for `selector-append'"; + error(msg, pstate, traces); + } + + // Cannot be a Universal selector + Element_Selector_Obj pType = Cast(childSeq->head()->first()); + if(pType && pType->name() == "*") { + std::string msg("Can't append \""); + msg += childSeq->to_string(); + msg += "\" to \""; + msg += parentSeqClone->to_string(); + msg += "\" for `selector-append'"; + error(msg, pstate, traces); + } + + // TODO: Add check for namespace stuff + + // append any selectors in childSeq's head + parentSeqClone->innermost()->head()->concat(base->head()); + + // Set parentSeqClone new tail + parentSeqClone->innermost()->tail( base->tail() ); + + newElements.push_back(parentSeqClone); + } + } + + result->elements(newElements); + } + + Listize listize; + return result->perform(&listize); + } + + Signature selector_unify_sig = "selector-unify($selector1, $selector2)"; + BUILT_IN(selector_unify) + { + Selector_List_Obj selector1 = ARGSEL("$selector1", Selector_List_Obj, p_contextualize); + Selector_List_Obj selector2 = ARGSEL("$selector2", Selector_List_Obj, p_contextualize); + + Selector_List_Obj result = selector1->unify_with(selector2); + Listize listize; + return result->perform(&listize); + } + + Signature simple_selectors_sig = "simple-selectors($selector)"; + BUILT_IN(simple_selectors) + { + Compound_Selector_Obj sel = ARGSEL("$selector", Compound_Selector_Obj, p_contextualize); + + List_Ptr l = SASS_MEMORY_NEW(List, sel->pstate(), sel->length(), SASS_COMMA); + + for (size_t i = 0, L = sel->length(); i < L; ++i) { + Simple_Selector_Obj ss = (*sel)[i]; + std::string ss_string = ss->to_string() ; + + l->append(SASS_MEMORY_NEW(String_Quoted, ss->pstate(), ss_string)); + } + + return l; + } + + Signature selector_extend_sig = "selector-extend($selector, $extendee, $extender)"; + BUILT_IN(selector_extend) + { + Selector_List_Obj selector = ARGSEL("$selector", Selector_List_Obj, p_contextualize); + Selector_List_Obj extendee = ARGSEL("$extendee", Selector_List_Obj, p_contextualize); + Selector_List_Obj extender = ARGSEL("$extender", Selector_List_Obj, p_contextualize); + + Subset_Map subset_map; + extender->populate_extends(extendee, subset_map); + Extend extend(subset_map); + + Selector_List_Obj result = extend.extendSelectorList(selector, false); + + Listize listize; + return result->perform(&listize); + } + + Signature selector_replace_sig = "selector-replace($selector, $original, $replacement)"; + BUILT_IN(selector_replace) + { + Selector_List_Obj selector = ARGSEL("$selector", Selector_List_Obj, p_contextualize); + Selector_List_Obj original = ARGSEL("$original", Selector_List_Obj, p_contextualize); + Selector_List_Obj replacement = ARGSEL("$replacement", Selector_List_Obj, p_contextualize); + Subset_Map subset_map; + replacement->populate_extends(original, subset_map); + Extend extend(subset_map); + + Selector_List_Obj result = extend.extendSelectorList(selector, true); + + Listize listize; + return result->perform(&listize); + } + + Signature selector_parse_sig = "selector-parse($selector)"; + BUILT_IN(selector_parse) + { + Selector_List_Obj sel = ARGSEL("$selector", Selector_List_Obj, p_contextualize); + + Listize listize; + return sel->perform(&listize); + } + + Signature is_superselector_sig = "is-superselector($super, $sub)"; + BUILT_IN(is_superselector) + { + Selector_List_Obj sel_sup = ARGSEL("$super", Selector_List_Obj, p_contextualize); + Selector_List_Obj sel_sub = ARGSEL("$sub", Selector_List_Obj, p_contextualize); + bool result = sel_sup->is_superselector_of(sel_sub); + return SASS_MEMORY_NEW(Boolean, pstate, result); + } + + Signature unique_id_sig = "unique-id()"; + BUILT_IN(unique_id) + { + std::stringstream ss; + std::uniform_real_distribution<> distributor(0, 4294967296); // 16^8 + uint_fast32_t distributed = static_cast(distributor(rand)); + ss << "u" << std::setfill('0') << std::setw(8) << std::hex << distributed; + return SASS_MEMORY_NEW(String_Quoted, pstate, ss.str()); + } + + Signature is_bracketed_sig = "is-bracketed($list)"; + BUILT_IN(is_bracketed) + { + Value_Obj value = ARG("$list", Value); + List_Obj list = Cast(value); + return SASS_MEMORY_NEW(Boolean, pstate, list && list->is_bracketed()); + } + + Signature content_exists_sig = "content-exists()"; + BUILT_IN(content_exists) + { + if (!d_env.has_global("is_in_mixin")) { + error("Cannot call content-exists() except within a mixin.", pstate, traces); + } + return SASS_MEMORY_NEW(Boolean, pstate, d_env.has_lexical("@content[m]")); + } + + Signature get_function_sig = "get-function($name, $css: false)"; + BUILT_IN(get_function) + { + String_Constant_Ptr ss = Cast(env["$name"]); + if (!ss) { + error("$name: " + (env["$name"]->to_string()) + " is not a string for `get-function'", pstate, traces); + } + + std::string name = Util::normalize_underscores(unquote(ss->value())); + std::string full_name = name + "[f]"; + + Boolean_Obj css = ARG("$css", Boolean); + if (!css->is_false()) { + Definition_Ptr def = SASS_MEMORY_NEW(Definition, + pstate, + name, + SASS_MEMORY_NEW(Parameters, pstate), + SASS_MEMORY_NEW(Block, pstate, 0, false), + Definition::FUNCTION); + return SASS_MEMORY_NEW(Function, pstate, def, true); + } + + + if (!d_env.has_global(full_name)) { + error("Function not found: " + name, pstate, traces); + } + + Definition_Ptr def = Cast(d_env[full_name]); + return SASS_MEMORY_NEW(Function, pstate, def, false); + } + } +} diff --git a/src/libsass/implementations.md b/src/libsass/implementations.md new file mode 100644 index 000000000..5239adcde --- /dev/null +++ b/src/libsass/implementations.md @@ -0,0 +1,65 @@ +There are several implementations of `libsass` for a variety of languages. Here are just a few of them. Note, some implementations may or may not be up to date. We have not verified whether they work. + +### C +* [sassc](https://github.com/hcatlin/sassc) + +### Crystal +* [sass.cr](https://github.com/straight-shoota/sass.cr) + +### Elixir +* [sass.ex](https://github.com/scottdavis/sass.ex) + +### Go +* [go-libsass](https://github.com/wellington/go-libsass) +* [go_sass](https://github.com/suapapa/go_sass) +* [go-sass](https://github.com/SamWhited/go-sass) + +### Haskell +* [hLibsass](https://github.com/jakubfijalkowski/hlibsass) +* [hSass](https://github.com/jakubfijalkowski/hsass) + +### Java +* [libsass-maven-plugin](https://github.com/warmuuh/libsass-maven-plugin) +* [jsass](https://github.com/bit3/jsass) + +### JavaScript +* [sass.js](https://github.com/medialize/sass.js) + +### Lua +* [lua-sass](https://github.com/craigbarnes/lua-sass) + +### .NET +* [libsass-net](https://github.com/darrenkopp/libsass-net) +* [NSass](https://github.com/TBAPI-0KA/NSass) +* [Sass.Net](https://github.com/andyalm/Sass.Net) +* [SharpScss](https://github.com/xoofx/SharpScss) +* [LibSassHost](https://github.com/Taritsyn/LibSassHost) + +### Nim +* [nim-sass](https://github.com/zacharycarter/nim-sass) + +### node.js +* [node-sass](https://github.com/sass/node-sass) + +### Perl +* [CSS::Sass](https://github.com/caldwell/CSS-Sass) +* [Text::Sass::XS](https://github.com/ysasaki/Text-Sass-XS) + +### PHP +* [sassphp](https://github.com/sensational/sassphp) +* [php-sass](https://github.com/lesstif/php-sass) + +### Python +* [libsass-python](https://github.com/dahlia/libsass-python) +* [SassPython](https://github.com/marianoguerra/SassPython) +* [pylibsass](https://github.com/rsenk330/pylibsass) +* [python-scss](https://github.com/pistolero/python-scss) + +### Ruby +* [sassruby](https://github.com/hcatlin/sassruby) + +### Scala +* [Sass-Scala](https://github.com/kkung/Sass-Scala) + +### Tcl +* [tclsass](https://github.com/flightaware/tclsass) diff --git a/src/libsass/inspect.cpp b/src/libsass/inspect.cpp new file mode 100644 index 000000000..b4a66fab8 --- /dev/null +++ b/src/libsass/inspect.cpp @@ -0,0 +1,1138 @@ +#include "sass.hpp" +#include +#include +#include +#include +#include +#include + +#include "ast.hpp" +#include "inspect.hpp" +#include "context.hpp" +#include "listize.hpp" +#include "color_maps.hpp" +#include "utf8/checked.h" + +namespace Sass { + + Inspect::Inspect(const Emitter& emi) + : Emitter(emi) + { } + Inspect::~Inspect() { } + + // statements + void Inspect::operator()(Block_Ptr 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_Ptr ruleset) + { + if (ruleset->selector()) { + opt.in_selector = true; + ruleset->selector()->perform(this); + opt.in_selector = false; + } + if (ruleset->block()) { + ruleset->block()->perform(this); + } + } + + void Inspect::operator()(Keyframe_Rule_Ptr rule) + { + if (rule->name()) rule->name()->perform(this); + if (rule->block()) rule->block()->perform(this); + } + + void Inspect::operator()(Bubble_Ptr bubble) + { + append_indentation(); + append_token("::BUBBLE", bubble); + append_scope_opener(); + bubble->node()->perform(this); + append_scope_closer(); + } + + void Inspect::operator()(Media_Block_Ptr 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()(Supports_Block_Ptr feature_block) + { + append_indentation(); + append_token("@supports", feature_block); + append_mandatory_space(); + feature_block->condition()->perform(this); + feature_block->block()->perform(this); + } + + void Inspect::operator()(At_Root_Block_Ptr at_root_block) + { + append_indentation(); + append_token("@at-root ", at_root_block); + append_mandatory_space(); + if(at_root_block->expression()) at_root_block->expression()->perform(this); + if(at_root_block->block()) at_root_block->block()->perform(this); + } + + void Inspect::operator()(Directive_Ptr at_rule) + { + append_indentation(); + append_token(at_rule->keyword(), at_rule); + if (at_rule->selector()) { + append_mandatory_space(); + bool was_wrapped = in_wrapped; + in_wrapped = true; + at_rule->selector()->perform(this); + in_wrapped = was_wrapped; + } + if (at_rule->value()) { + append_mandatory_space(); + at_rule->value()->perform(this); + } + if (at_rule->block()) { + at_rule->block()->perform(this); + } + else { + append_delimiter(); + } + } + + void Inspect::operator()(Declaration_Ptr dec) + { + if (dec->value()->concrete_type() == Expression::NULL_VAL) return; + bool was_decl = in_declaration; + in_declaration = true; + LOCAL_FLAG(in_custom_property, dec->is_custom_property()); + + if (output_style() == NESTED) + indentation += dec->tabs(); + append_indentation(); + if (dec->property()) + dec->property()->perform(this); + append_colon_separator(); + + if (dec->value()->concrete_type() == Expression::SELECTOR) { + Listize listize; + Expression_Obj ls = dec->value()->perform(&listize); + ls->perform(this); + } else { + 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 = was_decl; + } + + void Inspect::operator()(Assignment_Ptr 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_Ptr import) + { + if (!import->urls().empty()) { + append_token("@import", import); + append_mandatory_space(); + + import->urls().front()->perform(this); + if (import->urls().size() == 1) { + if (import->import_queries()) { + append_mandatory_space(); + import->import_queries()->perform(this); + } + } + append_delimiter(); + for (size_t i = 1, S = import->urls().size(); i < S; ++i) { + append_mandatory_linefeed(); + append_token("@import", import); + append_mandatory_space(); + + import->urls()[i]->perform(this); + if (import->urls().size() - 1 == i) { + if (import->import_queries()) { + append_mandatory_space(); + import->import_queries()->perform(this); + } + } + append_delimiter(); + } + } + } + + void Inspect::operator()(Import_Stub_Ptr import) + { + append_indentation(); + append_token("@import", import); + append_mandatory_space(); + append_string(import->imp_path()); + append_delimiter(); + } + + void Inspect::operator()(Warning_Ptr warning) + { + append_indentation(); + append_token("@warn", warning); + append_mandatory_space(); + warning->message()->perform(this); + append_delimiter(); + } + + void Inspect::operator()(Error_Ptr error) + { + append_indentation(); + append_token("@error", error); + append_mandatory_space(); + error->message()->perform(this); + append_delimiter(); + } + + void Inspect::operator()(Debug_Ptr debug) + { + append_indentation(); + append_token("@debug", debug); + append_mandatory_space(); + debug->value()->perform(this); + append_delimiter(); + } + + void Inspect::operator()(Comment_Ptr comment) + { + in_comment = true; + comment->text()->perform(this); + in_comment = false; + } + + void Inspect::operator()(If_Ptr cond) + { + append_indentation(); + append_token("@if", cond); + append_mandatory_space(); + cond->predicate()->perform(this); + cond->block()->perform(this); + if (cond->alternative()) { + append_optional_linefeed(); + append_indentation(); + append_string("else"); + cond->alternative()->perform(this); + } + } + + void Inspect::operator()(For_Ptr 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_Ptr 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_Ptr loop) + { + append_indentation(); + append_token("@while", loop); + append_mandatory_space(); + loop->predicate()->perform(this); + loop->block()->perform(this); + } + + void Inspect::operator()(Return_Ptr ret) + { + append_indentation(); + append_token("@return", ret); + append_mandatory_space(); + ret->value()->perform(this); + append_delimiter(); + } + + void Inspect::operator()(Extension_Ptr extend) + { + append_indentation(); + append_token("@extend", extend); + append_mandatory_space(); + extend->selector()->perform(this); + append_delimiter(); + } + + void Inspect::operator()(Definition_Ptr 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_Ptr 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_Ptr content) + { + append_indentation(); + append_token("@content", content); + append_delimiter(); + } + + void Inspect::operator()(Map_Ptr map) + { + if (output_style() == TO_SASS && map->empty()) { + append_string("()"); + return; + } + if (map->empty()) return; + if (map->is_invisible()) return; + bool items_output = false; + append_string("("); + for (auto key : map->keys()) { + if (items_output) append_comma_separator(); + key->perform(this); + append_colon_separator(); + LOCAL_FLAG(in_space_array, true); + LOCAL_FLAG(in_comma_array, true); + map->at(key)->perform(this); + items_output = true; + } + append_string(")"); + } + + std::string Inspect::lbracket(List_Ptr list) { + return list->is_bracketed() ? "[" : "("; + } + + std::string Inspect::rbracket(List_Ptr list) { + return list->is_bracketed() ? "]" : ")"; + } + + void Inspect::operator()(List_Ptr list) + { + if (list->empty() && (output_style() == TO_SASS || list->is_bracketed())) { + append_string(lbracket(list)); + append_string(rbracket(list)); + return; + } + std::string sep(list->separator() == SASS_SPACE ? " " : ","); + if ((output_style() != COMPRESSED) && sep == ",") sep += " "; + else if (in_media_block && sep != " ") sep += " "; // verified + if (list->empty()) return; + bool items_output = false; + + bool was_space_array = in_space_array; + bool was_comma_array = in_comma_array; + // if the list is bracketed, always include the left bracket + if (list->is_bracketed()) { + append_string(lbracket(list)); + } + // probably ruby sass eqivalent of element_needs_parens + else if (output_style() == TO_SASS && + list->length() == 1 && + !list->from_selector() && + !Cast(list->at(0)) && + !Cast(list->at(0)) + ) { + append_string(lbracket(list)); + } + else if (!in_declaration && (list->separator() == SASS_HASH || + (list->separator() == SASS_SPACE && in_space_array) || + (list->separator() == SASS_COMMA && in_comma_array) + )) { + append_string(lbracket(list)); + } + + if (list->separator() == SASS_SPACE) in_space_array = true; + else if (list->separator() == SASS_COMMA) in_comma_array = true; + + for (size_t i = 0, L = list->size(); i < L; ++i) { + if (list->separator() == SASS_HASH) + { sep[0] = i % 2 ? ':' : ','; } + Expression_Obj list_item = list->at(i); + if (output_style() != TO_SASS) { + if (list_item->is_invisible()) { + // this fixes an issue with "" in a list + if (!Cast(list_item)) { + continue; + } + } + } + if (items_output) { + append_string(sep); + } + if (items_output && sep != " ") + append_optional_space(); + list_item->perform(this); + items_output = true; + } + + in_comma_array = was_comma_array; + in_space_array = was_space_array; + + // if the list is bracketed, always include the right bracket + if (list->is_bracketed()) { + if (list->separator() == SASS_COMMA && list->size() == 1) { + append_string(","); + } + append_string(rbracket(list)); + } + // probably ruby sass eqivalent of element_needs_parens + else if (output_style() == TO_SASS && + list->length() == 1 && + !list->from_selector() && + !Cast(list->at(0)) && + !Cast(list->at(0)) + ) { + append_string(","); + append_string(rbracket(list)); + } + else if (!in_declaration && (list->separator() == SASS_HASH || + (list->separator() == SASS_SPACE && in_space_array) || + (list->separator() == SASS_COMMA && in_comma_array) + )) { + append_string(rbracket(list)); + } + + } + + void Inspect::operator()(Binary_Expression_Ptr expr) + { + expr->left()->perform(this); + if ( in_media_block || + (output_style() == INSPECT) || ( + expr->op().ws_before + && (!expr->is_interpolant()) + && (expr->is_left_interpolant() || + expr->is_right_interpolant()) + + )) append_string(" "); + switch (expr->optype()) { + case Sass_OP::AND: append_string("&&"); break; + case Sass_OP::OR: append_string("||"); break; + case Sass_OP::EQ: append_string("=="); break; + case Sass_OP::NEQ: append_string("!="); break; + case Sass_OP::GT: append_string(">"); break; + case Sass_OP::GTE: append_string(">="); break; + case Sass_OP::LT: append_string("<"); break; + case Sass_OP::LTE: append_string("<="); break; + case Sass_OP::ADD: append_string("+"); break; + case Sass_OP::SUB: append_string("-"); break; + case Sass_OP::MUL: append_string("*"); break; + case Sass_OP::DIV: append_string("/"); break; + case Sass_OP::MOD: append_string("%"); break; + default: break; // shouldn't get here + } + if ( in_media_block || + (output_style() == INSPECT) || ( + expr->op().ws_after + && (!expr->is_interpolant()) + && (expr->is_left_interpolant() || + expr->is_right_interpolant()) + )) append_string(" "); + expr->right()->perform(this); + } + + void Inspect::operator()(Unary_Expression_Ptr expr) + { + if (expr->optype() == Unary_Expression::PLUS) append_string("+"); + else if (expr->optype() == Unary_Expression::SLASH) append_string("/"); + else append_string("-"); + expr->operand()->perform(this); + } + + void Inspect::operator()(Function_Call_Ptr call) + { + append_token(call->name(), call); + call->arguments()->perform(this); + } + + void Inspect::operator()(Function_Call_Schema_Ptr call) + { + call->name()->perform(this); + call->arguments()->perform(this); + } + + void Inspect::operator()(Variable_Ptr var) + { + append_token(var->name(), var); + } + + void Inspect::operator()(Number_Ptr n) + { + + std::string res; + + // reduce units + n->reduce(); + + // check if the fractional part of the value equals to zero + // neat trick from http://stackoverflow.com/a/1521682/1550314 + // double int_part; bool is_int = modf(value, &int_part) == 0.0; + + // this all cannot be done with one run only, since fixed + // output differs from normal output and regular output + // can contain scientific notation which we do not want! + + // first sample + std::stringstream ss; + ss.precision(12); + ss << n->value(); + + // check if we got scientific notation in result + if (ss.str().find_first_of("e") != std::string::npos) { + ss.clear(); ss.str(std::string()); + ss.precision(std::max(12, opt.precision)); + ss << std::fixed << n->value(); + } + + std::string tmp = ss.str(); + size_t pos_point = tmp.find_first_of(".,"); + size_t pos_fract = tmp.find_last_not_of("0"); + bool is_int = pos_point == pos_fract || + pos_point == std::string::npos; + + // reset stream for another run + ss.clear(); ss.str(std::string()); + + // take a shortcut for integers + if (is_int) + { + ss.precision(0); + ss << std::fixed << n->value(); + res = std::string(ss.str()); + } + // process floats + else + { + // do we have have too much precision? + if (pos_fract < opt.precision + pos_point) + { ss.precision((int)(pos_fract - pos_point)); } + else { ss.precision(opt.precision); } + // round value again + ss << std::fixed << n->value(); + res = std::string(ss.str()); + // maybe we truncated up to decimal point + size_t pos = res.find_last_not_of("0"); + // handle case where we have a "0" + if (pos == std::string::npos) { + res = "0.0"; + } else { + bool at_dec_point = res[pos] == '.' || + res[pos] == ','; + // don't leave a blank point + if (at_dec_point) ++ pos; + res.resize (pos + 1); + } + } + + // some final cosmetics + if (res == "0.0") res = "0"; + else if (res == "") res = "0"; + else if (res == "-0") res = "0"; + else if (res == "-0.0") res = "0"; + else if (opt.output_style == COMPRESSED) + { + // check if handling negative nr + size_t off = res[0] == '-' ? 1 : 0; + // remove leading zero from floating point in compressed mode + if (n->zero() && res[off] == '0' && res[off+1] == '.') res.erase(off, 1); + } + + // add unit now + res += n->unit(); + + // output the final token + append_token(res, n); + } + + // helper function for serializing colors + template + static double cap_channel(double c) { + if (c > range) return range; + else if (c < 0) return 0; + else return c; + } + + void Inspect::operator()(Color_Ptr c) + { + // output the final token + std::stringstream ss; + + // original color name + // maybe an unknown token + std::string name = c->disp(); + + if (opt.in_selector && name != "") { + append_token(name, c); + return; + } + + // resolved color + std::string res_name = name; + + double r = Sass::round(cap_channel<0xff>(c->r()), opt.precision); + double g = Sass::round(cap_channel<0xff>(c->g()), opt.precision); + double b = Sass::round(cap_channel<0xff>(c->b()), opt.precision); + double a = cap_channel<1> (c->a()); + + // get color from given name (if one was given at all) + if (name != "" && name_to_color(name)) { + Color_Ptr_Const n = name_to_color(name); + r = Sass::round(cap_channel<0xff>(n->r()), opt.precision); + g = Sass::round(cap_channel<0xff>(n->g()), opt.precision); + b = Sass::round(cap_channel<0xff>(n->b()), opt.precision); + a = cap_channel<1> (n->a()); + } + // otherwise get the possible resolved color name + else { + double numval = r * 0x10000 + g * 0x100 + b; + if (color_to_name(numval)) + res_name = color_to_name(numval); + } + + std::stringstream hexlet; + // dart sass compressed all colors in regular css always + // ruby sass and libsass does it only when not delayed + // since color math is going to be removed, this can go too + bool compressed = opt.output_style == COMPRESSED; + hexlet << '#' << std::setw(1) << std::setfill('0'); + // create a short color hexlet if there is any need for it + if (compressed && is_color_doublet(r, g, b) && a == 1) { + hexlet << std::hex << std::setw(1) << (static_cast(r) >> 4); + hexlet << std::hex << std::setw(1) << (static_cast(g) >> 4); + hexlet << std::hex << std::setw(1) << (static_cast(b) >> 4); + } else { + hexlet << std::hex << std::setw(2) << static_cast(r); + hexlet << std::hex << std::setw(2) << static_cast(g); + hexlet << std::hex << std::setw(2) << static_cast(b); + } + + if (compressed && !c->is_delayed()) name = ""; + if (opt.output_style == INSPECT && a >= 1) { + append_token(hexlet.str(), c); + return; + } + + // retain the originally specified color definition if unchanged + if (name != "") { + ss << name; + } + else if (a >= 1) { + if (res_name != "") { + if (compressed && hexlet.str().size() < res_name.size()) { + ss << hexlet.str(); + } else { + ss << res_name; + } + } + else { + ss << hexlet.str(); + } + } + else { + ss << "rgba("; + ss << static_cast(r) << ","; + if (!compressed) ss << " "; + ss << static_cast(g) << ","; + if (!compressed) ss << " "; + ss << static_cast(b) << ","; + if (!compressed) ss << " "; + ss << a << ')'; + } + + append_token(ss.str(), c); + + } + + void Inspect::operator()(Boolean_Ptr b) + { + // output the final token + append_token(b->value() ? "true" : "false", b); + } + + void Inspect::operator()(String_Schema_Ptr 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_Ptr s) + { + append_token(s->value(), s); + } + + void Inspect::operator()(String_Quoted_Ptr s) + { + if (const char q = s->quote_mark()) { + append_token(quote(s->value(), q), s); + } else { + append_token(s->value(), s); + } + } + + void Inspect::operator()(Custom_Error_Ptr e) + { + append_token(e->message(), e); + } + + void Inspect::operator()(Custom_Warning_Ptr w) + { + append_token(w->message(), w); + } + + void Inspect::operator()(Supports_Operator_Ptr so) + { + + if (so->needs_parens(so->left())) append_string("("); + so->left()->perform(this); + if (so->needs_parens(so->left())) append_string(")"); + + if (so->operand() == Supports_Operator::AND) { + append_mandatory_space(); + append_token("and", so); + append_mandatory_space(); + } else if (so->operand() == Supports_Operator::OR) { + append_mandatory_space(); + append_token("or", so); + append_mandatory_space(); + } + + if (so->needs_parens(so->right())) append_string("("); + so->right()->perform(this); + if (so->needs_parens(so->right())) append_string(")"); + } + + void Inspect::operator()(Supports_Negation_Ptr sn) + { + append_token("not", sn); + append_mandatory_space(); + if (sn->needs_parens(sn->condition())) append_string("("); + sn->condition()->perform(this); + if (sn->needs_parens(sn->condition())) append_string(")"); + } + + void Inspect::operator()(Supports_Declaration_Ptr sd) + { + append_string("("); + sd->feature()->perform(this); + append_string(": "); + sd->value()->perform(this); + append_string(")"); + } + + void Inspect::operator()(Supports_Interpolation_Ptr sd) + { + sd->value()->perform(this); + } + + void Inspect::operator()(Media_Query_Ptr 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_Ptr 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_Query_Ptr ae) + { + if (ae->feature()) { + append_string("("); + ae->feature()->perform(this); + if (ae->value()) { + append_colon_separator(); + ae->value()->perform(this); + } + append_string(")"); + } + } + + void Inspect::operator()(Function_Ptr f) + { + append_token("get-function", f); + append_string("("); + append_string(quote(f->name())); + append_string(")"); + } + + void Inspect::operator()(Null_Ptr n) + { + // output the final token + append_token("null", n); + } + + // parameters and arguments + void Inspect::operator()(Parameter_Ptr 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_Ptr 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_Ptr a) + { + if (!a->name().empty()) { + append_token(a->name(), a); + append_colon_separator(); + } + if (!a->value()) return; + // Special case: argument nulls can be ignored + if (a->value()->concrete_type() == Expression::NULL_VAL) { + return; + } + if (a->value()->concrete_type() == Expression::STRING) { + String_Constant_Ptr s = Cast(a->value()); + if (s) s->perform(this); + } else { + a->value()->perform(this); + } + if (a->is_rest_argument()) { + append_string("..."); + } + } + + void Inspect::operator()(Arguments_Ptr 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_Ptr s) + { + opt.in_selector = true; + s->contents()->perform(this); + opt.in_selector = false; + } + + void Inspect::operator()(Parent_Selector_Ptr p) + { + if (p->is_real_parent_ref()) append_string("&"); + } + + void Inspect::operator()(Placeholder_Selector_Ptr s) + { + append_token(s->name(), s); + if (s->has_line_break()) append_optional_linefeed(); + if (s->has_line_break()) append_indentation(); + + } + + void Inspect::operator()(Element_Selector_Ptr s) + { + append_token(s->ns_name(), s); + } + + void Inspect::operator()(Class_Selector_Ptr s) + { + append_token(s->ns_name(), s); + if (s->has_line_break()) append_optional_linefeed(); + if (s->has_line_break()) append_indentation(); + } + + void Inspect::operator()(Id_Selector_Ptr s) + { + append_token(s->ns_name(), s); + if (s->has_line_break()) append_optional_linefeed(); + if (s->has_line_break()) append_indentation(); + } + + void Inspect::operator()(Attribute_Selector_Ptr s) + { + append_string("["); + add_open_mapping(s); + append_token(s->ns_name(), s); + if (!s->matcher().empty()) { + append_string(s->matcher()); + if (s->value() && *s->value()) { + s->value()->perform(this); + } + } + add_close_mapping(s); + if (s->modifier() != 0) { + append_mandatory_space(); + append_char(s->modifier()); + } + append_string("]"); + } + + void Inspect::operator()(Pseudo_Selector_Ptr s) + { + append_token(s->ns_name(), s); + if (s->expression()) { + append_string("("); + s->expression()->perform(this); + append_string(")"); + } + } + + void Inspect::operator()(Wrapped_Selector_Ptr s) + { + if (s->name() == " ") { + append_string(""); + } else { + bool was = in_wrapped; + in_wrapped = true; + append_token(s->name(), s); + append_string("("); + bool was_comma_array = in_comma_array; + in_comma_array = false; + s->selector()->perform(this); + in_comma_array = was_comma_array; + append_string(")"); + in_wrapped = was; + } + } + + void Inspect::operator()(Compound_Selector_Ptr s) + { + for (size_t i = 0, L = s->length(); i < L; ++i) { + (*s)[i]->perform(this); + } + if (s->has_line_break()) { + if (output_style() != COMPACT) { + append_optional_linefeed(); + } + } + } + + void Inspect::operator()(Complex_Selector_Ptr c) + { + Compound_Selector_Obj head = c->head(); + Complex_Selector_Obj tail = c->tail(); + Complex_Selector::Combinator comb = c->combinator(); + + if (comb == Complex_Selector::ANCESTOR_OF && (!head || head->empty())) { + if (tail) tail->perform(this); + return; + } + + if (c->has_line_feed()) { + if (!(c->has_parent_ref())) { + append_optional_linefeed(); + append_indentation(); + } + } + + if (head && head->length() != 0) head->perform(this); + bool is_empty = !head || head->length() == 0 || head->is_empty_reference(); + bool is_tail = head && !head->is_empty_reference() && tail; + if (output_style() == COMPRESSED && comb != Complex_Selector::ANCESTOR_OF) scheduled_space = 0; + + switch (comb) { + case Complex_Selector::ANCESTOR_OF: + if (is_tail) append_mandatory_space(); + break; + case Complex_Selector::PARENT_OF: + append_optional_space(); + append_string(">"); + append_optional_space(); + break; + case Complex_Selector::ADJACENT_TO: + append_optional_space(); + append_string("+"); + append_optional_space(); + break; + case Complex_Selector::REFERENCE: + append_mandatory_space(); + append_string("/"); + c->reference()->perform(this); + append_string("/"); + append_mandatory_space(); + break; + case Complex_Selector::PRECEDES: + if (is_empty) append_optional_space(); + else append_mandatory_space(); + append_string("~"); + if (tail) append_mandatory_space(); + else append_optional_space(); + break; + default: break; + } + if (tail && comb != Complex_Selector::ANCESTOR_OF) { + if (c->has_line_break()) append_optional_linefeed(); + } + if (tail) tail->perform(this); + if (!tail && c->has_line_break()) { + if (output_style() == COMPACT) { + append_mandatory_space(); + } + } + } + + void Inspect::operator()(Selector_List_Ptr g) + { + + if (g->empty()) { + if (output_style() == TO_SASS) { + append_token("()", g); + } + return; + } + + + bool was_comma_array = in_comma_array; + // probably ruby sass eqivalent of element_needs_parens + if (output_style() == TO_SASS && g->length() == 1 && + (!Cast((*g)[0]) && + !Cast((*g)[0]))) { + append_string("("); + } + else if (!in_declaration && in_comma_array) { + append_string("("); + } + + if (in_declaration) in_comma_array = true; + + for (size_t i = 0, L = g->length(); i < L; ++i) { + if (!in_wrapped && i == 0) append_indentation(); + if ((*g)[i] == 0) continue; + schedule_mapping(g->at(i)->last()); + // add_open_mapping((*g)[i]->last()); + (*g)[i]->perform(this); + // add_close_mapping((*g)[i]->last()); + if (i < L - 1) { + scheduled_space = 0; + append_comma_separator(); + } + } + + in_comma_array = was_comma_array; + // probably ruby sass eqivalent of element_needs_parens + if (output_style() == TO_SASS && g->length() == 1 && + (!Cast((*g)[0]) && + !Cast((*g)[0]))) { + append_string(",)"); + } + else if (!in_declaration && in_comma_array) { + append_string(")"); + } + + } + + void Inspect::fallback_impl(AST_Node_Ptr n) + { + } + +} diff --git a/src/libsass/operators.cpp b/src/libsass/operators.cpp new file mode 100644 index 000000000..02e303738 --- /dev/null +++ b/src/libsass/operators.cpp @@ -0,0 +1,240 @@ +#include "sass.hpp" +#include "operators.hpp" + +namespace Sass { + + namespace Operators { + + inline double add(double x, double y) { return x + y; } + inline double sub(double x, double y) { return x - y; } + inline double mul(double x, double y) { return x * y; } + inline double div(double x, double y) { return x / y; } // x/0 checked by caller + + inline double mod(double x, double y) { // x/0 checked by caller + if ((x > 0 && y < 0) || (x < 0 && y > 0)) { + double ret = std::fmod(x, y); + return ret ? ret + y : ret; + } else { + return std::fmod(x, y); + } + } + + typedef double (*bop)(double, double); + bop ops[Sass_OP::NUM_OPS] = { + 0, 0, // and, or + 0, 0, 0, 0, 0, 0, // eq, neq, gt, gte, lt, lte + add, sub, mul, div, mod + }; + + /* static function, has no pstate or traces */ + bool eq(Expression_Obj lhs, Expression_Obj rhs) + { + // operation is undefined if one is not a number + if (!lhs || !rhs) throw Exception::UndefinedOperation(lhs, rhs, Sass_OP::EQ); + // use compare operator from ast node + return *lhs == *rhs; + } + + /* static function, throws OperationError, has no pstate or traces */ + bool cmp(Expression_Obj lhs, Expression_Obj rhs, const Sass_OP op) + { + // can only compare numbers!? + Number_Obj l = Cast(lhs); + Number_Obj r = Cast(rhs); + // operation is undefined if one is not a number + if (!l || !r) throw Exception::UndefinedOperation(lhs, rhs, op); + // use compare operator from ast node + return *l < *r; + } + + /* static functions, throws OperationError, has no pstate or traces */ + bool lt(Expression_Obj lhs, Expression_Obj rhs) { return cmp(lhs, rhs, Sass_OP::LT); } + bool neq(Expression_Obj lhs, Expression_Obj rhs) { return eq(lhs, rhs) == false; } + bool gt(Expression_Obj lhs, Expression_Obj rhs) { return !cmp(lhs, rhs, Sass_OP::GT) && neq(lhs, rhs); } + bool lte(Expression_Obj lhs, Expression_Obj rhs) { return cmp(lhs, rhs, Sass_OP::LTE) || eq(lhs, rhs); } + bool gte(Expression_Obj lhs, Expression_Obj rhs) { return !cmp(lhs, rhs, Sass_OP::GTE) || eq(lhs, rhs); } + + /* static function, throws OperationError, has no traces but optional pstate for returned value */ + Value_Ptr op_strings(Sass::Operand operand, Value& lhs, Value& rhs, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed) + { + enum Sass_OP op = operand.operand; + + String_Quoted_Ptr lqstr = Cast(&lhs); + String_Quoted_Ptr rqstr = Cast(&rhs); + + std::string lstr(lqstr ? lqstr->value() : lhs.to_string(opt)); + std::string rstr(rqstr ? rqstr->value() : rhs.to_string(opt)); + + if (Cast(&lhs)) throw Exception::InvalidNullOperation(&lhs, &rhs, op); + if (Cast(&rhs)) throw Exception::InvalidNullOperation(&lhs, &rhs, op); + + std::string sep; + switch (op) { + case Sass_OP::ADD: sep = ""; break; + case Sass_OP::SUB: sep = "-"; break; + case Sass_OP::DIV: sep = "/"; break; + case Sass_OP::EQ: sep = "=="; break; + case Sass_OP::NEQ: sep = "!="; break; + case Sass_OP::LT: sep = "<"; break; + case Sass_OP::GT: sep = ">"; break; + case Sass_OP::LTE: sep = "<="; break; + case Sass_OP::GTE: sep = ">="; break; + default: + throw Exception::UndefinedOperation(&lhs, &rhs, op); + break; + } + + if (op == Sass_OP::ADD) { + // create string that might be quoted on output (but do not unquote what we pass) + return SASS_MEMORY_NEW(String_Quoted, pstate, lstr + rstr, 0, false, true); + } + + // add whitespace around operator + // but only if result is not delayed + if (sep != "" && delayed == false) { + if (operand.ws_before) sep = " " + sep; + if (operand.ws_after) sep = sep + " "; + } + + if (op == Sass_OP::SUB || op == Sass_OP::DIV) { + if (lqstr && lqstr->quote_mark()) lstr = quote(lstr); + if (rqstr && rqstr->quote_mark()) rstr = quote(rstr); + } + + return SASS_MEMORY_NEW(String_Constant, pstate, lstr + sep + rstr); + } + + /* static function, throws OperationError, has no traces but optional pstate for returned value */ + Value_Ptr op_colors(enum Sass_OP op, const Color& lhs, const Color& rhs, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed) + { + if (lhs.a() != rhs.a()) { + throw Exception::AlphaChannelsNotEqual(&lhs, &rhs, op); + } + if (op == Sass_OP::DIV && (!rhs.r() || !rhs.g() || !rhs.b())) { + throw Exception::ZeroDivisionError(lhs, rhs); + } + return SASS_MEMORY_NEW(Color, + pstate, + ops[op](lhs.r(), rhs.r()), + ops[op](lhs.g(), rhs.g()), + ops[op](lhs.b(), rhs.b()), + lhs.a()); + } + + /* static function, throws OperationError, has no traces but optional pstate for returned value */ + Value_Ptr op_numbers(enum Sass_OP op, const Number& lhs, const Number& rhs, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed) + { + double lval = lhs.value(); + double rval = rhs.value(); + + if (op == Sass_OP::MOD && rval == 0) { + return SASS_MEMORY_NEW(String_Quoted, pstate, "NaN"); + } + + if (op == Sass_OP::DIV && rval == 0) { + std::string result(lval ? "Infinity" : "NaN"); + return SASS_MEMORY_NEW(String_Quoted, pstate, result); + } + + size_t l_n_units = lhs.numerators.size(); + size_t l_d_units = lhs.numerators.size(); + size_t r_n_units = rhs.denominators.size(); + size_t r_d_units = rhs.denominators.size(); + // optimize out the most common and simplest case + if (l_n_units == r_n_units && l_d_units == r_d_units) { + if (l_n_units + l_d_units <= 1 && r_n_units + r_d_units <= 1) { + if (lhs.numerators == rhs.numerators) { + if (lhs.denominators == rhs.denominators) { + Number_Ptr v = SASS_MEMORY_COPY(&lhs); + v->value(ops[op](lval, rval)); + return v; + } + } + } + } + + Number_Obj v = SASS_MEMORY_COPY(&lhs); + + if (lhs.is_unitless() && (op == Sass_OP::ADD || op == Sass_OP::SUB || op == Sass_OP::MOD)) { + v->numerators = rhs.numerators; + v->denominators = rhs.denominators; + } + + if (op == Sass_OP::MUL) { + v->value(ops[op](lval, rval)); + v->numerators.insert(v->numerators.end(), + rhs.numerators.begin(), rhs.numerators.end() + ); + v->denominators.insert(v->denominators.end(), + rhs.denominators.begin(), rhs.denominators.end() + ); + v->reduce(); + } + else if (op == Sass_OP::DIV) { + v->value(ops[op](lval, rval)); + v->numerators.insert(v->numerators.end(), + rhs.denominators.begin(), rhs.denominators.end() + ); + v->denominators.insert(v->denominators.end(), + rhs.numerators.begin(), rhs.numerators.end() + ); + v->reduce(); + } + else { + Number ln(lhs), rn(rhs); + ln.reduce(); rn.reduce(); + double f(rn.convert_factor(ln)); + v->value(ops[op](lval, rn.value() * f)); + } + + v->pstate(pstate); + return v.detach(); + } + + /* static function, throws OperationError, has no traces but optional pstate for returned value */ + Value_Ptr op_number_color(enum Sass_OP op, const Number& lhs, const Color& rhs, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed) + { + double lval = lhs.value(); + switch (op) { + case Sass_OP::ADD: + case Sass_OP::MUL: { + return SASS_MEMORY_NEW(Color, + pstate, + ops[op](lval, rhs.r()), + ops[op](lval, rhs.g()), + ops[op](lval, rhs.b()), + rhs.a()); + } + case Sass_OP::SUB: + case Sass_OP::DIV: { + std::string color(rhs.to_string(opt)); + return SASS_MEMORY_NEW(String_Quoted, + pstate, + lhs.to_string(opt) + + sass_op_separator(op) + + color); + } + default: break; + } + throw Exception::UndefinedOperation(&lhs, &rhs, op); + } + + /* static function, throws OperationError, has no traces but optional pstate for returned value */ + Value_Ptr op_color_number(enum Sass_OP op, const Color& lhs, const Number& rhs, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed) + { + double rval = rhs.value(); + if (op == Sass_OP::DIV && rval == 0) { + // comparison of Fixnum with Float failed? + throw Exception::ZeroDivisionError(lhs, rhs); + } + return SASS_MEMORY_NEW(Color, + pstate, + ops[op](lhs.r(), rval), + ops[op](lhs.g(), rval), + ops[op](lhs.b(), rval), + lhs.a()); + } + + } + +} diff --git a/src/libsass/parser.cpp b/src/libsass/parser.cpp new file mode 100644 index 000000000..ee51d56b6 --- /dev/null +++ b/src/libsass/parser.cpp @@ -0,0 +1,3137 @@ +#include "sass.hpp" +#include "parser.hpp" +#include "file.hpp" +#include "inspect.hpp" +#include "constants.hpp" +#include "util.hpp" +#include "prelexer.hpp" +#include "color_maps.hpp" +#include "sass/functions.h" +#include "error_handling.hpp" + +// Notes about delayed: some ast nodes can have delayed evaluation so +// they can preserve their original semantics if needed. This is most +// prominently exhibited by the division operation, since it is not +// only a valid operation, but also a valid css statement (i.e. for +// fonts, as in `16px/24px`). When parsing lists and expression we +// unwrap single items from lists and other operations. A nested list +// must not be delayed, only the items of the first level sometimes +// are delayed (as with argument lists). To achieve this we need to +// pass status to the list parser, so this can be set correctly. +// Another case with delayed values are colors. In compressed mode +// only processed values get compressed (other are left as written). + +#include +#include +#include +#include + +namespace Sass { + using namespace Constants; + using namespace Prelexer; + + Parser Parser::from_c_str(const char* beg, Context& ctx, Backtraces traces, ParserState pstate, const char* source) + { + pstate.offset.column = 0; + pstate.offset.line = 0; + Parser p(ctx, pstate, traces); + p.source = source ? source : beg; + p.position = beg ? beg : p.source; + p.end = p.position + strlen(p.position); + Block_Obj root = SASS_MEMORY_NEW(Block, pstate); + p.block_stack.push_back(root); + root->is_root(true); + return p; + } + + Parser Parser::from_c_str(const char* beg, const char* end, Context& ctx, Backtraces traces, ParserState pstate, const char* source) + { + pstate.offset.column = 0; + pstate.offset.line = 0; + Parser p(ctx, pstate, traces); + p.source = source ? source : beg; + p.position = beg ? beg : p.source; + p.end = end ? end : p.position + strlen(p.position); + Block_Obj root = SASS_MEMORY_NEW(Block, pstate); + p.block_stack.push_back(root); + root->is_root(true); + return p; + } + + void Parser::advanceToNextToken() { + lex < css_comments >(false); + // advance to position + pstate += pstate.offset; + pstate.offset.column = 0; + pstate.offset.line = 0; + } + + Selector_List_Obj Parser::parse_selector(const char* beg, Context& ctx, Backtraces traces, ParserState pstate, const char* source) + { + Parser p = Parser::from_c_str(beg, ctx, traces, pstate, source); + // ToDo: ruby sass errors on parent references + // ToDo: remap the source-map entries somehow + return p.parse_selector_list(false); + } + + bool Parser::peek_newline(const char* start) + { + return peek_linefeed(start ? start : position) + && ! peek_css>(start); + } + + Parser Parser::from_token(Token t, Context& ctx, Backtraces traces, ParserState pstate, const char* source) + { + Parser p(ctx, pstate, traces); + p.source = source ? source : t.begin; + p.position = t.begin ? t.begin : p.source; + p.end = t.end ? t.end : p.position + strlen(p.position); + Block_Obj root = SASS_MEMORY_NEW(Block, pstate); + p.block_stack.push_back(root); + root->is_root(true); + return p; + } + + /* main entry point to parse root block */ + Block_Obj Parser::parse() + { + + // consume unicode BOM + read_bom(); + + // scan the input to find invalid utf8 sequences + const char* it = utf8::find_invalid(position, end); + + // report invalid utf8 + if (it != end) { + pstate += Offset::init(position, it); + traces.push_back(Backtrace(pstate)); + throw Exception::InvalidSass(pstate, traces, "Invalid UTF-8 sequence"); + } + + // create a block AST node to hold children + Block_Obj root = SASS_MEMORY_NEW(Block, pstate, 0, true); + + // check seems a bit esoteric but works + if (ctx.resources.size() == 1) { + // apply headers only on very first include + ctx.apply_custom_headers(root, path, pstate); + } + + // parse children nodes + block_stack.push_back(root); + parse_block_nodes(true); + block_stack.pop_back(); + + // update final position + root->update_pstate(pstate); + + if (position != end) { + css_error("Invalid CSS", " after ", ": expected selector or at-rule, was "); + } + + return root; + } + + + // convenience function for block parsing + // will create a new block ad-hoc for you + // this is the base block parsing function + Block_Obj Parser::parse_css_block(bool is_root) + { + + // parse comments before block + // lex < optional_css_comments >(); + + // lex mandatory opener or error out + if (!lex_css < exactly<'{'> >()) { + css_error("Invalid CSS", " after ", ": expected \"{\", was "); + } + // create new block and push to the selector stack + Block_Obj block = SASS_MEMORY_NEW(Block, pstate, 0, is_root); + block_stack.push_back(block); + + if (!parse_block_nodes(is_root)) css_error("Invalid CSS", " after ", ": expected \"}\", was "); + + if (!lex_css < exactly<'}'> >()) { + css_error("Invalid CSS", " after ", ": expected \"}\", was "); + } + + // update for end position + // this seems to be done somewhere else + // but that fixed selector schema issue + // block->update_pstate(pstate); + + // parse comments after block + // lex < optional_css_comments >(); + + block_stack.pop_back(); + + return block; + } + + // convenience function for block parsing + // will create a new block ad-hoc for you + // also updates the `in_at_root` flag + Block_Obj Parser::parse_block(bool is_root) + { + return parse_css_block(is_root); + } + + // the main block parsing function + // parses stuff between `{` and `}` + bool Parser::parse_block_nodes(bool is_root) + { + + // loop until end of string + while (position < end) { + + // we should be able to refactor this + parse_block_comments(); + lex < css_whitespace >(); + + if (lex < exactly<';'> >()) continue; + if (peek < end_of_file >()) return true; + if (peek < exactly<'}'> >()) return true; + + if (parse_block_node(is_root)) continue; + + parse_block_comments(); + + if (lex_css < exactly<';'> >()) continue; + if (peek_css < end_of_file >()) return true; + if (peek_css < exactly<'}'> >()) return true; + + // illegal sass + return false; + } + // return success + return true; + } + + // parser for a single node in a block + // semicolons must be lexed beforehand + bool Parser::parse_block_node(bool is_root) { + + Block_Obj block = block_stack.back(); + + parse_block_comments(); + + // throw away white-space + // includes line comments + lex < css_whitespace >(); + + Lookahead lookahead_result; + + // also parse block comments + + // first parse everything that is allowed in functions + if (lex < variable >(true)) { block->append(parse_assignment()); } + else if (lex < kwd_err >(true)) { block->append(parse_error()); } + else if (lex < kwd_dbg >(true)) { block->append(parse_debug()); } + else if (lex < kwd_warn >(true)) { block->append(parse_warning()); } + else if (lex < kwd_if_directive >(true)) { block->append(parse_if_directive()); } + else if (lex < kwd_for_directive >(true)) { block->append(parse_for_directive()); } + else if (lex < kwd_each_directive >(true)) { block->append(parse_each_directive()); } + else if (lex < kwd_while_directive >(true)) { block->append(parse_while_directive()); } + else if (lex < kwd_return_directive >(true)) { block->append(parse_return_directive()); } + + // parse imports to process later + else if (lex < kwd_import >(true)) { + Scope parent = stack.empty() ? Scope::Rules : stack.back(); + if (parent != Scope::Function && parent != Scope::Root && parent != Scope::Rules && parent != Scope::Media) { + if (! peek_css< uri_prefix >(position)) { // this seems to go in ruby sass 3.4.20 + error("Import directives may not be used within control directives or mixins."); + } + } + // this puts the parsed doc into sheets + // import stub will fetch this in expand + Import_Obj imp = parse_import(); + // if it is a url, we only add the statement + if (!imp->urls().empty()) block->append(imp); + // process all resources now (add Import_Stub nodes) + for (size_t i = 0, S = imp->incs().size(); i < S; ++i) { + block->append(SASS_MEMORY_NEW(Import_Stub, pstate, imp->incs()[i])); + } + } + + else if (lex < kwd_extend >(true)) { + Lookahead lookahead = lookahead_for_include(position); + if (!lookahead.found) css_error("Invalid CSS", " after ", ": expected selector, was "); + Selector_List_Obj target; + if (!lookahead.has_interpolants) { + target = parse_selector_list(true); + } + else { + target = SASS_MEMORY_NEW(Selector_List, pstate); + target->schema(parse_selector_schema(lookahead.found, true)); + } + + block->append(SASS_MEMORY_NEW(Extension, pstate, target)); + } + + // selector may contain interpolations which need delayed evaluation + else if ( + !(lookahead_result = lookahead_for_selector(position)).error && + !lookahead_result.is_custom_property + ) + { + block->append(parse_ruleset(lookahead_result)); + } + + // parse multiple specific keyword directives + else if (lex < kwd_media >(true)) { block->append(parse_media_block()); } + else if (lex < kwd_at_root >(true)) { block->append(parse_at_root_block()); } + else if (lex < kwd_include_directive >(true)) { block->append(parse_include_directive()); } + else if (lex < kwd_content_directive >(true)) { block->append(parse_content_directive()); } + else if (lex < kwd_supports_directive >(true)) { block->append(parse_supports_directive()); } + else if (lex < kwd_mixin >(true)) { block->append(parse_definition(Definition::MIXIN)); } + else if (lex < kwd_function >(true)) { block->append(parse_definition(Definition::FUNCTION)); } + + // ignore the @charset directive for now + else if (lex< kwd_charset_directive >(true)) { parse_charset_directive(); } + + // generic at keyword (keep last) + else if (lex< re_special_directive >(true)) { block->append(parse_special_directive()); } + else if (lex< re_prefixed_directive >(true)) { block->append(parse_prefixed_directive()); } + else if (lex< at_keyword >(true)) { block->append(parse_directive()); } + + else if (is_root && stack.back() != Scope::AtRoot /* && block->is_root() */) { + lex< css_whitespace >(); + if (position >= end) return true; + css_error("Invalid CSS", " after ", ": expected 1 selector or at-rule, was "); + } + // parse a declaration + else + { + // ToDo: how does it handle parse errors? + // maybe we are expected to parse something? + Declaration_Obj decl = parse_declaration(); + decl->tabs(indentation); + block->append(decl); + // maybe we have a "sub-block" + if (peek< exactly<'{'> >()) { + if (decl->is_indented()) ++ indentation; + // parse a propset that rides on the declaration's property + stack.push_back(Scope::Properties); + decl->block(parse_block()); + stack.pop_back(); + if (decl->is_indented()) -- indentation; + } + } + // something matched + return true; + } + // EO parse_block_nodes + + // parse imports inside the + Import_Obj Parser::parse_import() + { + Import_Obj imp = SASS_MEMORY_NEW(Import, pstate); + std::vector> to_import; + bool first = true; + do { + while (lex< block_comment >()); + if (lex< quoted_string >()) { + to_import.push_back(std::pair(std::string(lexed), 0)); + } + else if (lex< uri_prefix >()) { + Arguments_Obj args = SASS_MEMORY_NEW(Arguments, pstate); + Function_Call_Obj result = SASS_MEMORY_NEW(Function_Call, pstate, "url", args); + + if (lex< quoted_string >()) { + Expression_Obj quoted_url = parse_string(); + args->append(SASS_MEMORY_NEW(Argument, quoted_url->pstate(), quoted_url)); + } + else if (String_Obj string_url = parse_url_function_argument()) { + args->append(SASS_MEMORY_NEW(Argument, string_url->pstate(), string_url)); + } + else if (peek < skip_over_scopes < exactly < '(' >, exactly < ')' > > >(position)) { + Expression_Obj braced_url = parse_list(); // parse_interpolated_chunk(lexed); + args->append(SASS_MEMORY_NEW(Argument, braced_url->pstate(), braced_url)); + } + else { + error("malformed URL"); + } + if (!lex< exactly<')'> >()) error("URI is missing ')'"); + to_import.push_back(std::pair("", result)); + } + else { + if (first) error("@import directive requires a url or quoted path"); + else error("expecting another url or quoted path in @import list"); + } + first = false; + } while (lex_css< exactly<','> >()); + + if (!peek_css< alternatives< exactly<';'>, exactly<'}'>, end_of_file > >()) { + List_Obj import_queries = parse_media_queries(); + imp->import_queries(import_queries); + } + + for(auto location : to_import) { + if (location.second) { + imp->urls().push_back(location.second); + } + // check if custom importers want to take over the handling + else if (!ctx.call_importers(unquote(location.first), path, pstate, imp)) { + // nobody wants it, so we do our import + ctx.import_url(imp, location.first, path); + } + } + + return imp; + } + + Definition_Obj Parser::parse_definition(Definition::Type which_type) + { + std::string which_str(lexed); + if (!lex< identifier >()) error("invalid name in " + which_str + " definition"); + std::string name(Util::normalize_underscores(lexed)); + if (which_type == Definition::FUNCTION && (name == "and" || name == "or" || name == "not")) + { error("Invalid function name \"" + name + "\"."); } + ParserState source_position_of_def = pstate; + Parameters_Obj params = parse_parameters(); + if (which_type == Definition::MIXIN) stack.push_back(Scope::Mixin); + else stack.push_back(Scope::Function); + Block_Obj body = parse_block(); + stack.pop_back(); + return SASS_MEMORY_NEW(Definition, source_position_of_def, name, params, body, which_type); + } + + Parameters_Obj Parser::parse_parameters() + { + Parameters_Obj params = SASS_MEMORY_NEW(Parameters, pstate); + if (lex_css< exactly<'('> >()) { + // if there's anything there at all + if (!peek_css< exactly<')'> >()) { + do { + if (peek< exactly<')'> >()) break; + params->append(parse_parameter()); + } while (lex_css< exactly<','> >()); + } + if (!lex_css< exactly<')'> >()) { + css_error("Invalid CSS", " after ", ": expected \")\", was "); + } + } + return params; + } + + Parameter_Obj Parser::parse_parameter() + { + if (peek< alternatives< exactly<','>, exactly< '{' >, exactly<';'> > >()) { + css_error("Invalid CSS", " after ", ": expected variable (e.g. $foo), was "); + } + while (lex< alternatives < spaces, block_comment > >()); + lex < variable >(); + std::string name(Util::normalize_underscores(lexed)); + ParserState pos = pstate; + Expression_Obj val; + bool is_rest = false; + while (lex< alternatives < spaces, block_comment > >()); + if (lex< exactly<':'> >()) { // there's a default value + while (lex< block_comment >()); + val = parse_space_list(); + } + else if (lex< exactly< ellipsis > >()) { + is_rest = true; + } + return SASS_MEMORY_NEW(Parameter, pos, name, val, is_rest); + } + + Arguments_Obj Parser::parse_arguments() + { + Arguments_Obj args = SASS_MEMORY_NEW(Arguments, pstate); + if (lex_css< exactly<'('> >()) { + // if there's anything there at all + if (!peek_css< exactly<')'> >()) { + do { + if (peek< exactly<')'> >()) break; + args->append(parse_argument()); + } while (lex_css< exactly<','> >()); + } + if (!lex_css< exactly<')'> >()) { + css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); + } + } + return args; + } + + Argument_Obj Parser::parse_argument() + { + if (peek< alternatives< exactly<','>, exactly< '{' >, exactly<';'> > >()) { + css_error("Invalid CSS", " after ", ": expected \")\", was "); + } + if (peek_css< sequence < exactly< hash_lbrace >, exactly< rbrace > > >()) { + position += 2; + css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); + } + + Argument_Obj arg; + if (peek_css< sequence < variable, optional_css_comments, exactly<':'> > >()) { + lex_css< variable >(); + std::string name(Util::normalize_underscores(lexed)); + ParserState p = pstate; + lex_css< exactly<':'> >(); + Expression_Obj val = parse_space_list(); + arg = SASS_MEMORY_NEW(Argument, p, val, name); + } + else { + bool is_arglist = false; + bool is_keyword = false; + Expression_Obj val = parse_space_list(); + List_Ptr l = Cast(val); + if (lex_css< exactly< ellipsis > >()) { + if (val->concrete_type() == Expression::MAP || ( + (l != NULL && l->separator() == SASS_HASH) + )) is_keyword = true; + else is_arglist = true; + } + arg = SASS_MEMORY_NEW(Argument, pstate, val, "", is_arglist, is_keyword); + } + return arg; + } + + Assignment_Obj Parser::parse_assignment() + { + std::string name(Util::normalize_underscores(lexed)); + ParserState var_source_position = pstate; + if (!lex< exactly<':'> >()) error("expected ':' after " + name + " in assignment statement"); + if (peek_css< alternatives < exactly<';'>, end_of_file > >()) { + css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); + } + Expression_Obj val; + Lookahead lookahead = lookahead_for_value(position); + if (lookahead.has_interpolants && lookahead.found) { + val = parse_value_schema(lookahead.found); + } else { + val = parse_list(); + } + bool is_default = false; + bool is_global = false; + while (peek< alternatives < default_flag, global_flag > >()) { + if (lex< default_flag >()) is_default = true; + else if (lex< global_flag >()) is_global = true; + } + return SASS_MEMORY_NEW(Assignment, var_source_position, name, val, is_default, is_global); + } + + // a ruleset connects a selector and a block + Ruleset_Obj Parser::parse_ruleset(Lookahead lookahead) + { + NESTING_GUARD(nestings); + // inherit is_root from parent block + Block_Obj parent = block_stack.back(); + bool is_root = parent && parent->is_root(); + // make sure to move up the the last position + lex < optional_css_whitespace >(false, true); + // create the connector object (add parts later) + Ruleset_Obj ruleset = SASS_MEMORY_NEW(Ruleset, pstate); + // parse selector static or as schema to be evaluated later + if (lookahead.parsable) ruleset->selector(parse_selector_list(false)); + else { + Selector_List_Obj list = SASS_MEMORY_NEW(Selector_List, pstate); + list->schema(parse_selector_schema(lookahead.position, false)); + ruleset->selector(list); + } + // then parse the inner block + stack.push_back(Scope::Rules); + ruleset->block(parse_block()); + stack.pop_back(); + // update for end position + ruleset->update_pstate(pstate); + ruleset->block()->update_pstate(pstate); + // need this info for sanity checks + ruleset->is_root(is_root); + // return AST Node + return ruleset; + } + + // parse a selector schema that will be evaluated in the eval stage + // uses a string schema internally to do the actual schema handling + // in the eval stage we will be re-parse it into an actual selector + Selector_Schema_Obj Parser::parse_selector_schema(const char* end_of_selector, bool chroot) + { + NESTING_GUARD(nestings); + // move up to the start + lex< optional_spaces >(); + const char* i = position; + // selector schema re-uses string schema implementation + String_Schema_Ptr schema = SASS_MEMORY_NEW(String_Schema, pstate); + // the selector schema is pretty much just a wrapper for the string schema + Selector_Schema_Obj selector_schema = SASS_MEMORY_NEW(Selector_Schema, pstate, schema); + selector_schema->connect_parent(chroot == false); + selector_schema->media_block(last_media_block); + + // process until end + while (i < end_of_selector) { + // try to parse mutliple interpolants + if (const char* p = find_first_in_interval< exactly, block_comment >(i, end_of_selector)) { + // accumulate the preceding segment if the position has advanced + if (i < p) { + std::string parsed(i, p); + String_Constant_Obj str = SASS_MEMORY_NEW(String_Constant, pstate, parsed); + pstate += Offset(parsed); + str->update_pstate(pstate); + schema->append(str); + } + + // skip over all nested inner interpolations up to our own delimiter + const char* j = skip_over_scopes< exactly, exactly >(p + 2, end_of_selector); + // check if the interpolation never ends of only contains white-space (error out) + if (!j || peek < sequence < optional_spaces, exactly > >(p+2)) { + position = p+2; + css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); + } + // pass inner expression to the parser to resolve nested interpolations + pstate.add(p, p+2); + Expression_Obj interpolant = Parser::from_c_str(p+2, j, ctx, traces, pstate).parse_list(); + // set status on the list expression + interpolant->is_interpolant(true); + // schema->has_interpolants(true); + // add to the string schema + schema->append(interpolant); + // advance parser state + pstate.add(p+2, j); + // advance position + i = j; + } + // no more interpolants have been found + // add the last segment if there is one + else { + // make sure to add the last bits of the string up to the end (if any) + if (i < end_of_selector) { + std::string parsed(i, end_of_selector); + String_Constant_Obj str = SASS_MEMORY_NEW(String_Constant, pstate, parsed); + pstate += Offset(parsed); + str->update_pstate(pstate); + i = end_of_selector; + schema->append(str); + } + // exit loop + } + } + // EO until eos + + // update position + position = i; + + // update for end position + selector_schema->update_pstate(pstate); + schema->update_pstate(pstate); + + after_token = before_token = pstate; + + // return parsed result + return selector_schema.detach(); + } + // EO parse_selector_schema + + void Parser::parse_charset_directive() + { + lex < + sequence < + quoted_string, + optional_spaces, + exactly <';'> + > + >(); + } + + // called after parsing `kwd_include_directive` + Mixin_Call_Obj Parser::parse_include_directive() + { + // lex identifier into `lexed` var + lex_identifier(); // may error out + // normalize underscores to hyphens + std::string name(Util::normalize_underscores(lexed)); + // create the initial mixin call object + Mixin_Call_Obj call = SASS_MEMORY_NEW(Mixin_Call, pstate, name, 0, 0); + // parse mandatory arguments + call->arguments(parse_arguments()); + // parse optional block + if (peek < exactly <'{'> >()) { + call->block(parse_block()); + } + // return ast node + return call.detach(); + } + // EO parse_include_directive + + // parse a list of complex selectors + // this is the main entry point for most + Selector_List_Obj Parser::parse_selector_list(bool chroot) + { + bool reloop; + bool had_linefeed = false; + NESTING_GUARD(nestings); + Complex_Selector_Obj sel; + Selector_List_Obj group = SASS_MEMORY_NEW(Selector_List, pstate); + group->media_block(last_media_block); + + if (peek_css< alternatives < end_of_file, exactly <'{'>, exactly <','> > >()) { + css_error("Invalid CSS", " after ", ": expected selector, was "); + } + + do { + reloop = false; + + had_linefeed = had_linefeed || peek_newline(); + + if (peek_css< alternatives < class_char < selector_list_delims > > >()) + break; // in case there are superfluous commas at the end + + // now parse the complex selector + sel = parse_complex_selector(chroot); + + if (!sel) return group.detach(); + + sel->has_line_feed(had_linefeed); + + had_linefeed = false; + + while (peek_css< exactly<','> >()) + { + lex< css_comments >(false); + // consume everything up and including the comma separator + reloop = lex< exactly<','> >() != 0; + // remember line break (also between some commas) + had_linefeed = had_linefeed || peek_newline(); + // remember line break (also between some commas) + } + group->append(sel); + } + while (reloop); + while (lex_css< kwd_optional >()) { + group->is_optional(true); + } + // update for end position + group->update_pstate(pstate); + if (sel) sel->last()->has_line_break(false); + return group.detach(); + } + // EO parse_selector_list + + // a complex selector combines a compound selector with another + // complex selector, with one of four combinator operations. + // the compound selector (head) is optional, since the combinator + // can come first in the whole selector sequence (like `> DIV'). + Complex_Selector_Obj Parser::parse_complex_selector(bool chroot) + { + + NESTING_GUARD(nestings); + String_Obj reference = 0; + lex < block_comment >(); + advanceToNextToken(); + Complex_Selector_Obj sel = SASS_MEMORY_NEW(Complex_Selector, pstate); + + if (peek < end_of_file >()) return 0; + + // parse the left hand side + Compound_Selector_Obj lhs; + // special case if it starts with combinator ([+~>]) + if (!peek_css< class_char < selector_combinator_ops > >()) { + // parse the left hand side + lhs = parse_compound_selector(); + } + + + // parse combinator between lhs and rhs + Complex_Selector::Combinator combinator = Complex_Selector::ANCESTOR_OF; + if (lex< exactly<'+'> >()) combinator = Complex_Selector::ADJACENT_TO; + else if (lex< exactly<'~'> >()) combinator = Complex_Selector::PRECEDES; + else if (lex< exactly<'>'> >()) combinator = Complex_Selector::PARENT_OF; + else if (lex< sequence < exactly<'/'>, negate < exactly < '*' > > > >()) { + // comments are allowed, but not spaces? + combinator = Complex_Selector::REFERENCE; + if (!lex < re_reference_combinator >()) return 0; + reference = SASS_MEMORY_NEW(String_Constant, pstate, lexed); + if (!lex < exactly < '/' > >()) return 0; // ToDo: error msg? + } + + if (!lhs && combinator == Complex_Selector::ANCESTOR_OF) return 0; + + // lex < block_comment >(); + sel->head(lhs); + sel->combinator(combinator); + sel->media_block(last_media_block); + + if (combinator == Complex_Selector::REFERENCE) sel->reference(reference); + // has linfeed after combinator? + sel->has_line_break(peek_newline()); + // sel->has_line_feed(has_line_feed); + + // check if we got the abort condition (ToDo: optimize) + if (!peek_css< class_char < complex_selector_delims > >()) { + // parse next selector in sequence + sel->tail(parse_complex_selector(true)); + } + + // add a parent selector if we are not in a root + // also skip adding parent ref if we only have refs + if (!sel->has_parent_ref() && !chroot) { + // create the objects to wrap parent selector reference + Compound_Selector_Obj head = SASS_MEMORY_NEW(Compound_Selector, pstate); + Parent_Selector_Ptr parent = SASS_MEMORY_NEW(Parent_Selector, pstate, false); + parent->media_block(last_media_block); + head->media_block(last_media_block); + // add simple selector + head->append(parent); + // selector may not have any head yet + if (!sel->head()) { sel->head(head); } + // otherwise we need to create a new complex selector and set the old one as its tail + else { + sel = SASS_MEMORY_NEW(Complex_Selector, pstate, Complex_Selector::ANCESTOR_OF, head, sel); + sel->media_block(last_media_block); + } + // peek for linefeed and remember result on head + // if (peek_newline()) head->has_line_break(true); + } + + sel->update_pstate(pstate); + // complex selector + return sel; + } + // EO parse_complex_selector + + // parse one compound selector, which is basically + // a list of simple selectors (directly adjacent) + // lex them exactly (without skipping white-space) + Compound_Selector_Obj Parser::parse_compound_selector() + { + // init an empty compound selector wrapper + Compound_Selector_Obj seq = SASS_MEMORY_NEW(Compound_Selector, pstate); + seq->media_block(last_media_block); + + // skip initial white-space + lex< css_whitespace >(); + + // parse list + while (true) + { + // remove all block comments (don't skip white-space) + lex< delimited_by< slash_star, star_slash, false > >(false); + // parse functional + if (match < re_pseudo_selector >()) + { + seq->append(parse_simple_selector()); + } + // parse parent selector + else if (lex< exactly<'&'> >(false)) + { + // this produces a linefeed!? + seq->has_parent_reference(true); + seq->append(SASS_MEMORY_NEW(Parent_Selector, pstate)); + // parent selector only allowed at start + // upcoming Sass may allow also trailing + if (seq->length() > 1) { + ParserState state(pstate); + Simple_Selector_Obj cur = (*seq)[seq->length()-1]; + Simple_Selector_Obj prev = (*seq)[seq->length()-2]; + std::string sel(prev->to_string({ NESTED, 5 })); + std::string found(cur->to_string({ NESTED, 5 })); + if (lex < identifier >()) { found += std::string(lexed); } + error("Invalid CSS after \"" + sel + "\": expected \"{\", was \"" + found + "\"\n\n" + "\"" + found + "\" may only be used at the beginning of a compound selector.", state); + } + } + // parse type selector + else if (lex< re_type_selector >(false)) + { + seq->append(SASS_MEMORY_NEW(Element_Selector, pstate, lexed)); + } + // peek for abort conditions + else if (peek< spaces >()) break; + else if (peek< end_of_file >()) { break; } + else if (peek_css < class_char < selector_combinator_ops > >()) break; + else if (peek_css < class_char < complex_selector_delims > >()) break; + // otherwise parse another simple selector + else { + Simple_Selector_Obj sel = parse_simple_selector(); + if (!sel) return 0; + seq->append(sel); + } + } + + if (seq && !peek_css>>()) { + seq->has_line_break(peek_newline()); + } + + // EO while true + return seq; + + } + // EO parse_compound_selector + + Simple_Selector_Obj Parser::parse_simple_selector() + { + lex < css_comments >(false); + if (lex< class_name >()) { + return SASS_MEMORY_NEW(Class_Selector, pstate, lexed); + } + else if (lex< id_name >()) { + return SASS_MEMORY_NEW(Id_Selector, pstate, lexed); + } + else if (lex< alternatives < variable, number, static_reference_combinator > >()) { + return SASS_MEMORY_NEW(Element_Selector, pstate, lexed); + } + else if (peek< pseudo_not >()) { + return parse_negated_selector(); + } + else if (peek< re_pseudo_selector >()) { + return parse_pseudo_selector(); + } + else if (peek< exactly<':'> >()) { + return parse_pseudo_selector(); + } + else if (lex < exactly<'['> >()) { + return parse_attribute_selector(); + } + else if (lex< placeholder >()) { + Placeholder_Selector_Ptr sel = SASS_MEMORY_NEW(Placeholder_Selector, pstate, lexed); + sel->media_block(last_media_block); + return sel; + } + else { + css_error("Invalid CSS", " after ", ": expected selector, was "); + } + // failed + return 0; + } + + Wrapped_Selector_Obj Parser::parse_negated_selector() + { + lex< pseudo_not >(); + std::string name(lexed); + ParserState nsource_position = pstate; + Selector_List_Obj negated = parse_selector_list(true); + if (!lex< exactly<')'> >()) { + error("negated selector is missing ')'"); + } + name.erase(name.size() - 1); + return SASS_MEMORY_NEW(Wrapped_Selector, nsource_position, name, negated); + } + + // a pseudo selector often starts with one or two colons + // it can contain more selectors inside parentheses + Simple_Selector_Obj Parser::parse_pseudo_selector() { + if (lex< sequence< + optional < pseudo_prefix >, + // we keep the space within the name, strange enough + // ToDo: refactor output to schedule the space for it + // or do we really want to keep the real white-space? + sequence< identifier, optional < block_comment >, exactly<'('> > + > >()) + { + + std::string name(lexed); + name.erase(name.size() - 1); + ParserState p = pstate; + + // specially parse static stuff + // ToDo: really everything static? + if (peek_css < + sequence < + alternatives < + static_value, + binomial + >, + optional_css_whitespace, + exactly<')'> + > + >() + ) { + lex_css< alternatives < static_value, binomial > >(); + String_Constant_Obj expr = SASS_MEMORY_NEW(String_Constant, pstate, lexed); + if (lex_css< exactly<')'> >()) { + expr->can_compress_whitespace(true); + return SASS_MEMORY_NEW(Pseudo_Selector, p, name, expr); + } + } + else if (Selector_List_Obj wrapped = parse_selector_list(true)) { + if (wrapped && lex_css< exactly<')'> >()) { + return SASS_MEMORY_NEW(Wrapped_Selector, p, name, wrapped); + } + } + + } + // EO if pseudo selector + + else if (lex < sequence< optional < pseudo_prefix >, identifier > >()) { + return SASS_MEMORY_NEW(Pseudo_Selector, pstate, lexed); + } + else if(lex < pseudo_prefix >()) { + css_error("Invalid CSS", " after ", ": expected pseudoclass or pseudoelement, was "); + } + + css_error("Invalid CSS", " after ", ": expected \")\", was "); + + // unreachable statement + return 0; + } + + const char* Parser::re_attr_sensitive_close(const char* src) + { + return alternatives < exactly<']'>, exactly<'/'> >(src); + } + + const char* Parser::re_attr_insensitive_close(const char* src) + { + return sequence < insensitive<'i'>, re_attr_sensitive_close >(src); + } + + Attribute_Selector_Obj Parser::parse_attribute_selector() + { + ParserState p = pstate; + if (!lex_css< attribute_name >()) error("invalid attribute name in attribute selector"); + std::string name(lexed); + if (lex_css< re_attr_sensitive_close >()) { + return SASS_MEMORY_NEW(Attribute_Selector, p, name, "", 0, 0); + } + else if (lex_css< re_attr_insensitive_close >()) { + char modifier = lexed.begin[0]; + return SASS_MEMORY_NEW(Attribute_Selector, p, name, "", 0, modifier); + } + if (!lex_css< alternatives< exact_match, class_match, dash_match, + prefix_match, suffix_match, substring_match > >()) { + error("invalid operator in attribute selector for " + name); + } + std::string matcher(lexed); + + String_Obj value = 0; + if (lex_css< identifier >()) { + value = SASS_MEMORY_NEW(String_Constant, p, lexed); + } + else if (lex_css< quoted_string >()) { + value = parse_interpolated_chunk(lexed, true); // needed! + } + else { + error("expected a string constant or identifier in attribute selector for " + name); + } + + if (lex_css< re_attr_sensitive_close >()) { + return SASS_MEMORY_NEW(Attribute_Selector, p, name, matcher, value, 0); + } + else if (lex_css< re_attr_insensitive_close >()) { + char modifier = lexed.begin[0]; + return SASS_MEMORY_NEW(Attribute_Selector, p, name, matcher, value, modifier); + } + error("unterminated attribute selector for " + name); + return NULL; // to satisfy compilers (error must not return) + } + + /* parse block comment and add to block */ + void Parser::parse_block_comments() + { + Block_Obj block = block_stack.back(); + + while (lex< block_comment >()) { + bool is_important = lexed.begin[2] == '!'; + // flag on second param is to skip loosely over comments + String_Obj contents = parse_interpolated_chunk(lexed, true, false); + block->append(SASS_MEMORY_NEW(Comment, pstate, contents, is_important)); + } + } + + Declaration_Obj Parser::parse_declaration() { + String_Obj prop; + bool is_custom_property = false; + if (lex< sequence< optional< exactly<'*'> >, identifier_schema > >()) { + const std::string property(lexed); + is_custom_property = property.compare(0, 2, "--") == 0; + prop = parse_identifier_schema(); + } + else if (lex< sequence< optional< exactly<'*'> >, identifier, zero_plus< block_comment > > >()) { + const std::string property(lexed); + is_custom_property = property.compare(0, 2, "--") == 0; + prop = SASS_MEMORY_NEW(String_Constant, pstate, lexed); + } + else { + css_error("Invalid CSS", " after ", ": expected \"}\", was "); + } + bool is_indented = true; + const std::string property(lexed); + if (!lex_css< one_plus< exactly<':'> > >()) error("property \"" + escape_string(property) + "\" must be followed by a ':'"); + if (!is_custom_property && match< sequence< optional_css_comments, exactly<';'> > >()) error("style declaration must contain a value"); + if (match< sequence< optional_css_comments, exactly<'{'> > >()) is_indented = false; // don't indent if value is empty + if (is_custom_property) { + return SASS_MEMORY_NEW(Declaration, prop->pstate(), prop, parse_css_variable_value(), false, true); + } + lex < css_comments >(false); + if (peek_css< static_value >()) { + return SASS_MEMORY_NEW(Declaration, prop->pstate(), prop, parse_static_value()/*, lex()*/); + } + else { + Expression_Obj value; + Lookahead lookahead = lookahead_for_value(position); + if (lookahead.found) { + if (lookahead.has_interpolants) { + value = parse_value_schema(lookahead.found); + } else { + value = parse_list(DELAYED); + } + } + else { + value = parse_list(DELAYED); + if (List_Ptr list = Cast(value)) { + if (!list->is_bracketed() && list->length() == 0 && !peek< exactly <'{'> >()) { + css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); + } + } + } + lex < css_comments >(false); + Declaration_Obj decl = SASS_MEMORY_NEW(Declaration, prop->pstate(), prop, value/*, lex()*/); + decl->is_indented(is_indented); + decl->update_pstate(pstate); + return decl; + } + } + + // parse +/- and return false if negative + // this is never hit via spec tests + bool Parser::parse_number_prefix() + { + bool positive = true; + while(true) { + if (lex < block_comment >()) continue; + if (lex < number_prefix >()) continue; + if (lex < exactly < '-' > >()) { + positive = !positive; + continue; + } + break; + } + return positive; + } + + Expression_Obj Parser::parse_map() + { + NESTING_GUARD(nestings); + Expression_Obj key = parse_list(); + List_Obj map = SASS_MEMORY_NEW(List, pstate, 0, SASS_HASH); + + // it's not a map so return the lexed value as a list value + if (!lex_css< exactly<':'> >()) + { return key; } + + List_Obj l = Cast(key); + if (l && l->separator() == SASS_COMMA) { + css_error("Invalid CSS", " after ", ": expected \")\", was "); + } + + Expression_Obj value = parse_space_list(); + + map->append(key); + map->append(value); + + while (lex_css< exactly<','> >()) + { + // allow trailing commas - #495 + if (peek_css< exactly<')'> >(position)) + { break; } + + key = parse_space_list(); + + if (!(lex< exactly<':'> >())) + { css_error("Invalid CSS", " after ", ": expected \":\", was "); } + + value = parse_space_list(); + + map->append(key); + map->append(value); + } + + ParserState ps = map->pstate(); + ps.offset = pstate - ps + pstate.offset; + map->pstate(ps); + + return map; + } + + Expression_Obj Parser::parse_bracket_list() + { + NESTING_GUARD(nestings); + // check if we have an empty list + // return the empty list as such + if (peek_css< list_terminator >(position)) + { + // return an empty list (nothing to delay) + return SASS_MEMORY_NEW(List, pstate, 0, SASS_SPACE, false, true); + } + + bool has_paren = peek_css< exactly<'('> >() != NULL; + + // now try to parse a space list + Expression_Obj list = parse_space_list(); + // if it's a singleton, return it (don't wrap it) + if (!peek_css< exactly<','> >(position)) { + List_Obj l = Cast(list); + if (!l || l->is_bracketed() || has_paren) { + List_Obj bracketed_list = SASS_MEMORY_NEW(List, pstate, 1, SASS_SPACE, false, true); + bracketed_list->append(list); + return bracketed_list; + } + l->is_bracketed(true); + return l; + } + + // if we got so far, we actually do have a comma list + List_Obj bracketed_list = SASS_MEMORY_NEW(List, pstate, 2, SASS_COMMA, false, true); + // wrap the first expression + bracketed_list->append(list); + + while (lex_css< exactly<','> >()) + { + // check for abort condition + if (peek_css< list_terminator >(position) + ) { break; } + // otherwise add another expression + bracketed_list->append(parse_space_list()); + } + // return the list + return bracketed_list; + } + + // parse list returns either a space separated list, + // a comma separated list or any bare expression found. + // so to speak: we unwrap items from lists if possible here! + Expression_Obj Parser::parse_list(bool delayed) + { + NESTING_GUARD(nestings); + return parse_comma_list(delayed); + } + + // will return singletons unwrapped + Expression_Obj Parser::parse_comma_list(bool delayed) + { + NESTING_GUARD(nestings); + // check if we have an empty list + // return the empty list as such + if (peek_css< list_terminator >(position)) + { + // return an empty list (nothing to delay) + return SASS_MEMORY_NEW(List, pstate, 0); + } + + // now try to parse a space list + Expression_Obj list = parse_space_list(); + // if it's a singleton, return it (don't wrap it) + if (!peek_css< exactly<','> >(position)) { + // set_delay doesn't apply to list children + // so this will only undelay single values + if (!delayed) list->set_delayed(false); + return list; + } + + // if we got so far, we actually do have a comma list + List_Obj comma_list = SASS_MEMORY_NEW(List, pstate, 2, SASS_COMMA); + // wrap the first expression + comma_list->append(list); + + while (lex_css< exactly<','> >()) + { + // check for abort condition + if (peek_css< list_terminator >(position) + ) { break; } + // otherwise add another expression + comma_list->append(parse_space_list()); + } + // return the list + return comma_list; + } + // EO parse_comma_list + + // will return singletons unwrapped + Expression_Obj Parser::parse_space_list() + { + NESTING_GUARD(nestings); + Expression_Obj disj1 = parse_disjunction(); + // if it's a singleton, return it (don't wrap it) + if (peek_css< space_list_terminator >(position) + ) { + return disj1; } + + List_Obj space_list = SASS_MEMORY_NEW(List, pstate, 2, SASS_SPACE); + space_list->append(disj1); + + while ( + !(peek_css< space_list_terminator >(position)) && + peek_css< optional_css_whitespace >() != end + ) { + // the space is parsed implicitly? + space_list->append(parse_disjunction()); + } + // return the list + return space_list; + } + // EO parse_space_list + + // parse logical OR operation + Expression_Obj Parser::parse_disjunction() + { + NESTING_GUARD(nestings); + advanceToNextToken(); + ParserState state(pstate); + // parse the left hand side conjunction + Expression_Obj conj = parse_conjunction(); + // parse multiple right hand sides + std::vector operands; + while (lex_css< kwd_or >()) + operands.push_back(parse_conjunction()); + // if it's a singleton, return it directly + if (operands.size() == 0) return conj; + // fold all operands into one binary expression + Expression_Obj ex = fold_operands(conj, operands, { Sass_OP::OR }); + state.offset = pstate - state + pstate.offset; + ex->pstate(state); + return ex; + } + // EO parse_disjunction + + // parse logical AND operation + Expression_Obj Parser::parse_conjunction() + { + NESTING_GUARD(nestings); + advanceToNextToken(); + ParserState state(pstate); + // parse the left hand side relation + Expression_Obj rel = parse_relation(); + // parse multiple right hand sides + std::vector operands; + while (lex_css< kwd_and >()) { + operands.push_back(parse_relation()); + } + // if it's a singleton, return it directly + if (operands.size() == 0) return rel; + // fold all operands into one binary expression + Expression_Obj ex = fold_operands(rel, operands, { Sass_OP::AND }); + state.offset = pstate - state + pstate.offset; + ex->pstate(state); + return ex; + } + // EO parse_conjunction + + // parse comparison operations + Expression_Obj Parser::parse_relation() + { + NESTING_GUARD(nestings); + advanceToNextToken(); + ParserState state(pstate); + // parse the left hand side expression + Expression_Obj lhs = parse_expression(); + std::vector operands; + std::vector operators; + // if it's a singleton, return it (don't wrap it) + while (peek< alternatives < + kwd_eq, + kwd_neq, + kwd_gte, + kwd_gt, + kwd_lte, + kwd_lt + > >(position)) + { + // is directly adjancent to expression? + bool left_ws = peek < css_comments >() != NULL; + // parse the operator + enum Sass_OP op + = lex() ? Sass_OP::EQ + : lex() ? Sass_OP::NEQ + : lex() ? Sass_OP::GTE + : lex() ? Sass_OP::LTE + : lex() ? Sass_OP::GT + : lex() ? Sass_OP::LT + // we checked the possibilities on top of fn + : Sass_OP::EQ; + // is directly adjacent to expression? + bool right_ws = peek < css_comments >() != NULL; + operators.push_back({ op, left_ws, right_ws }); + operands.push_back(parse_expression()); + } + // we are called recursively for list, so we first + // fold inner binary expression which has delayed + // correctly set to zero. After folding we also unwrap + // single nested items. So we cannot set delay on the + // returned result here, as we have lost nestings ... + Expression_Obj ex = fold_operands(lhs, operands, operators); + state.offset = pstate - state + pstate.offset; + ex->pstate(state); + return ex; + } + // parse_relation + + // parse expression valid for operations + // called from parse_relation + // called from parse_for_directive + // called from parse_media_expression + // parse addition and subtraction operations + Expression_Obj Parser::parse_expression() + { + NESTING_GUARD(nestings); + advanceToNextToken(); + ParserState state(pstate); + // parses multiple add and subtract operations + // NOTE: make sure that identifiers starting with + // NOTE: dashes do NOT count as subtract operation + Expression_Obj lhs = parse_operators(); + // if it's a singleton, return it (don't wrap it) + if (!(peek_css< exactly<'+'> >(position) || + // condition is a bit misterious, but some combinations should not be counted as operations + (peek< no_spaces >(position) && peek< sequence< negate< unsigned_number >, exactly<'-'>, negate< space > > >(position)) || + (peek< sequence< negate< unsigned_number >, exactly<'-'>, negate< unsigned_number > > >(position))) || + peek< sequence < zero_plus < exactly <'-' > >, identifier > >(position)) + { return lhs; } + + std::vector operands; + std::vector operators; + bool left_ws = peek < css_comments >() != NULL; + while ( + lex_css< exactly<'+'> >() || + + ( + ! peek_css< sequence < zero_plus < exactly <'-' > >, identifier > >(position) + && lex_css< sequence< negate< digit >, exactly<'-'> > >() + ) + + ) { + + bool right_ws = peek < css_comments >() != NULL; + operators.push_back({ lexed.to_string() == "+" ? Sass_OP::ADD : Sass_OP::SUB, left_ws, right_ws }); + operands.push_back(parse_operators()); + left_ws = peek < css_comments >() != NULL; + } + + if (operands.size() == 0) return lhs; + Expression_Obj ex = fold_operands(lhs, operands, operators); + state.offset = pstate - state + pstate.offset; + ex->pstate(state); + return ex; + } + + // parse addition and subtraction operations + Expression_Obj Parser::parse_operators() + { + NESTING_GUARD(nestings); + advanceToNextToken(); + ParserState state(pstate); + Expression_Obj factor = parse_factor(); + // if it's a singleton, return it (don't wrap it) + std::vector operands; // factors + std::vector operators; // ops + // lex operations to apply to lhs + const char* left_ws = peek < css_comments >(); + while (lex_css< class_char< static_ops > >()) { + const char* right_ws = peek < css_comments >(); + switch(*lexed.begin) { + case '*': operators.push_back({ Sass_OP::MUL, left_ws != 0, right_ws != 0 }); break; + case '/': operators.push_back({ Sass_OP::DIV, left_ws != 0, right_ws != 0 }); break; + case '%': operators.push_back({ Sass_OP::MOD, left_ws != 0, right_ws != 0 }); break; + default: throw std::runtime_error("unknown static op parsed"); + } + operands.push_back(parse_factor()); + left_ws = peek < css_comments >(); + } + // operands and operators to binary expression + Expression_Obj ex = fold_operands(factor, operands, operators); + state.offset = pstate - state + pstate.offset; + ex->pstate(state); + return ex; + } + // EO parse_operators + + + // called from parse_operators + // called from parse_value_schema + Expression_Obj Parser::parse_factor() + { + NESTING_GUARD(nestings); + lex < css_comments >(false); + if (lex_css< exactly<'('> >()) { + // parse_map may return a list + Expression_Obj value = parse_map(); + // lex the expected closing parenthesis + if (!lex_css< exactly<')'> >()) error("unclosed parenthesis"); + // expression can be evaluated + return value; + } + else if (lex_css< exactly<'['> >()) { + // explicit bracketed + Expression_Obj value = parse_bracket_list(); + // lex the expected closing square bracket + if (!lex_css< exactly<']'> >()) error("unclosed squared bracket"); + return value; + } + // string may be interpolated + // if (lex< quoted_string >()) { + // return &parse_string(); + // } + else if (peek< ie_property >()) { + return parse_ie_property(); + } + else if (peek< ie_keyword_arg >()) { + return parse_ie_keyword_arg(); + } + else if (peek< sequence < calc_fn_call, exactly <'('> > >()) { + return parse_calc_function(); + } + else if (lex < functional_schema >()) { + return parse_function_call_schema(); + } + else if (lex< identifier_schema >()) { + String_Obj string = parse_identifier_schema(); + if (String_Schema_Ptr schema = Cast(string)) { + if (lex < exactly < '(' > >()) { + schema->append(parse_list()); + lex < exactly < ')' > >(); + } + } + return string; + } + else if (peek< sequence< uri_prefix, W, real_uri_value > >()) { + return parse_url_function_string(); + } + else if (peek< re_functional >()) { + return parse_function_call(); + } + else if (lex< exactly<'+'> >()) { + Unary_Expression_Ptr ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::PLUS, parse_factor()); + if (ex && ex->operand()) ex->is_delayed(ex->operand()->is_delayed()); + return ex; + } + else if (lex< exactly<'-'> >()) { + Unary_Expression_Ptr ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::MINUS, parse_factor()); + if (ex && ex->operand()) ex->is_delayed(ex->operand()->is_delayed()); + return ex; + } + else if (lex< exactly<'/'> >()) { + Unary_Expression_Ptr ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::SLASH, parse_factor()); + if (ex && ex->operand()) ex->is_delayed(ex->operand()->is_delayed()); + return ex; + } + else if (lex< sequence< kwd_not > >()) { + Unary_Expression_Ptr ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::NOT, parse_factor()); + if (ex && ex->operand()) ex->is_delayed(ex->operand()->is_delayed()); + return ex; + } + // this whole branch is never hit via spec tests + else if (peek < sequence < one_plus < alternatives < css_whitespace, exactly<'-'>, exactly<'+'> > >, number > >()) { + if (parse_number_prefix()) return parse_value(); // prefix is positive + Unary_Expression_Ptr ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::MINUS, parse_value()); + if (ex->operand()) ex->is_delayed(ex->operand()->is_delayed()); + return ex; + } + else { + return parse_value(); + } + } + + bool number_has_zero(const std::string& parsed) + { + size_t L = parsed.length(); + return !( (L > 0 && parsed.substr(0, 1) == ".") || + (L > 1 && parsed.substr(0, 2) == "0.") || + (L > 1 && parsed.substr(0, 2) == "-.") || + (L > 2 && parsed.substr(0, 3) == "-0.") ); + } + + Number_Ptr Parser::lexed_number(const ParserState& pstate, const std::string& parsed) + { + Number_Ptr nr = SASS_MEMORY_NEW(Number, + pstate, + sass_strtod(parsed.c_str()), + "", + number_has_zero(parsed)); + nr->is_interpolant(false); + nr->is_delayed(true); + return nr; + } + + Number_Ptr Parser::lexed_percentage(const ParserState& pstate, const std::string& parsed) + { + Number_Ptr nr = SASS_MEMORY_NEW(Number, + pstate, + sass_strtod(parsed.c_str()), + "%", + true); + nr->is_interpolant(false); + nr->is_delayed(true); + return nr; + } + + Number_Ptr Parser::lexed_dimension(const ParserState& pstate, const std::string& parsed) + { + size_t L = parsed.length(); + size_t num_pos = parsed.find_first_not_of(" \n\r\t"); + if (num_pos == std::string::npos) num_pos = L; + size_t unit_pos = parsed.find_first_not_of("-+0123456789.", num_pos); + if (parsed[unit_pos] == 'e' && is_number(parsed[unit_pos+1]) ) { + unit_pos = parsed.find_first_not_of("-+0123456789.", ++ unit_pos); + } + if (unit_pos == std::string::npos) unit_pos = L; + const std::string& num = parsed.substr(num_pos, unit_pos - num_pos); + Number_Ptr nr = SASS_MEMORY_NEW(Number, + pstate, + sass_strtod(num.c_str()), + Token(number(parsed.c_str())), + number_has_zero(parsed)); + nr->is_interpolant(false); + nr->is_delayed(true); + return nr; + } + + Value_Ptr Parser::lexed_hex_color(const ParserState& pstate, const std::string& parsed) + { + Color_Ptr color = NULL; + if (parsed[0] != '#') { + return SASS_MEMORY_NEW(String_Quoted, pstate, parsed); + } + // chop off the '#' + std::string hext(parsed.substr(1)); + if (parsed.length() == 4) { + std::string r(2, parsed[1]); + std::string g(2, parsed[2]); + std::string b(2, parsed[3]); + color = SASS_MEMORY_NEW(Color, + pstate, + static_cast(strtol(r.c_str(), NULL, 16)), + static_cast(strtol(g.c_str(), NULL, 16)), + static_cast(strtol(b.c_str(), NULL, 16)), + 1, // alpha channel + parsed); + } + else if (parsed.length() == 7) { + std::string r(parsed.substr(1,2)); + std::string g(parsed.substr(3,2)); + std::string b(parsed.substr(5,2)); + color = SASS_MEMORY_NEW(Color, + pstate, + static_cast(strtol(r.c_str(), NULL, 16)), + static_cast(strtol(g.c_str(), NULL, 16)), + static_cast(strtol(b.c_str(), NULL, 16)), + 1, // alpha channel + parsed); + } + else if (parsed.length() == 9) { + std::string r(parsed.substr(1,2)); + std::string g(parsed.substr(3,2)); + std::string b(parsed.substr(5,2)); + std::string a(parsed.substr(7,2)); + color = SASS_MEMORY_NEW(Color, + pstate, + static_cast(strtol(r.c_str(), NULL, 16)), + static_cast(strtol(g.c_str(), NULL, 16)), + static_cast(strtol(b.c_str(), NULL, 16)), + static_cast(strtol(a.c_str(), NULL, 16)) / 255, + parsed); + } + color->is_interpolant(false); + color->is_delayed(false); + return color; + } + + Value_Ptr Parser::color_or_string(const std::string& lexed) const + { + if (auto color = name_to_color(lexed)) { + auto c = SASS_MEMORY_NEW(Color, color); + c->is_delayed(true); + c->pstate(pstate); + c->disp(lexed); + return c; + } else { + return SASS_MEMORY_NEW(String_Constant, pstate, lexed); + } + } + + // parse one value for a list + Expression_Obj Parser::parse_value() + { + lex< css_comments >(false); + if (lex< ampersand >()) + { + if (match< ampersand >()) { + warning("In Sass, \"&&\" means two copies of the parent selector. You probably want to use \"and\" instead.", pstate); + } + return SASS_MEMORY_NEW(Parent_Selector, pstate); } + + if (lex< kwd_important >()) + { return SASS_MEMORY_NEW(String_Constant, pstate, "!important"); } + + // parse `10%4px` into separated items and not a schema + if (lex< sequence < percentage, lookahead < number > > >()) + { return lexed_percentage(lexed); } + + if (lex< sequence < number, lookahead< sequence < op, number > > > >()) + { return lexed_number(lexed); } + + // string may be interpolated + if (lex< sequence < quoted_string, lookahead < exactly <'-'> > > >()) + { return parse_string(); } + + if (const char* stop = peek< value_schema >()) + { return parse_value_schema(stop); } + + // string may be interpolated + if (lex< quoted_string >()) + { return parse_string(); } + + if (lex< kwd_true >()) + { return SASS_MEMORY_NEW(Boolean, pstate, true); } + + if (lex< kwd_false >()) + { return SASS_MEMORY_NEW(Boolean, pstate, false); } + + if (lex< kwd_null >()) + { return SASS_MEMORY_NEW(Null, pstate); } + + if (lex< identifier >()) { + return color_or_string(lexed); + } + + if (lex< percentage >()) + { return lexed_percentage(lexed); } + + // match hex number first because 0x000 looks like a number followed by an identifier + if (lex< sequence < alternatives< hex, hex0 >, negate < exactly<'-'> > > >()) + { return lexed_hex_color(lexed); } + + if (lex< hexa >()) + { + std::string s = lexed.to_string(); + + deprecated( + "The value \""+s+"\" is currently parsed as a string, but it will be parsed as a color in", + "future versions of Sass. Use \"unquote('"+s+"')\" to continue parsing it as a string.", + true, pstate + ); + + return SASS_MEMORY_NEW(String_Quoted, pstate, lexed); + } + + if (lex< sequence < exactly <'#'>, identifier > >()) + { return SASS_MEMORY_NEW(String_Quoted, pstate, lexed); } + + // also handle the 10em- foo special case + // alternatives < exactly < '.' >, .. > -- `1.5em-.75em` is split into a list, not a binary expression + if (lex< sequence< dimension, optional< sequence< exactly<'-'>, lookahead< alternatives < space > > > > > >()) + { return lexed_dimension(lexed); } + + if (lex< sequence< static_component, one_plus< strict_identifier > > >()) + { return SASS_MEMORY_NEW(String_Constant, pstate, lexed); } + + if (lex< number >()) + { return lexed_number(lexed); } + + if (lex< variable >()) + { return SASS_MEMORY_NEW(Variable, pstate, Util::normalize_underscores(lexed)); } + + // Special case handling for `%` proceeding an interpolant. + if (lex< sequence< exactly<'%'>, optional< percentage > > >()) + { return SASS_MEMORY_NEW(String_Constant, pstate, lexed); } + + css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); + + // unreachable statement + return 0; + } + + // this parses interpolation inside other strings + // means the result should later be quoted again + String_Obj Parser::parse_interpolated_chunk(Token chunk, bool constant, bool css) + { + const char* i = chunk.begin; + // see if there any interpolants + const char* p = constant ? find_first_in_interval< exactly >(i, chunk.end) : + find_first_in_interval< exactly, block_comment >(i, chunk.end); + + if (!p) { + String_Quoted_Ptr str_quoted = SASS_MEMORY_NEW(String_Quoted, pstate, std::string(i, chunk.end), 0, false, false, true, css); + if (!constant && str_quoted->quote_mark()) str_quoted->quote_mark('*'); + return str_quoted; + } + + String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate, 0, css); + schema->is_interpolant(true); + while (i < chunk.end) { + p = constant ? find_first_in_interval< exactly >(i, chunk.end) : + find_first_in_interval< exactly, block_comment >(i, chunk.end); + if (p) { + if (i < p) { + // accumulate the preceding segment if it's nonempty + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string(i, p), css)); + } + // we need to skip anything inside strings + // create a new target in parser/prelexer + if (peek < sequence < optional_spaces, exactly > >(p+2)) { position = p+2; + css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); + } + const char* j = skip_over_scopes< exactly, exactly >(p + 2, chunk.end); // find the closing brace + if (j) { --j; + // parse the interpolant and accumulate it + Expression_Obj interp_node = Parser::from_token(Token(p+2, j), ctx, traces, pstate, source).parse_list(); + interp_node->is_interpolant(true); + schema->append(interp_node); + i = j; + } + else { + // throw an error if the interpolant is unterminated + error("unterminated interpolant inside string constant " + chunk.to_string()); + } + } + else { // no interpolants left; add the last segment if nonempty + // check if we need quotes here (was not sure after merge) + if (i < chunk.end) schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string(i, chunk.end), css)); + break; + } + ++ i; + } + + return schema.detach(); + } + + String_Schema_Obj Parser::parse_css_variable_value(bool top_level) + { + String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate); + String_Schema_Obj tok; + if (!(tok = parse_css_variable_value_token(top_level))) { + return NULL; + } + + schema->concat(tok); + while ((tok = parse_css_variable_value_token(top_level))) { + schema->concat(tok); + } + + return schema.detach(); + } + + String_Schema_Obj Parser::parse_css_variable_value_token(bool top_level) + { + String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate); + if ( + (top_level && lex< css_variable_top_level_value >(false)) || + (!top_level && lex< css_variable_value >(false)) + ) { + Token str(lexed); + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, str)); + } + else if (Expression_Obj tok = lex_interpolation()) { + if (String_Schema_Ptr s = Cast(tok)) { + schema->concat(s); + } else { + schema->append(tok); + } + } + else if (lex< quoted_string >()) { + Expression_Obj tok = parse_string(); + if (String_Schema_Ptr s = Cast(tok)) { + schema->concat(s); + } else { + schema->append(tok); + } + } + else { + if (peek< alternatives< exactly<'('>, exactly<'['>, exactly<'{'> > >()) { + if (lex< exactly<'('> >()) { + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string("("))); + if (String_Schema_Obj tok = parse_css_variable_value(false)) schema->concat(tok); + if (!lex< exactly<')'> >()) css_error("Invalid CSS", " after ", ": expected \")\", was "); + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string(")"))); + } + else if (lex< exactly<'['> >()) { + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string("["))); + if (String_Schema_Obj tok = parse_css_variable_value(false)) schema->concat(tok); + if (!lex< exactly<']'> >()) css_error("Invalid CSS", " after ", ": expected \"]\", was "); + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string("]"))); + } + else if (lex< exactly<'{'> >()) { + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string("{"))); + if (String_Schema_Obj tok = parse_css_variable_value(false)) schema->concat(tok); + if (!lex< exactly<'}'> >()) css_error("Invalid CSS", " after ", ": expected \"}\", was "); + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string("}"))); + } + } + } + + return schema->length() > 0 ? schema.detach() : NULL; + } + + Value_Obj Parser::parse_static_value() + { + lex< static_value >(); + Token str(lexed); + // static values always have trailing white- + // space and end delimiter (\s*[;]$) included + --pstate.offset.column; + --after_token.column; + --str.end; + --position; + + return color_or_string(str.time_wspace());; + } + + String_Obj Parser::parse_string() + { + return parse_interpolated_chunk(Token(lexed)); + } + + String_Obj Parser::parse_ie_property() + { + lex< ie_property >(); + Token str(lexed); + const char* i = str.begin; + // see if there any interpolants + const char* p = find_first_in_interval< exactly, block_comment >(str.begin, str.end); + if (!p) { + return SASS_MEMORY_NEW(String_Quoted, pstate, std::string(str.begin, str.end)); + } + + String_Schema_Ptr schema = SASS_MEMORY_NEW(String_Schema, pstate); + while (i < str.end) { + p = find_first_in_interval< exactly, block_comment >(i, str.end); + if (p) { + if (i < p) { + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string(i, p))); // accumulate the preceding segment if it's nonempty + } + if (peek < sequence < optional_spaces, exactly > >(p+2)) { position = p+2; + css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); + } + const char* j = skip_over_scopes< exactly, exactly >(p+2, str.end); // find the closing brace + if (j) { + // parse the interpolant and accumulate it + Expression_Obj interp_node = Parser::from_token(Token(p+2, j), ctx, traces, pstate, source).parse_list(); + interp_node->is_interpolant(true); + schema->append(interp_node); + i = j; + } + else { + // throw an error if the interpolant is unterminated + error("unterminated interpolant inside IE function " + str.to_string()); + } + } + else { // no interpolants left; add the last segment if nonempty + if (i < str.end) { + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string(i, str.end))); + } + break; + } + } + return schema; + } + + String_Obj Parser::parse_ie_keyword_arg() + { + String_Schema_Ptr kwd_arg = SASS_MEMORY_NEW(String_Schema, pstate, 3); + if (lex< variable >()) { + kwd_arg->append(SASS_MEMORY_NEW(Variable, pstate, Util::normalize_underscores(lexed))); + } else { + lex< alternatives< identifier_schema, identifier > >(); + kwd_arg->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed)); + } + lex< exactly<'='> >(); + kwd_arg->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed)); + if (peek< variable >()) kwd_arg->append(parse_list()); + else if (lex< number >()) { + std::string parsed(lexed); + Util::normalize_decimals(parsed); + kwd_arg->append(lexed_number(parsed)); + } + else if (peek < ie_keyword_arg_value >()) { kwd_arg->append(parse_list()); } + return kwd_arg; + } + + String_Schema_Obj Parser::parse_value_schema(const char* stop) + { + // initialize the string schema object to add tokens + String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate); + + if (peek>()) { + css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); + } + + const char* e; + const char* ee = end; + end = stop; + size_t num_items = 0; + bool need_space = false; + while (position < stop) { + // parse space between tokens + if (lex< spaces >() && num_items) { + need_space = true; + } + if (need_space) { + need_space = false; + // schema->append(SASS_MEMORY_NEW(String_Constant, pstate, " ")); + } + if ((e = peek< re_functional >()) && e < stop) { + schema->append(parse_function_call()); + } + // lex an interpolant /#{...}/ + else if (lex< exactly < hash_lbrace > >()) { + // Try to lex static expression first + if (peek< exactly< rbrace > >()) { + css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); + } + Expression_Obj ex; + if (lex< re_static_expression >()) { + ex = SASS_MEMORY_NEW(String_Constant, pstate, lexed); + } else { + ex = parse_list(true); + } + ex->is_interpolant(true); + schema->append(ex); + if (!lex < exactly < rbrace > >()) { + css_error("Invalid CSS", " after ", ": expected \"}\", was "); + } + } + // lex some string constants or other valid token + // Note: [-+] chars are left over from i.e. `#{3}+3` + else if (lex< alternatives < exactly<'%'>, exactly < '-' >, exactly < '+' > > >()) { + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed)); + } + // lex a quoted string + else if (lex< quoted_string >()) { + // need_space = true; + // if (schema->length()) schema->append(SASS_MEMORY_NEW(String_Constant, pstate, " ")); + // else need_space = true; + schema->append(parse_string()); + if ((*position == '"' || *position == '\'') || peek < alternatives < alpha > >()) { + // need_space = true; + } + if (peek < exactly < '-' > >()) break; + } + else if (lex< identifier >()) { + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed)); + if ((*position == '"' || *position == '\'') || peek < alternatives < alpha > >()) { + // need_space = true; + } + } + // lex (normalized) variable + else if (lex< variable >()) { + std::string name(Util::normalize_underscores(lexed)); + schema->append(SASS_MEMORY_NEW(Variable, pstate, name)); + } + // lex percentage value + else if (lex< percentage >()) { + schema->append(lexed_percentage(lexed)); + } + // lex dimension value + else if (lex< dimension >()) { + schema->append(lexed_dimension(lexed)); + } + // lex number value + else if (lex< number >()) { + schema->append(lexed_number(lexed)); + } + // lex hex color value + else if (lex< sequence < hex, negate < exactly < '-' > > > >()) { + schema->append(lexed_hex_color(lexed)); + } + else if (lex< sequence < exactly <'#'>, identifier > >()) { + schema->append(SASS_MEMORY_NEW(String_Quoted, pstate, lexed)); + } + // lex a value in parentheses + else if (peek< parenthese_scope >()) { + schema->append(parse_factor()); + } + else { + break; + } + ++num_items; + } + if (position != stop) { + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string(position, stop))); + position = stop; + } + end = ee; + return schema; + } + + // this parses interpolation outside other strings + // means the result must not be quoted again later + String_Obj Parser::parse_identifier_schema() + { + Token id(lexed); + const char* i = id.begin; + // see if there any interpolants + const char* p = find_first_in_interval< exactly, block_comment >(id.begin, id.end); + if (!p) { + return SASS_MEMORY_NEW(String_Constant, pstate, std::string(id.begin, id.end)); + } + + String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate); + while (i < id.end) { + p = find_first_in_interval< exactly, block_comment >(i, id.end); + if (p) { + if (i < p) { + // accumulate the preceding segment if it's nonempty + const char* o = position; position = i; + schema->append(parse_value_schema(p)); + position = o; + } + // we need to skip anything inside strings + // create a new target in parser/prelexer + if (peek < sequence < optional_spaces, exactly > >(p+2)) { position = p; + css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); + } + const char* j = skip_over_scopes< exactly, exactly >(p+2, id.end); // find the closing brace + if (j) { + // parse the interpolant and accumulate it + Expression_Obj interp_node = Parser::from_token(Token(p+2, j), ctx, traces, pstate, source).parse_list(DELAYED); + interp_node->is_interpolant(true); + schema->append(interp_node); + // schema->has_interpolants(true); + i = j; + } + else { + // throw an error if the interpolant is unterminated + error("unterminated interpolant inside interpolated identifier " + id.to_string()); + } + } + else { // no interpolants left; add the last segment if nonempty + if (i < end) { + const char* o = position; position = i; + schema->append(parse_value_schema(id.end)); + position = o; + } + break; + } + } + return schema ? schema.detach() : 0; + } + + // calc functions should preserve arguments + Function_Call_Obj Parser::parse_calc_function() + { + lex< identifier >(); + std::string name(lexed); + ParserState call_pos = pstate; + lex< exactly<'('> >(); + ParserState arg_pos = pstate; + const char* arg_beg = position; + parse_list(); + const char* arg_end = position; + lex< skip_over_scopes < + exactly < '(' >, + exactly < ')' > + > >(); + + Argument_Obj arg = SASS_MEMORY_NEW(Argument, arg_pos, parse_interpolated_chunk(Token(arg_beg, arg_end))); + Arguments_Obj args = SASS_MEMORY_NEW(Arguments, arg_pos); + args->append(arg); + return SASS_MEMORY_NEW(Function_Call, call_pos, name, args); + } + + String_Obj Parser::parse_url_function_string() + { + std::string prefix(""); + if (lex< uri_prefix >()) { + prefix = std::string(lexed); + } + + lex < optional_spaces >(); + String_Obj url_string = parse_url_function_argument(); + + std::string suffix(""); + if (lex< real_uri_suffix >()) { + suffix = std::string(lexed); + } + + std::string uri(""); + if (url_string) { + uri = url_string->to_string({ NESTED, 5 }); + } + + if (String_Schema_Ptr schema = Cast(url_string)) { + String_Schema_Obj res = SASS_MEMORY_NEW(String_Schema, pstate); + res->append(SASS_MEMORY_NEW(String_Constant, pstate, prefix)); + res->append(schema); + res->append(SASS_MEMORY_NEW(String_Constant, pstate, suffix)); + return res; + } else { + std::string res = prefix + uri + suffix; + return SASS_MEMORY_NEW(String_Constant, pstate, res); + } + } + + String_Obj Parser::parse_url_function_argument() + { + const char* p = position; + + std::string uri(""); + if (lex< real_uri_value >(false)) { + uri = lexed.to_string(); + } + + if (peek< exactly< hash_lbrace > >()) { + const char* pp = position; + // TODO: error checking for unclosed interpolants + while (pp && peek< exactly< hash_lbrace > >(pp)) { + pp = sequence< interpolant, real_uri_value >(pp); + } + position = pp; + return parse_interpolated_chunk(Token(p, position)); + } + else if (uri != "") { + std::string res = Util::rtrim(uri); + return SASS_MEMORY_NEW(String_Constant, pstate, res); + } + + return 0; + } + + Function_Call_Obj Parser::parse_function_call() + { + lex< identifier >(); + std::string name(lexed); + + if (Util::normalize_underscores(name) == "content-exists" && stack.back() != Scope::Mixin) + { error("Cannot call content-exists() except within a mixin."); } + + ParserState call_pos = pstate; + Arguments_Obj args = parse_arguments(); + return SASS_MEMORY_NEW(Function_Call, call_pos, name, args); + } + + Function_Call_Schema_Obj Parser::parse_function_call_schema() + { + String_Obj name = parse_identifier_schema(); + ParserState source_position_of_call = pstate; + Arguments_Obj args = parse_arguments(); + + return SASS_MEMORY_NEW(Function_Call_Schema, source_position_of_call, name, args); + } + + Content_Obj Parser::parse_content_directive() + { + return SASS_MEMORY_NEW(Content, pstate); + } + + If_Obj Parser::parse_if_directive(bool else_if) + { + stack.push_back(Scope::Control); + ParserState if_source_position = pstate; + bool root = block_stack.back()->is_root(); + Expression_Obj predicate = parse_list(); + Block_Obj block = parse_block(root); + Block_Obj alternative = NULL; + + // only throw away comment if we parse a case + // we want all other comments to be parsed + if (lex_css< elseif_directive >()) { + alternative = SASS_MEMORY_NEW(Block, pstate); + alternative->append(parse_if_directive(true)); + } + else if (lex_css< kwd_else_directive >()) { + alternative = parse_block(root); + } + stack.pop_back(); + return SASS_MEMORY_NEW(If, if_source_position, predicate, block, alternative); + } + + For_Obj Parser::parse_for_directive() + { + stack.push_back(Scope::Control); + ParserState for_source_position = pstate; + bool root = block_stack.back()->is_root(); + lex_variable(); + std::string var(Util::normalize_underscores(lexed)); + if (!lex< kwd_from >()) error("expected 'from' keyword in @for directive"); + Expression_Obj lower_bound = parse_expression(); + bool inclusive = false; + if (lex< kwd_through >()) inclusive = true; + else if (lex< kwd_to >()) inclusive = false; + else error("expected 'through' or 'to' keyword in @for directive"); + Expression_Obj upper_bound = parse_expression(); + Block_Obj body = parse_block(root); + stack.pop_back(); + return SASS_MEMORY_NEW(For, for_source_position, var, lower_bound, upper_bound, body, inclusive); + } + + // helper to parse a var token + Token Parser::lex_variable() + { + // peek for dollar sign first + if (!peek< exactly <'$'> >()) { + css_error("Invalid CSS", " after ", ": expected \"$\", was "); + } + // we expect a simple identifier as the call name + if (!lex< sequence < exactly <'$'>, identifier > >()) { + lex< exactly <'$'> >(); // move pstate and position up + css_error("Invalid CSS", " after ", ": expected identifier, was "); + } + // return object + return token; + } + // helper to parse identifier + Token Parser::lex_identifier() + { + // we expect a simple identifier as the call name + if (!lex< identifier >()) { // ToDo: pstate wrong? + css_error("Invalid CSS", " after ", ": expected identifier, was "); + } + // return object + return token; + } + + Each_Obj Parser::parse_each_directive() + { + stack.push_back(Scope::Control); + ParserState each_source_position = pstate; + bool root = block_stack.back()->is_root(); + std::vector vars; + lex_variable(); + vars.push_back(Util::normalize_underscores(lexed)); + while (lex< exactly<','> >()) { + if (!lex< variable >()) error("@each directive requires an iteration variable"); + vars.push_back(Util::normalize_underscores(lexed)); + } + if (!lex< kwd_in >()) error("expected 'in' keyword in @each directive"); + Expression_Obj list = parse_list(); + Block_Obj body = parse_block(root); + stack.pop_back(); + return SASS_MEMORY_NEW(Each, each_source_position, vars, list, body); + } + + // called after parsing `kwd_while_directive` + While_Obj Parser::parse_while_directive() + { + stack.push_back(Scope::Control); + bool root = block_stack.back()->is_root(); + // create the initial while call object + While_Obj call = SASS_MEMORY_NEW(While, pstate, 0, 0); + // parse mandatory predicate + Expression_Obj predicate = parse_list(); + List_Obj l = Cast(predicate); + if (!predicate || (l && !l->length())) { + css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was ", false); + } + call->predicate(predicate); + // parse mandatory block + call->block(parse_block(root)); + // return ast node + stack.pop_back(); + // return ast node + return call.detach(); + } + + // EO parse_while_directive + Media_Block_Obj Parser::parse_media_block() + { + stack.push_back(Scope::Media); + Media_Block_Obj media_block = SASS_MEMORY_NEW(Media_Block, pstate, 0, 0); + + media_block->media_queries(parse_media_queries()); + + Media_Block_Obj prev_media_block = last_media_block; + last_media_block = media_block; + media_block->block(parse_css_block()); + last_media_block = prev_media_block; + stack.pop_back(); + return media_block.detach(); + } + + List_Obj Parser::parse_media_queries() + { + advanceToNextToken(); + List_Obj queries = SASS_MEMORY_NEW(List, pstate, 0, SASS_COMMA); + if (!peek_css < exactly <'{'> >()) queries->append(parse_media_query()); + while (lex_css < exactly <','> >()) queries->append(parse_media_query()); + queries->update_pstate(pstate); + return queries.detach(); + } + + // Expression_Ptr Parser::parse_media_query() + Media_Query_Obj Parser::parse_media_query() + { + advanceToNextToken(); + Media_Query_Obj media_query = SASS_MEMORY_NEW(Media_Query, pstate); + if (lex < kwd_not >()) { media_query->is_negated(true); lex < css_comments >(false); } + else if (lex < kwd_only >()) { media_query->is_restricted(true); lex < css_comments >(false); } + + if (lex < identifier_schema >()) media_query->media_type(parse_identifier_schema()); + else if (lex < identifier >()) media_query->media_type(parse_interpolated_chunk(lexed)); + else media_query->append(parse_media_expression()); + + while (lex_css < kwd_and >()) media_query->append(parse_media_expression()); + if (lex < identifier_schema >()) { + String_Schema_Ptr schema = SASS_MEMORY_NEW(String_Schema, pstate); + schema->append(media_query->media_type()); + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, " ")); + schema->append(parse_identifier_schema()); + media_query->media_type(schema); + } + while (lex_css < kwd_and >()) media_query->append(parse_media_expression()); + + media_query->update_pstate(pstate); + + return media_query; + } + + Media_Query_Expression_Obj Parser::parse_media_expression() + { + if (lex < identifier_schema >()) { + String_Obj ss = parse_identifier_schema(); + return SASS_MEMORY_NEW(Media_Query_Expression, pstate, ss, 0, true); + } + if (!lex_css< exactly<'('> >()) { + error("media query expression must begin with '('"); + } + Expression_Obj feature; + if (peek_css< exactly<')'> >()) { + error("media feature required in media query expression"); + } + feature = parse_expression(); + Expression_Obj expression = 0; + if (lex_css< exactly<':'> >()) { + expression = parse_list(DELAYED); + } + if (!lex_css< exactly<')'> >()) { + error("unclosed parenthesis in media query expression"); + } + return SASS_MEMORY_NEW(Media_Query_Expression, feature->pstate(), feature, expression); + } + + // lexed after `kwd_supports_directive` + // these are very similar to media blocks + Supports_Block_Obj Parser::parse_supports_directive() + { + Supports_Condition_Obj cond = parse_supports_condition(); + if (!cond) { + css_error("Invalid CSS", " after ", ": expected @supports condition (e.g. (display: flexbox)), was ", false); + } + // create the ast node object for the support queries + Supports_Block_Obj query = SASS_MEMORY_NEW(Supports_Block, pstate, cond); + // additional block is mandatory + // parse inner block + query->block(parse_block()); + // return ast node + return query; + } + + // parse one query operation + // may encounter nested queries + Supports_Condition_Obj Parser::parse_supports_condition() + { + lex < css_whitespace >(); + Supports_Condition_Obj cond; + if ((cond = parse_supports_negation())) return cond; + if ((cond = parse_supports_operator())) return cond; + if ((cond = parse_supports_interpolation())) return cond; + return cond; + } + + Supports_Condition_Obj Parser::parse_supports_negation() + { + if (!lex < kwd_not >()) return 0; + Supports_Condition_Obj cond = parse_supports_condition_in_parens(); + return SASS_MEMORY_NEW(Supports_Negation, pstate, cond); + } + + Supports_Condition_Obj Parser::parse_supports_operator() + { + Supports_Condition_Obj cond = parse_supports_condition_in_parens(); + if (cond.isNull()) return 0; + + while (true) { + Supports_Operator::Operand op = Supports_Operator::OR; + if (lex < kwd_and >()) { op = Supports_Operator::AND; } + else if(!lex < kwd_or >()) { break; } + + lex < css_whitespace >(); + Supports_Condition_Obj right = parse_supports_condition_in_parens(); + + // Supports_Condition_Ptr cc = SASS_MEMORY_NEW(Supports_Condition, *static_cast(cond)); + cond = SASS_MEMORY_NEW(Supports_Operator, pstate, cond, right, op); + } + return cond; + } + + Supports_Condition_Obj Parser::parse_supports_interpolation() + { + if (!lex < interpolant >()) return 0; + + String_Obj interp = parse_interpolated_chunk(lexed); + if (!interp) return 0; + + return SASS_MEMORY_NEW(Supports_Interpolation, pstate, interp); + } + + // TODO: This needs some major work. Although feature conditions + // look like declarations their semantics differ significantly + Supports_Condition_Obj Parser::parse_supports_declaration() + { + Supports_Condition_Ptr cond; + // parse something declaration like + Expression_Obj feature = parse_expression(); + Expression_Obj expression = 0; + if (lex_css< exactly<':'> >()) { + expression = parse_list(DELAYED); + } + if (!feature || !expression) error("@supports condition expected declaration"); + cond = SASS_MEMORY_NEW(Supports_Declaration, + feature->pstate(), + feature, + expression); + // ToDo: maybe we need an additional error condition? + return cond; + } + + Supports_Condition_Obj Parser::parse_supports_condition_in_parens() + { + Supports_Condition_Obj interp = parse_supports_interpolation(); + if (interp != 0) return interp; + + if (!lex < exactly <'('> >()) return 0; + lex < css_whitespace >(); + + Supports_Condition_Obj cond = parse_supports_condition(); + if (cond != 0) { + if (!lex < exactly <')'> >()) error("unclosed parenthesis in @supports declaration"); + } else { + cond = parse_supports_declaration(); + if (!lex < exactly <')'> >()) error("unclosed parenthesis in @supports declaration"); + } + lex < css_whitespace >(); + return cond; + } + + At_Root_Block_Obj Parser::parse_at_root_block() + { + stack.push_back(Scope::AtRoot); + ParserState at_source_position = pstate; + Block_Obj body = 0; + At_Root_Query_Obj expr; + Lookahead lookahead_result; + if (lex_css< exactly<'('> >()) { + expr = parse_at_root_query(); + } + if (peek_css < exactly<'{'> >()) { + lex (); + body = parse_block(true); + } + else if ((lookahead_result = lookahead_for_selector(position)).found) { + Ruleset_Obj r = parse_ruleset(lookahead_result); + body = SASS_MEMORY_NEW(Block, r->pstate(), 1, true); + body->append(r); + } + At_Root_Block_Obj at_root = SASS_MEMORY_NEW(At_Root_Block, at_source_position, body); + if (!expr.isNull()) at_root->expression(expr); + stack.pop_back(); + return at_root; + } + + At_Root_Query_Obj Parser::parse_at_root_query() + { + if (peek< exactly<')'> >()) error("at-root feature required in at-root expression"); + + if (!peek< alternatives< kwd_with_directive, kwd_without_directive > >()) { + css_error("Invalid CSS", " after ", ": expected \"with\" or \"without\", was "); + } + + Expression_Obj feature = parse_list(); + if (!lex_css< exactly<':'> >()) error("style declaration must contain a value"); + Expression_Obj expression = parse_list(); + List_Obj value = SASS_MEMORY_NEW(List, feature->pstate(), 1); + + if (expression->concrete_type() == Expression::LIST) { + value = Cast(expression); + } + else value->append(expression); + + At_Root_Query_Obj cond = SASS_MEMORY_NEW(At_Root_Query, + value->pstate(), + feature, + value); + if (!lex_css< exactly<')'> >()) error("unclosed parenthesis in @at-root expression"); + return cond; + } + + Directive_Obj Parser::parse_special_directive() + { + std::string kwd(lexed); + + if (lexed == "@else") error("Invalid CSS: @else must come after @if"); + + // this whole branch is never hit via spec tests + + Directive_Ptr at_rule = SASS_MEMORY_NEW(Directive, pstate, kwd); + Lookahead lookahead = lookahead_for_include(position); + if (lookahead.found && !lookahead.has_interpolants) { + at_rule->selector(parse_selector_list(false)); + } + + lex < css_comments >(false); + + if (lex < static_property >()) { + at_rule->value(parse_interpolated_chunk(Token(lexed))); + } else if (!(peek < alternatives < exactly<'{'>, exactly<'}'>, exactly<';'> > >())) { + at_rule->value(parse_list()); + } + + lex < css_comments >(false); + + if (peek< exactly<'{'> >()) { + at_rule->block(parse_block()); + } + + return at_rule; + } + + // this whole branch is never hit via spec tests + Directive_Obj Parser::parse_prefixed_directive() + { + std::string kwd(lexed); + + if (lexed == "@else") error("Invalid CSS: @else must come after @if"); + + Directive_Obj at_rule = SASS_MEMORY_NEW(Directive, pstate, kwd); + Lookahead lookahead = lookahead_for_include(position); + if (lookahead.found && !lookahead.has_interpolants) { + at_rule->selector(parse_selector_list(false)); + } + + lex < css_comments >(false); + + if (lex < static_property >()) { + at_rule->value(parse_interpolated_chunk(Token(lexed))); + } else if (!(peek < alternatives < exactly<'{'>, exactly<'}'>, exactly<';'> > >())) { + at_rule->value(parse_list()); + } + + lex < css_comments >(false); + + if (peek< exactly<'{'> >()) { + at_rule->block(parse_block()); + } + + return at_rule; + } + + + Directive_Obj Parser::parse_directive() + { + Directive_Obj directive = SASS_MEMORY_NEW(Directive, pstate, lexed); + String_Schema_Obj val = parse_almost_any_value(); + // strip left and right if they are of type string + directive->value(val); + if (peek< exactly<'{'> >()) { + directive->block(parse_block()); + } + return directive; + } + + Expression_Obj Parser::lex_interpolation() + { + if (lex < interpolant >(true) != NULL) { + return parse_interpolated_chunk(lexed, true); + } + return 0; + } + + Expression_Obj Parser::lex_interp_uri() + { + // create a string schema by lexing optional interpolations + return lex_interp< re_string_uri_open, re_string_uri_close >(); + } + + Expression_Obj Parser::lex_interp_string() + { + Expression_Obj rv; + if ((rv = lex_interp< re_string_double_open, re_string_double_close >())) return rv; + if ((rv = lex_interp< re_string_single_open, re_string_single_close >())) return rv; + return rv; + } + + Expression_Obj Parser::lex_almost_any_value_chars() + { + const char* match = + lex < + one_plus < + alternatives < + sequence < + exactly <'\\'>, + any_char + >, + sequence < + negate < + sequence < + exactly < url_kwd >, + exactly <'('> + > + >, + neg_class_char < + almost_any_value_class + > + >, + sequence < + exactly <'/'>, + negate < + alternatives < + exactly <'/'>, + exactly <'*'> + > + > + >, + sequence < + exactly <'\\'>, + exactly <'#'>, + negate < + exactly <'{'> + > + >, + sequence < + exactly <'!'>, + negate < + alpha + > + > + > + > + >(false); + if (match) { + return SASS_MEMORY_NEW(String_Constant, pstate, lexed); + } + return NULL; + } + + Expression_Obj Parser::lex_almost_any_value_token() + { + Expression_Obj rv; + if (*position == 0) return 0; + if ((rv = lex_almost_any_value_chars())) return rv; + // if ((rv = lex_block_comment())) return rv; + // if ((rv = lex_single_line_comment())) return rv; + if ((rv = lex_interp_string())) return rv; + if ((rv = lex_interp_uri())) return rv; + if ((rv = lex_interpolation())) return rv; + if (lex< alternatives< hex, hex0 > >()) + { return lexed_hex_color(lexed); } + return rv; + } + + String_Schema_Obj Parser::parse_almost_any_value() + { + + String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate); + if (*position == 0) return 0; + lex < spaces >(false); + Expression_Obj token = lex_almost_any_value_token(); + if (!token) return 0; + schema->append(token); + if (*position == 0) { + schema->rtrim(); + return schema.detach(); + } + + while ((token = lex_almost_any_value_token())) { + schema->append(token); + } + + lex < css_whitespace >(); + + schema->rtrim(); + + return schema.detach(); + } + + Warning_Obj Parser::parse_warning() + { + if (stack.back() != Scope::Root && + stack.back() != Scope::Function && + stack.back() != Scope::Mixin && + stack.back() != Scope::Control && + stack.back() != Scope::Rules) { + error("Illegal nesting: Only properties may be nested beneath properties."); + } + return SASS_MEMORY_NEW(Warning, pstate, parse_list(DELAYED)); + } + + Error_Obj Parser::parse_error() + { + if (stack.back() != Scope::Root && + stack.back() != Scope::Function && + stack.back() != Scope::Mixin && + stack.back() != Scope::Control && + stack.back() != Scope::Rules) { + error("Illegal nesting: Only properties may be nested beneath properties."); + } + return SASS_MEMORY_NEW(Error, pstate, parse_list(DELAYED)); + } + + Debug_Obj Parser::parse_debug() + { + if (stack.back() != Scope::Root && + stack.back() != Scope::Function && + stack.back() != Scope::Mixin && + stack.back() != Scope::Control && + stack.back() != Scope::Rules) { + error("Illegal nesting: Only properties may be nested beneath properties."); + } + return SASS_MEMORY_NEW(Debug, pstate, parse_list(DELAYED)); + } + + Return_Obj Parser::parse_return_directive() + { + // check that we do not have an empty list (ToDo: check if we got all cases) + if (peek_css < alternatives < exactly < ';' >, exactly < '}' >, end_of_file > >()) + { css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); } + return SASS_MEMORY_NEW(Return, pstate, parse_list()); + } + + Lookahead Parser::lookahead_for_selector(const char* start) + { + // init result struct + Lookahead rv = Lookahead(); + // get start position + const char* p = start ? start : position; + // match in one big "regex" + rv.error = p; + if (const char* q = + peek < + re_selector_list + >(p) + ) { + bool could_be_property = peek< sequence< exactly<'-'>, exactly<'-'> > >(p) != 0; + bool could_be_escaped = false; + while (p < q) { + // did we have interpolations? + if (*p == '#' && *(p+1) == '{') { + rv.has_interpolants = true; + p = q; break; + } + // A property that's ambiguous with a nested selector is interpreted as a + // custom property. + if (*p == ':' && !could_be_escaped) { + rv.is_custom_property = could_be_property || p+1 == q || peek< space >(p+1); + } + could_be_escaped = *p == '\\'; + ++ p; + } + // store anyway } + + + // ToDo: remove + rv.error = q; + rv.position = q; + // check expected opening bracket + // only after successfull matching + if (peek < exactly<'{'> >(q)) rv.found = q; + // else if (peek < end_of_file >(q)) rv.found = q; + else if (peek < exactly<'('> >(q)) rv.found = q; + // else if (peek < exactly<';'> >(q)) rv.found = q; + // else if (peek < exactly<'}'> >(q)) rv.found = q; + if (rv.found || *p == 0) rv.error = 0; + } + + rv.parsable = ! rv.has_interpolants; + + // return result + return rv; + + } + // EO lookahead_for_selector + + // used in parse_block_nodes and parse_special_directive + // ToDo: actual usage is still not really clear to me? + Lookahead Parser::lookahead_for_include(const char* start) + { + // we actually just lookahead for a selector + Lookahead rv = lookahead_for_selector(start); + // but the "found" rules are different + if (const char* p = rv.position) { + // check for additional abort condition + if (peek < exactly<';'> >(p)) rv.found = p; + else if (peek < exactly<'}'> >(p)) rv.found = p; + } + // return result + return rv; + } + // EO lookahead_for_include + + // look ahead for a token with interpolation in it + // we mostly use the result if there is an interpolation + // everything that passes here gets parsed as one schema + // meaning it will not be parsed as a space separated list + Lookahead Parser::lookahead_for_value(const char* start) + { + // init result struct + Lookahead rv = Lookahead(); + // get start position + const char* p = start ? start : position; + // match in one big "regex" + if (const char* q = + peek < + non_greedy < + alternatives < + // consume whitespace + block_comment, // spaces, + // main tokens + sequence < + interpolant, + optional < + quoted_string + > + >, + identifier, + variable, + // issue #442 + sequence < + parenthese_scope, + interpolant, + optional < + quoted_string + > + > + >, + sequence < + // optional_spaces, + alternatives < + // end_of_file, + exactly<'{'>, + exactly<'}'>, + exactly<';'> + > + > + > + >(p) + ) { + if (p == q) return rv; + while (p < q) { + // did we have interpolations? + if (*p == '#' && *(p+1) == '{') { + rv.has_interpolants = true; + p = q; break; + } + ++ p; + } + // store anyway + // ToDo: remove + rv.position = q; + // check expected opening bracket + // only after successful matching + if (peek < exactly<'{'> >(q)) rv.found = q; + else if (peek < exactly<';'> >(q)) rv.found = q; + else if (peek < exactly<'}'> >(q)) rv.found = q; + } + + // return result + return rv; + } + // EO lookahead_for_value + + void Parser::read_bom() + { + size_t skip = 0; + std::string encoding; + bool utf_8 = false; + switch ((unsigned char) source[0]) { + case 0xEF: + skip = check_bom_chars(source, end, utf_8_bom, 3); + encoding = "UTF-8"; + utf_8 = true; + break; + case 0xFE: + skip = check_bom_chars(source, end, utf_16_bom_be, 2); + encoding = "UTF-16 (big endian)"; + break; + case 0xFF: + skip = check_bom_chars(source, end, utf_16_bom_le, 2); + skip += (skip ? check_bom_chars(source, end, utf_32_bom_le, 4) : 0); + encoding = (skip == 2 ? "UTF-16 (little endian)" : "UTF-32 (little endian)"); + break; + case 0x00: + skip = check_bom_chars(source, end, utf_32_bom_be, 4); + encoding = "UTF-32 (big endian)"; + break; + case 0x2B: + skip = check_bom_chars(source, end, utf_7_bom_1, 4) + | check_bom_chars(source, end, utf_7_bom_2, 4) + | check_bom_chars(source, end, utf_7_bom_3, 4) + | check_bom_chars(source, end, utf_7_bom_4, 4) + | check_bom_chars(source, end, utf_7_bom_5, 5); + encoding = "UTF-7"; + break; + case 0xF7: + skip = check_bom_chars(source, end, utf_1_bom, 3); + encoding = "UTF-1"; + break; + case 0xDD: + skip = check_bom_chars(source, end, utf_ebcdic_bom, 4); + encoding = "UTF-EBCDIC"; + break; + case 0x0E: + skip = check_bom_chars(source, end, scsu_bom, 3); + encoding = "SCSU"; + break; + case 0xFB: + skip = check_bom_chars(source, end, bocu_1_bom, 3); + encoding = "BOCU-1"; + break; + case 0x84: + skip = check_bom_chars(source, end, gb_18030_bom, 4); + encoding = "GB-18030"; + break; + default: break; + } + if (skip > 0 && !utf_8) error("only UTF-8 documents are currently supported; your document appears to be " + encoding); + position += skip; + } + + size_t check_bom_chars(const char* src, const char *end, const unsigned char* bom, size_t len) + { + size_t skip = 0; + if (src + len > end) return 0; + for (size_t i = 0; i < len; ++i, ++skip) { + if ((unsigned char) src[i] != bom[i]) return 0; + } + return skip; + } + + + Expression_Obj Parser::fold_operands(Expression_Obj base, std::vector& operands, Operand op) + { + for (size_t i = 0, S = operands.size(); i < S; ++i) { + base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), op, base, operands[i]); + } + return base; + } + + Expression_Obj Parser::fold_operands(Expression_Obj base, std::vector& operands, std::vector& ops, size_t i) + { + if (String_Schema_Ptr schema = Cast(base)) { + // return schema; + if (schema->has_interpolants()) { + if (i + 1 < operands.size() && ( + (ops[0].operand == Sass_OP::EQ) + || (ops[0].operand == Sass_OP::ADD) + || (ops[0].operand == Sass_OP::DIV) + || (ops[0].operand == Sass_OP::MUL) + || (ops[0].operand == Sass_OP::NEQ) + || (ops[0].operand == Sass_OP::LT) + || (ops[0].operand == Sass_OP::GT) + || (ops[0].operand == Sass_OP::LTE) + || (ops[0].operand == Sass_OP::GTE) + )) { + Expression_Obj rhs = fold_operands(operands[i], operands, ops, i + 1); + rhs = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[0], schema, rhs); + return rhs; + } + // return schema; + } + } + + for (size_t S = operands.size(); i < S; ++i) { + if (String_Schema_Ptr schema = Cast(operands[i])) { + if (schema->has_interpolants()) { + if (i + 1 < S) { + // this whole branch is never hit via spec tests + Expression_Obj rhs = fold_operands(operands[i+1], operands, ops, i + 2); + rhs = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], schema, rhs); + base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], base, rhs); + return base; + } + base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], base, operands[i]); + return base; + } else { + base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], base, operands[i]); + } + } else { + base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], base, operands[i]); + } + Binary_Expression_Ptr b = Cast(base.ptr()); + if (b && ops[i].operand == Sass_OP::DIV && b->left()->is_delayed() && b->right()->is_delayed()) { + base->is_delayed(true); + } + } + // nested binary expression are never to be delayed + if (Binary_Expression_Ptr b = Cast(base)) { + if (Cast(b->left())) base->set_delayed(false); + if (Cast(b->right())) base->set_delayed(false); + } + return base; + } + + void Parser::error(std::string msg, Position pos) + { + Position p(pos.line ? pos : before_token); + ParserState pstate(path, source, p, Offset(0, 0)); + traces.push_back(Backtrace(pstate)); + throw Exception::InvalidSass(pstate, traces, msg); + } + + void Parser::error(std::string msg) + { + error(msg, pstate); + } + + // print a css parsing error with actual context information from parsed source + void Parser::css_error(const std::string& msg, const std::string& prefix, const std::string& middle, const bool trim) + { + int max_len = 18; + const char* end = this->end; + while (*end != 0) ++ end; + const char* pos = peek < optional_spaces >(); + if (!pos) pos = position; + + const char* last_pos(pos); + if (last_pos > source) { + utf8::prior(last_pos, source); + } + // backup position to last significant char + while (trim && last_pos > source && last_pos < end) { + if (!Prelexer::is_space(*last_pos)) break; + utf8::prior(last_pos, source); + } + + bool ellipsis_left = false; + const char* pos_left(last_pos); + const char* end_left(last_pos); + + if (*pos_left) utf8::next(pos_left, end); + if (*end_left) utf8::next(end_left, end); + while (pos_left > source) { + if (utf8::distance(pos_left, end_left) >= max_len) { + utf8::prior(pos_left, source); + ellipsis_left = *(pos_left) != '\n' && + *(pos_left) != '\r'; + utf8::next(pos_left, end); + break; + } + + const char* prev = pos_left; + utf8::prior(prev, source); + if (*prev == '\r') break; + if (*prev == '\n') break; + pos_left = prev; + } + if (pos_left < source) { + pos_left = source; + } + + bool ellipsis_right = false; + const char* end_right(pos); + const char* pos_right(pos); + while (end_right < end) { + if (utf8::distance(pos_right, end_right) > max_len) { + ellipsis_left = *(pos_right) != '\n' && + *(pos_right) != '\r'; + break; + } + if (*end_right == '\r') break; + if (*end_right == '\n') break; + utf8::next(end_right, end); + } + // if (*end_right == 0) end_right ++; + + std::string left(pos_left, end_left); + std::string right(pos_right, end_right); + size_t left_subpos = left.size() > 15 ? left.size() - 15 : 0; + size_t right_subpos = right.size() > 15 ? right.size() - 15 : 0; + if (left_subpos && ellipsis_left) left = ellipsis + left.substr(left_subpos); + if (right_subpos && ellipsis_right) right = right.substr(right_subpos) + ellipsis; + // Hotfix when source is null, probably due to interpolation parsing!? + if (source == NULL || *source == 0) source = pstate.src; + // now pass new message to the more generic error function + error(msg + prefix + quote(left) + middle + quote(right)); + } + +} diff --git a/src/libsass/parser.hpp b/src/libsass/parser.hpp new file mode 100644 index 000000000..d2a6ddc1a --- /dev/null +++ b/src/libsass/parser.hpp @@ -0,0 +1,400 @@ +#ifndef SASS_PARSER_H +#define SASS_PARSER_H + +#include +#include + +#include "ast.hpp" +#include "position.hpp" +#include "context.hpp" +#include "position.hpp" +#include "prelexer.hpp" + +#ifndef MAX_NESTING +// Note that this limit is not an exact science +// it depends on various factors, which some are +// not under our control (compile time or even OS +// dependent settings on the available stack size) +// It should fix most common segfault cases though. +#define MAX_NESTING 512 +#endif + +struct Lookahead { + const char* found; + const char* error; + const char* position; + bool parsable; + bool has_interpolants; + bool is_custom_property; +}; + +namespace Sass { + + class Parser : public ParserState { + public: + + enum Scope { Root, Mixin, Function, Media, Control, Properties, Rules, AtRoot }; + + Context& ctx; + std::vector block_stack; + std::vector stack; + Media_Block_Ptr last_media_block; + const char* source; + const char* position; + const char* end; + Position before_token; + Position after_token; + ParserState pstate; + Backtraces traces; + size_t indentation; + size_t nestings; + + Token lexed; + + Parser(Context& ctx, const ParserState& pstate, Backtraces traces) + : ParserState(pstate), ctx(ctx), block_stack(), stack(0), last_media_block(), + source(0), position(0), end(0), before_token(pstate), after_token(pstate), + pstate(pstate), traces(traces), indentation(0), nestings(0) + { + stack.push_back(Scope::Root); + } + + // static Parser from_string(const std::string& src, Context& ctx, ParserState pstate = ParserState("[STRING]")); + static Parser from_c_str(const char* src, Context& ctx, Backtraces, ParserState pstate = ParserState("[CSTRING]"), const char* source = 0); + static Parser from_c_str(const char* beg, const char* end, Context& ctx, Backtraces, ParserState pstate = ParserState("[CSTRING]"), const char* source = 0); + static Parser from_token(Token t, Context& ctx, Backtraces, ParserState pstate = ParserState("[TOKEN]"), const char* source = 0); + // special static parsers to convert strings into certain selectors + static Selector_List_Obj parse_selector(const char* src, Context& ctx, Backtraces, ParserState pstate = ParserState("[SELECTOR]"), const char* source = 0); + +#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 + + + // skip current token and next whitespace + // moves ParserState right before next token + void advanceToNextToken(); + + bool peek_newline(const char* start = 0); + + // skip over spaces, tabs and line comments + template + const char* sneak(const char* start = 0) + { + using namespace Prelexer; + + // maybe use optional start position from arguments? + const char* it_position = start ? start : position; + + // skip white-space? + if (mx == spaces || + mx == no_spaces || + mx == css_comments || + mx == css_whitespace || + mx == optional_spaces || + mx == optional_css_comments || + mx == optional_css_whitespace + ) { + return it_position; + } + + // skip over spaces, tabs and sass line comments + const char* pos = optional_css_whitespace(it_position); + // always return a valid position + return pos ? pos : it_position; + + } + + // match will not skip over space, tabs and line comment + // return the position where the lexer match will occur + template + const char* match(const char* start = 0) + { + // match the given prelexer + return mx(position); + } + + // peek will only skip over space, tabs and line comment + // return the position where the lexer match will occur + template + const char* peek(const char* start = 0) + { + + // 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 + const char* match = mx(it_before_token); + + // check if match is in valid range + return match <= end ? match : 0; + + } + + // white-space handling is built into the lexer + // this way you do not need to parse it yourself + // some matchers don't accept certain white-space + // we do not support start arg, since we manipulate + // sourcemap offset and we modify the position pointer! + // lex will only skip over space, tabs and line comment + template + const char* lex(bool lazy = true, bool force = false) + { + + if (*position == 0) return 0; + + // 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); + + // check if match is in valid range + if (it_after_token > end) return 0; + + // maybe we want to update the parser state anyway? + if (force == false) { + // assertion that we got a valid match + if (it_after_token == 0) return 0; + // assertion that we actually lexed something + if (it_after_token == it_before_token) return 0; + } + + // create new lexed token object (holds the parse results) + 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 + const char* lex_css() + { + // copy old token + Token prev = lexed; + // store previous pointer + const char* oldpos = position; + Position bt = before_token; + Position at = after_token; + ParserState op = pstate; + // throw away comments + // update srcmap position + lex < Prelexer::css_comments >(); + // now lex a new token + const char* pos = lex< mx >(); + // maybe restore prev state + if (pos == 0) { + pstate = op; + lexed = prev; + position = oldpos; + after_token = at; + before_token = bt; + } + // return match + return pos; + } + + // all block comments will be skipped and thrown away + template + const char* peek_css(const char* start = 0) + { + // now peek a token (skip comments first) + return peek< mx >(peek < Prelexer::css_comments >(start)); + } + +#ifdef __clang__ + +#pragma clang diagnostic pop + +#endif + + void error(std::string msg); + void error(std::string msg, Position pos); + // generate message with given and expected sample + // text before and in the middle are configurable + void css_error(const std::string& msg, + const std::string& prefix = " after ", + const std::string& middle = ", was: ", + const bool trim = true); + void read_bom(); + + Block_Obj parse(); + Import_Obj parse_import(); + Definition_Obj parse_definition(Definition::Type which_type); + Parameters_Obj parse_parameters(); + Parameter_Obj parse_parameter(); + Mixin_Call_Obj parse_include_directive(); + Arguments_Obj parse_arguments(); + Argument_Obj parse_argument(); + Assignment_Obj parse_assignment(); + Ruleset_Obj parse_ruleset(Lookahead lookahead); + Selector_List_Obj parse_selector_list(bool chroot); + Complex_Selector_Obj parse_complex_selector(bool chroot); + Selector_Schema_Obj parse_selector_schema(const char* end_of_selector, bool chroot); + Compound_Selector_Obj parse_compound_selector(); + Simple_Selector_Obj parse_simple_selector(); + Wrapped_Selector_Obj parse_negated_selector(); + Simple_Selector_Obj parse_pseudo_selector(); + Attribute_Selector_Obj parse_attribute_selector(); + Block_Obj parse_block(bool is_root = false); + Block_Obj parse_css_block(bool is_root = false); + bool parse_block_nodes(bool is_root = false); + bool parse_block_node(bool is_root = false); + + bool parse_number_prefix(); + Declaration_Obj parse_declaration(); + Expression_Obj parse_map(); + Expression_Obj parse_bracket_list(); + Expression_Obj parse_list(bool delayed = false); + Expression_Obj parse_comma_list(bool delayed = false); + Expression_Obj parse_space_list(); + Expression_Obj parse_disjunction(); + Expression_Obj parse_conjunction(); + Expression_Obj parse_relation(); + Expression_Obj parse_expression(); + Expression_Obj parse_operators(); + Expression_Obj parse_factor(); + Expression_Obj parse_value(); + Function_Call_Obj parse_calc_function(); + Function_Call_Obj parse_function_call(); + Function_Call_Schema_Obj parse_function_call_schema(); + String_Obj parse_url_function_string(); + String_Obj parse_url_function_argument(); + String_Obj parse_interpolated_chunk(Token, bool constant = false, bool css = true); + String_Obj parse_string(); + Value_Obj parse_static_value(); + String_Schema_Obj parse_css_variable_value(bool top_level = true); + String_Schema_Obj parse_css_variable_value_token(bool top_level = true); + String_Obj parse_ie_property(); + String_Obj parse_ie_keyword_arg(); + String_Schema_Obj parse_value_schema(const char* stop); + String_Obj parse_identifier_schema(); + If_Obj parse_if_directive(bool else_if = false); + For_Obj parse_for_directive(); + Each_Obj parse_each_directive(); + While_Obj parse_while_directive(); + Return_Obj parse_return_directive(); + Content_Obj parse_content_directive(); + void parse_charset_directive(); + Media_Block_Obj parse_media_block(); + List_Obj parse_media_queries(); + Media_Query_Obj parse_media_query(); + Media_Query_Expression_Obj parse_media_expression(); + Supports_Block_Obj parse_supports_directive(); + Supports_Condition_Obj parse_supports_condition(); + Supports_Condition_Obj parse_supports_negation(); + Supports_Condition_Obj parse_supports_operator(); + Supports_Condition_Obj parse_supports_interpolation(); + Supports_Condition_Obj parse_supports_declaration(); + Supports_Condition_Obj parse_supports_condition_in_parens(); + At_Root_Block_Obj parse_at_root_block(); + At_Root_Query_Obj parse_at_root_query(); + String_Schema_Obj parse_almost_any_value(); + Directive_Obj parse_special_directive(); + Directive_Obj parse_prefixed_directive(); + Directive_Obj parse_directive(); + Warning_Obj parse_warning(); + Error_Obj parse_error(); + Debug_Obj parse_debug(); + + Value_Ptr color_or_string(const std::string& lexed) const; + + // be more like ruby sass + Expression_Obj lex_almost_any_value_token(); + Expression_Obj lex_almost_any_value_chars(); + Expression_Obj lex_interp_string(); + Expression_Obj lex_interp_uri(); + Expression_Obj lex_interpolation(); + + // these will throw errors + Token lex_variable(); + Token lex_identifier(); + + void parse_block_comments(); + + Lookahead lookahead_for_value(const char* start = 0); + Lookahead lookahead_for_selector(const char* start = 0); + Lookahead lookahead_for_include(const char* start = 0); + + Expression_Obj fold_operands(Expression_Obj base, std::vector& operands, Operand op); + Expression_Obj fold_operands(Expression_Obj base, std::vector& operands, std::vector& ops, size_t i = 0); + + void throw_syntax_error(std::string message, size_t ln = 0); + void throw_read_error(std::string message, size_t ln = 0); + + + template + Expression_Obj lex_interp() + { + if (lex < open >(false)) { + String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate); + // std::cerr << "LEX [[" << std::string(lexed) << "]]\n"; + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed)); + if (position[0] == '#' && position[1] == '{') { + Expression_Obj itpl = lex_interpolation(); + if (!itpl.isNull()) schema->append(itpl); + while (lex < close >(false)) { + // std::cerr << "LEX [[" << std::string(lexed) << "]]\n"; + schema->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed)); + if (position[0] == '#' && position[1] == '{') { + Expression_Obj itpl = lex_interpolation(); + if (!itpl.isNull()) schema->append(itpl); + } else { + return schema; + } + } + } else { + return SASS_MEMORY_NEW(String_Constant, pstate, lexed); + } + } + return 0; + } + + public: + static Number_Ptr lexed_number(const ParserState& pstate, const std::string& parsed); + static Number_Ptr lexed_dimension(const ParserState& pstate, const std::string& parsed); + static Number_Ptr lexed_percentage(const ParserState& pstate, const std::string& parsed); + static Value_Ptr lexed_hex_color(const ParserState& pstate, const std::string& parsed); + private: + Number_Ptr lexed_number(const std::string& parsed) { return lexed_number(pstate, parsed); }; + Number_Ptr lexed_dimension(const std::string& parsed) { return lexed_dimension(pstate, parsed); }; + Number_Ptr lexed_percentage(const std::string& parsed) { return lexed_percentage(pstate, parsed); }; + Value_Ptr lexed_hex_color(const std::string& parsed) { return lexed_hex_color(pstate, parsed); }; + + static const char* re_attr_sensitive_close(const char* src); + static const char* re_attr_insensitive_close(const char* src); + + }; + + size_t check_bom_chars(const char* src, const char *end, const unsigned char* bom, size_t len); +} + +#endif diff --git a/src/libsass/plugins.md b/src/libsass/plugins.md new file mode 100644 index 000000000..a9711e3e1 --- /dev/null +++ b/src/libsass/plugins.md @@ -0,0 +1,47 @@ +Plugins are shared object files (.so on *nix and .dll on win) that can be loaded by LibSass on runtime. Currently we only provide a way to load internal/custom functions from plugins. In the future we probably will also add a way to provide custom importers via plugins (needs more refactoring to [support multiple importers with some kind of priority system](https://github.com/sass/libsass/issues/962)). + +## plugin.cpp + +```C++ +#include +#include +#include +#include "sass_values.h" + +union Sass_Value* ADDCALL call_fn_foo(const union Sass_Value* s_args, void* cookie) +{ + // we actually abuse the void* to store an "int" + return sass_make_number((intptr_t)cookie, "px"); +} + +extern "C" const char* ADDCALL libsass_get_version() { + return libsass_version(); +} + +extern "C" Sass_C_Function_List ADDCALL libsass_load_functions() +{ + // allocate a custom function caller + Sass_C_Function_Callback fn_foo = + sass_make_function("foo()", call_fn_foo, (void*)42); + // create list of all custom functions + Sass_C_Function_List fn_list = sass_make_function_list(1); + // put the only function in this plugin to the list + sass_function_set_list_entry(fn_list, 0, fn_foo); + // return the list + return fn_list; +} +``` + +To compile the plugin you need to have LibSass already built as a shared library (to link against it). The commands below expect the shared library in the `lib` sub-directory (`-Llib`). The plugin and the main LibSass process should "consume" the same shared LibSass library on runtime. It will propably also work if they use different LibSass versions. In this case we check if the major versions are compatible (i.e. 3.1.3 and 3.1.1 would be considered compatible). + +## Compile with gcc on linux + +```bash +g++ -O2 -shared plugin.cpp -o plugin.so -fPIC -Llib -lsass +``` + +## Compile with mingw on windows + +```bash +g++ -O2 -shared plugin.cpp -o plugin.dll -Llib -lsass +``` diff --git a/src/libsass/prelexer.cpp b/src/libsass/prelexer.cpp new file mode 100644 index 000000000..a43b1ee3c --- /dev/null +++ b/src/libsass/prelexer.cpp @@ -0,0 +1,1774 @@ +#include "sass.hpp" +#include +#include +#include +#include "util.hpp" +#include "position.hpp" +#include "prelexer.hpp" +#include "constants.hpp" + + +namespace Sass { + // using namespace Lexer; + using namespace Constants; + + namespace Prelexer { + + + /* + + def string_re(open, close) + /#{open}((?:\\.|\#(?!\{)|[^#{close}\\#])*)(#{close}|#\{)/m + end + end + + # A hash of regular expressions that are used for tokenizing strings. + # + # The key is a `[Symbol, Boolean]` pair. + # The symbol represents which style of quotation to use, + # while the boolean represents whether or not the string + # is following an interpolated segment. + STRING_REGULAR_EXPRESSIONS = { + :double => { + /#{open}((?:\\.|\#(?!\{)|[^#{close}\\#])*)(#{close}|#\{)/m + false => string_re('"', '"'), + true => string_re('', '"') + }, + :single => { + false => string_re("'", "'"), + true => string_re('', "'") + }, + :uri => { + false => /url\(#{W}(#{URLCHAR}*?)(#{W}\)|#\{)/, + true => /(#{URLCHAR}*?)(#{W}\)|#\{)/ + }, + # Defined in https://developer.mozilla.org/en/CSS/@-moz-document as a + # non-standard version of http://www.w3.org/TR/css3-conditional/ + :url_prefix => { + false => /url-prefix\(#{W}(#{URLCHAR}*?)(#{W}\)|#\{)/, + true => /(#{URLCHAR}*?)(#{W}\)|#\{)/ + }, + :domain => { + false => /domain\(#{W}(#{URLCHAR}*?)(#{W}\)|#\{)/, + true => /(#{URLCHAR}*?)(#{W}\)|#\{)/ + } + } + */ + + /* + /#{open} + ( + \\. + | + \# (?!\{) + | + [^#{close}\\#] + )* + (#{close}|#\{) + /m + false => string_re('"', '"'), + true => string_re('', '"') + */ + extern const char string_double_negates[] = "\"\\#"; + const char* re_string_double_close(const char* src) + { + return sequence < + // valid chars + zero_plus < + alternatives < + // escaped char + sequence < + exactly <'\\'>, + any_char + >, + // non interpolate hash + sequence < + exactly <'#'>, + negate < + exactly <'{'> + > + >, + // other valid chars + neg_class_char < + string_double_negates + > + > + >, + // quoted string closer + // or interpolate opening + alternatives < + exactly <'"'>, + lookahead < exactly< hash_lbrace > > + > + >(src); + } + + const char* re_string_double_open(const char* src) + { + return sequence < + // quoted string opener + exactly <'"'>, + // valid chars + zero_plus < + alternatives < + // escaped char + sequence < + exactly <'\\'>, + any_char + >, + // non interpolate hash + sequence < + exactly <'#'>, + negate < + exactly <'{'> + > + >, + // other valid chars + neg_class_char < + string_double_negates + > + > + >, + // quoted string closer + // or interpolate opening + alternatives < + exactly <'"'>, + lookahead < exactly< hash_lbrace > > + > + >(src); + } + + extern const char string_single_negates[] = "'\\#"; + const char* re_string_single_close(const char* src) + { + return sequence < + // valid chars + zero_plus < + alternatives < + // escaped char + sequence < + exactly <'\\'>, + any_char + >, + // non interpolate hash + sequence < + exactly <'#'>, + negate < + exactly <'{'> + > + >, + // other valid chars + neg_class_char < + string_single_negates + > + > + >, + // quoted string closer + // or interpolate opening + alternatives < + exactly <'\''>, + lookahead < exactly< hash_lbrace > > + > + >(src); + } + + const char* re_string_single_open(const char* src) + { + return sequence < + // quoted string opener + exactly <'\''>, + // valid chars + zero_plus < + alternatives < + // escaped char + sequence < + exactly <'\\'>, + any_char + >, + // non interpolate hash + sequence < + exactly <'#'>, + negate < + exactly <'{'> + > + >, + // other valid chars + neg_class_char < + string_single_negates + > + > + >, + // quoted string closer + // or interpolate opening + alternatives < + exactly <'\''>, + lookahead < exactly< hash_lbrace > > + > + >(src); + } + + /* + :uri => { + false => /url\(#{W}(#{URLCHAR}*?)(#{W}\)|#\{)/, + true => /(#{URLCHAR}*?)(#{W}\)|#\{)/ + }, + */ + const char* re_string_uri_close(const char* src) + { + return sequence < + non_greedy< + alternatives< + class_char< real_uri_chars >, + uri_character, + NONASCII, + ESCAPE + >, + alternatives< + sequence < optional < W >, exactly <')'> >, + lookahead < exactly< hash_lbrace > > + > + >, + optional < + sequence < optional < W >, exactly <')'> > + > + >(src); + } + + const char* re_string_uri_open(const char* src) + { + return sequence < + exactly <'u'>, + exactly <'r'>, + exactly <'l'>, + exactly <'('>, + W, + alternatives< + quoted_string, + non_greedy< + alternatives< + class_char< real_uri_chars >, + uri_character, + NONASCII, + ESCAPE + >, + alternatives< + sequence < W, exactly <')'> >, + exactly< hash_lbrace > + > + > + > + >(src); + } + + // Match a line comment (/.*?(?=\n|\r\n?|\Z)/. + const char* line_comment(const char* src) + { + return sequence< + exactly < + slash_slash + >, + non_greedy< + any_char, + end_of_line + > + >(src); + } + + // Match a block comment. + const char* block_comment(const char* src) + { + return sequence< + delimited_by< + slash_star, + star_slash, + false + > + >(src); + } + /* not use anymore - remove? + const char* block_comment_prefix(const char* src) { + return exactly(src); + } + // Match either comment. + const char* comment(const char* src) { + return line_comment(src); + } + */ + + // Match zero plus white-space or line_comments + const char* optional_css_whitespace(const char* src) { + return zero_plus< alternatives >(src); + } + const char* css_whitespace(const char* src) { + return one_plus< alternatives >(src); + } + // Match optional_css_whitepace plus block_comments + const char* optional_css_comments(const char* src) { + return zero_plus< alternatives >(src); + } + const char* css_comments(const char* src) { + return one_plus< alternatives >(src); + } + + // Match one backslash escaped char /\\./ + const char* escape_seq(const char* src) + { + return sequence< + exactly<'\\'>, + alternatives < + minmax_range< + 1, 3, xdigit + >, + any_char + >, + optional < + exactly <' '> + > + >(src); + } + + // Match identifier start + const char* identifier_alpha(const char* src) + { + return alternatives< + unicode_seq, + alpha, + unicode, + exactly<'-'>, + exactly<'_'>, + NONASCII, + ESCAPE, + escape_seq + >(src); + } + + // Match identifier after start + const char* identifier_alnum(const char* src) + { + return alternatives< + unicode_seq, + alnum, + unicode, + exactly<'-'>, + exactly<'_'>, + NONASCII, + ESCAPE, + escape_seq + >(src); + } + + // Match CSS identifiers. + const char* strict_identifier(const char* src) + { + return sequence< + one_plus < strict_identifier_alpha >, + zero_plus < strict_identifier_alnum > + // word_boundary not needed + >(src); + } + + // Match CSS identifiers. + const char* identifier(const char* src) + { + return sequence< + zero_plus< exactly<'-'> >, + one_plus < identifier_alpha >, + zero_plus < identifier_alnum > + // word_boundary not needed + >(src); + } + + const char* strict_identifier_alpha(const char* src) + { + return alternatives < + alpha, + unicode, + escape_seq, + exactly<'_'> + >(src); + } + + const char* strict_identifier_alnum(const char* src) + { + return alternatives < + alnum, + unicode, + escape_seq, + exactly<'_'> + >(src); + } + + // Match a single CSS unit + const char* one_unit(const char* src) + { + return sequence < + optional < exactly <'-'> >, + strict_identifier_alpha, + zero_plus < alternatives< + strict_identifier_alnum, + sequence < + one_plus < exactly<'-'> >, + strict_identifier_alpha + > + > > + >(src); + } + + // Match numerator/denominator CSS units + const char* multiple_units(const char* src) + { + return + sequence < + one_unit, + zero_plus < + sequence < + exactly <'*'>, + one_unit + > + > + >(src); + } + + // Match complex CSS unit identifiers + const char* unit_identifier(const char* src) + { + return sequence < + multiple_units, + optional < + sequence < + exactly <'/'>, + negate < sequence < + exactly < calc_fn_kwd >, + exactly < '(' > + > >, + multiple_units + > > + >(src); + } + + const char* identifier_alnums(const char* src) + { + return one_plus< identifier_alnum >(src); + } + + // Match number prefix ([\+\-]+) + const char* number_prefix(const char* src) { + return alternatives < + exactly < '+' >, + sequence < + exactly < '-' >, + optional_css_whitespace, + exactly< '-' > + > + >(src); + } + + // Match interpolant schemas + const char* identifier_schema(const char* src) { + + return sequence < + one_plus < + sequence < + zero_plus < + alternatives < + sequence < + optional < + exactly <'$'> + >, + identifier + >, + exactly <'-'> + > + >, + interpolant, + zero_plus < + alternatives < + digits, + sequence < + optional < + exactly <'$'> + >, + identifier + >, + quoted_string, + exactly<'-'> + > + > + > + >, + negate < + exactly<'%'> + > + > (src); + } + + // interpolants can be recursive/nested + const char* interpolant(const char* src) { + return recursive_scopes< exactly, exactly >(src); + } + + // $re_squote = /'(?:$re_itplnt|\\.|[^'])*'/ + const char* single_quoted_string(const char* src) { + // match a single quoted string, while skipping interpolants + return sequence < + exactly <'\''>, + zero_plus < + alternatives < + // skip escapes + sequence < + exactly < '\\' >, + re_linebreak + >, + escape_seq, + unicode_seq, + // skip interpolants + interpolant, + // skip non delimiters + any_char_but < '\'' > + > + >, + exactly <'\''> + >(src); + } + + // $re_dquote = /"(?:$re_itp|\\.|[^"])*"/ + const char* double_quoted_string(const char* src) { + // match a single quoted string, while skipping interpolants + return sequence < + exactly <'"'>, + zero_plus < + alternatives < + // skip escapes + sequence < + exactly < '\\' >, + re_linebreak + >, + escape_seq, + unicode_seq, + // skip interpolants + interpolant, + // skip non delimiters + any_char_but < '"' > + > + >, + exactly <'"'> + >(src); + } + + // $re_quoted = /(?:$re_squote|$re_dquote)/ + const char* quoted_string(const char* src) { + // match a quoted string, while skipping interpolants + return alternatives< + single_quoted_string, + double_quoted_string + >(src); + } + + const char* sass_value(const char* src) { + return alternatives < + quoted_string, + identifier, + percentage, + hex, + dimension, + number + >(src); + } + + // this is basically `one_plus < sass_value >` + // takes care to not parse invalid combinations + const char* value_combinations(const char* src) { + // `2px-2px` is invalid combo + bool was_number = false; + const char* pos; + while (src) { + if ((pos = alternatives < quoted_string, identifier, percentage, hex >(src))) { + was_number = false; + src = pos; + } else if (!was_number && !exactly<'+'>(src) && (pos = alternatives < dimension, number >(src))) { + was_number = true; + src = pos; + } else { + break; + } + } + return src; + } + + // must be at least one interpolant + // can be surrounded by sass values + // make sure to never parse (dim)(dim) + // since this wrongly consumes `2px-1px` + // `2px1px` is valid number (unit `px1px`) + const char* value_schema(const char* src) + { + return sequence < + one_plus < + sequence < + optional < value_combinations >, + interpolant, + optional < value_combinations > + > + > + >(src); + } + + // Match CSS '@' keywords. + const char* at_keyword(const char* src) { + return sequence, identifier>(src); + } + + /* + tok(%r{ + ( + \\. + | + (?!url\() + [^"'/\#!;\{\}] # " + | + /(?![\*\/]) + | + \#(?!\{) + | + !(?![a-z]) # TODO: never consume "!" when issue 1126 is fixed. + )+ + }xi) || tok(COMMENT) || tok(SINGLE_LINE_COMMENT) || interp_string || interp_uri || + interpolation(:warn_for_color) + */ + const char* re_almost_any_value_token(const char* src) { + + return alternatives < + one_plus < + alternatives < + sequence < + exactly <'\\'>, + any_char + >, + sequence < + negate < + uri_prefix + >, + neg_class_char < + almost_any_value_class + > + >, + sequence < + exactly <'/'>, + negate < + alternatives < + exactly <'/'>, + exactly <'*'> + > + > + >, + sequence < + exactly <'\\'>, + exactly <'#'>, + negate < + exactly <'{'> + > + >, + sequence < + exactly <'!'>, + negate < + alpha + > + > + > + >, + block_comment, + line_comment, + interpolant, + space, + sequence < + exactly<'u'>, + exactly<'r'>, + exactly<'l'>, + exactly<'('>, + zero_plus < + alternatives < + class_char< real_uri_chars >, + uri_character, + NONASCII, + ESCAPE + > + >, + // false => /url\(#{W}(#{URLCHAR}*?)(#{W}\)|#\{)/, + // true => /(#{URLCHAR}*?)(#{W}\)|#\{)/ + exactly<')'> + > + >(src); + } + + /* + DIRECTIVES = Set[:mixin, :include, :function, :return, :debug, :warn, :for, + :each, :while, :if, :else, :extend, :import, :media, :charset, :content, + :_moz_document, :at_root, :error] + */ + const char* re_special_directive(const char* src) { + return alternatives < + word < mixin_kwd >, + word < include_kwd >, + word < function_kwd >, + word < return_kwd >, + word < debug_kwd >, + word < warn_kwd >, + word < for_kwd >, + word < each_kwd >, + word < while_kwd >, + word < if_kwd >, + word < else_kwd >, + word < extend_kwd >, + word < import_kwd >, + word < media_kwd >, + word < charset_kwd >, + word < content_kwd >, + // exactly < moz_document_kwd >, + word < at_root_kwd >, + word < error_kwd > + >(src); + } + + const char* re_prefixed_directive(const char* src) { + return sequence < + optional < + sequence < + exactly <'-'>, + one_plus < alnum >, + exactly <'-'> + > + >, + exactly < supports_kwd > + >(src); + } + + const char* re_reference_combinator(const char* src) { + return sequence < + optional < + sequence < + zero_plus < + exactly <'-'> + >, + identifier, + exactly <'|'> + > + >, + zero_plus < + exactly <'-'> + >, + identifier + >(src); + } + + const char* static_reference_combinator(const char* src) { + return sequence < + exactly <'/'>, + re_reference_combinator, + exactly <'/'> + >(src); + } + + const char* schema_reference_combinator(const char* src) { + return sequence < + exactly <'/'>, + optional < + sequence < + css_ip_identifier, + exactly <'|'> + > + >, + css_ip_identifier, + exactly <'/'> + > (src); + } + + const char* kwd_import(const char* src) { + return word(src); + } + + const char* kwd_at_root(const char* src) { + return word(src); + } + + const char* kwd_with_directive(const char* src) { + return word(src); + } + + const char* kwd_without_directive(const char* src) { + return word(src); + } + + const char* kwd_media(const char* src) { + return word(src); + } + + const char* kwd_supports_directive(const char* src) { + return word(src); + } + + const char* kwd_mixin(const char* src) { + return word(src); + } + + const char* kwd_function(const char* src) { + return word(src); + } + + const char* kwd_return_directive(const char* src) { + return word(src); + } + + const char* kwd_include_directive(const char* src) { + return word(src); + } + + const char* kwd_content_directive(const char* src) { + return word(src); + } + + const char* kwd_charset_directive(const char* src) { + return word(src); + } + + const char* kwd_extend(const char* src) { + return word(src); + } + + + const char* kwd_if_directive(const char* src) { + return word(src); + } + + const char* kwd_else_directive(const char* src) { + return word(src); + } + const char* elseif_directive(const char* src) { + return sequence< exactly< else_kwd >, + optional_css_comments, + word< if_after_else_kwd > >(src); + } + + const char* kwd_for_directive(const char* src) { + return word(src); + } + + const char* kwd_from(const char* src) { + return word(src); + } + + const char* kwd_to(const char* src) { + return word(src); + } + + const char* kwd_through(const char* src) { + return word(src); + } + + const char* kwd_each_directive(const char* src) { + return word(src); + } + + const char* kwd_in(const char* src) { + return word(src); + } + + const char* kwd_while_directive(const char* src) { + return word(src); + } + + const char* name(const char* src) { + return one_plus< alternatives< alnum, + exactly<'-'>, + exactly<'_'>, + escape_seq > >(src); + } + + const char* kwd_warn(const char* src) { + return word(src); + } + + const char* kwd_err(const char* src) { + return word(src); + } + + const char* kwd_dbg(const char* src) { + return word(src); + } + + /* not used anymore - remove? + const char* directive(const char* src) { + return sequence< exactly<'@'>, identifier >(src); + } */ + + const char* kwd_null(const char* src) { + return word(src); + } + + const char* css_identifier(const char* src) { + return sequence < + zero_plus < + exactly <'-'> + >, + identifier + >(src); + } + + const char* css_ip_identifier(const char* src) { + return sequence < + zero_plus < + exactly <'-'> + >, + alternatives < + identifier, + interpolant + > + >(src); + } + + // Match CSS type selectors + const char* namespace_prefix(const char* src) { + return sequence < + optional < + alternatives < + exactly <'*'>, + css_identifier + > + >, + exactly <'|'>, + negate < + exactly <'='> + > + >(src); + } + + // Match CSS type selectors + const char* namespace_schema(const char* src) { + return sequence < + optional < + alternatives < + exactly <'*'>, + css_ip_identifier + > + >, + exactly<'|'>, + negate < + exactly <'='> + > + >(src); + } + + const char* hyphens_and_identifier(const char* src) { + return sequence< zero_plus< exactly< '-' > >, identifier_alnums >(src); + } + const char* hyphens_and_name(const char* src) { + return sequence< zero_plus< exactly< '-' > >, name >(src); + } + const char* universal(const char* src) { + return sequence< optional, exactly<'*'> >(src); + } + // Match CSS id names. + const char* id_name(const char* src) { + return sequence, identifier_alnums >(src); + } + // Match CSS class names. + const char* class_name(const char* src) { + return sequence, identifier >(src); + } + // Attribute name in an attribute selector. + const char* attribute_name(const char* src) { + return alternatives< sequence< optional, identifier>, + identifier >(src); + } + // match placeholder selectors + const char* placeholder(const char* src) { + return sequence, identifier_alnums >(src); + } + // Match CSS numeric constants. + + const char* op(const char* src) { + return class_char(src); + } + const char* sign(const char* src) { + return class_char(src); + } + const char* unsigned_number(const char* src) { + return alternatives, + exactly<'.'>, + one_plus >, + digits>(src); + } + const char* number(const char* src) { + return sequence< + optional, + unsigned_number, + optional< + sequence< + exactly<'e'>, + optional, + unsigned_number + > + > + >(src); + } + const char* coefficient(const char* src) { + return alternatives< sequence< optional, digits >, + sign >(src); + } + const char* binomial(const char* src) { + return sequence < + optional < sign >, + optional < digits >, + exactly <'n'>, + zero_plus < sequence < + optional_css_whitespace, sign, + optional_css_whitespace, digits + > > + >(src); + } + const char* percentage(const char* src) { + return sequence< number, exactly<'%'> >(src); + } + const char* ampersand(const char* src) { + return exactly<'&'>(src); + } + + /* not used anymore - remove? + const char* em(const char* src) { + return sequence< number, exactly >(src); + } */ + const char* dimension(const char* src) { + return sequence(src); + } + const char* hex(const char* src) { + const char* p = sequence< exactly<'#'>, one_plus >(src); + ptrdiff_t len = p - src; + return (len != 4 && len != 7) ? 0 : p; + } + const char* hexa(const char* src) { + const char* p = sequence< exactly<'#'>, one_plus >(src); + ptrdiff_t len = p - src; + return (len != 5 && len != 9) ? 0 : p; + } + const char* hex0(const char* src) { + const char* p = sequence< exactly<'0'>, exactly<'x'>, one_plus >(src); + ptrdiff_t len = p - src; + return (len != 5 && len != 8) ? 0 : p; + } + + /* no longer used - remove? + const char* rgb_prefix(const char* src) { + return word(src); + }*/ + // Match CSS uri specifiers. + + const char* uri_prefix(const char* src) { + return sequence < + exactly < + url_kwd + >, + zero_plus < + sequence < + exactly <'-'>, + one_plus < + alpha + > + > + >, + exactly <'('> + >(src); + } + + // TODO: rename the following two functions + /* no longer used - remove? + const char* uri(const char* src) { + return sequence< exactly, + optional, + quoted_string, + optional, + exactly<')'> >(src); + }*/ + /* no longer used - remove? + const char* url_value(const char* src) { + return sequence< optional< sequence< identifier, exactly<':'> > >, // optional protocol + one_plus< sequence< zero_plus< exactly<'/'> >, filename > >, // one or more folders and/or trailing filename + optional< exactly<'/'> > >(src); + }*/ + /* no longer used - remove? + const char* url_schema(const char* src) { + return sequence< optional< sequence< identifier, exactly<':'> > >, // optional protocol + filename_schema >(src); // optional trailing slash + }*/ + // Match CSS "!important" keyword. + const char* kwd_important(const char* src) { + return sequence< exactly<'!'>, + optional_css_whitespace, + word >(src); + } + // Match CSS "!optional" keyword. + const char* kwd_optional(const char* src) { + return sequence< exactly<'!'>, + optional_css_whitespace, + word >(src); + } + // Match Sass "!default" keyword. + const char* default_flag(const char* src) { + return sequence< exactly<'!'>, + optional_css_whitespace, + word >(src); + } + // Match Sass "!global" keyword. + const char* global_flag(const char* src) { + return sequence< exactly<'!'>, + optional_css_whitespace, + word >(src); + } + // Match CSS pseudo-class/element prefixes. + const char* pseudo_prefix(const char* src) { + return sequence< exactly<':'>, optional< exactly<':'> > >(src); + } + // Match CSS function call openers. + const char* functional_schema(const char* src) { + return sequence < + one_plus < + sequence < + zero_plus < + alternatives < + identifier, + exactly <'-'> + > + >, + one_plus < + sequence < + interpolant, + alternatives < + digits, + identifier, + exactly<'+'>, + exactly<'-'> + > + > + > + > + >, + negate < + exactly <'%'> + >, + lookahead < + exactly <'('> + > + > (src); + } + + const char* re_nothing(const char* src) { + return src; + } + + const char* re_functional(const char* src) { + return sequence< identifier, optional < block_comment >, exactly<'('> >(src); + } + const char* re_pseudo_selector(const char* src) { + return sequence< identifier, optional < block_comment >, exactly<'('> >(src); + } + // Match the CSS negation pseudo-class. + const char* pseudo_not(const char* src) { + return word< pseudo_not_fn_kwd >(src); + } + // Match CSS 'odd' and 'even' keywords for functional pseudo-classes. + const char* even(const char* src) { + return word(src); + } + const char* odd(const char* src) { + return word(src); + } + // Match CSS attribute-matching operators. + const char* exact_match(const char* src) { return exactly<'='>(src); } + const char* class_match(const char* src) { return exactly(src); } + const char* dash_match(const char* src) { return exactly(src); } + const char* prefix_match(const char* src) { return exactly(src); } + const char* suffix_match(const char* src) { return exactly(src); } + const char* substring_match(const char* src) { return exactly(src); } + // Match CSS combinators. + /* not used anymore - remove? + const char* adjacent_to(const char* src) { + return sequence< optional_spaces, exactly<'+'> >(src); + } + const char* precedes(const char* src) { + return sequence< optional_spaces, exactly<'~'> >(src); + } + const char* parent_of(const char* src) { + return sequence< optional_spaces, exactly<'>'> >(src); + } + const char* ancestor_of(const char* src) { + return sequence< spaces, negate< exactly<'{'> > >(src); + }*/ + + // Match SCSS variable names. + const char* variable(const char* src) { + return sequence, identifier>(src); + } + + // parse `calc`, `-a-calc` and `--b-c-calc` + // but do not parse `foocalc` or `foo-calc` + const char* calc_fn_call(const char* src) { + return sequence < + optional < sequence < + hyphens, + one_plus < sequence < + strict_identifier, + hyphens + > > + > >, + exactly < calc_fn_kwd >, + word_boundary + >(src); + } + + // Match Sass boolean keywords. + const char* kwd_true(const char* src) { + return word(src); + } + const char* kwd_false(const char* src) { + return word(src); + } + const char* kwd_only(const char* src) { + return keyword < only_kwd >(src); + } + const char* kwd_and(const char* src) { + return keyword < and_kwd >(src); + } + const char* kwd_or(const char* src) { + return keyword < or_kwd >(src); + } + const char* kwd_not(const char* src) { + return keyword < not_kwd >(src); + } + const char* kwd_eq(const char* src) { + return exactly(src); + } + const char* kwd_neq(const char* src) { + return exactly(src); + } + const char* kwd_gt(const char* src) { + return exactly(src); + } + const char* kwd_gte(const char* src) { + return exactly(src); + } + const char* kwd_lt(const char* src) { + return exactly(src); + } + const char* kwd_lte(const char* src) { + return exactly(src); + } + + // match specific IE syntax + const char* ie_progid(const char* src) { + return sequence < + word, + exactly<':'>, + alternatives< identifier_schema, identifier >, + zero_plus< sequence< + exactly<'.'>, + alternatives< identifier_schema, identifier > + > >, + zero_plus < sequence< + exactly<'('>, + optional_css_whitespace, + optional < sequence< + alternatives< variable, identifier_schema, identifier >, + optional_css_whitespace, + exactly<'='>, + optional_css_whitespace, + alternatives< variable, identifier_schema, identifier, quoted_string, number, hex, hexa >, + zero_plus< sequence< + optional_css_whitespace, + exactly<','>, + optional_css_whitespace, + sequence< + alternatives< variable, identifier_schema, identifier >, + optional_css_whitespace, + exactly<'='>, + optional_css_whitespace, + alternatives< variable, identifier_schema, identifier, quoted_string, number, hex, hexa > + > + > > + > >, + optional_css_whitespace, + exactly<')'> + > > + >(src); + } + const char* ie_expression(const char* src) { + return sequence < word, exactly<'('>, skip_over_scopes< exactly<'('>, exactly<')'> > >(src); + } + const char* ie_property(const char* src) { + return alternatives < ie_expression, ie_progid >(src); + } + + // const char* ie_args(const char* src) { + // return sequence< alternatives< ie_keyword_arg, value_schema, quoted_string, interpolant, number, identifier, delimited_by< '(', ')', true> >, + // zero_plus< sequence< optional_css_whitespace, exactly<','>, optional_css_whitespace, alternatives< ie_keyword_arg, value_schema, quoted_string, interpolant, number, identifier, delimited_by<'(', ')', true> > > > >(src); + // } + + const char* ie_keyword_arg_property(const char* src) { + return alternatives < + variable, + identifier_schema, + identifier + >(src); + } + const char* ie_keyword_arg_value(const char* src) { + return alternatives < + variable, + identifier_schema, + identifier, + quoted_string, + number, + hex, + hexa, + sequence < + exactly < '(' >, + skip_over_scopes < + exactly < '(' >, + exactly < ')' > + > + > + >(src); + } + + const char* ie_keyword_arg(const char* src) { + return sequence < + ie_keyword_arg_property, + optional_css_whitespace, + exactly<'='>, + optional_css_whitespace, + ie_keyword_arg_value + >(src); + } + + // Path matching functions. + /* not used anymore - remove? + const char* folder(const char* src) { + return sequence< zero_plus< any_char_except<'/'> >, + exactly<'/'> >(src); + } + const char* folders(const char* src) { + return zero_plus< folder >(src); + }*/ + /* not used anymore - remove? + const char* chunk(const char* src) { + char inside_str = 0; + const char* p = src; + size_t depth = 0; + while (true) { + if (!*p) { + return 0; + } + else if (!inside_str && (*p == '"' || *p == '\'')) { + inside_str = *p; + } + else if (*p == inside_str && *(p-1) != '\\') { + inside_str = 0; + } + else if (*p == '(' && !inside_str) { + ++depth; + } + else if (*p == ')' && !inside_str) { + if (depth == 0) return p; + else --depth; + } + ++p; + } + // unreachable + return 0; + } + */ + + // follow the CSS spec more closely and see if this helps us scan URLs correctly + /* not used anymore - remove? + const char* NL(const char* src) { + return alternatives< exactly<'\n'>, + sequence< exactly<'\r'>, exactly<'\n'> >, + exactly<'\r'>, + exactly<'\f'> >(src); + }*/ + + const char* H(const char* src) { + return std::isxdigit(*src) ? src+1 : 0; + } + + const char* W(const char* src) { + return zero_plus< alternatives< + space, + exactly< '\t' >, + exactly< '\r' >, + exactly< '\n' >, + exactly< '\f' > + > >(src); + } + + const char* UUNICODE(const char* src) { + return sequence< exactly<'\\'>, + between, + optional< W > + >(src); + } + + const char* NONASCII(const char* src) { + return nonascii(src); + } + + const char* ESCAPE(const char* src) { + return alternatives< + UUNICODE, + sequence< + exactly<'\\'>, + alternatives< + NONASCII, + escapable_character + > + > + >(src); + } + + const char* list_terminator(const char* src) { + return alternatives < + exactly<';'>, + exactly<'}'>, + exactly<'{'>, + exactly<')'>, + exactly<']'>, + exactly<':'>, + end_of_file, + exactly, + default_flag, + global_flag + >(src); + }; + + const char* space_list_terminator(const char* src) { + return alternatives < + exactly<','>, + list_terminator + >(src); + }; + + + // const char* real_uri_prefix(const char* src) { + // return alternatives< + // exactly< url_kwd >, + // exactly< url_prefix_kwd > + // >(src); + // } + + const char* real_uri(const char* src) { + return sequence< + exactly< url_kwd >, + exactly< '(' >, + W, + real_uri_value, + exactly< ')' > + >(src); + } + + const char* real_uri_suffix(const char* src) { + return sequence< W, exactly< ')' > >(src); + } + + const char* real_uri_value(const char* src) { + return + sequence< + non_greedy< + alternatives< + class_char< real_uri_chars >, + uri_character, + NONASCII, + ESCAPE + >, + alternatives< + real_uri_suffix, + exactly< hash_lbrace > + > + > + > + (src); + } + + const char* static_string(const char* src) { + const char* pos = src; + const char * s = quoted_string(pos); + Token t(pos, s); + const unsigned int p = count_interval< interpolant >(t.begin, t.end); + return (p == 0) ? t.end : 0; + } + + const char* unicode_seq(const char* src) { + return sequence < + alternatives < + exactly< 'U' >, + exactly< 'u' > + >, + exactly< '+' >, + padded_token < + 6, xdigit, + exactly < '?' > + > + >(src); + } + + const char* static_component(const char* src) { + return alternatives< identifier, + static_string, + percentage, + hex, + hexa, + exactly<'|'>, + // exactly<'+'>, + sequence < number, unit_identifier >, + number, + sequence< exactly<'!'>, word > + >(src); + } + + const char* static_property(const char* src) { + return + sequence < + zero_plus< + sequence < + optional_css_comments, + alternatives < + exactly<','>, + exactly<'('>, + exactly<')'>, + kwd_optional, + quoted_string, + interpolant, + identifier, + percentage, + dimension, + variable, + alnum, + sequence < + exactly <'\\'>, + any_char + > + > + > + >, + lookahead < + sequence < + optional_css_comments, + alternatives < + exactly <';'>, + exactly <'}'>, + end_of_file + > + > + > + >(src); + } + + const char* static_value(const char* src) { + return sequence< sequence< + static_component, + zero_plus< identifier > + >, + zero_plus < sequence< + alternatives< + sequence< optional_spaces, alternatives< + exactly < '/' >, + exactly < ',' >, + exactly < ' ' > + >, optional_spaces >, + spaces + >, + static_component + > >, + zero_plus < spaces >, + alternatives< exactly<';'>, exactly<'}'> > + >(src); + } + + extern const char css_variable_url_negates[] = "()[]{}\"'#/"; + const char* css_variable_value(const char* src) { + return sequence< + alternatives< + sequence< + negate< exactly< url_fn_kwd > >, + one_plus< neg_class_char< css_variable_url_negates > > + >, + sequence< exactly<'#'>, negate< exactly<'{'> > >, + sequence< exactly<'/'>, negate< exactly<'*'> > >, + static_string, + real_uri, + block_comment + > + >(src); + } + + extern const char css_variable_url_top_level_negates[] = "()[]{}\"'#/;"; + const char* css_variable_top_level_value(const char* src) { + return sequence< + alternatives< + sequence< + negate< exactly< url_fn_kwd > >, + one_plus< neg_class_char< css_variable_url_top_level_negates > > + >, + sequence< exactly<'#'>, negate< exactly<'{'> > >, + sequence< exactly<'/'>, negate< exactly<'*'> > >, + static_string, + real_uri, + block_comment + > + >(src); + } + + const char* parenthese_scope(const char* src) { + return sequence < + exactly < '(' >, + skip_over_scopes < + exactly < '(' >, + exactly < ')' > + > + >(src); + } + + const char* re_selector_list(const char* src) { + return alternatives < + // partial bem selector + sequence < + ampersand, + one_plus < + exactly < '-' > + >, + word_boundary, + optional_spaces + >, + // main selector matching + one_plus < + alternatives < + // consume whitespace and comments + spaces, block_comment, line_comment, + // match `/deep/` selector (pass-trough) + // there is no functionality for it yet + schema_reference_combinator, + // match selector ops /[*&%,\[\]]/ + class_char < selector_lookahead_ops >, + // match selector combinators /[>+~]/ + class_char < selector_combinator_ops >, + // match pseudo selectors + sequence < + exactly <'('>, + optional_spaces, + optional , + optional_spaces, + exactly <')'> + >, + // match attribute compare operators + alternatives < + exact_match, class_match, dash_match, + prefix_match, suffix_match, substring_match + >, + // main selector match + sequence < + // allow namespace prefix + optional < namespace_schema >, + // modifiers prefixes + alternatives < + sequence < + exactly <'#'>, + // not for interpolation + negate < exactly <'{'> > + >, + // class match + exactly <'.'>, + // single or double colon + sequence < + optional < pseudo_prefix >, + // fix libsass issue 2376 + negate < uri_prefix > + > + >, + // accept hypens in token + one_plus < sequence < + // can start with hyphens + zero_plus < + sequence < + exactly <'-'>, + optional_spaces + > + >, + // now the main token + alternatives < + kwd_optional, + exactly <'*'>, + quoted_string, + interpolant, + identifier, + variable, + percentage, + binomial, + dimension, + alnum + > + > >, + // can also end with hyphens + zero_plus < exactly<'-'> > + > + > + > + >(src); + } + + const char* type_selector(const char* src) { + return sequence< optional, identifier>(src); + } + const char* re_type_selector(const char* src) { + return alternatives< type_selector, universal, dimension, percentage, number, identifier_alnums >(src); + } + const char* re_static_expression(const char* src) { + return sequence< number, optional_spaces, exactly<'/'>, optional_spaces, number >(src); + } + + // lexer special_fn: these functions cannot be overloaded + // (/((-[\w-]+-)?(calc|element)|expression|progid:[a-z\.]*)\(/i) + const char* re_special_fun(const char* src) { + + // match this first as we test prefix hyphens + if (const char* calc = calc_fn_call(src)) { + return calc; + } + + return sequence < + optional < + sequence < + exactly <'-'>, + one_plus < + alternatives < + alpha, + exactly <'+'>, + exactly <'-'> + > + > + > + >, + alternatives < + word < expression_kwd >, + sequence < + sequence < + exactly < progid_kwd >, + exactly <':'> + >, + zero_plus < + alternatives < + char_range <'a', 'z'>, + exactly <'.'> + > + > + > + > + >(src); + } + + } +} diff --git a/src/libsass/sass.cpp b/src/libsass/sass.cpp new file mode 100644 index 000000000..72edd7ced --- /dev/null +++ b/src/libsass/sass.cpp @@ -0,0 +1,151 @@ +#include "sass.hpp" +#include +#include +#include +#include + +#include "sass.h" +#include "file.hpp" +#include "util.hpp" +#include "sass_context.hpp" +#include "sass_functions.hpp" + +namespace Sass { + + // helper to convert string list to vector + std::vector list2vec(struct string_list* cur) + { + std::vector list; + while (cur) { + list.push_back(cur->string); + cur = cur->next; + } + return list; + } + +} + +extern "C" { + using namespace Sass; + + // Allocate libsass heap memory + // Don't forget string termination! + void* ADDCALL sass_alloc_memory(size_t size) + { + void* ptr = malloc(size); + if (ptr == NULL) { + std::cerr << "Out of memory.\n"; + exit(EXIT_FAILURE); + } + return ptr; + } + + char* ADDCALL sass_copy_c_string(const char* str) + { + size_t len = strlen(str) + 1; + char* cpy = (char*) sass_alloc_memory(len); + std::memcpy(cpy, str, len); + return cpy; + } + + // Deallocate libsass heap memory + void ADDCALL sass_free_memory(void* ptr) + { + if (ptr) free (ptr); + } + + // caller must free the returned memory + char* ADDCALL sass_string_quote (const char *str, const char quote_mark) + { + std::string quoted = quote(str, quote_mark); + return sass_copy_c_string(quoted.c_str()); + } + + // caller must free the returned memory + char* ADDCALL sass_string_unquote (const char *str) + { + std::string unquoted = unquote(str); + return sass_copy_c_string(unquoted.c_str()); + } + + char* ADDCALL sass_compiler_find_include (const char* file, struct Sass_Compiler* compiler) + { + // get the last import entry to get current base directory + Sass_Import_Entry import = sass_compiler_get_last_import(compiler); + const std::vector& incs = compiler->cpp_ctx->include_paths; + // create the vector with paths to lookup + std::vector paths(1 + incs.size()); + paths.push_back(File::dir_name(import->abs_path)); + paths.insert( paths.end(), incs.begin(), incs.end() ); + // now resolve the file path relative to lookup paths + std::string resolved(File::find_include(file, paths)); + return sass_copy_c_string(resolved.c_str()); + } + + char* ADDCALL sass_compiler_find_file (const char* file, struct Sass_Compiler* compiler) + { + // get the last import entry to get current base directory + Sass_Import_Entry import = sass_compiler_get_last_import(compiler); + const std::vector& incs = compiler->cpp_ctx->include_paths; + // create the vector with paths to lookup + std::vector paths(1 + incs.size()); + paths.push_back(File::dir_name(import->abs_path)); + paths.insert( paths.end(), incs.begin(), incs.end() ); + // now resolve the file path relative to lookup paths + std::string resolved(File::find_file(file, paths)); + return sass_copy_c_string(resolved.c_str()); + } + + // Make sure to free the returned value! + // Incs array has to be null terminated! + // this has the original resolve logic for sass include + char* ADDCALL sass_find_include (const char* file, struct Sass_Options* opt) + { + std::vector vec(list2vec(opt->include_paths)); + std::string resolved(File::find_include(file, vec)); + return sass_copy_c_string(resolved.c_str()); + } + + // Make sure to free the returned value! + // Incs array has to be null terminated! + char* ADDCALL sass_find_file (const char* file, struct Sass_Options* opt) + { + std::vector vec(list2vec(opt->include_paths)); + std::string resolved(File::find_file(file, vec)); + return sass_copy_c_string(resolved.c_str()); + } + + // Get compiled libsass version + const char* ADDCALL libsass_version(void) + { + return LIBSASS_VERSION; + } + + // Get compiled libsass version + const char* ADDCALL libsass_language_version(void) + { + return LIBSASS_LANGUAGE_VERSION; + } + +} + +namespace Sass { + + // helper to aid dreaded MSVC debug mode + char* sass_copy_string(std::string str) + { + // In MSVC the following can lead to segfault: + // sass_copy_c_string(stream.str().c_str()); + // Reason is that the string returned by str() is disposed before + // sass_copy_c_string is invoked. The string is actually a stack + // object, so indeed nobody is holding on to it. So it seems + // perfectly fair to release it right away. So the const char* + // by c_str will point to invalid memory. I'm not sure if this is + // the behavior for all compiler, but I'm pretty sure we would + // have gotten more issues reported if that would be the case. + // Wrapping it in a functions seems the cleanest approach as the + // function must hold on to the stack variable until it's done. + return sass_copy_c_string(str.c_str()); + } + +} \ No newline at end of file diff --git a/src/libsass/sass2scss.cpp b/src/libsass/sass2scss.cpp new file mode 100644 index 000000000..8645d0c37 --- /dev/null +++ b/src/libsass/sass2scss.cpp @@ -0,0 +1,895 @@ +/** + * sass2scss + * Licensed under the MIT License + * Copyright (c) Marcel Greter + */ + +#ifdef _MSC_VER +#define _CRT_SECURE_NO_WARNINGS +#define _CRT_NONSTDC_NO_DEPRECATE +#endif + +// include library +#include +#include +#include +#include +#include +#include +#include + +///* +// +// src comments: comments in sass syntax (staring with //) +// css comments: multiline comments in css syntax (starting with /*) +// +// KEEP_COMMENT: keep src comments in the resulting css code +// STRIP_COMMENT: strip out all comments (either src or css) +// CONVERT_COMMENT: convert all src comments to css comments +// +//*/ + +// our own header +#include "sass2scss.h" + +// add namespace for c++ +namespace Sass +{ + + // return the actual prettify value from options + #define PRETTIFY(converter) (converter.options - (converter.options & 248)) + // query the options integer to check if the option is enables + #define KEEP_COMMENT(converter) ((converter.options & SASS2SCSS_KEEP_COMMENT) == SASS2SCSS_KEEP_COMMENT) + #define STRIP_COMMENT(converter) ((converter.options & SASS2SCSS_STRIP_COMMENT) == SASS2SCSS_STRIP_COMMENT) + #define CONVERT_COMMENT(converter) ((converter.options & SASS2SCSS_CONVERT_COMMENT) == SASS2SCSS_CONVERT_COMMENT) + + // some makros to access the indentation stack + #define INDENT(converter) (converter.indents.top()) + + // some makros to query comment parser status + #define IS_PARSING(converter) (converter.comment == "") + #define IS_COMMENT(converter) (converter.comment != "") + #define IS_SRC_COMMENT(converter) (converter.comment == "//" && ! CONVERT_COMMENT(converter)) + #define IS_CSS_COMMENT(converter) (converter.comment == "/*" || (converter.comment == "//" && CONVERT_COMMENT(converter))) + + // pretty printer helper function + static std::string closer (const converter& converter) + { + return PRETTIFY(converter) == 0 ? " }" : + PRETTIFY(converter) <= 1 ? " }" : + "\n" + INDENT(converter) + "}"; + } + + // pretty printer helper function + static std::string opener (const converter& converter) + { + return PRETTIFY(converter) == 0 ? " { " : + PRETTIFY(converter) <= 2 ? " {" : + "\n" + INDENT(converter) + "{"; + } + + // check if the given string is a pseudo selector + // needed to differentiate from sass property syntax + static bool isPseudoSelector (std::string& sel) + { + + size_t len = sel.length(); + if (len < 1) return false; + size_t pos = sel.find_first_not_of("abcdefghijklmnopqrstuvwxyz-ABCDEFGHIJKLMNOPQRSTUVWXYZ", 1); + if (pos != std::string::npos) sel.erase(pos, std::string::npos); + size_t i = sel.length(); + while (i -- > 0) { sel.at(i) = tolower(sel.at(i)); } + + // CSS Level 1 - Recommendation + if (sel == ":link") return true; + if (sel == ":visited") return true; + if (sel == ":active") return true; + + // CSS Level 2 (Revision 1) - Recommendation + if (sel == ":lang") return true; + if (sel == ":first-child") return true; + if (sel == ":hover") return true; + if (sel == ":focus") return true; + // disabled - also valid properties + // if (sel == ":left") return true; + // if (sel == ":right") return true; + if (sel == ":first") return true; + + // Selectors Level 3 - Recommendation + if (sel == ":target") return true; + if (sel == ":root") return true; + if (sel == ":nth-child") return true; + if (sel == ":nth-last-of-child") return true; + if (sel == ":nth-of-type") return true; + if (sel == ":nth-last-of-type") return true; + if (sel == ":last-child") return true; + if (sel == ":first-of-type") return true; + if (sel == ":last-of-type") return true; + if (sel == ":only-child") return true; + if (sel == ":only-of-type") return true; + if (sel == ":empty") return true; + if (sel == ":not") return true; + + // CSS Basic User Interface Module Level 3 - Working Draft + if (sel == ":default") return true; + if (sel == ":valid") return true; + if (sel == ":invalid") return true; + if (sel == ":in-range") return true; + if (sel == ":out-of-range") return true; + if (sel == ":required") return true; + if (sel == ":optional") return true; + if (sel == ":read-only") return true; + if (sel == ":read-write") return true; + if (sel == ":dir") return true; + if (sel == ":enabled") return true; + if (sel == ":disabled") return true; + if (sel == ":checked") return true; + if (sel == ":indeterminate") return true; + if (sel == ":nth-last-child") return true; + + // Selectors Level 4 - Working Draft + if (sel == ":any-link") return true; + if (sel == ":local-link") return true; + if (sel == ":scope") return true; + if (sel == ":active-drop-target") return true; + if (sel == ":valid-drop-target") return true; + if (sel == ":invalid-drop-target") return true; + if (sel == ":current") return true; + if (sel == ":past") return true; + if (sel == ":future") return true; + if (sel == ":placeholder-shown") return true; + if (sel == ":user-error") return true; + if (sel == ":blank") return true; + if (sel == ":nth-match") return true; + if (sel == ":nth-last-match") return true; + if (sel == ":nth-column") return true; + if (sel == ":nth-last-column") return true; + if (sel == ":matches") return true; + + // Fullscreen API - Living Standard + if (sel == ":fullscreen") return true; + + // not a pseudo selector + return false; + + } + + static size_t findFirstCharacter (std::string& sass, size_t pos) + { + return sass.find_first_not_of(SASS2SCSS_FIND_WHITESPACE, pos); + } + + static size_t findLastCharacter (std::string& sass, size_t pos) + { + return sass.find_last_not_of(SASS2SCSS_FIND_WHITESPACE, pos); + } + + static bool isUrl (std::string& sass, size_t pos) + { + return sass[pos] == 'u' && sass[pos+1] == 'r' && sass[pos+2] == 'l' && sass[pos+3] == '('; + } + + // check if there is some char data + // will ignore everything in comments + static bool hasCharData (std::string& sass) + { + + size_t col_pos = 0; + + while (true) + { + + // try to find some meaningfull char + col_pos = sass.find_first_not_of(" \t\n\v\f\r", col_pos); + + // there was no meaningfull char found + if (col_pos == std::string::npos) return false; + + // found a multiline comment opener + if (sass.substr(col_pos, 2) == "/*") + { + // find the multiline comment closer + col_pos = sass.find("*/", col_pos); + // maybe we did not find the closer here + if (col_pos == std::string::npos) return false; + // skip closer + col_pos += 2; + } + else + { + return true; + } + + } + + } + // EO hasCharData + + // find src comment opener + // correctly skips quoted strings + static size_t findCommentOpener (std::string& sass) + { + + size_t col_pos = 0; + bool apoed = false; + bool quoted = false; + bool comment = false; + size_t brackets = 0; + + while (col_pos != std::string::npos) + { + + // process all interesting chars + col_pos = sass.find_first_of("\"\'/\\*()", col_pos); + + // assertion for valid result + if (col_pos != std::string::npos) + { + char character = sass.at(col_pos); + + if (character == '(') + { + if (!quoted && !apoed) brackets ++; + } + else if (character == ')') + { + if (!quoted && !apoed) brackets --; + } + else if (character == '\"') + { + // invert quote bool + if (!apoed && !comment) quoted = !quoted; + } + else if (character == '\'') + { + // invert quote bool + if (!quoted && !comment) apoed = !apoed; + } + else if (col_pos > 0 && character == '/') + { + if (sass.at(col_pos - 1) == '*') + { + comment = false; + } + // next needs to be a slash too + else if (sass.at(col_pos - 1) == '/') + { + // only found if not in single or double quote, bracket or comment + if (!quoted && !apoed && !comment && brackets == 0) return col_pos - 1; + } + } + else if (character == '\\') + { + // skip next char if in quote + if (quoted || apoed) col_pos ++; + } + // this might be a comment opener + else if (col_pos > 0 && character == '*') + { + // opening a multiline comment + if (sass.at(col_pos - 1) == '/') + { + // we are now in a comment + if (!quoted && !apoed) comment = true; + } + } + + // skip char + col_pos ++; + + } + + } + // EO while + + return col_pos; + + } + // EO findCommentOpener + + // remove multiline comments from sass string + // correctly skips quoted strings + static std::string removeMultilineComment (std::string &sass) + { + + std::string clean = ""; + size_t col_pos = 0; + size_t open_pos = 0; + size_t close_pos = 0; + bool apoed = false; + bool quoted = false; + bool comment = false; + + // process sass til string end + while (col_pos != std::string::npos) + { + + // process all interesting chars + col_pos = sass.find_first_of("\"\'/\\*", col_pos); + + // assertion for valid result + if (col_pos != std::string::npos) + { + char character = sass.at(col_pos); + + // found quoted string delimiter + if (character == '\"') + { + if (!apoed && !comment) quoted = !quoted; + } + else if (character == '\'') + { + if (!quoted && !comment) apoed = !apoed; + } + // found possible comment closer + else if (character == '/') + { + // look back to see if it is actually a closer + if (comment && col_pos > 0 && sass.at(col_pos - 1) == '*') + { + close_pos = col_pos + 1; comment = false; + } + } + else if (character == '\\') + { + // skip escaped char + if (quoted || apoed) col_pos ++; + } + // this might be a comment opener + else if (character == '*') + { + // look back to see if it is actually an opener + if (!quoted && !apoed && col_pos > 0 && sass.at(col_pos - 1) == '/') + { + comment = true; open_pos = col_pos - 1; + clean += sass.substr(close_pos, open_pos - close_pos); + } + } + + // skip char + col_pos ++; + + } + + } + // EO while + + // add final parts (add half open comment text) + if (comment) clean += sass.substr(open_pos); + else clean += sass.substr(close_pos); + + // return string + return clean; + + } + // EO removeMultilineComment + + // right trim a given string + std::string rtrim(const std::string &sass) + { + std::string trimmed = sass; + size_t pos_ws = trimmed.find_last_not_of(" \t\n\v\f\r"); + if (pos_ws != std::string::npos) + { trimmed.erase(pos_ws + 1); } + else { trimmed.clear(); } + return trimmed; + } + // EO rtrim + + // flush whitespace and print additional text, but + // only print additional chars and buffer whitespace + std::string flush (std::string& sass, converter& converter) + { + + // return flushed + std::string scss = ""; + + // print whitespace buffer + scss += PRETTIFY(converter) > 0 ? + converter.whitespace : ""; + // reset whitespace buffer + converter.whitespace = ""; + + // remove possible newlines from string + size_t pos_right = sass.find_last_not_of("\n\r"); + if (pos_right == std::string::npos) return scss; + + // get the linefeeds from the string + std::string lfs = sass.substr(pos_right + 1); + sass = sass.substr(0, pos_right + 1); + + // find some source comment opener + size_t comment_pos = findCommentOpener(sass); + // check if there was a source comment + if (comment_pos != std::string::npos) + { + // convert comment (but only outside other coments) + if (CONVERT_COMMENT(converter) && !IS_COMMENT(converter)) + { + // convert to multiline comment + sass.at(comment_pos + 1) = '*'; + // add comment node to the whitespace + sass += " */"; + } + // not at line start + if (comment_pos > 0) + { + // also include whitespace before the actual comment opener + size_t ws_pos = sass.find_last_not_of(SASS2SCSS_FIND_WHITESPACE, comment_pos - 1); + comment_pos = ws_pos == std::string::npos ? 0 : ws_pos + 1; + } + if (!STRIP_COMMENT(converter)) + { + // add comment node to the whitespace + converter.whitespace += sass.substr(comment_pos); + } + else + { + // sass = removeMultilineComments(sass); + } + // update the actual sass code + sass = sass.substr(0, comment_pos); + } + + // add newline as getline discharged it + converter.whitespace += lfs + "\n"; + + // maybe remove any leading whitespace + if (PRETTIFY(converter) == 0) + { + // remove leading whitespace and update string + size_t pos_left = sass.find_first_not_of(SASS2SCSS_FIND_WHITESPACE); + if (pos_left != std::string::npos) sass = sass.substr(pos_left); + } + + // add flushed data + scss += sass; + + // return string + return scss; + + } + // EO flush + + // process a line of the sass text + std::string process (std::string& sass, converter& converter) + { + + // resulting string + std::string scss = ""; + + // strip multi line comments + if (STRIP_COMMENT(converter)) + { + sass = removeMultilineComment(sass); + } + + // right trim input + sass = rtrim(sass); + + // get 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 == std::string::npos) + { + // just add complete whitespace + converter.whitespace += sass + "\n"; + } + // have meaningfull first char + else + { + + // extract and store indentation string + std::string indent = sass.substr(0, pos_left); + + // check if current line starts a comment + std::string open = sass.substr(pos_left, 2); + + // line has less or same indentation + // finalize previous open parser context + if (indent.length() <= INDENT(converter).length()) + { + + // close multilinie comment + if (IS_CSS_COMMENT(converter)) + { + // check if comments will be stripped anyway + if (!STRIP_COMMENT(converter)) scss += " */"; + } + // close src comment comment + else if (IS_SRC_COMMENT(converter)) + { + // add a newline to avoid closer on same line + // this would put the bracket in the comment node + // no longer needed since we parse them correctly + // if (KEEP_COMMENT(converter)) scss += "\n"; + } + // close css properties + else if (converter.property) + { + // add closer unless in concat mode + if (!converter.comma) + { + // if there was no colon we have a selector + // looks like there were no inner properties + if (converter.selector) scss += " {}"; + // add final semicolon + else if (!converter.semicolon) scss += ";"; + } + } + + // reset comment state + converter.comment = ""; + + } + + // make sure we close every "higher" block + while (indent.length() < INDENT(converter).length()) + { + // pop stacked context + converter.indents.pop(); + // print close bracket + if (IS_PARSING(converter)) + { scss += closer(converter); } + else { scss += " */"; } + // reset comment state + converter.comment = ""; + } + + // reset converter state + converter.selector = false; + + // looks like some undocumented behavior ... + // https://github.com/mgreter/sass2scss/issues/29 + if (sass.substr(pos_left, 1) == "\\") { + converter.selector = true; + sass[pos_left] = ' '; + } + + // check if we have sass property syntax + if (sass.substr(pos_left, 1) == ":" && sass.substr(pos_left, 2) != "::") + { + + // default to a selector + // change back if property found + converter.selector = true; + // get 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 != std::string::npos) + { + // get the possible pseudo selector + std::string pseudo = sass.substr(pos_left, pos_wspace - pos_left); + // get position of the first real property value char + // pseudo selectors get this far, but have no actual value + size_t pos_value = sass.find_first_not_of(SASS2SCSS_FIND_WHITESPACE, pos_wspace); + // assertion check for valid result + if (pos_value != std::string::npos) + { + // only process if not (fallowed by a semicolon or is a pseudo selector) + if (!(sass.at(pos_value) == ':' || isPseudoSelector(pseudo))) + { + // create new string by interchanging the colon sign for property and value + sass = indent + sass.substr(pos_left + 1, pos_wspace - pos_left - 1) + ":" + sass.substr(pos_wspace); + // try to find a colon in the current line, but only ... + size_t pos_colon = sass.find_first_not_of(":", pos_left); + // assertion for valid result + if (pos_colon != std::string::npos) + { + // ... after the first word (skip begining colons) + pos_colon = sass.find_first_of(":", pos_colon); + // it is a selector if there was no colon found + converter.selector = pos_colon == std::string::npos; + } + } + } + } + + // check if we have a BEM property (one colon and no selector) + if (sass.substr(pos_left, 1) == ":" && converter.selector == true) { + size_t pos_wspace = sass.find_first_of(SASS2SCSS_FIND_WHITESPACE, pos_left); + sass = indent + sass.substr(pos_left + 1, pos_wspace) + ":"; + } + + } + + // terminate some statements immediately + else if ( + sass.substr(pos_left, 5) == "@warn" || + sass.substr(pos_left, 6) == "@debug" || + sass.substr(pos_left, 6) == "@error" || + sass.substr(pos_left, 6) == "@value" || + sass.substr(pos_left, 8) == "@charset" || + sass.substr(pos_left, 10) == "@namespace" + ) { sass = indent + sass.substr(pos_left); } + // replace some specific sass shorthand directives (if not fallowed by a white space character) + else if (sass.substr(pos_left, 1) == "=") + { sass = indent + "@mixin " + sass.substr(pos_left + 1); } + else if (sass.substr(pos_left, 1) == "+") + { + // must be followed by a mixin call (no whitespace afterwards or at ending directly) + if (sass[pos_left+1] != 0 && sass[pos_left+1] != ' ' && sass[pos_left+1] != '\t') { + sass = indent + "@include " + sass.substr(pos_left + 1); + } + } + + // add quotes for import if needed + else if (sass.substr(pos_left, 7) == "@import") + { + // get positions for the actual import url + size_t pos_import = sass.find_first_of(SASS2SCSS_FIND_WHITESPACE, pos_left + 7); + size_t pos = sass.find_first_not_of(SASS2SCSS_FIND_WHITESPACE, pos_import); + size_t start = pos; + bool in_dqstr = false; + bool in_sqstr = false; + bool is_escaped = false; + do { + if (is_escaped) { + is_escaped = false; + } + else if (sass[pos] == '\\') { + is_escaped = true; + } + else if (sass[pos] == '"') { + if (!in_sqstr) in_dqstr = ! in_dqstr; + } + else if (sass[pos] == '\'') { + if (!in_dqstr) in_sqstr = ! in_sqstr; + } + else if (in_dqstr || in_sqstr) { + // skip over quoted stuff + } + else if (sass[pos] == ',' || sass[pos] == 0) { + if (sass[start] != '"' && sass[start] != '\'' && !isUrl(sass, start)) { + size_t end = findLastCharacter(sass, pos - 1) + 1; + sass = sass.replace(end, 0, "\""); + sass = sass.replace(start, 0, "\""); + pos += 2; + } + start = findFirstCharacter(sass, pos + 1); + } + } + while (sass[pos++] != 0); + + } + else if ( + sass.substr(pos_left, 7) != "@return" && + sass.substr(pos_left, 7) != "@extend" && + sass.substr(pos_left, 8) != "@include" && + sass.substr(pos_left, 8) != "@content" + ) { + + // probably a selector anyway + converter.selector = true; + // try to find first colon in the current line + size_t pos_colon = sass.find_first_of(":", pos_left); + // assertion that we have a colon + if (pos_colon != std::string::npos) + { + // it is not a selector if we have a space after a colon + if (sass[pos_colon+1] == ' ') converter.selector = false; + if (sass[pos_colon+1] == ' ') converter.selector = false; + } + + } + + // current line has more indentation + if (indent.length() >= INDENT(converter).length()) + { + // not in comment mode + if (IS_PARSING(converter)) + { + // has meaningfull chars + if (hasCharData(sass)) + { + // is probably a property + // also true for selectors + converter.property = true; + } + } + } + // current line has more indentation + if (indent.length() > INDENT(converter).length()) + { + // not in comment mode + if (IS_PARSING(converter)) + { + // had meaningfull chars + if (converter.property) + { + // print block opener + scss += opener(converter); + // push new stack context + converter.indents.push(""); + // store block indentation + INDENT(converter) = indent; + } + } + // is and will be a src comment + else if (!IS_CSS_COMMENT(converter)) + { + // scss does not allow multiline src comments + // therefore add forward slashes to all lines + sass.at(INDENT(converter).length()+0) = '/'; + // there is an edge case here if indentation + // is minimal (will overwrite the fist char) + sass.at(INDENT(converter).length()+1) = '/'; + // could code around that, but I dont' think + // this will ever be the cause for any trouble + } + } + + // line is opening a new comment + if (open == "/*" || open == "//") + { + // reset the property state + converter.property = false; + // close previous comment + if (IS_CSS_COMMENT(converter) && open != "") + { + if (!STRIP_COMMENT(converter) && !CONVERT_COMMENT(converter)) scss += " */"; + } + // force single line comments + // into a correct css comment + if (CONVERT_COMMENT(converter)) + { + if (IS_PARSING(converter)) + { sass.at(pos_left + 1) = '*'; } + } + // set comment flag + converter.comment = open; + + } + + // flush data only under certain conditions + if (!( + // strip css and src comments if option is set + (IS_COMMENT(converter) && STRIP_COMMENT(converter)) || + // strip src comment even if strip option is not set + // but only if the keep src comment option is not set + (IS_SRC_COMMENT(converter) && ! KEEP_COMMENT(converter)) + )) + { + // flush data and buffer whitespace + scss += flush(sass, converter); + } + + // get postion of last meaningfull char + size_t pos_right = sass.find_last_not_of(SASS2SCSS_FIND_WHITESPACE); + + // check for invalid result + if (pos_right != std::string::npos) + { + + // get the last meaningfull char + std::string close = sass.substr(pos_right, 1); + + // check if next line should be concatenated (list mode) + converter.comma = IS_PARSING(converter) && close == ","; + converter.semicolon = IS_PARSING(converter) && close == ";"; + + // check if we have more than + // one meaningfull char + if (pos_right > 0) + { + + // get the last two chars from string + std::string close = sass.substr(pos_right - 1, 2); + // update parser status for expicitly closed comment + if (close == "*/") converter.comment = ""; + + } + + } + // EO have meaningfull chars from end + + } + // EO have meaningfull chars from start + + // return scss + return scss; + + } + // EO process + + // read line with either CR, LF or CR LF format + // http://stackoverflow.com/a/6089413/1550314 + static std::istream& safeGetline(std::istream& is, std::string& t) + { + t.clear(); + + // The characters in the stream are read one-by-one using a std::streambuf. + // That is faster than reading them one-by-one using the std::istream. + // Code that uses streambuf this way must be guarded by a sentry object. + // The sentry object performs various tasks, + // such as thread synchronization and updating the stream state. + + std::istream::sentry se(is, true); + std::streambuf* sb = is.rdbuf(); + + for(;;) { + int c = sb->sbumpc(); + switch (c) { + case '\n': + return is; + case '\r': + if(sb->sgetc() == '\n') + sb->sbumpc(); + return is; + case EOF: + // Also handle the case when the last line has no line ending + if(t.empty()) + is.setstate(std::ios::eofbit); + return is; + default: + t += (char)c; + } + } + } + + // the main converter function for c++ + char* sass2scss (const std::string& sass, const int options) + { + + // local variables + std::string line; + std::string scss = ""; + std::stringstream stream(sass); + + // create converter variable + converter converter; + // initialise all options + converter.comma = false; + converter.property = false; + converter.selector = false; + converter.semicolon = false; + converter.end_of_file = false; + converter.comment = ""; + converter.whitespace = ""; + converter.indents.push(""); + converter.options = options; + + // read line by line and process them + while(safeGetline(stream, line) && !stream.eof()) + { scss += process(line, converter); } + + // create mutable string + std::string closer = ""; + // set the end of file flag + converter.end_of_file = true; + // process to close all open blocks + scss += process(closer, converter); + + // allocate new memory on the heap + // caller has to free it after use + char * cstr = (char*) malloc (scss.length() + 1); + // create a copy of the string + strcpy (cstr, scss.c_str()); + // return pointer + return &cstr[0]; + + } + // EO sass2scss + +} +// EO namespace + +// implement for c +extern "C" +{ + + char* ADDCALL sass2scss (const char* sass, const int options) + { + return Sass::sass2scss(sass, options); + } + + // Get compiled sass2scss version + const char* ADDCALL sass2scss_version(void) { + return SASS2SCSS_VERSION; + } + +} diff --git a/src/libsass/sass_context.cpp b/src/libsass/sass_context.cpp new file mode 100644 index 000000000..7a0a49ce1 --- /dev/null +++ b/src/libsass/sass_context.cpp @@ -0,0 +1,799 @@ +#include "sass.hpp" +#include +#include +#include +#include +#include + +#include "sass.h" +#include "ast.hpp" +#include "file.hpp" +#include "json.hpp" +#include "util.hpp" +#include "context.hpp" +#include "sass_context.hpp" +#include "sass_functions.hpp" +#include "ast_fwd_decl.hpp" +#include "error_handling.hpp" + +#define LFEED "\n" + +// C++ helper +namespace Sass { + // see sass_copy_c_string(std::string str) + static inline JsonNode* json_mkstream(const std::stringstream& stream) + { + // hold on to string on stack! + std::string str(stream.str()); + return json_mkstring(str.c_str()); + } + + static int handle_error(Sass_Context* c_ctx) { + try { + throw; + } + catch (Exception::Base& e) { + std::stringstream msg_stream; + std::string cwd(Sass::File::get_cwd()); + std::string msg_prefix(e.errtype()); + bool got_newline = false; + msg_stream << msg_prefix << ": "; + const char* msg = e.what(); + while (msg && *msg) { + if (*msg == '\r') { + got_newline = true; + } + else if (*msg == '\n') { + got_newline = true; + } + else if (got_newline) { + msg_stream << std::string(msg_prefix.size() + 2, ' '); + got_newline = false; + } + msg_stream << *msg; + ++msg; + } + if (!got_newline) msg_stream << "\n"; + + if (e.traces.empty()) { + // we normally should have some traces, still here as a fallback + std::string rel_path(Sass::File::abs2rel(e.pstate.path, cwd, cwd)); + msg_stream << std::string(msg_prefix.size() + 2, ' '); + msg_stream << " on line " << e.pstate.line + 1 << " of " << rel_path << "\n"; + } + else { + std::string rel_path(Sass::File::abs2rel(e.pstate.path, cwd, cwd)); + msg_stream << traces_to_string(e.traces, " "); + } + + // now create the code trace (ToDo: maybe have util functions?) + if (e.pstate.line != std::string::npos && e.pstate.column != std::string::npos) { + size_t lines = e.pstate.line; + const char* line_beg = e.pstate.src; + // scan through src until target line + // move line_beg pointer to line start + while (line_beg && *line_beg && lines != 0) { + if (*line_beg == '\n') --lines; + utf8::unchecked::next(line_beg); + } + const char* line_end = line_beg; + // move line_end before next newline character + while (line_end && *line_end && *line_end != '\n') { + if (*line_end == '\n') break; + if (*line_end == '\r') break; + utf8::unchecked::next(line_end); + } + if (line_end && *line_end != 0) ++ line_end; + size_t line_len = line_end - line_beg; + size_t move_in = 0; size_t shorten = 0; + size_t left_chars = 42; size_t max_chars = 76; + // reported excerpt should not exceed `max_chars` chars + if (e.pstate.column > line_len) left_chars = e.pstate.column; + if (e.pstate.column > left_chars) move_in = e.pstate.column - left_chars; + if (line_len > max_chars + move_in) shorten = line_len - move_in - max_chars; + utf8::advance(line_beg, move_in, line_end); + utf8::retreat(line_end, shorten, line_beg); + std::string sanitized; std::string marker(e.pstate.column - move_in, '-'); + utf8::replace_invalid(line_beg, line_end, std::back_inserter(sanitized)); + msg_stream << ">> " << sanitized << "\n"; + msg_stream << " " << marker << "^\n"; + } + + JsonNode* json_err = json_mkobject(); + json_append_member(json_err, "status", json_mknumber(1)); + json_append_member(json_err, "file", json_mkstring(e.pstate.path)); + json_append_member(json_err, "line", json_mknumber((double)(e.pstate.line + 1))); + json_append_member(json_err, "column", json_mknumber((double)(e.pstate.column + 1))); + json_append_member(json_err, "message", json_mkstring(e.what())); + json_append_member(json_err, "formatted", json_mkstream(msg_stream)); + try { c_ctx->error_json = json_stringify(json_err, " "); } + catch (...) {} + c_ctx->error_message = sass_copy_string(msg_stream.str()); + c_ctx->error_text = sass_copy_c_string(e.what()); + c_ctx->error_status = 1; + c_ctx->error_file = sass_copy_c_string(e.pstate.path); + c_ctx->error_line = e.pstate.line + 1; + c_ctx->error_column = e.pstate.column + 1; + c_ctx->error_src = e.pstate.src; + c_ctx->output_string = 0; + c_ctx->source_map_string = 0; + json_delete(json_err); + } + catch (std::bad_alloc& ba) { + std::stringstream msg_stream; + JsonNode* json_err = json_mkobject(); + msg_stream << "Unable to allocate memory: " << ba.what() << std::endl; + json_append_member(json_err, "status", json_mknumber(2)); + json_append_member(json_err, "message", json_mkstring(ba.what())); + json_append_member(json_err, "formatted", json_mkstream(msg_stream)); + try { c_ctx->error_json = json_stringify(json_err, " "); } + catch (...) {} + c_ctx->error_message = sass_copy_string(msg_stream.str()); + c_ctx->error_text = sass_copy_c_string(ba.what()); + c_ctx->error_status = 2; + c_ctx->output_string = 0; + c_ctx->source_map_string = 0; + json_delete(json_err); + } + catch (std::exception& e) { + std::stringstream msg_stream; + JsonNode* json_err = json_mkobject(); + msg_stream << "Internal Error: " << e.what() << std::endl; + json_append_member(json_err, "status", json_mknumber(3)); + json_append_member(json_err, "message", json_mkstring(e.what())); + json_append_member(json_err, "formatted", json_mkstream(msg_stream)); + try { c_ctx->error_json = json_stringify(json_err, " "); } + catch (...) {} + c_ctx->error_message = sass_copy_string(msg_stream.str()); + c_ctx->error_text = sass_copy_c_string(e.what()); + c_ctx->error_status = 3; + c_ctx->output_string = 0; + c_ctx->source_map_string = 0; + json_delete(json_err); + } + catch (std::string& e) { + std::stringstream msg_stream; + JsonNode* json_err = json_mkobject(); + msg_stream << "Internal Error: " << e << std::endl; + json_append_member(json_err, "status", json_mknumber(4)); + json_append_member(json_err, "message", json_mkstring(e.c_str())); + json_append_member(json_err, "formatted", json_mkstream(msg_stream)); + try { c_ctx->error_json = json_stringify(json_err, " "); } + catch (...) {} + c_ctx->error_message = sass_copy_string(msg_stream.str()); + c_ctx->error_text = sass_copy_c_string(e.c_str()); + c_ctx->error_status = 4; + c_ctx->output_string = 0; + c_ctx->source_map_string = 0; + json_delete(json_err); + } + catch (const char* e) { + std::stringstream msg_stream; + JsonNode* json_err = json_mkobject(); + msg_stream << "Internal Error: " << e << std::endl; + json_append_member(json_err, "status", json_mknumber(4)); + json_append_member(json_err, "message", json_mkstring(e)); + json_append_member(json_err, "formatted", json_mkstream(msg_stream)); + try { c_ctx->error_json = json_stringify(json_err, " "); } + catch (...) {} + c_ctx->error_message = sass_copy_string(msg_stream.str()); + c_ctx->error_text = sass_copy_c_string(e); + c_ctx->error_status = 4; + c_ctx->output_string = 0; + c_ctx->source_map_string = 0; + json_delete(json_err); + } + catch (...) { + std::stringstream msg_stream; + JsonNode* json_err = json_mkobject(); + msg_stream << "Unknown error occurred" << std::endl; + json_append_member(json_err, "status", json_mknumber(5)); + json_append_member(json_err, "message", json_mkstring("unknown")); + try { c_ctx->error_json = json_stringify(json_err, " "); } + catch (...) {} + c_ctx->error_message = sass_copy_string(msg_stream.str()); + c_ctx->error_text = sass_copy_c_string("unknown"); + c_ctx->error_status = 5; + c_ctx->output_string = 0; + c_ctx->source_map_string = 0; + json_delete(json_err); + } + return c_ctx->error_status; + } + + // allow one error handler to throw another error + // this can happen with invalid utf8 and json lib + static int handle_errors(Sass_Context* c_ctx) { + try { return handle_error(c_ctx); } + catch (...) { return handle_error(c_ctx); } + } + + static Block_Obj sass_parse_block(Sass_Compiler* compiler) throw() + { + + // assert valid pointer + if (compiler == 0) return 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 + std::string input_path = safe_str(c_ctx->input_path); + std::string output_path = safe_str(c_ctx->output_path); + + // maybe skip some entries of included files + // we do not include stdin for data contexts + bool skip = c_ctx->type == SASS_CONTEXT_DATA; + + // dispatch parse call + Block_Obj root(cpp_ctx->parse()); + // abort on errors + if (!root) return 0; + + // skip all prefixed files? (ToDo: check srcmap) + // IMO source-maps should point to headers already + // therefore don't skip it for now. re-enable or + // remove completely once this is tested + size_t headers = cpp_ctx->head_imports; + + // copy the included files on to the context (dont forget to free later) + if (copy_strings(cpp_ctx->get_included_files(skip, headers), &c_ctx->included_files) == NULL) + throw(std::bad_alloc()); + + // return parsed block + return root; + + } + // pass errors to generic error handler + catch (...) { handle_errors(c_ctx); } + + // error + return 0; + + } + +} + +extern "C" { + using namespace Sass; + + static void sass_clear_options (struct Sass_Options* options); + static void sass_reset_options (struct Sass_Options* options); + static void copy_options(struct Sass_Options* to, struct Sass_Options* from) { + // do not overwrite ourself + if (to == from) return; + // free assigned memory + sass_clear_options(to); + // move memory + *to = *from; + // Reset pointers on source + sass_reset_options(from); + } + + #define IMPLEMENT_SASS_OPTION_ACCESSOR(type, option) \ + type ADDCALL sass_option_get_##option (struct Sass_Options* options) { return options->option; } \ + void ADDCALL sass_option_set_##option (struct Sass_Options* options, type option) { options->option = option; } + #define IMPLEMENT_SASS_OPTION_STRING_GETTER(type, option, def) \ + type ADDCALL sass_option_get_##option (struct Sass_Options* options) { return safe_str(options->option, def); } + #define IMPLEMENT_SASS_OPTION_STRING_SETTER(type, option, def) \ + void ADDCALL sass_option_set_##option (struct Sass_Options* options, type option) \ + { free(options->option); options->option = option || def ? sass_copy_c_string(option ? option : def) : 0; } + #define IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(type, option, def) \ + IMPLEMENT_SASS_OPTION_STRING_GETTER(type, option, def) \ + IMPLEMENT_SASS_OPTION_STRING_SETTER(type, option, def) + + #define IMPLEMENT_SASS_CONTEXT_GETTER(type, option) \ + type ADDCALL sass_context_get_##option (struct Sass_Context* ctx) { return ctx->option; } + #define IMPLEMENT_SASS_CONTEXT_TAKER(type, option) \ + type sass_context_take_##option (struct Sass_Context* ctx) \ + { type foo = ctx->option; ctx->option = 0; return foo; } + + + // generic compilation function (not exported, use file/data compile instead) + static Sass_Compiler* sass_prepare_context (Sass_Context* c_ctx, Context* cpp_ctx) throw() + { + try { + // register our custom functions + if (c_ctx->c_functions) { + auto this_func_data = c_ctx->c_functions; + while (this_func_data && *this_func_data) { + cpp_ctx->add_c_function(*this_func_data); + ++this_func_data; + } + } + + // register our custom headers + if (c_ctx->c_headers) { + auto this_head_data = c_ctx->c_headers; + while (this_head_data && *this_head_data) { + cpp_ctx->add_c_header(*this_head_data); + ++this_head_data; + } + } + + // register our custom importers + if (c_ctx->c_importers) { + auto this_imp_data = c_ctx->c_importers; + while (this_imp_data && *this_imp_data) { + cpp_ctx->add_c_importer(*this_imp_data); + ++this_imp_data; + } + } + + // reset error status + c_ctx->error_json = 0; + c_ctx->error_text = 0; + c_ctx->error_message = 0; + c_ctx->error_status = 0; + // reset error position + c_ctx->error_src = 0; + c_ctx->error_file = 0; + c_ctx->error_line = std::string::npos; + c_ctx->error_column = std::string::npos; + + // allocate a new compiler instance + void* ctxmem = calloc(1, sizeof(struct Sass_Compiler)); + if (ctxmem == 0) { std::cerr << "Error allocating memory for context" << std::endl; return 0; } + Sass_Compiler* compiler = (struct Sass_Compiler*) ctxmem; + compiler->state = SASS_COMPILER_CREATED; + + // store in sass compiler + compiler->c_ctx = c_ctx; + compiler->cpp_ctx = cpp_ctx; + cpp_ctx->c_compiler = compiler; + + // use to parse block + return compiler; + + } + // pass errors to generic error handler + catch (...) { handle_errors(c_ctx); } + + // error + return 0; + + } + + // generic compilation function (not exported, use file/data compile instead) + static int sass_compile_context (Sass_Context* c_ctx, Context* cpp_ctx) + { + + // prepare sass compiler with context and options + Sass_Compiler* compiler = sass_prepare_context(c_ctx, cpp_ctx); + + try { + // call each compiler step + sass_compiler_parse(compiler); + sass_compiler_execute(compiler); + } + // pass errors to generic error handler + catch (...) { handle_errors(c_ctx); } + + sass_delete_compiler(compiler); + + return c_ctx->error_status; + } + + inline void init_options (struct Sass_Options* options) + { + options->precision = 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) { std::cerr << "Error allocating memory for options" << std::endl; return 0; } + init_options(options); + return options; + } + + Sass_File_Context* ADDCALL sass_make_file_context(const char* input_path) + { + SharedObj::setTaint(true); // needed for static colors + struct Sass_File_Context* ctx = (struct Sass_File_Context*) calloc(1, sizeof(struct Sass_File_Context)); + if (ctx == 0) { std::cerr << "Error allocating memory for file context" << std::endl; return 0; } + ctx->type = SASS_CONTEXT_FILE; + init_options(ctx); + try { + if (input_path == 0) { throw(std::runtime_error("File context created without an input path")); } + if (*input_path == 0) { throw(std::runtime_error("File context created with empty input path")); } + sass_option_set_input_path(ctx, input_path); + } catch (...) { + handle_errors(ctx); + } + return ctx; + } + + Sass_Data_Context* ADDCALL sass_make_data_context(char* source_string) + { + struct Sass_Data_Context* ctx = (struct Sass_Data_Context*) calloc(1, sizeof(struct Sass_Data_Context)); + if (ctx == 0) { std::cerr << "Error allocating memory for data context" << std::endl; return 0; } + ctx->type = SASS_CONTEXT_DATA; + init_options(ctx); + try { + if (source_string == 0) { throw(std::runtime_error("Data context created without a source string")); } + if (*source_string == 0) { throw(std::runtime_error("Data context created with empty source string")); } + ctx->source_string = source_string; + } catch (...) { + handle_errors(ctx); + } + return ctx; + } + + struct Sass_Compiler* ADDCALL sass_make_data_compiler (struct Sass_Data_Context* data_ctx) + { + if (data_ctx == 0) return 0; + Context* cpp_ctx = new Data_Context(*data_ctx); + return sass_prepare_context(data_ctx, cpp_ctx); + } + + struct Sass_Compiler* ADDCALL sass_make_file_compiler (struct Sass_File_Context* file_ctx) + { + if (file_ctx == 0) return 0; + Context* cpp_ctx = new File_Context(*file_ctx); + return sass_prepare_context(file_ctx, cpp_ctx); + } + + int ADDCALL sass_compile_data_context(Sass_Data_Context* data_ctx) + { + if (data_ctx == 0) return 1; + if (data_ctx->error_status) + return data_ctx->error_status; + try { + if (data_ctx->source_string == 0) { throw(std::runtime_error("Data context has no source string")); } + // empty source string is a valid case, even if not really usefull (different than with file context) + // if (*data_ctx->source_string == 0) { throw(std::runtime_error("Data context has empty source string")); } + } + catch (...) { return handle_errors(data_ctx) | 1; } + Context* cpp_ctx = new Data_Context(*data_ctx); + return sass_compile_context(data_ctx, cpp_ctx); + } + + int ADDCALL sass_compile_file_context(Sass_File_Context* file_ctx) + { + if (file_ctx == 0) return 1; + if (file_ctx->error_status) + return file_ctx->error_status; + try { + if (file_ctx->input_path == 0) { throw(std::runtime_error("File context has no input path")); } + if (*file_ctx->input_path == 0) { throw(std::runtime_error("File context has empty input path")); } + } + catch (...) { return handle_errors(file_ctx) | 1; } + Context* cpp_ctx = new File_Context(*file_ctx); + return sass_compile_context(file_ctx, cpp_ctx); + } + + int ADDCALL sass_compiler_parse(struct Sass_Compiler* compiler) + { + if (compiler == 0) return 1; + if (compiler->state == SASS_COMPILER_PARSED) return 0; + if (compiler->state != SASS_COMPILER_CREATED) return -1; + if (compiler->c_ctx == NULL) return 1; + if (compiler->cpp_ctx == NULL) return 1; + if (compiler->c_ctx->error_status) + return compiler->c_ctx->error_status; + // parse the context we have set up (file or data) + compiler->root = sass_parse_block(compiler); + // success + return 0; + } + + int ADDCALL sass_compiler_execute(struct Sass_Compiler* compiler) + { + if (compiler == 0) return 1; + if (compiler->state == SASS_COMPILER_EXECUTED) return 0; + if (compiler->state != SASS_COMPILER_PARSED) return -1; + if (compiler->c_ctx == NULL) return 1; + if (compiler->cpp_ctx == NULL) return 1; + if (compiler->root.isNull()) return 1; + if (compiler->c_ctx->error_status) + return compiler->c_ctx->error_status; + compiler->state = SASS_COMPILER_EXECUTED; + Context* cpp_ctx = compiler->cpp_ctx; + Block_Obj root = compiler->root; + // compile the parsed root block + try { compiler->c_ctx->output_string = cpp_ctx->render(root); } + // pass catched errors to generic error handler + catch (...) { return handle_errors(compiler->c_ctx) | 1; } + // generate source map json and store on context + compiler->c_ctx->source_map_string = cpp_ctx->render_srcmap(); + // success + return 0; + } + + // helper function, not exported, only accessible locally + static void sass_reset_options (struct Sass_Options* options) + { + // free pointer before + // or copy/move them + options->input_path = 0; + options->output_path = 0; + options->plugin_path = 0; + options->include_path = 0; + options->source_map_file = 0; + options->source_map_root = 0; + options->c_functions = 0; + options->c_importers = 0; + options->c_headers = 0; + options->plugin_paths = 0; + options->include_paths = 0; + options->extensions = 0; + } + + // helper function, not exported, only accessible locally + static void sass_clear_options (struct Sass_Options* options) + { + if (options == 0) return; + // Deallocate custom functions, headers and importes + sass_delete_function_list(options->c_functions); + sass_delete_importer_list(options->c_importers); + sass_delete_importer_list(options->c_headers); + // Deallocate inc paths + if (options->plugin_paths) { + struct string_list* cur; + 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; + } + } + // Deallocate extension + if (options->extensions) { + struct string_list* cur; + struct string_list* next; + cur = options->extensions; + while (cur) { + next = cur->next; + free(cur->string); + free(cur); + cur = next; + } + } + // Free options strings + free(options->input_path); + free(options->output_path); + free(options->plugin_path); + free(options->include_path); + free(options->source_map_file); + free(options->source_map_root); + // Reset our pointers + options->input_path = 0; + options->output_path = 0; + options->plugin_path = 0; + options->include_path = 0; + options->source_map_file = 0; + options->source_map_root = 0; + options->c_functions = 0; + options->c_importers = 0; + options->c_headers = 0; + options->plugin_paths = 0; + options->include_paths = 0; + options->extensions = 0; + } + + // helper function, not exported, only accessible locally + // sass_free_context is also defined in old sass_interface + static void sass_clear_context (struct Sass_Context* ctx) + { + if (ctx == 0) return; + // release the allocated memory (mostly via sass_copy_c_string) + if (ctx->output_string) free(ctx->output_string); + if (ctx->source_map_string) free(ctx->source_map_string); + if (ctx->error_message) free(ctx->error_message); + if (ctx->error_text) free(ctx->error_text); + if (ctx->error_json) free(ctx->error_json); + if (ctx->error_file) free(ctx->error_file); + 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->included_files = 0; + // debug leaked memory + #ifdef DEBUG_SHARED_PTR + SharedObj::dumpMemLeaks(); + #endif + // now clear the options + sass_clear_options(ctx); + } + + void ADDCALL sass_delete_compiler (struct Sass_Compiler* compiler) + { + if (compiler == 0) { + return; + } + Context* cpp_ctx = compiler->cpp_ctx; + if (cpp_ctx) delete(cpp_ctx); + compiler->cpp_ctx = NULL; + compiler->c_ctx = NULL; + compiler->root = NULL; + free(compiler); + } + + void ADDCALL sass_delete_options (struct Sass_Options* options) + { + sass_clear_options(options); free(options); + } + + // Deallocate all associated memory with file context + void ADDCALL sass_delete_file_context (struct Sass_File_Context* ctx) + { + // clear the context and free it + sass_clear_context(ctx); free(ctx); + } + // Deallocate all associated memory with data context + void ADDCALL sass_delete_data_context (struct Sass_Data_Context* ctx) + { + // clean the source string if it was not passed + // we reset this member once we start parsing + if (ctx->source_string) free(ctx->source_string); + // clear the context and free it + sass_clear_context(ctx); free(ctx); + } + + // Getters for sass context from specific implementations + struct Sass_Context* ADDCALL sass_file_context_get_context(struct Sass_File_Context* ctx) { return ctx; } + struct Sass_Context* ADDCALL sass_data_context_get_context(struct Sass_Data_Context* ctx) { return ctx; } + + // Getters for context options from Sass_Context + struct Sass_Options* ADDCALL sass_context_get_options(struct Sass_Context* ctx) { return ctx; } + struct Sass_Options* ADDCALL sass_file_context_get_options(struct Sass_File_Context* ctx) { return ctx; } + struct Sass_Options* ADDCALL sass_data_context_get_options(struct Sass_Data_Context* ctx) { return ctx; } + void ADDCALL sass_file_context_set_options (struct Sass_File_Context* ctx, struct Sass_Options* opt) { copy_options(ctx, opt); } + void ADDCALL sass_data_context_set_options (struct Sass_Data_Context* ctx, struct Sass_Options* opt) { copy_options(ctx, opt); } + + // Getters for Sass_Compiler options (get 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; } + struct Sass_Options* ADDCALL sass_compiler_get_options(struct Sass_Compiler* compiler) { return compiler->c_ctx; } + // Getters for Sass_Compiler options (query import stack) + size_t ADDCALL sass_compiler_get_import_stack_size(struct Sass_Compiler* compiler) { return compiler->cpp_ctx->import_stack.size(); } + Sass_Import_Entry ADDCALL sass_compiler_get_last_import(struct Sass_Compiler* compiler) { return compiler->cpp_ctx->import_stack.back(); } + Sass_Import_Entry ADDCALL sass_compiler_get_import_entry(struct Sass_Compiler* compiler, size_t idx) { return compiler->cpp_ctx->import_stack[idx]; } + // Getters for Sass_Compiler options (query function stack) + size_t ADDCALL sass_compiler_get_callee_stack_size(struct Sass_Compiler* compiler) { return compiler->cpp_ctx->callee_stack.size(); } + Sass_Callee_Entry ADDCALL sass_compiler_get_last_callee(struct Sass_Compiler* compiler) { return &compiler->cpp_ctx->callee_stack.back(); } + Sass_Callee_Entry ADDCALL sass_compiler_get_callee_entry(struct Sass_Compiler* compiler, size_t idx) { return &compiler->cpp_ctx->callee_stack[idx]; } + + // Calculate the size of the stored null terminated array + size_t ADDCALL sass_context_get_included_files_size (struct Sass_Context* ctx) + { size_t l = 0; auto i = ctx->included_files; while (i && *i) { ++i; ++l; } return l; } + + // Create getter and setters for options + IMPLEMENT_SASS_OPTION_ACCESSOR(int, precision); + IMPLEMENT_SASS_OPTION_ACCESSOR(enum Sass_Output_Style, output_style); + IMPLEMENT_SASS_OPTION_ACCESSOR(bool, source_comments); + IMPLEMENT_SASS_OPTION_ACCESSOR(bool, source_map_embed); + IMPLEMENT_SASS_OPTION_ACCESSOR(bool, source_map_contents); + IMPLEMENT_SASS_OPTION_ACCESSOR(bool, source_map_file_urls); + IMPLEMENT_SASS_OPTION_ACCESSOR(bool, omit_source_map_url); + IMPLEMENT_SASS_OPTION_ACCESSOR(bool, is_indented_syntax_src); + IMPLEMENT_SASS_OPTION_ACCESSOR(Sass_Function_List, c_functions); + IMPLEMENT_SASS_OPTION_ACCESSOR(Sass_Importer_List, c_importers); + IMPLEMENT_SASS_OPTION_ACCESSOR(Sass_Importer_List, c_headers); + IMPLEMENT_SASS_OPTION_ACCESSOR(const char*, indent); + IMPLEMENT_SASS_OPTION_ACCESSOR(const char*, linefeed); + IMPLEMENT_SASS_OPTION_STRING_SETTER(const char*, plugin_path, 0); + IMPLEMENT_SASS_OPTION_STRING_SETTER(const char*, include_path, 0); + IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(const char*, input_path, 0); + IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(const char*, output_path, 0); + IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(const char*, source_map_file, 0); + IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(const char*, source_map_root, 0); + + // Create getter and setters for context + IMPLEMENT_SASS_CONTEXT_GETTER(int, error_status); + IMPLEMENT_SASS_CONTEXT_GETTER(const char*, error_json); + IMPLEMENT_SASS_CONTEXT_GETTER(const char*, error_message); + IMPLEMENT_SASS_CONTEXT_GETTER(const char*, error_text); + IMPLEMENT_SASS_CONTEXT_GETTER(const char*, error_file); + IMPLEMENT_SASS_CONTEXT_GETTER(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 import extenions + void ADDCALL sass_option_push_import_extension(struct Sass_Options* options, const char* ext) + { + struct string_list* extension = (struct string_list*) calloc(1, sizeof(struct string_list)); + if (extension == 0) return; + extension->string = ext ? sass_copy_c_string(ext) : 0; + struct string_list* last = options->extensions; + if (!options->extensions) { + options->extensions = extension; + } else { + while (last->next) + last = last->next; + last->next = extension; + } + } + + // Push function for include paths (no manipulation support for now) + void ADDCALL sass_option_push_include_path(struct Sass_Options* options, const char* path) + { + + struct string_list* include_path = (struct string_list*) calloc(1, sizeof(struct string_list)); + if (include_path == 0) return; + include_path->string = path ? sass_copy_c_string(path) : 0; + struct string_list* last = options->include_paths; + if (!options->include_paths) { + options->include_paths = include_path; + } else { + while (last->next) + last = last->next; + last->next = include_path; + } + + } + + // Push function for include paths (no manipulation support for now) + size_t ADDCALL sass_option_get_include_path_size(struct Sass_Options* options) + { + size_t len = 0; + struct string_list* cur = options->include_paths; + while (cur) { len ++; cur = cur->next; } + return len; + } + + // Push function for include paths (no manipulation support for now) + const char* ADDCALL sass_option_get_include_path(struct Sass_Options* options, size_t i) + { + struct string_list* cur = options->include_paths; + while (i) { i--; cur = cur->next; } + return cur->string; + } + + // Push function for plugin paths (no manipulation support for now) + void ADDCALL sass_option_push_plugin_path(struct Sass_Options* options, const char* path) + { + + struct string_list* plugin_path = (struct string_list*) calloc(1, sizeof(struct string_list)); + if (plugin_path == 0) return; + plugin_path->string = path ? sass_copy_c_string(path) : 0; + struct string_list* last = options->plugin_paths; + if (!options->plugin_paths) { + options->plugin_paths = plugin_path; + } else { + while (last->next) + last = last->next; + last->next = plugin_path; + } + + } + +} diff --git a/src/libsass/sass_context.hpp b/src/libsass/sass_context.hpp new file mode 100644 index 000000000..9d192a301 --- /dev/null +++ b/src/libsass/sass_context.hpp @@ -0,0 +1,132 @@ +#ifndef SASS_SASS_CONTEXT_H +#define SASS_SASS_CONTEXT_H + +#include "sass/base.h" +#include "sass/context.h" +#include "ast_fwd_decl.hpp" + +// sass config options structure +struct Sass_Options : Sass_Output_Options { + + // embed sourceMappingUrl as data uri + bool source_map_embed; + + // embed include contents in maps + bool source_map_contents; + + // create file urls for sources + bool source_map_file_urls; + + // Disable sourceMappingUrl in css output + bool omit_source_map_url; + + // Treat source_string as sass (as opposed to scss) + bool is_indented_syntax_src; + + // The input path is used for source map + // generation. It can be used to define + // something with string compilation or to + // overload the input file path. It is + // set to "stdin" for data contexts and + // to the input file on file contexts. + char* input_path; + + // The output path is used for source map + // generation. LibSass will not write to + // this file, it is just used to create + // information in source-maps etc. + char* output_path; + + // Colon-separated list of paths + // Semicolon-separated on Windows + // Maybe use array interface instead? + char* extension; + char* include_path; + char* plugin_path; + + // Extensions (linked string list) + struct string_list* extensions; + // 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; + char* srcmap_string; + +}; + +// link c and cpp context +struct Sass_Compiler { + // progress status + Sass_Compiler_State state; + // original c context + Sass_Context* c_ctx; + // Sass::Context + Sass::Context* cpp_ctx; + // Sass::Block + Sass::Block_Obj root; +}; + +#endif diff --git a/src/libsass/setup-environment.md b/src/libsass/setup-environment.md new file mode 100644 index 000000000..805613656 --- /dev/null +++ b/src/libsass/setup-environment.md @@ -0,0 +1,68 @@ +## Requirements +In order to install and setup your local development environment, there are some prerequisites: + +* git +* gcc/clang/llvm (Linux: build tools, Mac OS X: XCode w/ Command Line Tools) +* ruby w/ bundler + +OS X: +First you'll need to install XCode which you can now get from the AppStore installed on your mac. After you download that and run it, then run this on the command line: + +```` +xcode-select --install +```` + +## Cloning the Projects + +First, clone the project and then add a line to your `~/.bash_profile` that will let other programs know where the LibSass dev files are. + +```` +git clone git@github.com:sass/libsass.git +cd libsass +echo "export SASS_LIBSASS_PATH=$(pwd)" >> ~/.bash_profile + +```` + +Then, if you run the "bootstrap" script, it should clone all the other required projects. + +```` +./script/bootstrap +```` + +You should now have a `sass-spec` and `sassc` folder within the libsass folder. Both of these are clones of their respective git projects. If you want to do a pull request, remember to work in those folders. For instance, if you want to add a test (see other documentation for how to do that), make sure to commit it to your *fork* of the sass-spec github project. Also, whenever you are running tests, make sure to `pull` from the origin! We want to make sure we are testing against the newest libsass, sassc, and sass-spec! + +Now, try and see if you can build the project. We do that with the `make` command. + +```` +make +```` + +At this point, if you get an error, something is most likely wrong with your compiler installation. Yikes. It's hard to cover how to fix this in an article. Feel free to open an issue and we'll try and help! But, remember, before you do that, googling the error message is your friend! Many problems are solved quickly that way. + +## Running The Spec Against LibSass + +Then, to run the spec against LibSass, just run: + +```` +./script/spec +```` + +If you get an error about `SASS_LIBSASS_PATH`, you may still need to set a variable pointing to the libsass folder, like this: + +```` +export SASS_LIBSASS_PATH=/Users/you/path/libsass +```` + +...where the latter part is to the `libsass` directory you've cloned. You can get this path by typing `pwd` in the Terminal + +## Running the Spec Against Ruby Sass + +Go into the sass-spec folder that should have been cloned earlier with the "bootstrap" command. Run the following. + +```` +bundle install +./sass-spec.rb +```` + +Voila! Now you are testing against Sass too! + diff --git a/src/libsass/source-map-internals.md b/src/libsass/source-map-internals.md new file mode 100644 index 000000000..50f83b54f --- /dev/null +++ b/src/libsass/source-map-internals.md @@ -0,0 +1,51 @@ +This document is mainly intended for developers! + +# Documenting some of the source map internals + +Since source maps are somewhat a black box to all LibSass maintainers, [I](@mgreter) will try to document my findings with source maps in LibSass, as I come across them. This document will also brievely explain how LibSass parses the source and how it outputs the result. + +The main storage for SourceMap mappings is the `mappings` vector: + +``` +# in source_map.hpp +vector mappings +# in mappings.hpp +struct Mapping ... + Position original_position; + Position generated_position; +``` + +## Every parsed token has its source associated + +LibSass uses a lexical parser. Whenever LibSass finds a token of interest, it creates a specific `AST_Node`, which will hold a reference to the input source with line/column information. `AST_Node` is the base class for all parsed items. They are declared in `ast.hpp` and are used in `parser.hpp`. Here a simple example: + +``` +if (lex< custom_property_name >()) { + Sass::String* prop = new (ctx.mem) String_Constant(path, source_position, lexed); + return new (ctx.mem) Declaration(path, prop->position(), prop, ...); +} +``` + +## How is the `source_position` calculated + +This is automatically done with `lex` in `parser.hpp`. Whenever something is lexed, the `source_position` is updated. But be aware that `source_position` points to the begining of the parsed text. If you need a mapping for the position where the parsing ended, you need to add another call to `lex` (to match nothing)! + +``` +lex< exactly < empty_str > >(); +end = new (ctx.mem) String_Constant(path, source_position, lexed); +``` + +## How are mappings for the output created + +So far we have collected all needed data for all tokens in the input stream. We can now use this information to create mappings when we put things into the output stream. Mappings are created via the `add_mappings` method: + +``` +# in source_map.hpp +void add_mapping(AST_Node* node); +``` + +This method is called in two places: +- `Inspect::append_to_buffer` +- `Output_[Nested|Compressed]::append_to_buffer` + +Mappings can only be created for things that have been parsed into a `AST_Node`. Otherwise we do not have the information to create the mappings, which is the reason why LibSass currently only maps the most important tokens in source maps. diff --git a/src/libsass/tap-runner b/src/libsass/tap-runner new file mode 100755 index 000000000..56c13bfb4 --- /dev/null +++ b/src/libsass/tap-runner @@ -0,0 +1 @@ +$@ $TEST_FLAGS --tap --silent | tapout tap diff --git a/src/libsass/trace.md b/src/libsass/trace.md new file mode 100644 index 000000000..4a57c901f --- /dev/null +++ b/src/libsass/trace.md @@ -0,0 +1,26 @@ +## This is proposed interface in https://github.com/sass/libsass/pull/1288 + +Additional debugging macros with low overhead are available, `TRACE()` and `TRACEINST()`. + +Both macros simulate a string stream, so they can be used like this: + + TRACE() << "Reached."; + +produces: + + [LibSass] parse_value parser.cpp:1384 Reached. + +`TRACE()` + logs function name, source filename, source file name to the standard error and the attached + stream to the standard error. + +`TRACEINST(obj)` + logs object instance address, function name, source filename, source file name to the standard error and the attached stream to the standard error, for example: + + TRACEINST(this) << "String_Constant created " << this; + +produces: + + [LibSass] 0x8031ba980:String_Constant ./ast.hpp:1371 String_Constant created (0,"auto") + +The macros generate output only of `LibSass_TRACE` is set in the environment. diff --git a/src/libsass/triage.md b/src/libsass/triage.md new file mode 100644 index 000000000..0fc11784c --- /dev/null +++ b/src/libsass/triage.md @@ -0,0 +1,17 @@ +This is an article about how to help with LibSass issues. Issue triage is a fancy word for explaining how we deal with incoming issues and make sure that the right problems get worked on. The lifecycle of an issue goes like this: + +1. Issue is reported by a user. +2. If the issue seems like a bug, then the "bug" tag is added. +3. If the reporting user didn't also create a spec test over at sass/sass-spec, the "needs test" tag is added. +4. Verify that Ruby Sass *does not* have the same bug. LibSass strives to be an exact replica of how Ruby Sass works. If it's an issue that neither project has solved, please close the ticket with the "not in sass" label. +5. The smallest possible breaking test is created in sass-spec. Cut away any extra information or non-breaking code until the core issue is made clear. +6. Again, verify that the expected output matches the latest Ruby Sass release. Do this by using your own tool OR by running ./sass-spec.rb in the spec folder and making sure that your test passes! +7. Create the test cases in sass-spec with the name spec/LibSass-todo-issues/issue_XXX/input.scss and expected_output.css where the XXX is the issue number here. +8. Commit that test to sass-spec, making sure to reference the issue in the comment message like "Test to demonstrate sass/LibSass#XXX". +9. Once the spec test exists, remove the "needs test" tag and replace it with "test written". +10. A C++ developer will then work on the issue and issue a pull request to fix the issue. +11. A core member verifies that the fix does actually fix the spec tests. +12. The fix is merged into the project. +13. The spec is moved from the LibSass-todo-issues folder into LibSass-closed-issues +14. The issue is closed +15. Have a soda pop or enjoyable beverage of your choice diff --git a/src/libsass/unicode.md b/src/libsass/unicode.md new file mode 100644 index 000000000..a1eb5b1cf --- /dev/null +++ b/src/libsass/unicode.md @@ -0,0 +1,45 @@ +LibSass currently expects all input to be utf8 encoded (and outputs only utf8), if you actually have any unicode characters at all. We do not support conversion between encodings, even if you declare it with a `@charset` rule. The text below was originally posted as an [issue](https://github.com/sass/libsass/issues/381) on the LibSass tracker. Since then the status is outdated as LibSass now expects your +input to be utf8/ascii compatible, as it has been proven that reading ANSI (e.g. single byte encodings) as utf8 can lead to unexpected +behavior, which can in the worst case lead to buffer overruns/segfaults. Therefore LibSass now checks your input to be valid utf8 encoded! + +### [Declaring character encodings in CSS](http://www.w3.org/International/questions/qa-css-charset.en) + +This [explains](http://www.w3.org/International/questions/qa-css-charset.en) how the character encoding of a css file is determined. Since we are only dealing with local files, we never have a HTTP header. So the precedence should be 'charset' rule, byte-order mark (BOM) or auto-detection (finally falling back to system default/UTF-8). This may not sound too hard to implement, but what about import rules? The CSS specs do not forbid the mixing of different encodings! I [solved that](https://github.com/mgreter/webmerge/) by converting all files to UTF-8 internally. On writing there is an option to tell the tool what encoding it should be (UTF-8 by default). One can also define if it should write a BOM or not and if it should add the charset declaration. + +Since my [tool]((https://github.com/mgreter/webmerge/)) is written in perl, I have a lot of utilities at hand to deal with different unicode charsets. I'm pretty sure that most OSS uses [ICU](http://site.icu-project.org/) or [libiconv](https://www.gnu.org/software/libiconv/) to convert between different encodings. But I have now idea how easy/hard this would be to integrate platform independent (it seems doable). ANSII (single byte encoding) to utf8 is basically just a conversion table (for every supported code-page). + +### Current status on LibSass unicode support + +LibSass should/is fully UTF (and therefore plain ASCII) compatible. + +~~Currently LibSass seems to handle the common UTF-8 case pretty well. I believe it should correctly support all ASCII compatible encodings (like UTF-8 or Latin-1). If all includes use the same encoding, the output should be correct (in the same encoding). It should also handle unicode chars in [selectors, variable names and other identifiers](https://github.com/hcatlin/libsass/issues/244#issuecomment-34681227). This is true for all ASCII compatible encodings. So the main incompatible encodings (I'm aware of) are UTF-16/UTF-32 (which could be converted to UTF-8 with libiconv).~~ + +LibSass 3.5 will enforce that your input is either plain ASCII (chars below 127) or utf8. It does not handle anything else, but therefore ensures that the output is in a valid form. Before version 3.5 you were able to mix different code-pages, which yielded unexpected behavior. + +### Current encoding auto detection + +LibSass currently reads all kind of BOMs and will error out if it finds something it doesn't know how to handle! It seems that it throws away the optional UTF-8 BOM (if any is found). IMO it would be nice if users could configure that (also if a charset rule should be added to the output). But it does not really take any `@charset` into account, it always assumes your input is utf8 and ignores any given `@charset`! + +### What is currently not supported + +- Using non ASCII compatible encodings (like UTF-16, Latin-1 etc.) +- Using non ASCII characters in different encodings in different includes + +### What is missing to support the above cases + +- A way to convert between encodings (like libiconv/ICU) +- Sniffing the charset inside the file (source is available) +- Handling the conversion on import (and export) +- Optional: Make output encoding configurable +- Optional: Add optional/mandatory BOM (configurable) + +### Low priority feature + +I guess the current implementation should handle more than 99% of all real world use cases. +A) Unicode characters are still seldomly seen (as they can be written escaped) +~~B) It will still work if it's UTF-8 or in any of the most common known western ISO codepages. +Although I'm not sure how this applies to asian and other "exotic" codepages!~~ + +I guess the biggest Problem is to have libiconv/ICU (or some other) library as a dependency. Since it contains a lot of rules for the conversions, I see it as the only way to handle this correctly. Once that is sorted out it should be pretty much straight forward to implement the missing pieces (in parser.cpp - Parser::parse should return encoding and add Parser::sniff_charset, then convert the source byte stream to UTF-8). + +I hope the statements above all hold true. Unicode is really not the easiest topic to wrap your head around. But since I did all the above recently in Perl, I wanted to document it here. Feel free to extend or criticize. diff --git a/src/libsass/util.hpp b/src/libsass/util.hpp new file mode 100644 index 000000000..f23475fe0 --- /dev/null +++ b/src/libsass/util.hpp @@ -0,0 +1,56 @@ +#ifndef SASS_UTIL_H +#define SASS_UTIL_H + +#include +#include +#include +#include "sass.hpp" +#include "sass/base.h" +#include "ast_fwd_decl.hpp" + +#define SASS_ASSERT(cond, msg) assert(cond && msg) + +namespace Sass { + + double round(double val, size_t precision = 0); + double sass_strtod(const char* str); + const char* safe_str(const char *, const char* = ""); + void free_string_array(char **); + char **copy_strings(const std::vector&, char ***, int = 0); + std::string read_css_string(const std::string& str, bool css = true); + std::string evacuate_escapes(const std::string& str); + std::string string_to_output(const std::string& str); + std::string comment_to_string(const std::string& text); + std::string read_hex_escapes(const std::string& str); + std::string escape_string(const std::string& str); + void newline_to_space(std::string& str); + + std::string quote(const std::string&, char q = 0); + std::string unquote(const std::string&, char* q = 0, bool keep_utf8_sequences = false, bool strict = true); + char detect_best_quotemark(const char* s, char qm = '"'); + + bool is_hex_doublet(double n); + bool is_color_doublet(double r, double g, double b); + + bool peek_linefeed(const char* start); + + namespace Util { + + std::string rtrim(const std::string& str); + + std::string normalize_underscores(const std::string& str); + std::string normalize_decimals(const std::string& str); + + bool isPrintable(Ruleset_Ptr r, Sass_Output_Style style = NESTED); + bool isPrintable(Supports_Block_Ptr r, Sass_Output_Style style = NESTED); + bool isPrintable(Media_Block_Ptr r, Sass_Output_Style style = NESTED); + bool isPrintable(Comment_Ptr b, Sass_Output_Style style = NESTED); + bool isPrintable(Block_Obj b, Sass_Output_Style style = NESTED); + bool isPrintable(String_Constant_Ptr s, Sass_Output_Style style = NESTED); + bool isPrintable(String_Quoted_Ptr s, Sass_Output_Style style = NESTED); + bool isPrintable(Declaration_Ptr d, Sass_Output_Style style = NESTED); + bool isAscii(const char chr); + + } +} +#endif From 415abe5ff053a77df8eaf95628b58022ffb861d5 Mon Sep 17 00:00:00 2001 From: Michael Mifsud Date: Mon, 23 Apr 2018 23:24:39 +1000 Subject: [PATCH 2/2] Workaround sass-spec A patch landed in sass-spec that is not compatible with 3.5. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7a9e236ad..313587830 100644 --- a/package.json +++ b/package.json @@ -83,7 +83,7 @@ "object-merge": "^2.5.1", "read-yaml": "^1.0.0", "rimraf": "^2.5.2", - "sass-spec": "3.5.3", + "sass-spec": "3.5.1", "unique-temp-dir": "^1.0.0" } }