1 | // sass.hpp must go before all system headers to get the |
2 | // __EXTENSIONS__ fix on Solaris. |
3 | #include "sass.hpp" |
4 | |
5 | #include <iostream> |
6 | #include <typeinfo> |
7 | |
8 | #include "ast.hpp" |
9 | #include "expand.hpp" |
10 | #include "bind.hpp" |
11 | #include "eval.hpp" |
12 | #include "backtrace.hpp" |
13 | #include "context.hpp" |
14 | #include "parser.hpp" |
15 | #include "sass_functions.hpp" |
16 | #include "error_handling.hpp" |
17 | |
18 | namespace Sass { |
19 | |
20 | // simple endless recursion protection |
21 | const size_t maxRecursion = 500; |
22 | |
23 | Expand::Expand(Context& ctx, Env* env, SelectorStack* stack, SelectorStack* originals) |
24 | : ctx(ctx), |
25 | traces(ctx.traces), |
26 | eval(Eval(*this)), |
27 | recursions(0), |
28 | in_keyframes(false), |
29 | at_root_without_rule(false), |
30 | old_at_root_without_rule(false), |
31 | env_stack(), |
32 | block_stack(), |
33 | call_stack(), |
34 | selector_stack(), |
35 | originalStack(), |
36 | mediaStack() |
37 | { |
38 | env_stack.push_back(x: nullptr); |
39 | env_stack.push_back(x: env); |
40 | block_stack.push_back(x: nullptr); |
41 | call_stack.push_back(x: {}); |
42 | if (stack == NULL) { pushToSelectorStack(selector: {}); } |
43 | else { |
44 | for (auto item : *stack) { |
45 | if (item.isNull()) pushToSelectorStack(selector: {}); |
46 | else pushToSelectorStack(selector: item); |
47 | } |
48 | } |
49 | if (originals == NULL) { pushToOriginalStack(selector: {}); } |
50 | else { |
51 | for (auto item : *stack) { |
52 | if (item.isNull()) pushToOriginalStack(selector: {}); |
53 | else pushToOriginalStack(selector: item); |
54 | } |
55 | } |
56 | mediaStack.push_back(x: {}); |
57 | } |
58 | |
59 | Env* Expand::environment() |
60 | { |
61 | if (env_stack.size() > 0) |
62 | return env_stack.back(); |
63 | return 0; |
64 | } |
65 | |
66 | void Expand::pushNullSelector() |
67 | { |
68 | pushToSelectorStack(selector: {}); |
69 | pushToOriginalStack(selector: {}); |
70 | } |
71 | |
72 | void Expand::popNullSelector() |
73 | { |
74 | popFromOriginalStack(); |
75 | popFromSelectorStack(); |
76 | } |
77 | |
78 | SelectorStack Expand::getOriginalStack() |
79 | { |
80 | return originalStack; |
81 | } |
82 | |
83 | SelectorStack Expand::getSelectorStack() |
84 | { |
85 | return selector_stack; |
86 | } |
87 | |
88 | SelectorListObj& Expand::selector() |
89 | { |
90 | if (selector_stack.size() > 0) { |
91 | auto& sel = selector_stack.back(); |
92 | if (sel.isNull()) return sel; |
93 | return sel; |
94 | } |
95 | // Avoid the need to return copies |
96 | // We always want an empty first item |
97 | selector_stack.push_back(x: {}); |
98 | return selector_stack.back();; |
99 | } |
100 | |
101 | SelectorListObj& Expand::original() |
102 | { |
103 | if (originalStack.size() > 0) { |
104 | auto& sel = originalStack.back(); |
105 | if (sel.isNull()) return sel; |
106 | return sel; |
107 | } |
108 | // Avoid the need to return copies |
109 | // We always want an empty first item |
110 | originalStack.push_back(x: {}); |
111 | return originalStack.back(); |
112 | } |
113 | |
114 | SelectorListObj Expand::popFromSelectorStack() |
115 | { |
116 | SelectorListObj last = selector_stack.back(); |
117 | if (selector_stack.size() > 0) |
118 | selector_stack.pop_back(); |
119 | if (last.isNull()) return {}; |
120 | return last; |
121 | } |
122 | |
123 | void Expand::pushToSelectorStack(SelectorListObj selector) |
124 | { |
125 | selector_stack.push_back(x: selector); |
126 | } |
127 | |
128 | SelectorListObj Expand::popFromOriginalStack() |
129 | { |
130 | SelectorListObj last = originalStack.back(); |
131 | if (originalStack.size() > 0) |
132 | originalStack.pop_back(); |
133 | if (last.isNull()) return {}; |
134 | return last; |
135 | } |
136 | |
137 | void Expand::pushToOriginalStack(SelectorListObj selector) |
138 | { |
139 | originalStack.push_back(x: selector); |
140 | } |
141 | |
142 | // blocks create new variable scopes |
143 | Block* Expand::operator()(Block* b) |
144 | { |
145 | // create new local environment |
146 | // set the current env as parent |
147 | Env env(environment()); |
148 | // copy the block object (add items later) |
149 | Block_Obj bb = SASS_MEMORY_NEW(Block, |
150 | b->pstate(), |
151 | b->length(), |
152 | b->is_root()); |
153 | // setup block and env stack |
154 | this->block_stack.push_back(x: bb); |
155 | this->env_stack.push_back(x: &env); |
156 | // operate on block |
157 | // this may throw up! |
158 | this->append_block(b); |
159 | // revert block and env stack |
160 | this->block_stack.pop_back(); |
161 | this->env_stack.pop_back(); |
162 | // return copy |
163 | return bb.detach(); |
164 | } |
165 | |
166 | Statement* Expand::operator()(StyleRule* r) |
167 | { |
168 | LOCAL_FLAG(old_at_root_without_rule, at_root_without_rule); |
169 | |
170 | if (in_keyframes) { |
171 | Block* bb = operator()(b: r->block()); |
172 | Keyframe_Rule_Obj k = SASS_MEMORY_NEW(Keyframe_Rule, r->pstate(), bb); |
173 | if (r->schema()) { |
174 | pushNullSelector(); |
175 | k->name(name__: eval(r->schema())); |
176 | popNullSelector(); |
177 | } |
178 | else if (r->selector()) { |
179 | if (SelectorListObj s = r->selector()) { |
180 | pushNullSelector(); |
181 | k->name(name__: eval(s)); |
182 | popNullSelector(); |
183 | } |
184 | } |
185 | |
186 | return k.detach(); |
187 | } |
188 | |
189 | if (r->schema()) { |
190 | SelectorListObj sel = eval(r->schema()); |
191 | r->selector(selector__: sel); |
192 | for (auto complex : sel->elements()) { |
193 | // ToDo: maybe we can get rid of chroots? |
194 | complex->chroots(chroots__: complex->has_real_parent_ref()); |
195 | } |
196 | |
197 | } |
198 | |
199 | // reset when leaving scope |
200 | LOCAL_FLAG(at_root_without_rule, false); |
201 | |
202 | SelectorListObj evaled = eval(r->selector()); |
203 | // do not connect parent again |
204 | Env env(environment()); |
205 | if (block_stack.back()->is_root()) { |
206 | env_stack.push_back(x: &env); |
207 | } |
208 | Block_Obj blk; |
209 | pushToSelectorStack(selector: evaled); |
210 | // The copy is needed for parent reference evaluation |
211 | // dart-sass stores it as `originalSelector` member |
212 | pushToOriginalStack(SASS_MEMORY_COPY(evaled)); |
213 | ctx.extender.addSelector(selector: evaled, mediaContext: mediaStack.back()); |
214 | if (r->block()) blk = operator()(b: r->block()); |
215 | popFromOriginalStack(); |
216 | popFromSelectorStack(); |
217 | StyleRule* rr = SASS_MEMORY_NEW(StyleRule, |
218 | r->pstate(), |
219 | evaled, |
220 | blk); |
221 | |
222 | if (block_stack.back()->is_root()) { |
223 | env_stack.pop_back(); |
224 | } |
225 | |
226 | rr->is_root(is_root__: r->is_root()); |
227 | rr->tabs(tabs__: r->tabs()); |
228 | |
229 | return rr; |
230 | } |
231 | |
232 | Statement* Expand::operator()(SupportsRule* f) |
233 | { |
234 | ExpressionObj condition = f->condition()->perform(op: &eval); |
235 | SupportsRuleObj ff = SASS_MEMORY_NEW(SupportsRule, |
236 | f->pstate(), |
237 | Cast<SupportsCondition>(condition), |
238 | operator()(f->block())); |
239 | return ff.detach(); |
240 | } |
241 | |
242 | sass::vector<CssMediaQuery_Obj> Expand::mergeMediaQueries( |
243 | const sass::vector<CssMediaQuery_Obj>& lhs, |
244 | const sass::vector<CssMediaQuery_Obj>& rhs) |
245 | { |
246 | sass::vector<CssMediaQuery_Obj> queries; |
247 | for (CssMediaQuery_Obj query1 : lhs) { |
248 | for (CssMediaQuery_Obj query2 : rhs) { |
249 | CssMediaQuery_Obj result = query1->merge(other&: query2); |
250 | if (result && !result->empty()) { |
251 | queries.push_back(x: result); |
252 | } |
253 | } |
254 | } |
255 | return queries; |
256 | } |
257 | |
258 | Statement* Expand::operator()(MediaRule* m) |
259 | { |
260 | ExpressionObj mq = eval(m->schema()); |
261 | sass::string str_mq(mq->to_css(opt: ctx.c_options)); |
262 | ItplFile* source = SASS_MEMORY_NEW(ItplFile, |
263 | str_mq.c_str(), m->pstate()); |
264 | Parser parser(source, ctx, traces); |
265 | // Create a new CSS only representation of the media rule |
266 | CssMediaRuleObj css = SASS_MEMORY_NEW(CssMediaRule, m->pstate(), m->block()); |
267 | sass::vector<CssMediaQuery_Obj> parsed = parser.parseCssMediaQueries(); |
268 | if (mediaStack.size() && mediaStack.back()) { |
269 | auto& parent = mediaStack.back()->elements(); |
270 | css->concat(v: mergeMediaQueries(lhs: parent, rhs: parsed)); |
271 | } |
272 | else { |
273 | css->concat(v: parsed); |
274 | } |
275 | mediaStack.push_back(x: css); |
276 | css->block(block__: operator()(b: m->block())); |
277 | mediaStack.pop_back(); |
278 | return css.detach(); |
279 | |
280 | } |
281 | |
282 | Statement* Expand::operator()(AtRootRule* a) |
283 | { |
284 | Block_Obj ab = a->block(); |
285 | ExpressionObj ae = a->expression(); |
286 | |
287 | if (ae) ae = ae->perform(op: &eval); |
288 | else ae = SASS_MEMORY_NEW(At_Root_Query, a->pstate()); |
289 | |
290 | LOCAL_FLAG(at_root_without_rule, Cast<At_Root_Query>(ae)->exclude("rule" )); |
291 | LOCAL_FLAG(in_keyframes, false); |
292 | |
293 | ; |
294 | |
295 | Block_Obj bb = ab ? operator()(b: ab) : NULL; |
296 | AtRootRuleObj aa = SASS_MEMORY_NEW(AtRootRule, |
297 | a->pstate(), |
298 | bb, |
299 | Cast<At_Root_Query>(ae)); |
300 | return aa.detach(); |
301 | } |
302 | |
303 | Statement* Expand::operator()(AtRule* a) |
304 | { |
305 | LOCAL_FLAG(in_keyframes, a->is_keyframes()); |
306 | Block* ab = a->block(); |
307 | SelectorList* as = a->selector(); |
308 | Expression* av = a->value(); |
309 | pushNullSelector(); |
310 | if (av) av = av->perform(op: &eval); |
311 | if (as) as = eval(as); |
312 | popNullSelector(); |
313 | Block* bb = ab ? operator()(b: ab) : NULL; |
314 | AtRule* aa = SASS_MEMORY_NEW(AtRule, |
315 | a->pstate(), |
316 | a->keyword(), |
317 | as, |
318 | bb, |
319 | av); |
320 | return aa; |
321 | } |
322 | |
323 | Statement* Expand::operator()(Declaration* d) |
324 | { |
325 | Block_Obj ab = d->block(); |
326 | String_Obj old_p = d->property(); |
327 | ExpressionObj prop = old_p->perform(op: &eval); |
328 | String_Obj new_p = Cast<String>(ptr: prop); |
329 | // we might get a color back |
330 | if (!new_p) { |
331 | sass::string str(prop->to_string(opt: ctx.c_options)); |
332 | new_p = SASS_MEMORY_NEW(String_Constant, old_p->pstate(), str); |
333 | } |
334 | ExpressionObj value = d->value(); |
335 | if (value) value = value->perform(op: &eval); |
336 | Block_Obj bb = ab ? operator()(b: ab) : NULL; |
337 | if (!bb) { |
338 | if (!value || (value->is_invisible() && !d->is_important())) { |
339 | if (d->is_custom_property()) { |
340 | error(msg: "Custom property values may not be empty." , pstate: d->value()->pstate(), traces); |
341 | } else { |
342 | return nullptr; |
343 | } |
344 | } |
345 | } |
346 | Declaration* decl = SASS_MEMORY_NEW(Declaration, |
347 | d->pstate(), |
348 | new_p, |
349 | value, |
350 | d->is_important(), |
351 | d->is_custom_property(), |
352 | bb); |
353 | decl->tabs(tabs__: d->tabs()); |
354 | return decl; |
355 | } |
356 | |
357 | Statement* Expand::operator()(Assignment* a) |
358 | { |
359 | Env* env = environment(); |
360 | const sass::string& var(a->variable()); |
361 | if (a->is_global()) { |
362 | if (!env->has_global(key: var)) { |
363 | deprecated( |
364 | msg: "!global assignments won't be able to declare new variables in future versions." , |
365 | msg2: "Consider adding `" + var + ": null` at the top level." , |
366 | with_column: true, pstate: a->pstate()); |
367 | } |
368 | if (a->is_default()) { |
369 | if (env->has_global(key: var)) { |
370 | ExpressionObj e = Cast<Expression>(ptr: env->get_global(key: var)); |
371 | if (!e || e->concrete_type() == Expression::NULL_VAL) { |
372 | env->set_global(key: var, val: a->value()->perform(op: &eval)); |
373 | } |
374 | } |
375 | else { |
376 | env->set_global(key: var, val: a->value()->perform(op: &eval)); |
377 | } |
378 | } |
379 | else { |
380 | env->set_global(key: var, val: a->value()->perform(op: &eval)); |
381 | } |
382 | } |
383 | else if (a->is_default()) { |
384 | if (env->has_lexical(key: var)) { |
385 | auto cur = env; |
386 | while (cur && cur->is_lexical()) { |
387 | if (cur->has_local(key: var)) { |
388 | if (AST_Node_Obj node = cur->get_local(key: var)) { |
389 | ExpressionObj e = Cast<Expression>(ptr: node); |
390 | if (!e || e->concrete_type() == Expression::NULL_VAL) { |
391 | cur->set_local(key: var, val: a->value()->perform(op: &eval)); |
392 | } |
393 | } |
394 | else { |
395 | throw std::runtime_error("Env not in sync" ); |
396 | } |
397 | return 0; |
398 | } |
399 | cur = cur->parent(); |
400 | } |
401 | throw std::runtime_error("Env not in sync" ); |
402 | } |
403 | else if (env->has_global(key: var)) { |
404 | if (AST_Node_Obj node = env->get_global(key: var)) { |
405 | ExpressionObj e = Cast<Expression>(ptr: node); |
406 | if (!e || e->concrete_type() == Expression::NULL_VAL) { |
407 | env->set_global(key: var, val: a->value()->perform(op: &eval)); |
408 | } |
409 | } |
410 | } |
411 | else if (env->is_lexical()) { |
412 | env->set_local(key: var, val: a->value()->perform(op: &eval)); |
413 | } |
414 | else { |
415 | env->set_local(key: var, val: a->value()->perform(op: &eval)); |
416 | } |
417 | } |
418 | else { |
419 | env->set_lexical(key: var, val: a->value()->perform(op: &eval)); |
420 | } |
421 | return 0; |
422 | } |
423 | |
424 | Statement* Expand::operator()(Import* imp) |
425 | { |
426 | Import_Obj result = SASS_MEMORY_NEW(Import, imp->pstate()); |
427 | if (imp->import_queries() && imp->import_queries()->size()) { |
428 | ExpressionObj ex = imp->import_queries()->perform(op: &eval); |
429 | result->import_queries(import_queries__: Cast<List>(ptr: ex)); |
430 | } |
431 | for ( size_t i = 0, S = imp->urls().size(); i < S; ++i) { |
432 | result->urls().push_back(x: imp->urls()[i]->perform(op: &eval)); |
433 | } |
434 | // all resources have been dropped for Input_Stubs |
435 | // for ( size_t i = 0, S = imp->incs().size(); i < S; ++i) {} |
436 | return result.detach(); |
437 | } |
438 | |
439 | Statement* Expand::operator()(Import_Stub* i) |
440 | { |
441 | traces.push_back(x: Backtrace(i->pstate())); |
442 | // get parent node from call stack |
443 | AST_Node_Obj parent = call_stack.back(); |
444 | if (Cast<Block>(ptr: parent) == NULL) { |
445 | error(msg: "Import directives may not be used within control directives or mixins." , pstate: i->pstate(), traces); |
446 | } |
447 | // we don't seem to need that actually afterall |
448 | Sass_Import_Entry import = sass_make_import( |
449 | imp_path: i->imp_path().c_str(), |
450 | abs_base: i->abs_path().c_str(), |
451 | source: 0, srcmap: 0 |
452 | ); |
453 | ctx.import_stack.push_back(x: import); |
454 | |
455 | Block_Obj trace_block = SASS_MEMORY_NEW(Block, i->pstate()); |
456 | Trace_Obj trace = SASS_MEMORY_NEW(Trace, i->pstate(), i->imp_path(), trace_block, 'i'); |
457 | block_stack.back()->append(element: trace); |
458 | block_stack.push_back(x: trace_block); |
459 | |
460 | const sass::string& abs_path(i->resource().abs_path); |
461 | append_block(ctx.sheets.at(k: abs_path).root); |
462 | sass_delete_import(ctx.import_stack.back()); |
463 | ctx.import_stack.pop_back(); |
464 | block_stack.pop_back(); |
465 | traces.pop_back(); |
466 | return 0; |
467 | } |
468 | |
469 | Statement* Expand::operator()(WarningRule* w) |
470 | { |
471 | // eval handles this too, because warnings may occur in functions |
472 | w->perform(op: &eval); |
473 | return 0; |
474 | } |
475 | |
476 | Statement* Expand::operator()(ErrorRule* e) |
477 | { |
478 | // eval handles this too, because errors may occur in functions |
479 | e->perform(op: &eval); |
480 | return 0; |
481 | } |
482 | |
483 | Statement* Expand::operator()(DebugRule* d) |
484 | { |
485 | // eval handles this too, because warnings may occur in functions |
486 | d->perform(op: &eval); |
487 | return 0; |
488 | } |
489 | |
490 | Statement* Expand::operator()(Comment* c) |
491 | { |
492 | if (ctx.output_style() == COMPRESSED) { |
493 | // comments should not be evaluated in compact |
494 | // https://github.com/sass/libsass/issues/2359 |
495 | if (!c->is_important()) return NULL; |
496 | } |
497 | eval.is_in_comment = true; |
498 | Comment* rv = SASS_MEMORY_NEW(Comment, c->pstate(), Cast<String>(c->text()->perform(&eval)), c->is_important()); |
499 | eval.is_in_comment = false; |
500 | // TODO: eval the text, once we're parsing/storing it as a String_Schema |
501 | return rv; |
502 | } |
503 | |
504 | Statement* Expand::operator()(If* i) |
505 | { |
506 | Env env(environment(), true); |
507 | env_stack.push_back(x: &env); |
508 | call_stack.push_back(x: i); |
509 | ExpressionObj rv = i->predicate()->perform(op: &eval); |
510 | if (*rv) { |
511 | append_block(i->block()); |
512 | } |
513 | else { |
514 | Block* alt = i->alternative(); |
515 | if (alt) append_block(alt); |
516 | } |
517 | call_stack.pop_back(); |
518 | env_stack.pop_back(); |
519 | return 0; |
520 | } |
521 | |
522 | // For does not create a new env scope |
523 | // But iteration vars are reset afterwards |
524 | Statement* Expand::operator()(ForRule* f) |
525 | { |
526 | sass::string variable(f->variable()); |
527 | ExpressionObj low = f->lower_bound()->perform(op: &eval); |
528 | if (low->concrete_type() != Expression::NUMBER) { |
529 | traces.push_back(x: Backtrace(low->pstate())); |
530 | throw Exception::TypeMismatch(traces, *low, "integer" ); |
531 | } |
532 | ExpressionObj high = f->upper_bound()->perform(op: &eval); |
533 | if (high->concrete_type() != Expression::NUMBER) { |
534 | traces.push_back(x: Backtrace(high->pstate())); |
535 | throw Exception::TypeMismatch(traces, *high, "integer" ); |
536 | } |
537 | Number_Obj sass_start = Cast<Number>(ptr: low); |
538 | Number_Obj sass_end = Cast<Number>(ptr: high); |
539 | // check if units are valid for sequence |
540 | if (sass_start->unit() != sass_end->unit()) { |
541 | sass::ostream msg; msg << "Incompatible units: '" |
542 | << sass_start->unit() << "' and '" |
543 | << sass_end->unit() << "'." ; |
544 | error(msg: msg.str(), pstate: low->pstate(), traces); |
545 | } |
546 | double start = sass_start->value(); |
547 | double end = sass_end->value(); |
548 | // only create iterator once in this environment |
549 | Env env(environment(), true); |
550 | env_stack.push_back(x: &env); |
551 | call_stack.push_back(x: f); |
552 | Block* body = f->block(); |
553 | if (start < end) { |
554 | if (f->is_inclusive()) ++end; |
555 | for (double i = start; |
556 | i < end; |
557 | ++i) { |
558 | Number_Obj it = SASS_MEMORY_NEW(Number, low->pstate(), i, sass_end->unit()); |
559 | env.set_local(key: variable, val: it); |
560 | append_block(body); |
561 | } |
562 | } else { |
563 | if (f->is_inclusive()) --end; |
564 | for (double i = start; |
565 | i > end; |
566 | --i) { |
567 | Number_Obj it = SASS_MEMORY_NEW(Number, low->pstate(), i, sass_end->unit()); |
568 | env.set_local(key: variable, val: it); |
569 | append_block(body); |
570 | } |
571 | } |
572 | call_stack.pop_back(); |
573 | env_stack.pop_back(); |
574 | return 0; |
575 | } |
576 | |
577 | // Eval does not create a new env scope |
578 | // But iteration vars are reset afterwards |
579 | Statement* Expand::operator()(EachRule* e) |
580 | { |
581 | sass::vector<sass::string> variables(e->variables()); |
582 | ExpressionObj expr = e->list()->perform(op: &eval); |
583 | List_Obj list; |
584 | Map_Obj map; |
585 | if (expr->concrete_type() == Expression::MAP) { |
586 | map = Cast<Map>(ptr: expr); |
587 | } |
588 | else if (SelectorList * ls = Cast<SelectorList>(ptr: expr)) { |
589 | ExpressionObj rv = Listize::perform(node: ls); |
590 | list = Cast<List>(ptr: rv); |
591 | } |
592 | else if (expr->concrete_type() != Expression::LIST) { |
593 | list = SASS_MEMORY_NEW(List, expr->pstate(), 1, SASS_COMMA); |
594 | list->append(element: expr); |
595 | } |
596 | else { |
597 | list = Cast<List>(ptr: expr); |
598 | } |
599 | // remember variables and then reset them |
600 | Env env(environment(), true); |
601 | env_stack.push_back(x: &env); |
602 | call_stack.push_back(x: e); |
603 | Block* body = e->block(); |
604 | |
605 | if (map) { |
606 | for (auto key : map->keys()) { |
607 | ExpressionObj k = key->perform(op: &eval); |
608 | ExpressionObj v = map->at(k: key)->perform(op: &eval); |
609 | |
610 | if (variables.size() == 1) { |
611 | List_Obj variable = SASS_MEMORY_NEW(List, map->pstate(), 2, SASS_SPACE); |
612 | variable->append(element: k); |
613 | variable->append(element: v); |
614 | env.set_local(key: variables[0], val: variable); |
615 | } else { |
616 | env.set_local(key: variables[0], val: k); |
617 | env.set_local(key: variables[1], val: v); |
618 | } |
619 | append_block(body); |
620 | } |
621 | } |
622 | else { |
623 | // bool arglist = list->is_arglist(); |
624 | if (list->length() == 1 && Cast<SelectorList>(ptr: list)) { |
625 | list = Cast<List>(ptr: list); |
626 | } |
627 | for (size_t i = 0, L = list->length(); i < L; ++i) { |
628 | ExpressionObj item = list->at(i); |
629 | // unwrap value if the expression is an argument |
630 | if (Argument_Obj arg = Cast<Argument>(ptr: item)) item = arg->value(); |
631 | // check if we got passed a list of args (investigate) |
632 | if (List_Obj scalars = Cast<List>(ptr: item)) { |
633 | if (variables.size() == 1) { |
634 | List_Obj var = scalars; |
635 | // if (arglist) var = (*scalars)[0]; |
636 | env.set_local(key: variables[0], val: var); |
637 | } else { |
638 | for (size_t j = 0, K = variables.size(); j < K; ++j) { |
639 | env.set_local(key: variables[j], val: j >= scalars->length() |
640 | ? SASS_MEMORY_NEW(Null, expr->pstate()) |
641 | : (*scalars)[j]->perform(op: &eval)); |
642 | } |
643 | } |
644 | } else { |
645 | if (variables.size() > 0) { |
646 | env.set_local(key: variables.at(n: 0), val: item); |
647 | for (size_t j = 1, K = variables.size(); j < K; ++j) { |
648 | ExpressionObj res = SASS_MEMORY_NEW(Null, expr->pstate()); |
649 | env.set_local(key: variables[j], val: res); |
650 | } |
651 | } |
652 | } |
653 | append_block(body); |
654 | } |
655 | } |
656 | call_stack.pop_back(); |
657 | env_stack.pop_back(); |
658 | return 0; |
659 | } |
660 | |
661 | Statement* Expand::operator()(WhileRule* w) |
662 | { |
663 | ExpressionObj pred = w->predicate(); |
664 | Block* body = w->block(); |
665 | Env env(environment(), true); |
666 | env_stack.push_back(x: &env); |
667 | call_stack.push_back(x: w); |
668 | ExpressionObj cond = pred->perform(op: &eval); |
669 | while (!cond->is_false()) { |
670 | append_block(body); |
671 | cond = pred->perform(op: &eval); |
672 | } |
673 | call_stack.pop_back(); |
674 | env_stack.pop_back(); |
675 | return 0; |
676 | } |
677 | |
678 | Statement* Expand::operator()(Return* r) |
679 | { |
680 | error(msg: "@return may only be used within a function" , pstate: r->pstate(), traces); |
681 | return 0; |
682 | } |
683 | |
684 | Statement* Expand::operator()(ExtendRule* e) |
685 | { |
686 | |
687 | // evaluate schema first |
688 | if (e->schema()) { |
689 | e->selector(selector__: eval(e->schema())); |
690 | e->isOptional(isOptional__: e->selector()->is_optional()); |
691 | } |
692 | // evaluate the selector |
693 | e->selector(selector__: eval(e->selector())); |
694 | |
695 | if (e->selector()) { |
696 | |
697 | for (auto complex : e->selector()->elements()) { |
698 | |
699 | if (complex->length() != 1) { |
700 | error(msg: "complex selectors may not be extended." , pstate: complex->pstate(), traces); |
701 | } |
702 | |
703 | if (const CompoundSelector* compound = complex->first()->getCompound()) { |
704 | |
705 | if (compound->length() != 1) { |
706 | |
707 | sass::ostream sels; bool addComma = false; |
708 | sels << "Compound selectors may no longer be extended.\n" ; |
709 | sels << "Consider `@extend " ; |
710 | for (auto sel : compound->elements()) { |
711 | if (addComma) sels << ", " ; |
712 | sels << sel->to_sass(); |
713 | addComma = true; |
714 | } |
715 | sels << "` instead.\n" ; |
716 | sels << "See http://bit.ly/ExtendCompound for details." ; |
717 | |
718 | warning(msg: sels.str(), pstate: compound->pstate()); |
719 | |
720 | // Make this an error once deprecation is over |
721 | for (SimpleSelectorObj simple : compound->elements()) { |
722 | // Pass every selector we ever see to extender (to make them findable for extend) |
723 | ctx.extender.addExtension(extender: selector(), target: simple, mediaQueryContext: mediaStack.back(), is_optional: e->isOptional()); |
724 | } |
725 | |
726 | } |
727 | else { |
728 | // Pass every selector we ever see to extender (to make them findable for extend) |
729 | ctx.extender.addExtension(extender: selector(), target: compound->first(), mediaQueryContext: mediaStack.back(), is_optional: e->isOptional()); |
730 | } |
731 | |
732 | } |
733 | else { |
734 | error(msg: "complex selectors may not be extended." , pstate: complex->pstate(), traces); |
735 | } |
736 | } |
737 | } |
738 | |
739 | return nullptr; |
740 | |
741 | } |
742 | |
743 | Statement* Expand::operator()(Definition* d) |
744 | { |
745 | Env* env = environment(); |
746 | Definition_Obj dd = SASS_MEMORY_COPY(d); |
747 | env->local_frame()[d->name() + |
748 | (d->type() == Definition::MIXIN ? "[m]" : "[f]" )] = dd; |
749 | |
750 | if (d->type() == Definition::FUNCTION && ( |
751 | Prelexer::calc_fn_call(src: d->name().c_str()) || |
752 | d->name() == "element" || |
753 | d->name() == "expression" || |
754 | d->name() == "url" |
755 | )) { |
756 | deprecated( |
757 | msg: "Naming a function \"" + d->name() + "\" is disallowed and will be an error in future versions of Sass." , |
758 | msg2: "This name conflicts with an existing CSS function with special parse rules." , |
759 | with_column: false, pstate: d->pstate() |
760 | ); |
761 | } |
762 | |
763 | // set the static link so we can have lexical scoping |
764 | dd->environment(environment__: env); |
765 | return 0; |
766 | } |
767 | |
768 | Statement* Expand::operator()(Mixin_Call* c) |
769 | { |
770 | |
771 | if (recursions > maxRecursion) { |
772 | throw Exception::StackError(traces, *c); |
773 | } |
774 | |
775 | recursions ++; |
776 | |
777 | Env* env = environment(); |
778 | sass::string full_name(c->name() + "[m]" ); |
779 | if (!env->has(key: full_name)) { |
780 | error(msg: "no mixin named " + c->name(), pstate: c->pstate(), traces); |
781 | } |
782 | Definition_Obj def = Cast<Definition>(ptr: (*env)[full_name]); |
783 | Block_Obj body = def->block(); |
784 | Parameters_Obj params = def->parameters(); |
785 | |
786 | if (c->block() && c->name() != "@content" && !body->has_content()) { |
787 | error(msg: "Mixin \"" + c->name() + "\" does not accept a content block." , pstate: c->pstate(), traces); |
788 | } |
789 | ExpressionObj rv = c->arguments()->perform(op: &eval); |
790 | Arguments_Obj args = Cast<Arguments>(ptr: rv); |
791 | sass::string msg(", in mixin `" + c->name() + "`" ); |
792 | traces.push_back(x: Backtrace(c->pstate(), msg)); |
793 | ctx.callee_stack.push_back(x: { |
794 | .name: c->name().c_str(), |
795 | .path: c->pstate().getPath(), |
796 | .line: c->pstate().getLine(), |
797 | .column: c->pstate().getColumn(), |
798 | .type: SASS_CALLEE_MIXIN, |
799 | .env: { .frame: env } |
800 | }); |
801 | |
802 | Env new_env(def->environment()); |
803 | env_stack.push_back(x: &new_env); |
804 | if (c->block()) { |
805 | Parameters_Obj params = c->block_parameters(); |
806 | if (!params) params = SASS_MEMORY_NEW(Parameters, c->pstate()); |
807 | // represent mixin content blocks as thunks/closures |
808 | Definition_Obj thunk = SASS_MEMORY_NEW(Definition, |
809 | c->pstate(), |
810 | "@content" , |
811 | params, |
812 | c->block(), |
813 | Definition::MIXIN); |
814 | thunk->environment(environment__: env); |
815 | new_env.local_frame()["@content[m]" ] = thunk; |
816 | } |
817 | |
818 | bind(type: sass::string("Mixin" ), name: c->name(), params, args, &new_env, &eval, traces); |
819 | |
820 | Block_Obj trace_block = SASS_MEMORY_NEW(Block, c->pstate()); |
821 | Trace_Obj trace = SASS_MEMORY_NEW(Trace, c->pstate(), c->name(), trace_block); |
822 | |
823 | env->set_global(key: "is_in_mixin" , val: bool_true); |
824 | if (Block* pr = block_stack.back()) { |
825 | trace_block->is_root(is_root__: pr->is_root()); |
826 | } |
827 | block_stack.push_back(x: trace_block); |
828 | for (auto bb : body->elements()) { |
829 | if (StyleRule* r = Cast<StyleRule>(ptr: bb)) { |
830 | r->is_root(is_root__: trace_block->is_root()); |
831 | } |
832 | Statement_Obj ith = bb->perform(op: this); |
833 | if (ith) trace->block()->append(element: ith); |
834 | } |
835 | block_stack.pop_back(); |
836 | env->del_global(key: "is_in_mixin" ); |
837 | |
838 | ctx.callee_stack.pop_back(); |
839 | env_stack.pop_back(); |
840 | traces.pop_back(); |
841 | |
842 | recursions --; |
843 | return trace.detach(); |
844 | } |
845 | |
846 | Statement* Expand::operator()(Content* c) |
847 | { |
848 | Env* env = environment(); |
849 | // convert @content directives into mixin calls to the underlying thunk |
850 | if (!env->has(key: "@content[m]" )) return 0; |
851 | Arguments_Obj args = c->arguments(); |
852 | if (!args) args = SASS_MEMORY_NEW(Arguments, c->pstate()); |
853 | |
854 | Mixin_Call_Obj call = SASS_MEMORY_NEW(Mixin_Call, |
855 | c->pstate(), |
856 | "@content" , |
857 | args); |
858 | |
859 | Trace_Obj trace = Cast<Trace>(ptr: call->perform(op: this)); |
860 | return trace.detach(); |
861 | } |
862 | |
863 | // process and add to last block on stack |
864 | inline void Expand::append_block(Block* b) |
865 | { |
866 | if (b->is_root()) call_stack.push_back(x: b); |
867 | for (size_t i = 0, L = b->length(); i < L; ++i) { |
868 | Statement* stm = b->at(i); |
869 | Statement_Obj ith = stm->perform(op: this); |
870 | if (ith) block_stack.back()->append(element: ith); |
871 | } |
872 | if (b->is_root()) call_stack.pop_back(); |
873 | } |
874 | |
875 | } |
876 | |