diff --git a/context.cpp b/context.cpp index 544b14a7e8..a1fba715b5 100644 --- a/context.cpp +++ b/context.cpp @@ -54,6 +54,7 @@ namespace Sass { source_map (resolve_relative_path(initializers.output_path(), initializers.source_map_file(), get_cwd())), c_functions (vector()), image_path (initializers.image_path()), + input_path (make_canonical_path(initializers.input_path())), output_path (make_canonical_path(initializers.output_path())), source_comments (initializers.source_comments()), output_style (initializers.output_style()), @@ -71,6 +72,11 @@ namespace Sass { { cwd = get_cwd(); + // enforce some safe defaults + // used to create relative file links + if (input_path == "") input_path = "stdin"; + if (output_path == "") output_path = "stdout"; + include_paths.push_back(cwd); collect_include_paths(initializers.include_paths_c_str()); collect_include_paths(initializers.include_paths_array()); @@ -207,7 +213,36 @@ namespace Sass { void register_c_functions(Context&, Env* env, Sass_C_Function_List); void register_c_function(Context&, Env* env, Sass_C_Function_Callback); - char* Context::compile_file() + char* Context::compile_block(Block* root) + { + char* result = 0; + if (!root) return 0; + switch (output_style) { + case COMPRESSED: { + Output_Compressed output_compressed(this); + root->perform(&output_compressed); + string output = output_compressed.get_buffer(); + if (source_map_file != "" && !omit_source_map_url) { + output += format_source_mapping_url(source_map_file); + } + result = copy_c_str(output.c_str()); + } break; + + default: { + Output_Nested output_nested(source_comments, this); + root->perform(&output_nested); + string output = output_nested.get_buffer(); + if (source_map_file != "" && !omit_source_map_url) { + output += "\n" + format_source_mapping_url(source_map_file); + } + result = copy_c_str(output.c_str()); + + } break; + } + return result; + } + + Block* Context::parse_file() { Block* root = 0; for (size_t i = 0; i < queue.size(); ++i) { @@ -237,31 +272,32 @@ namespace Sass { Remove_Placeholders remove_placeholders(*this); root->perform(&remove_placeholders); - char* result = 0; - switch (output_style) { - case COMPRESSED: { - Output_Compressed output_compressed(this); - root->perform(&output_compressed); - string output = output_compressed.get_buffer(); - if (source_map_file != "" && !omit_source_map_url) { - output += format_source_mapping_url(source_map_file); - } - result = copy_c_str(output.c_str()); - } break; - - default: { - Output_Nested output_nested(source_comments, this); - root->perform(&output_nested); - string output = output_nested.get_buffer(); - if (source_map_file != "" && !omit_source_map_url) { - output += "\n" + format_source_mapping_url(source_map_file); - } - result = copy_c_str(output.c_str()); + return root; + } - } break; + Block* Context::parse_string() + { + if (!source_c_str) return 0; + queue.clear(); + if(is_indented_syntax_src) { + char * contents = sass2scss(source_c_str, SASS2SCSS_PRETTIFY_1); + add_source(input_path, input_path, contents); + return parse_file(); } + add_source(input_path, input_path, strdup(source_c_str)); + return parse_file(); + } - return result; + char* Context::compile_file() + { + // returns NULL if something fails + return compile_block(parse_file()); + } + + char* Context::compile_string() + { + // returns NULL if something fails + return compile_block(parse_string()); } string Context::format_source_mapping_url(const string& file) @@ -288,21 +324,6 @@ namespace Sass { return result; } - // allow to optionally overwrite the input path - // default argument for input_path is string("stdin") - // usefull to influence the source-map generating etc. - char* Context::compile_string(const string& input_path) - { - if (!source_c_str) return 0; - queue.clear(); - if(is_indented_syntax_src) { - char * contents = sass2scss(source_c_str, SASS2SCSS_PRETTIFY_1); - add_source(input_path, input_path, contents); - return compile_file(); - } - add_source(input_path, input_path, strdup(source_c_str)); - return compile_file(); - } std::vector Context::get_included_files(size_t skip) { diff --git a/context.hpp b/context.hpp index 9568294f57..8bffb87cdb 100644 --- a/context.hpp +++ b/context.hpp @@ -63,6 +63,7 @@ namespace Sass { vector c_functions; string image_path; // for the image-url Sass function + string input_path; // for relative paths in src-map string output_path; // for relative paths to the output bool source_comments; // for inline debug comments in css output Output_Style output_style; // output style for the generated css code @@ -84,6 +85,7 @@ namespace Sass { KWD_ARG_SET(Data) { KWD_ARG(Data, const char*, source_c_str); KWD_ARG(Data, string, entry_point); + KWD_ARG(Data, string, input_path); KWD_ARG(Data, string, output_path); KWD_ARG(Data, string, image_path); KWD_ARG(Data, const char*, include_paths_c_str); @@ -105,13 +107,16 @@ namespace Sass { ~Context(); void setup_color_map(); string add_file(string); + Block* parse_file(); string add_file(string, string); + Block* parse_string(); void add_source(string, string, const char*); // allow to optionally overwrite the input path // default argument for input_path is string("stdin") // usefull to influence the source-map generating etc. - char* compile_string(const string& input_path = "stdin"); char* compile_file(); + char* compile_string(); + char* compile_block(Block* root); char* generate_source_map(); vector get_included_files(size_t skip = 0); diff --git a/sass_context.cpp b/sass_context.cpp index e11c2bb911..abb3afa81f 100644 --- a/sass_context.cpp +++ b/sass_context.cpp @@ -11,6 +11,7 @@ extern "C" { using namespace std; + using namespace Sass; // Input behaviours enum Sass_Input_Style { @@ -129,6 +130,25 @@ extern "C" { }; + // 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; + // must be Sass::Block + void* root; + // must be Sass::Context + void* cpp_ctx; + // original c context + Sass_Context* c_ctx; + }; + #define IMPLEMENT_SASS_OPTION_GETTER(type, option) \ type sass_option_get_##option (struct Sass_Options* options) { return options->option; } @@ -177,11 +197,79 @@ extern "C" { free(arr); } + static int handle_errors(Sass_Context* c_ctx) { + try { + throw; + } + catch (Error& e) { + stringstream msg_stream; + JsonNode* json_err = json_mkobject(); + json_append_member(json_err, "status", json_mknumber(1)); + json_append_member(json_err, "path", json_mkstring(e.path.c_str())); + json_append_member(json_err, "line", json_mknumber(e.position.line)); + json_append_member(json_err, "column", json_mknumber(e.position.column)); + json_append_member(json_err, "message", json_mkstring(e.message.c_str())); + msg_stream << e.path << ":" << e.position.line << ": " << e.message << endl; + c_ctx->error_json = json_stringify(json_err, "\t");; + c_ctx->error_message = strdup(msg_stream.str().c_str()); + c_ctx->error_status = 1; + c_ctx->output_string = 0; + c_ctx->source_map_string = 0; + } + catch(bad_alloc& ba) { + stringstream msg_stream; + JsonNode* json_err = json_mkobject(); + msg_stream << "Unable to allocate memory: " << ba.what() << endl; + json_append_member(json_err, "status", json_mknumber(2)); + json_append_member(json_err, "message", json_mkstring(ba.what())); + c_ctx->error_json = json_stringify(json_err, "\t");; + c_ctx->error_message = strdup(msg_stream.str().c_str()); + c_ctx->error_status = 2; + c_ctx->output_string = 0; + c_ctx->source_map_string = 0; + } + catch (std::exception& e) { + stringstream msg_stream; + JsonNode* json_err = json_mkobject(); + msg_stream << "Error: " << e.what() << endl; + json_append_member(json_err, "status", json_mknumber(3)); + json_append_member(json_err, "message", json_mkstring(e.what())); + c_ctx->error_json = json_stringify(json_err, "\t");; + c_ctx->error_message = strdup(msg_stream.str().c_str()); + c_ctx->error_status = 3; + c_ctx->output_string = 0; + c_ctx->source_map_string = 0; + } + catch (string& e) { + stringstream msg_stream; + JsonNode* json_err = json_mkobject(); + msg_stream << "Error: " << e << endl; + json_append_member(json_err, "status", json_mknumber(4)); + json_append_member(json_err, "message", json_mkstring(e.c_str())); + c_ctx->error_json = json_stringify(json_err, "\t");; + c_ctx->error_message = strdup(msg_stream.str().c_str()); + c_ctx->error_status = 4; + c_ctx->output_string = 0; + c_ctx->source_map_string = 0; + } + catch (...) { + stringstream msg_stream; + JsonNode* json_err = json_mkobject(); + msg_stream << "Unknown error occurred" << endl; + json_append_member(json_err, "status", json_mknumber(5)); + json_append_member(json_err, "message", json_mkstring("unknown")); + c_ctx->error_json = json_stringify(json_err, "\t");; + c_ctx->error_message = strdup(msg_stream.str().c_str()); + c_ctx->error_status = 5; + c_ctx->output_string = 0; + c_ctx->source_map_string = 0; + } + return c_ctx->error_status; + } + // generic compilation function (not exported, use file/data compile instead) - static int sass_compile_context (Sass_Context* c_ctx, Sass::Context::Data cpp_opt) + static Context* sass_prepare_context (Sass_Context* c_ctx, Context::Data cpp_opt) { - - using namespace Sass; try { // get input/output path from options @@ -206,9 +294,9 @@ extern "C" { include_paths[i] = cur->string; cur = cur->next; } - // transfer the options to c++ - cpp_opt.output_path(output_path) + cpp_opt.input_path(input_path) + .output_path(output_path) .output_style((Output_Style) c_ctx->output_style) .is_indented_syntax_src(c_ctx->is_indented_syntax_src) .source_comments(c_ctx->source_comments) @@ -224,7 +312,7 @@ extern "C" { .precision(c_ctx->precision ? c_ctx->precision : 5); // create new c++ Context - Context cpp_ctx(cpp_opt); + Context* cpp_ctx = new Context(cpp_opt); // free intermediate data free(include_paths); @@ -232,7 +320,7 @@ extern "C" { if (c_ctx->c_functions) { Sass_C_Function_List this_func_data = c_ctx->c_functions; while ((this_func_data) && (*this_func_data)) { - cpp_ctx.c_functions.push_back((*this_func_data)); + cpp_ctx->c_functions.push_back((*this_func_data)); ++this_func_data; } } @@ -242,88 +330,82 @@ extern "C" { c_ctx->error_message = 0; c_ctx->error_status = 0; + // use to parse block + return cpp_ctx; + + } + // pass errors to generic error handler + catch (...) { handle_errors(c_ctx); } + + // error + return 0; + + } + + static Block* sass_parse_block (Sass_Context* c_ctx, Context* cpp_ctx) + { + try { + + // get input/output path from options + string input_path = safe_str(c_ctx->input_path); + string output_path = safe_str(c_ctx->output_path); + + // parsed root block + Block* root = 0; + // maybe skip some entries of included files // we do not include stdin for data contexts size_t skip = 0; // dispatch to the correct render function if (c_ctx->type == SASS_CONTEXT_FILE) { - c_ctx->output_string = cpp_ctx.compile_file(); + root = cpp_ctx->parse_file(); } else if (c_ctx->type == SASS_CONTEXT_DATA) { - if (input_path == "") { c_ctx->output_string = cpp_ctx.compile_string(); } - else { c_ctx->output_string = cpp_ctx.compile_string(input_path); } - skip = 1; // skip stdin (first) entry of included files + root = cpp_ctx->parse_string(); + skip = 1; // skip first entry of includes } - // generate source map json and store on context - c_ctx->source_map_string = cpp_ctx.generate_source_map(); // copy the included files on to the context (dont forget to free) - copy_strings(cpp_ctx.get_included_files(skip), &c_ctx->included_files, skip); + copy_strings(cpp_ctx->get_included_files(skip), &c_ctx->included_files, skip); + + // return parsed block + return root; } - catch (Error& e) { - stringstream msg_stream; - JsonNode* json_err = json_mkobject(); - json_append_member(json_err, "status", json_mknumber(1)); - json_append_member(json_err, "path", json_mkstring(e.path.c_str())); - json_append_member(json_err, "line", json_mknumber(e.position.line)); - json_append_member(json_err, "column", json_mknumber(e.position.column)); - json_append_member(json_err, "message", json_mkstring(e.message.c_str())); - msg_stream << e.path << ":" << e.position.line << ": " << e.message << endl; - c_ctx->error_json = json_stringify(json_err, "\t");; - c_ctx->error_message = strdup(msg_stream.str().c_str()); - c_ctx->error_status = 1; - c_ctx->output_string = 0; - c_ctx->source_map_string = 0; - } - catch(bad_alloc& ba) { - stringstream msg_stream; - JsonNode* json_err = json_mkobject(); - msg_stream << "Unable to allocate memory: " << ba.what() << endl; - json_append_member(json_err, "status", json_mknumber(2)); - json_append_member(json_err, "message", json_mkstring(ba.what())); - c_ctx->error_json = json_stringify(json_err, "\t");; - c_ctx->error_message = strdup(msg_stream.str().c_str()); - c_ctx->error_status = 2; - c_ctx->output_string = 0; - c_ctx->source_map_string = 0; - } - catch (std::exception& e) { - stringstream msg_stream; - JsonNode* json_err = json_mkobject(); - msg_stream << "Error: " << e.what() << endl; - json_append_member(json_err, "status", json_mknumber(3)); - json_append_member(json_err, "message", json_mkstring(e.what())); - c_ctx->error_json = json_stringify(json_err, "\t");; - c_ctx->error_message = strdup(msg_stream.str().c_str()); - c_ctx->error_status = 3; - c_ctx->output_string = 0; - c_ctx->source_map_string = 0; - } - catch (string& e) { - stringstream msg_stream; - JsonNode* json_err = json_mkobject(); - msg_stream << "Error: " << e << endl; - json_append_member(json_err, "status", json_mknumber(4)); - json_append_member(json_err, "message", json_mkstring(e.c_str())); - c_ctx->error_json = json_stringify(json_err, "\t");; - c_ctx->error_message = strdup(msg_stream.str().c_str()); - c_ctx->error_status = 4; - c_ctx->output_string = 0; - c_ctx->source_map_string = 0; - } - catch (...) { - stringstream msg_stream; - JsonNode* json_err = json_mkobject(); - msg_stream << "Unknown error occurred" << endl; - json_append_member(json_err, "status", json_mknumber(5)); - json_append_member(json_err, "message", json_mkstring("unknown")); - c_ctx->error_json = json_stringify(json_err, "\t");; - c_ctx->error_message = strdup(msg_stream.str().c_str()); - c_ctx->error_status = 5; - c_ctx->output_string = 0; - c_ctx->source_map_string = 0; + // pass errors to generic error handler + catch (...) { handle_errors(c_ctx); } + + // error + return 0; + + } + + // generic compilation function (not exported, use file/data compile instead) + static int sass_compile_context (Sass_Context* c_ctx, Context::Data cpp_opt) + { + + Context* cpp_ctx = 0; + + try { + + // first prepare the c++ context + cpp_ctx = sass_prepare_context(c_ctx, cpp_opt); + + // parse given context and return root block + Block* root = sass_parse_block(c_ctx, cpp_ctx); + + // now compile the parsed root block + c_ctx->output_string = cpp_ctx->compile_block(root); + + // generate source map json and store on context + c_ctx->source_map_string = cpp_ctx->generate_source_map(); + } + // pass errors to generic error handler + catch (...) { delete cpp_ctx; return handle_errors(c_ctx); } + + delete cpp_ctx; + return c_ctx->error_status; } @@ -348,15 +430,31 @@ extern "C" { return ctx; } + struct Sass_Compiler* sass_make_file_compiler (struct Sass_File_Context* c_ctx) + { + struct Sass_Compiler* compiler = (struct Sass_Compiler*) calloc(1, sizeof(struct Sass_Compiler)); + compiler->state = SASS_COMPILER_CREATED; + compiler->c_ctx = c_ctx; + Context::Data cpp_opt = Context::Data(); + cpp_opt.entry_point(c_ctx->input_path); + compiler->cpp_ctx = sass_prepare_context(c_ctx, cpp_opt); + return compiler; + } + + struct Sass_Compiler* sass_make_data_compiler (struct Sass_Data_Context* c_ctx) + { + struct Sass_Compiler* compiler = (struct Sass_Compiler*) calloc(1, sizeof(struct Sass_Compiler)); + compiler->state = SASS_COMPILER_CREATED; + compiler->c_ctx = c_ctx; + Context::Data cpp_opt = Context::Data(); + cpp_opt.source_c_str(c_ctx->source_string); + compiler->cpp_ctx = sass_prepare_context(c_ctx, cpp_opt); + return compiler; + } + int sass_compile_data_context(Sass_Data_Context* data_ctx) { - using namespace Sass; Sass_Context* c_ctx = data_ctx; - if (!c_ctx->input_path) { - // use a default value which - // will be seen relative to cwd - c_ctx->input_path = "stdin"; - } Context::Data cpp_opt = Context::Data(); cpp_opt.source_c_str(data_ctx->source_string); return sass_compile_context(c_ctx, cpp_opt); @@ -364,13 +462,43 @@ extern "C" { int sass_compile_file_context(Sass_File_Context* file_ctx) { - using namespace Sass; Sass_Context* c_ctx = file_ctx; Context::Data cpp_opt = Context::Data(); cpp_opt.entry_point(file_ctx->input_path); return sass_compile_context(c_ctx, cpp_opt); } + int sass_compiler_parse(struct Sass_Compiler* compiler) + { + 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; + compiler->state = SASS_COMPILER_PARSED; + Context* cpp_ctx = (Context*) compiler->cpp_ctx; + compiler->root = sass_parse_block(compiler->c_ctx, cpp_ctx); + // success + return 0; + } + + int sass_compiler_execute(struct Sass_Compiler* compiler) + { + if (compiler->state == SASS_COMPILER_EXECUTED) return 0; + if (compiler->state != SASS_COMPILER_PARSED) return -1; + if (compiler->c_ctx == NULL) return 1; + if (compiler->cpp_ctx == NULL) return 1; + if (compiler->root == NULL) return 1; + compiler->state = SASS_COMPILER_EXECUTED; + Context* cpp_ctx = (Context*) compiler->cpp_ctx; + Block* root = (Block*) compiler->root; + // now compile the parsed root block + compiler->c_ctx->output_string = cpp_ctx->compile_block(root); + // generate source map json and store on context + compiler->c_ctx->source_map_string = cpp_ctx->generate_source_map(); + // success + return 0; + } + // helper function, not exported, only accessible locally static void sass_clear_options (struct Sass_Options* options) { @@ -411,6 +539,13 @@ extern "C" { sass_clear_options(ctx); } + void sass_delete_compiler (struct Sass_Compiler* compiler) + { + Context* cpp_ctx = (Context*) compiler->cpp_ctx; + delete cpp_ctx; + free(compiler); + } + // Deallocate all associated memory with contexts void sass_delete_file_context (struct Sass_File_Context* ctx) { sass_clear_context(ctx); free(ctx); } void sass_delete_data_context (struct Sass_Data_Context* ctx) { sass_clear_context(ctx); free(ctx); } diff --git a/sass_context.h b/sass_context.h index d0b51a6223..ff73fdfdf0 100644 --- a/sass_context.h +++ b/sass_context.h @@ -10,6 +10,9 @@ extern "C" { #endif +// Forward declaration +struct Sass_Compiler; + // Forward declaration struct Sass_Options; struct Sass_Context; // : Sass_Options @@ -26,20 +29,33 @@ struct Sass_Data_Context* sass_make_data_context (char* source_string); 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); + // 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* ctx); -struct Sass_Context* sass_data_context_get_context (struct Sass_Data_Context* ctx); +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* ctx); -struct Sass_Options* sass_data_context_get_options (struct Sass_Data_Context* ctx); -void sass_file_context_set_options (struct Sass_File_Context* ctx, struct Sass_Options* opt); -void sass_data_context_set_options (struct Sass_Data_Context* ctx, struct Sass_Options* opt); +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 options diff --git a/sass_interface.cpp b/sass_interface.cpp index 3c36c51440..5e304d9ac7 100644 --- a/sass_interface.cpp +++ b/sass_interface.cpp @@ -130,8 +130,7 @@ extern "C" { ++this_func_data; } } - c_ctx->output_string = c_ctx->input_path ? cpp_ctx.compile_string(input_path) : - cpp_ctx.compile_string(); + c_ctx->output_string = cpp_ctx.compile_string(); c_ctx->source_map_string = cpp_ctx.generate_source_map(); c_ctx->error_message = 0; c_ctx->error_status = 0;