1 | // sass.hpp must go before all system headers to get the |
2 | // __EXTENSIONS__ fix on Solaris. |
3 | #include "sass.hpp" |
4 | #include "ast.hpp" |
5 | |
6 | #include "remove_placeholders.hpp" |
7 | #include "sass_functions.hpp" |
8 | #include "check_nesting.hpp" |
9 | #include "fn_selectors.hpp" |
10 | #include "fn_strings.hpp" |
11 | #include "fn_numbers.hpp" |
12 | #include "fn_colors.hpp" |
13 | #include "fn_miscs.hpp" |
14 | #include "fn_lists.hpp" |
15 | #include "fn_maps.hpp" |
16 | #include "context.hpp" |
17 | #include "expand.hpp" |
18 | #include "parser.hpp" |
19 | #include "cssize.hpp" |
20 | #include "source.hpp" |
21 | |
22 | namespace Sass { |
23 | using namespace Constants; |
24 | using namespace File; |
25 | using namespace Sass; |
26 | |
27 | inline bool sort_importers (const Sass_Importer_Entry& i, const Sass_Importer_Entry& j) |
28 | { return sass_importer_get_priority(cb: i) > sass_importer_get_priority(cb: j); } |
29 | |
30 | static sass::string safe_input(const char* in_path) |
31 | { |
32 | if (in_path == nullptr || in_path[0] == '\0') return "stdin" ; |
33 | return in_path; |
34 | } |
35 | |
36 | static sass::string safe_output(const char* out_path, sass::string input_path) |
37 | { |
38 | if (out_path == nullptr || out_path[0] == '\0') { |
39 | if (input_path.empty()) return "stdout" ; |
40 | return input_path.substr(pos: 0, n: input_path.find_last_of(s: "." )) + ".css" ; |
41 | } |
42 | return out_path; |
43 | } |
44 | |
45 | Context::Context(struct Sass_Context& c_ctx) |
46 | : CWD(File::get_cwd()), |
47 | c_options(c_ctx), |
48 | entry_path("" ), |
49 | head_imports(0), |
50 | plugins(), |
51 | emitter(c_options), |
52 | |
53 | ast_gc(), |
54 | strings(), |
55 | resources(), |
56 | sheets(), |
57 | import_stack(), |
58 | callee_stack(), |
59 | traces(), |
60 | extender(Extender::NORMAL, traces), |
61 | c_compiler(NULL), |
62 | |
63 | c_headers (sass::vector<Sass_Importer_Entry>()), |
64 | c_importers (sass::vector<Sass_Importer_Entry>()), |
65 | c_functions (sass::vector<Sass_Function_Entry>()), |
66 | |
67 | indent (safe_str(c_options.indent, " " )), |
68 | linefeed (safe_str(c_options.linefeed, "\n" )), |
69 | |
70 | input_path (make_canonical_path(path: safe_input(in_path: c_options.input_path))), |
71 | output_path (make_canonical_path(path: safe_output(out_path: c_options.output_path, input_path))), |
72 | source_map_file (make_canonical_path(path: safe_str(c_options.source_map_file, "" ))), |
73 | source_map_root (make_canonical_path(path: safe_str(c_options.source_map_root, "" ))) |
74 | |
75 | { |
76 | |
77 | // Sass 3.4: The current working directory will no longer be placed onto the Sass load path by default. |
78 | // If you need the current working directory to be available, set SASS_PATH=. in your shell's environment. |
79 | // include_paths.push_back(CWD); |
80 | |
81 | // collect more paths from different options |
82 | collect_include_paths(paths_str: c_options.include_path); |
83 | collect_include_paths(paths_array: c_options.include_paths); |
84 | collect_plugin_paths(paths_str: c_options.plugin_path); |
85 | collect_plugin_paths(paths_array: c_options.plugin_paths); |
86 | |
87 | // load plugins and register custom behaviors |
88 | for(auto plug : plugin_paths) plugins.load_plugins(path: plug); |
89 | for(auto fn : plugins.get_headers()) c_headers.push_back(x: fn); |
90 | for(auto fn : plugins.get_importers()) c_importers.push_back(x: fn); |
91 | for(auto fn : plugins.get_functions()) c_functions.push_back(x: fn); |
92 | |
93 | // sort the items by priority (lowest first) |
94 | sort (first: c_headers.begin(), last: c_headers.end(), comp: sort_importers); |
95 | sort (first: c_importers.begin(), last: c_importers.end(), comp: sort_importers); |
96 | |
97 | emitter.set_filename(abs2rel(path: output_path, base: source_map_file, cwd: CWD)); |
98 | |
99 | } |
100 | |
101 | void Context::add_c_function(Sass_Function_Entry function) |
102 | { |
103 | c_functions.push_back(x: function); |
104 | } |
105 | void Context::(Sass_Importer_Entry ) |
106 | { |
107 | c_headers.push_back(x: header); |
108 | // need to sort the array afterwards (no big deal) |
109 | sort (first: c_headers.begin(), last: c_headers.end(), comp: sort_importers); |
110 | } |
111 | void Context::add_c_importer(Sass_Importer_Entry importer) |
112 | { |
113 | c_importers.push_back(x: importer); |
114 | // need to sort the array afterwards (no big deal) |
115 | sort (first: c_importers.begin(), last: c_importers.end(), comp: sort_importers); |
116 | } |
117 | |
118 | Context::~Context() |
119 | { |
120 | // resources were allocated by malloc |
121 | for (size_t i = 0; i < resources.size(); ++i) { |
122 | free(ptr: resources[i].contents); |
123 | free(ptr: resources[i].srcmap); |
124 | } |
125 | // free all strings we kept alive during compiler execution |
126 | for (size_t n = 0; n < strings.size(); ++n) free(ptr: strings[n]); |
127 | // everything that gets put into sources will be freed by us |
128 | // this shouldn't have anything in it anyway!? |
129 | for (size_t m = 0; m < import_stack.size(); ++m) { |
130 | sass_import_take_source(import_stack[m]); |
131 | sass_import_take_srcmap(import_stack[m]); |
132 | sass_delete_import(import_stack[m]); |
133 | } |
134 | // clear inner structures (vectors) and input source |
135 | resources.clear(); import_stack.clear(); |
136 | sheets.clear(); |
137 | } |
138 | |
139 | Data_Context::~Data_Context() |
140 | { |
141 | // --> this will be freed by resources |
142 | // make sure we free the source even if not processed! |
143 | // if (resources.size() == 0 && source_c_str) free(source_c_str); |
144 | // if (resources.size() == 0 && srcmap_c_str) free(srcmap_c_str); |
145 | // source_c_str = 0; srcmap_c_str = 0; |
146 | } |
147 | |
148 | File_Context::~File_Context() |
149 | { |
150 | } |
151 | |
152 | void Context::collect_include_paths(const char* paths_str) |
153 | { |
154 | if (paths_str) { |
155 | const char* beg = paths_str; |
156 | const char* end = Prelexer::find_first<PATH_SEP>(src: beg); |
157 | |
158 | while (end) { |
159 | sass::string path(beg, end - beg); |
160 | if (!path.empty()) { |
161 | if (*path.rbegin() != '/') path += '/'; |
162 | include_paths.push_back(x: path); |
163 | } |
164 | beg = end + 1; |
165 | end = Prelexer::find_first<PATH_SEP>(src: beg); |
166 | } |
167 | |
168 | sass::string path(beg); |
169 | if (!path.empty()) { |
170 | if (*path.rbegin() != '/') path += '/'; |
171 | include_paths.push_back(x: path); |
172 | } |
173 | } |
174 | } |
175 | |
176 | void Context::collect_include_paths(string_list* paths_array) |
177 | { |
178 | while (paths_array) |
179 | { |
180 | collect_include_paths(paths_str: paths_array->string); |
181 | paths_array = paths_array->next; |
182 | } |
183 | } |
184 | |
185 | void Context::collect_plugin_paths(const char* paths_str) |
186 | { |
187 | if (paths_str) { |
188 | const char* beg = paths_str; |
189 | const char* end = Prelexer::find_first<PATH_SEP>(src: beg); |
190 | |
191 | while (end) { |
192 | sass::string path(beg, end - beg); |
193 | if (!path.empty()) { |
194 | if (*path.rbegin() != '/') path += '/'; |
195 | plugin_paths.push_back(x: path); |
196 | } |
197 | beg = end + 1; |
198 | end = Prelexer::find_first<PATH_SEP>(src: beg); |
199 | } |
200 | |
201 | sass::string path(beg); |
202 | if (!path.empty()) { |
203 | if (*path.rbegin() != '/') path += '/'; |
204 | plugin_paths.push_back(x: path); |
205 | } |
206 | } |
207 | } |
208 | |
209 | void Context::collect_plugin_paths(string_list* paths_array) |
210 | { |
211 | while (paths_array) |
212 | { |
213 | collect_plugin_paths(paths_str: paths_array->string); |
214 | paths_array = paths_array->next; |
215 | } |
216 | } |
217 | |
218 | // resolve the imp_path in base_path or include_paths |
219 | // looks for alternatives and returns a list from one directory |
220 | sass::vector<Include> Context::find_includes(const Importer& import) |
221 | { |
222 | // make sure we resolve against an absolute path |
223 | sass::string base_path(rel2abs(path: import.base_path)); |
224 | // first try to resolve the load path relative to the base path |
225 | sass::vector<Include> vec(resolve_includes(root: base_path, file: import.imp_path)); |
226 | // then search in every include path (but only if nothing found yet) |
227 | for (size_t i = 0, S = include_paths.size(); vec.size() == 0 && i < S; ++i) |
228 | { |
229 | // call resolve_includes and individual base path and append all results |
230 | sass::vector<Include> resolved(resolve_includes(root: include_paths[i], file: import.imp_path)); |
231 | if (resolved.size()) vec.insert(position: vec.end(), first: resolved.begin(), last: resolved.end()); |
232 | } |
233 | // return vector |
234 | return vec; |
235 | } |
236 | |
237 | // register include with resolved path and its content |
238 | // memory of the resources will be freed by us on exit |
239 | void Context::register_resource(const Include& inc, const Resource& res) |
240 | { |
241 | |
242 | // do not parse same resource twice |
243 | // maybe raise an error in this case |
244 | // if (sheets.count(inc.abs_path)) { |
245 | // free(res.contents); free(res.srcmap); |
246 | // throw std::runtime_error("duplicate resource registered"); |
247 | // return; |
248 | // } |
249 | |
250 | // get index for this resource |
251 | size_t idx = resources.size(); |
252 | |
253 | // tell emitter about new resource |
254 | emitter.add_source_index(idx); |
255 | |
256 | // put resources under our control |
257 | // the memory will be freed later |
258 | resources.push_back(x: res); |
259 | |
260 | // add a relative link to the working directory |
261 | included_files.push_back(x: inc.abs_path); |
262 | // add a relative link to the source map output file |
263 | srcmap_links.push_back(x: abs2rel(path: inc.abs_path, base: source_map_file, cwd: CWD)); |
264 | |
265 | // get pointer to the loaded content |
266 | Sass_Import_Entry import = sass_make_import( |
267 | imp_path: inc.imp_path.c_str(), |
268 | abs_base: inc.abs_path.c_str(), |
269 | source: res.contents, |
270 | srcmap: res.srcmap |
271 | ); |
272 | // add the entry to the stack |
273 | import_stack.push_back(x: import); |
274 | |
275 | // get pointer to the loaded content |
276 | const char* contents = resources[idx].contents; |
277 | SourceFileObj source = SASS_MEMORY_NEW(SourceFile, |
278 | inc.abs_path.c_str(), contents, idx); |
279 | |
280 | // create the initial parser state from resource |
281 | SourceSpan pstate(source); |
282 | |
283 | // check existing import stack for possible recursion |
284 | for (size_t i = 0; i < import_stack.size() - 2; ++i) { |
285 | auto parent = import_stack[i]; |
286 | if (std::strcmp(s1: parent->abs_path, s2: import->abs_path) == 0) { |
287 | sass::string cwd(File::get_cwd()); |
288 | // make path relative to the current directory |
289 | sass::string stack("An @import loop has been found:" ); |
290 | for (size_t n = 1; n < i + 2; ++n) { |
291 | stack += "\n " + sass::string(File::abs2rel(path: import_stack[n]->abs_path, base: cwd, cwd)) + |
292 | " imports " + sass::string(File::abs2rel(path: import_stack[n+1]->abs_path, base: cwd, cwd)); |
293 | } |
294 | // implement error throw directly until we |
295 | // decided how to handle full stack traces |
296 | throw Exception::InvalidSyntax(pstate, traces, stack); |
297 | // error(stack, prstate ? *prstate : pstate, import_stack); |
298 | } |
299 | } |
300 | |
301 | // create a parser instance from the given c_str buffer |
302 | Parser p(source, *this, traces); |
303 | // do not yet dispose these buffers |
304 | sass_import_take_source(import); |
305 | sass_import_take_srcmap(import); |
306 | // then parse the root block |
307 | Block_Obj root = p.parse(); |
308 | // delete memory of current stack frame |
309 | sass_delete_import(import_stack.back()); |
310 | // remove current stack frame |
311 | import_stack.pop_back(); |
312 | // create key/value pair for ast node |
313 | std::pair<const sass::string, StyleSheet> |
314 | ast_pair(inc.abs_path, { res, root }); |
315 | // register resulting resource |
316 | sheets.insert(x&: ast_pair); |
317 | } |
318 | |
319 | // register include with resolved path and its content |
320 | // memory of the resources will be freed by us on exit |
321 | void Context::register_resource(const Include& inc, const Resource& res, SourceSpan& prstate) |
322 | { |
323 | traces.push_back(x: Backtrace(prstate)); |
324 | register_resource(inc, res); |
325 | traces.pop_back(); |
326 | } |
327 | |
328 | // Add a new import to the context (called from `import_url`) |
329 | Include Context::load_import(const Importer& imp, SourceSpan pstate) |
330 | { |
331 | |
332 | // search for valid imports (ie. partials) on the filesystem |
333 | // this may return more than one valid result (ambiguous imp_path) |
334 | const sass::vector<Include> resolved(find_includes(import: imp)); |
335 | |
336 | // error nicely on ambiguous imp_path |
337 | if (resolved.size() > 1) { |
338 | sass::ostream msg_stream; |
339 | msg_stream << "It's not clear which file to import for " ; |
340 | msg_stream << "'@import \"" << imp.imp_path << "\"'." << "\n" ; |
341 | msg_stream << "Candidates:" << "\n" ; |
342 | for (size_t i = 0, L = resolved.size(); i < L; ++i) |
343 | { msg_stream << " " << resolved[i].imp_path << "\n" ; } |
344 | msg_stream << "Please delete or rename all but one of these files." << "\n" ; |
345 | error(msg: msg_stream.str(), pstate, traces); |
346 | } |
347 | |
348 | // process the resolved entry |
349 | else if (resolved.size() == 1) { |
350 | bool use_cache = c_importers.size() == 0; |
351 | // use cache for the resource loading |
352 | if (use_cache && sheets.count(x: resolved[0].abs_path)) return resolved[0]; |
353 | // try to read the content of the resolved file entry |
354 | // the memory buffer returned must be freed by us! |
355 | if (char* contents = read_file(file: resolved[0].abs_path)) { |
356 | // register the newly resolved file resource |
357 | register_resource(inc: resolved[0], res: { contents, 0 }, prstate&: pstate); |
358 | // return resolved entry |
359 | return resolved[0]; |
360 | } |
361 | } |
362 | |
363 | // nothing found |
364 | return { imp, "" }; |
365 | |
366 | } |
367 | |
368 | void Context::import_url (Import* imp, sass::string load_path, const sass::string& ctx_path) { |
369 | |
370 | SourceSpan pstate(imp->pstate()); |
371 | sass::string imp_path(unquote(load_path)); |
372 | sass::string protocol("file" ); |
373 | |
374 | using namespace Prelexer; |
375 | if (const char* proto = sequence< identifier, exactly<':'>, exactly<'/'>, exactly<'/'> >(src: imp_path.c_str())) { |
376 | |
377 | protocol = sass::string(imp_path.c_str(), proto - 3); |
378 | // if (protocol.compare("file") && true) { } |
379 | } |
380 | |
381 | // add urls (protocol other than file) and urls without protocol to `urls` member |
382 | // ToDo: if ctx_path is already a file resource, we should not add it here? |
383 | if (imp->import_queries() || protocol != "file" || imp_path.substr(pos: 0, n: 2) == "//" ) { |
384 | imp->urls().push_back(SASS_MEMORY_NEW(String_Quoted, imp->pstate(), load_path)); |
385 | } |
386 | else if (imp_path.length() > 4 && imp_path.substr(pos: imp_path.length() - 4, n: 4) == ".css" ) { |
387 | String_Constant* loc = SASS_MEMORY_NEW(String_Constant, pstate, unquote(load_path)); |
388 | Argument_Obj loc_arg = SASS_MEMORY_NEW(Argument, pstate, loc); |
389 | Arguments_Obj loc_args = SASS_MEMORY_NEW(Arguments, pstate); |
390 | loc_args->append(element: loc_arg); |
391 | Function_Call* new_url = SASS_MEMORY_NEW(Function_Call, pstate, sass::string("url" ), loc_args); |
392 | imp->urls().push_back(x: new_url); |
393 | } |
394 | else { |
395 | const Importer importer(imp_path, ctx_path); |
396 | Include include(load_import(imp: importer, pstate)); |
397 | if (include.abs_path.empty()) { |
398 | error(msg: "File to import not found or unreadable: " + imp_path + "." , pstate, traces); |
399 | } |
400 | imp->incs().push_back(x: include); |
401 | } |
402 | |
403 | } |
404 | |
405 | |
406 | // call custom importers on the given (unquoted) load_path and eventually parse the resulting style_sheet |
407 | bool Context::call_loader(const sass::string& load_path, const char* ctx_path, SourceSpan& pstate, Import* imp, sass::vector<Sass_Importer_Entry> importers, bool only_one) |
408 | { |
409 | // unique counter |
410 | size_t count = 0; |
411 | // need one correct import |
412 | bool has_import = false; |
413 | // process all custom importers (or custom headers) |
414 | for (Sass_Importer_Entry& importer_ent : importers) { |
415 | // int priority = sass_importer_get_priority(importer); |
416 | Sass_Importer_Fn fn = sass_importer_get_function(cb: importer_ent); |
417 | // skip importer if it returns NULL |
418 | if (Sass_Import_List includes = |
419 | fn(load_path.c_str(), importer_ent, c_compiler) |
420 | ) { |
421 | // get c pointer copy to iterate over |
422 | Sass_Import_List it_includes = includes; |
423 | while (*it_includes) { ++count; |
424 | // create unique path to use as key |
425 | sass::string uniq_path = load_path; |
426 | if (!only_one && count) { |
427 | sass::ostream path_strm; |
428 | path_strm << uniq_path << ":" << count; |
429 | uniq_path = path_strm.str(); |
430 | } |
431 | // create the importer struct |
432 | Importer importer(uniq_path, ctx_path); |
433 | // query data from the current include |
434 | Sass_Import_Entry include_ent = *it_includes; |
435 | char* source = sass_import_take_source(include_ent); |
436 | char* srcmap = sass_import_take_srcmap(include_ent); |
437 | size_t line = sass_import_get_error_line(include_ent); |
438 | size_t column = sass_import_get_error_column(include_ent); |
439 | const char *abs_path = sass_import_get_abs_path(include_ent); |
440 | // handle error message passed back from custom importer |
441 | // it may (or may not) override the line and column info |
442 | if (const char* err_message = sass_import_get_error_message(include_ent)) { |
443 | if (source || srcmap) register_resource(inc: { importer, uniq_path }, res: { source, srcmap }, prstate&: pstate); |
444 | if (line == sass::string::npos && column == sass::string::npos) error(msg: err_message, pstate, traces); |
445 | else { error(msg: err_message, pstate: { pstate.source, { line, column } }, traces); } |
446 | } |
447 | // content for import was set |
448 | else if (source) { |
449 | // resolved abs_path should be set by custom importer |
450 | // use the created uniq_path as fallback (maybe enforce) |
451 | sass::string path_key(abs_path ? abs_path : uniq_path); |
452 | // create the importer struct |
453 | Include include(importer, path_key); |
454 | // attach information to AST node |
455 | imp->incs().push_back(x: include); |
456 | // register the resource buffers |
457 | register_resource(inc: include, res: { source, srcmap }, prstate&: pstate); |
458 | } |
459 | // only a path was retuned |
460 | // try to load it like normal |
461 | else if(abs_path) { |
462 | // checks some urls to preserve |
463 | // `http://`, `https://` and `//` |
464 | // or dispatchs to `import_file` |
465 | // which will check for a `.css` extension |
466 | // or resolves the file on the filesystem |
467 | // added and resolved via `add_file` |
468 | // finally stores everything on `imp` |
469 | import_url(imp, load_path: abs_path, ctx_path); |
470 | } |
471 | // move to next |
472 | ++it_includes; |
473 | } |
474 | // deallocate the returned memory |
475 | sass_delete_import_list(includes); |
476 | // set success flag |
477 | has_import = true; |
478 | // break out of loop |
479 | if (only_one) break; |
480 | } |
481 | } |
482 | // return result |
483 | return has_import; |
484 | } |
485 | |
486 | void register_function(Context&, Signature sig, Native_Function f, Env* env); |
487 | void register_function(Context&, Signature sig, Native_Function f, size_t arity, Env* env); |
488 | void register_overload_stub(Context&, sass::string name, Env* env); |
489 | void register_built_in_functions(Context&, Env* env); |
490 | void register_c_functions(Context&, Env* env, Sass_Function_List); |
491 | void register_c_function(Context&, Env* env, Sass_Function_Entry); |
492 | |
493 | char* Context::render(Block_Obj root) |
494 | { |
495 | // check for valid block |
496 | if (!root) return 0; |
497 | // start the render process |
498 | root->perform(op: &emitter); |
499 | // finish emitter stream |
500 | emitter.finalize(); |
501 | // get the resulting buffer from stream |
502 | OutputBuffer emitted = emitter.get_buffer(); |
503 | // should we append a source map url? |
504 | if (!c_options.omit_source_map_url) { |
505 | // generate an embedded source map |
506 | if (c_options.source_map_embed) { |
507 | emitted.buffer += linefeed; |
508 | emitted.buffer += format_embedded_source_map(); |
509 | } |
510 | // or just link the generated one |
511 | else if (source_map_file != "" ) { |
512 | emitted.buffer += linefeed; |
513 | emitted.buffer += format_source_mapping_url(out_path: source_map_file); |
514 | } |
515 | } |
516 | // create a copy of the resulting buffer string |
517 | // this must be freed or taken over by implementor |
518 | return sass_copy_c_string(str: emitted.buffer.c_str()); |
519 | } |
520 | |
521 | void Context::(Block_Obj root, const char* ctx_path, SourceSpan pstate) |
522 | { |
523 | // create a custom import to resolve headers |
524 | Import_Obj imp = SASS_MEMORY_NEW(Import, pstate); |
525 | // dispatch headers which will add custom functions |
526 | // custom headers are added to the import instance |
527 | call_headers(load_path: entry_path, ctx_path, pstate, imp); |
528 | // increase head count to skip later |
529 | head_imports += resources.size() - 1; |
530 | // add the statement if we have urls |
531 | if (!imp->urls().empty()) root->append(element: imp); |
532 | // process all other resources (add Import_Stub nodes) |
533 | for (size_t i = 0, S = imp->incs().size(); i < S; ++i) { |
534 | root->append(SASS_MEMORY_NEW(Import_Stub, pstate, imp->incs()[i])); |
535 | } |
536 | } |
537 | |
538 | Block_Obj File_Context::parse() |
539 | { |
540 | |
541 | // check if entry file is given |
542 | if (input_path.empty()) return {}; |
543 | |
544 | // create absolute path from input filename |
545 | // ToDo: this should be resolved via custom importers |
546 | sass::string abs_path(rel2abs(path: input_path, base: CWD)); |
547 | |
548 | // try to load the entry file |
549 | char* contents = read_file(file: abs_path); |
550 | |
551 | // alternatively also look inside each include path folder |
552 | // I think this differs from ruby sass (IMO too late to remove) |
553 | for (size_t i = 0, S = include_paths.size(); contents == 0 && i < S; ++i) { |
554 | // build absolute path for this include path entry |
555 | abs_path = rel2abs(path: input_path, base: include_paths[i]); |
556 | // try to load the resulting path |
557 | contents = read_file(file: abs_path); |
558 | } |
559 | |
560 | // abort early if no content could be loaded (various reasons) |
561 | if (!contents) throw std::runtime_error( |
562 | "File to read not found or unreadable: " |
563 | + std::string(input_path.c_str())); |
564 | |
565 | // store entry path |
566 | entry_path = abs_path; |
567 | |
568 | // create entry only for import stack |
569 | Sass_Import_Entry import = sass_make_import( |
570 | imp_path: input_path.c_str(), |
571 | abs_base: entry_path.c_str(), |
572 | source: contents, |
573 | srcmap: 0 |
574 | ); |
575 | // add the entry to the stack |
576 | import_stack.push_back(x: import); |
577 | |
578 | // create the source entry for file entry |
579 | register_resource(inc: {{ input_path, "." }, abs_path }, res: { contents, 0 }); |
580 | |
581 | // create root ast tree node |
582 | return compile(); |
583 | |
584 | } |
585 | |
586 | Block_Obj Data_Context::parse() |
587 | { |
588 | |
589 | // check if source string is given |
590 | if (!source_c_str) return {}; |
591 | |
592 | // convert indented sass syntax |
593 | if(c_options.is_indented_syntax_src) { |
594 | // call sass2scss to convert the string |
595 | char * converted = sass2scss(sass: source_c_str, |
596 | // preserve the structure as much as possible |
597 | SASS2SCSS_PRETTIFY_1 | SASS2SCSS_KEEP_COMMENT); |
598 | // replace old source_c_str with converted |
599 | free(ptr: source_c_str); source_c_str = converted; |
600 | } |
601 | |
602 | // remember entry path (defaults to stdin for string) |
603 | entry_path = input_path.empty() ? "stdin" : input_path; |
604 | |
605 | // ToDo: this may be resolved via custom importers |
606 | sass::string abs_path(rel2abs(path: entry_path)); |
607 | char* abs_path_c_str = sass_copy_c_string(str: abs_path.c_str()); |
608 | strings.push_back(x: abs_path_c_str); |
609 | |
610 | // create entry only for the import stack |
611 | Sass_Import_Entry import = sass_make_import( |
612 | imp_path: entry_path.c_str(), |
613 | abs_base: abs_path_c_str, |
614 | source: source_c_str, |
615 | srcmap: srcmap_c_str |
616 | ); |
617 | // add the entry to the stack |
618 | import_stack.push_back(x: import); |
619 | |
620 | // register a synthetic resource (path does not really exist, skip in includes) |
621 | register_resource(inc: {{ input_path, "." }, input_path }, res: { source_c_str, srcmap_c_str }); |
622 | |
623 | // create root ast tree node |
624 | return compile(); |
625 | } |
626 | |
627 | // parse root block from includes |
628 | Block_Obj Context::compile() |
629 | { |
630 | // abort if there is no data |
631 | if (resources.size() == 0) return {}; |
632 | // get root block from the first style sheet |
633 | Block_Obj root = sheets.at(k: entry_path).root; |
634 | // abort on invalid root |
635 | if (root.isNull()) return {}; |
636 | Env global; // create root environment |
637 | // register built-in functions on env |
638 | register_built_in_functions(*this, env: &global); |
639 | // register custom functions (defined via C-API) |
640 | for (size_t i = 0, S = c_functions.size(); i < S; ++i) |
641 | { register_c_function(*this, env: &global, c_functions[i]); } |
642 | // create initial backtrace entry |
643 | // create crtp visitor objects |
644 | Expand expand(*this, &global); |
645 | Cssize cssize(*this); |
646 | CheckNesting check_nesting; |
647 | // check nesting in all files |
648 | for (auto sheet : sheets) { |
649 | auto styles = sheet.second; |
650 | check_nesting(styles.root); |
651 | } |
652 | // expand and eval the tree |
653 | root = expand(root); |
654 | |
655 | Extension unsatisfied; |
656 | // check that all extends were used |
657 | if (extender.checkForUnsatisfiedExtends(unsatisfied)) { |
658 | throw Exception::UnsatisfiedExtend(traces, unsatisfied); |
659 | } |
660 | |
661 | // check nesting |
662 | check_nesting(root); |
663 | // merge and bubble certain rules |
664 | root = cssize(root); |
665 | |
666 | // clean up by removing empty placeholders |
667 | // ToDo: maybe we can do this somewhere else? |
668 | Remove_Placeholders remove_placeholders; |
669 | root->perform(op: &remove_placeholders); |
670 | |
671 | // return processed tree |
672 | return root; |
673 | } |
674 | // EO compile |
675 | |
676 | sass::string Context::format_embedded_source_map() |
677 | { |
678 | sass::string map = emitter.render_srcmap(ctx&: *this); |
679 | sass::istream is( map.c_str() ); |
680 | sass::ostream buffer; |
681 | base64::encoder E; |
682 | E.encode(istream_in&: is, ostream_in&: buffer); |
683 | sass::string url = "data:application/json;base64," + buffer.str(); |
684 | url.erase(pos: url.size() - 1); |
685 | return "/*# sourceMappingURL=" + url + " */" ; |
686 | } |
687 | |
688 | sass::string Context::format_source_mapping_url(const sass::string& file) |
689 | { |
690 | sass::string url = abs2rel(path: file, base: output_path, cwd: CWD); |
691 | return "/*# sourceMappingURL=" + url + " */" ; |
692 | } |
693 | |
694 | char* Context::render_srcmap() |
695 | { |
696 | if (source_map_file == "" ) return 0; |
697 | sass::string map = emitter.render_srcmap(ctx&: *this); |
698 | return sass_copy_c_string(str: map.c_str()); |
699 | } |
700 | |
701 | |
702 | // for data context we want to start after "stdin" |
703 | // we probably always want to skip the header includes? |
704 | sass::vector<sass::string> Context::get_included_files(bool skip, size_t ) |
705 | { |
706 | // create a copy of the vector for manipulations |
707 | sass::vector<sass::string> includes = included_files; |
708 | if (includes.size() == 0) return includes; |
709 | if (skip) { includes.erase( first: includes.begin(), last: includes.begin() + 1 + headers); } |
710 | else { includes.erase( first: includes.begin() + 1, last: includes.begin() + 1 + headers); } |
711 | includes.erase( first: std::unique( first: includes.begin(), last: includes.end() ), last: includes.end() ); |
712 | std::sort( first: includes.begin() + (skip ? 0 : 1), last: includes.end() ); |
713 | return includes; |
714 | } |
715 | |
716 | void register_function(Context& ctx, Signature sig, Native_Function f, Env* env) |
717 | { |
718 | Definition* def = make_native_function(sig, f, ctx); |
719 | def->environment(environment__: env); |
720 | (*env)[def->name() + "[f]" ] = def; |
721 | } |
722 | |
723 | void register_function(Context& ctx, Signature sig, Native_Function f, size_t arity, Env* env) |
724 | { |
725 | Definition* def = make_native_function(sig, f, ctx); |
726 | sass::ostream ss; |
727 | ss << def->name() << "[f]" << arity; |
728 | def->environment(environment__: env); |
729 | (*env)[ss.str()] = def; |
730 | } |
731 | |
732 | void register_overload_stub(Context& ctx, sass::string name, Env* env) |
733 | { |
734 | Definition* stub = SASS_MEMORY_NEW(Definition, |
735 | SourceSpan{ "[built-in function]" }, |
736 | nullptr, |
737 | name, |
738 | Parameters_Obj{}, |
739 | nullptr, |
740 | true); |
741 | (*env)[name + "[f]" ] = stub; |
742 | } |
743 | |
744 | |
745 | void register_built_in_functions(Context& ctx, Env* env) |
746 | { |
747 | using namespace Functions; |
748 | // RGB Functions |
749 | register_function(ctx, sig: rgb_sig, f: rgb, env); |
750 | register_overload_stub(ctx, name: "rgba" , env); |
751 | register_function(ctx, sig: rgba_4_sig, f: rgba_4, arity: 4, env); |
752 | register_function(ctx, sig: rgba_2_sig, f: rgba_2, arity: 2, env); |
753 | register_function(ctx, sig: red_sig, f: red, env); |
754 | register_function(ctx, sig: green_sig, f: green, env); |
755 | register_function(ctx, sig: blue_sig, f: blue, env); |
756 | register_function(ctx, sig: mix_sig, f: mix, env); |
757 | // HSL Functions |
758 | register_function(ctx, sig: hsl_sig, f: hsl, env); |
759 | register_function(ctx, sig: hsla_sig, f: hsla, env); |
760 | register_function(ctx, sig: hue_sig, f: hue, env); |
761 | register_function(ctx, sig: saturation_sig, f: saturation, env); |
762 | register_function(ctx, sig: lightness_sig, f: lightness, env); |
763 | register_function(ctx, sig: adjust_hue_sig, f: adjust_hue, env); |
764 | register_function(ctx, sig: lighten_sig, f: lighten, env); |
765 | register_function(ctx, sig: darken_sig, f: darken, env); |
766 | register_function(ctx, sig: saturate_sig, f: saturate, env); |
767 | register_function(ctx, sig: desaturate_sig, f: desaturate, env); |
768 | register_function(ctx, sig: grayscale_sig, f: grayscale, env); |
769 | register_function(ctx, sig: complement_sig, f: complement, env); |
770 | register_function(ctx, sig: invert_sig, f: invert, env); |
771 | // Opacity Functions |
772 | register_function(ctx, sig: alpha_sig, f: alpha, env); |
773 | register_function(ctx, sig: opacity_sig, f: alpha, env); |
774 | register_function(ctx, sig: opacify_sig, f: opacify, env); |
775 | register_function(ctx, sig: fade_in_sig, f: opacify, env); |
776 | register_function(ctx, sig: transparentize_sig, f: transparentize, env); |
777 | register_function(ctx, sig: fade_out_sig, f: transparentize, env); |
778 | // Other Color Functions |
779 | register_function(ctx, sig: adjust_color_sig, f: adjust_color, env); |
780 | register_function(ctx, sig: scale_color_sig, f: scale_color, env); |
781 | register_function(ctx, sig: change_color_sig, f: change_color, env); |
782 | register_function(ctx, sig: ie_hex_str_sig, f: ie_hex_str, env); |
783 | // String Functions |
784 | register_function(ctx, sig: unquote_sig, f: sass_unquote, env); |
785 | register_function(ctx, sig: quote_sig, f: sass_quote, env); |
786 | register_function(ctx, sig: str_length_sig, f: str_length, env); |
787 | register_function(ctx, sig: str_insert_sig, f: str_insert, env); |
788 | register_function(ctx, sig: str_index_sig, f: str_index, env); |
789 | register_function(ctx, sig: str_slice_sig, f: str_slice, env); |
790 | register_function(ctx, sig: to_upper_case_sig, f: to_upper_case, env); |
791 | register_function(ctx, sig: to_lower_case_sig, f: to_lower_case, env); |
792 | // Number Functions |
793 | register_function(ctx, sig: percentage_sig, f: percentage, env); |
794 | register_function(ctx, sig: round_sig, f: round, env); |
795 | register_function(ctx, sig: ceil_sig, f: ceil, env); |
796 | register_function(ctx, sig: floor_sig, f: floor, env); |
797 | register_function(ctx, sig: abs_sig, f: abs, env); |
798 | register_function(ctx, sig: min_sig, f: min, env); |
799 | register_function(ctx, sig: max_sig, f: max, env); |
800 | register_function(ctx, sig: random_sig, f: random, env); |
801 | // List Functions |
802 | register_function(ctx, sig: length_sig, f: length, env); |
803 | register_function(ctx, sig: nth_sig, f: nth, env); |
804 | register_function(ctx, sig: set_nth_sig, f: set_nth, env); |
805 | register_function(ctx, sig: index_sig, f: index, env); |
806 | register_function(ctx, sig: join_sig, f: join, env); |
807 | register_function(ctx, sig: append_sig, f: append, env); |
808 | register_function(ctx, sig: zip_sig, f: zip, env); |
809 | register_function(ctx, sig: list_separator_sig, f: list_separator, env); |
810 | register_function(ctx, sig: is_bracketed_sig, f: is_bracketed, env); |
811 | // Map Functions |
812 | register_function(ctx, sig: map_get_sig, f: map_get, env); |
813 | register_function(ctx, sig: map_merge_sig, f: map_merge, env); |
814 | register_function(ctx, sig: map_remove_sig, f: map_remove, env); |
815 | register_function(ctx, sig: map_keys_sig, f: map_keys, env); |
816 | register_function(ctx, sig: map_values_sig, f: map_values, env); |
817 | register_function(ctx, sig: map_has_key_sig, f: map_has_key, env); |
818 | register_function(ctx, sig: keywords_sig, f: keywords, env); |
819 | // Introspection Functions |
820 | register_function(ctx, sig: type_of_sig, f: type_of, env); |
821 | register_function(ctx, sig: unit_sig, f: unit, env); |
822 | register_function(ctx, sig: unitless_sig, f: unitless, env); |
823 | register_function(ctx, sig: comparable_sig, f: comparable, env); |
824 | register_function(ctx, sig: variable_exists_sig, f: variable_exists, env); |
825 | register_function(ctx, sig: global_variable_exists_sig, f: global_variable_exists, env); |
826 | register_function(ctx, sig: function_exists_sig, f: function_exists, env); |
827 | register_function(ctx, sig: mixin_exists_sig, f: mixin_exists, env); |
828 | register_function(ctx, sig: feature_exists_sig, f: feature_exists, env); |
829 | register_function(ctx, sig: call_sig, f: call, env); |
830 | register_function(ctx, sig: content_exists_sig, f: content_exists, env); |
831 | register_function(ctx, sig: get_function_sig, f: get_function, env); |
832 | // Boolean Functions |
833 | register_function(ctx, sig: not_sig, f: sass_not, env); |
834 | register_function(ctx, sig: if_sig, f: sass_if, env); |
835 | // Misc Functions |
836 | register_function(ctx, sig: inspect_sig, f: inspect, env); |
837 | register_function(ctx, sig: unique_id_sig, f: unique_id, env); |
838 | // Selector functions |
839 | register_function(ctx, sig: selector_nest_sig, f: selector_nest, env); |
840 | register_function(ctx, sig: selector_append_sig, f: selector_append, env); |
841 | register_function(ctx, sig: selector_extend_sig, f: selector_extend, env); |
842 | register_function(ctx, sig: selector_replace_sig, f: selector_replace, env); |
843 | register_function(ctx, sig: selector_unify_sig, f: selector_unify, env); |
844 | register_function(ctx, sig: is_superselector_sig, f: is_superselector, env); |
845 | register_function(ctx, sig: simple_selectors_sig, f: simple_selectors, env); |
846 | register_function(ctx, sig: selector_parse_sig, f: selector_parse, env); |
847 | } |
848 | |
849 | void register_c_functions(Context& ctx, Env* env, Sass_Function_List descrs) |
850 | { |
851 | while (descrs && *descrs) { |
852 | register_c_function(ctx, env, *descrs); |
853 | ++descrs; |
854 | } |
855 | } |
856 | void register_c_function(Context& ctx, Env* env, Sass_Function_Entry descr) |
857 | { |
858 | Definition* def = make_c_function(c_func: descr, ctx); |
859 | def->environment(environment__: env); |
860 | (*env)[def->name() + "[f]" ] = def; |
861 | } |
862 | |
863 | } |
864 | |