Initial commit
[yaffs-website] / node_modules / node-sass / src / libsass / src / inspect.cpp
1 #include "sass.hpp"
2 #include <cmath>
3 #include <string>
4 #include <iostream>
5 #include <iomanip>
6 #include <stdint.h>
7 #include <stdint.h>
8
9 #include "ast.hpp"
10 #include "inspect.hpp"
11 #include "context.hpp"
12 #include "listize.hpp"
13 #include "color_maps.hpp"
14 #include "utf8/checked.h"
15
16 namespace Sass {
17
18   Inspect::Inspect(Emitter emi)
19   : Emitter(emi)
20   { }
21   Inspect::~Inspect() { }
22
23   // statements
24   void Inspect::operator()(Block_Ptr block)
25   {
26     if (!block->is_root()) {
27       add_open_mapping(block);
28       append_scope_opener();
29     }
30     if (output_style() == NESTED) indentation += block->tabs();
31     for (size_t i = 0, L = block->length(); i < L; ++i) {
32       (*block)[i]->perform(this);
33     }
34     if (output_style() == NESTED) indentation -= block->tabs();
35     if (!block->is_root()) {
36       append_scope_closer();
37       add_close_mapping(block);
38     }
39
40   }
41
42   void Inspect::operator()(Ruleset_Ptr ruleset)
43   {
44     if (ruleset->selector()) {
45       ruleset->selector()->perform(this);
46     }
47     if (ruleset->block()) {
48       ruleset->block()->perform(this);
49     }
50   }
51
52   void Inspect::operator()(Keyframe_Rule_Ptr rule)
53   {
54     if (rule->name()) rule->name()->perform(this);
55     if (rule->block()) rule->block()->perform(this);
56   }
57
58   void Inspect::operator()(Bubble_Ptr bubble)
59   {
60     append_indentation();
61     append_token("::BUBBLE", bubble);
62     append_scope_opener();
63     bubble->node()->perform(this);
64     append_scope_closer();
65   }
66
67   void Inspect::operator()(Media_Block_Ptr media_block)
68   {
69     append_indentation();
70     append_token("@media", media_block);
71     append_mandatory_space();
72     in_media_block = true;
73     media_block->media_queries()->perform(this);
74     in_media_block = false;
75     media_block->block()->perform(this);
76   }
77
78   void Inspect::operator()(Supports_Block_Ptr feature_block)
79   {
80     append_indentation();
81     append_token("@supports", feature_block);
82     append_mandatory_space();
83     feature_block->condition()->perform(this);
84     feature_block->block()->perform(this);
85   }
86
87   void Inspect::operator()(At_Root_Block_Ptr at_root_block)
88   {
89     append_indentation();
90     append_token("@at-root ", at_root_block);
91     append_mandatory_space();
92     if(at_root_block->expression()) at_root_block->expression()->perform(this);
93     at_root_block->block()->perform(this);
94   }
95
96   void Inspect::operator()(Directive_Ptr at_rule)
97   {
98     append_indentation();
99     append_token(at_rule->keyword(), at_rule);
100     if (at_rule->selector()) {
101       append_mandatory_space();
102       bool was_wrapped = in_wrapped;
103       in_wrapped = true;
104       at_rule->selector()->perform(this);
105       in_wrapped = was_wrapped;
106     }
107     if (at_rule->value()) {
108       append_mandatory_space();
109       at_rule->value()->perform(this);
110     }
111     if (at_rule->block()) {
112       at_rule->block()->perform(this);
113     }
114     else {
115       append_delimiter();
116     }
117   }
118
119   void Inspect::operator()(Declaration_Ptr dec)
120   {
121     if (dec->value()->concrete_type() == Expression::NULL_VAL) return;
122     bool was_decl = in_declaration;
123     in_declaration = true;
124     if (output_style() == NESTED)
125       indentation += dec->tabs();
126     append_indentation();
127     if (dec->property())
128       dec->property()->perform(this);
129     append_colon_separator();
130
131     if (dec->value()->concrete_type() == Expression::SELECTOR) {
132       Listize listize;
133       Expression_Obj ls = dec->value()->perform(&listize);
134       ls->perform(this);
135     } else {
136       dec->value()->perform(this);
137     }
138
139     if (dec->is_important()) {
140       append_optional_space();
141       append_string("!important");
142     }
143     append_delimiter();
144     if (output_style() == NESTED)
145       indentation -= dec->tabs();
146     in_declaration = was_decl;
147   }
148
149   void Inspect::operator()(Assignment_Ptr assn)
150   {
151     append_token(assn->variable(), assn);
152     append_colon_separator();
153     assn->value()->perform(this);
154     if (assn->is_default()) {
155       append_optional_space();
156       append_string("!default");
157     }
158     append_delimiter();
159   }
160
161   void Inspect::operator()(Import_Ptr import)
162   {
163     if (!import->urls().empty()) {
164       append_token("@import", import);
165       append_mandatory_space();
166
167       import->urls().front()->perform(this);
168       if (import->urls().size() == 1) {
169         if (import->import_queries()) {
170           append_mandatory_space();
171           import->import_queries()->perform(this);
172         }
173       }
174       append_delimiter();
175       for (size_t i = 1, S = import->urls().size(); i < S; ++i) {
176         append_mandatory_linefeed();
177         append_token("@import", import);
178         append_mandatory_space();
179
180         import->urls()[i]->perform(this);
181         if (import->urls().size() - 1 == i) {
182           if (import->import_queries()) {
183             append_mandatory_space();
184             import->import_queries()->perform(this);
185           }
186         }
187         append_delimiter();
188       }
189     }
190   }
191
192   void Inspect::operator()(Import_Stub_Ptr import)
193   {
194     append_indentation();
195     append_token("@import", import);
196     append_mandatory_space();
197     append_string(import->imp_path());
198     append_delimiter();
199   }
200
201   void Inspect::operator()(Warning_Ptr warning)
202   {
203     append_indentation();
204     append_token("@warn", warning);
205     append_mandatory_space();
206     warning->message()->perform(this);
207     append_delimiter();
208   }
209
210   void Inspect::operator()(Error_Ptr error)
211   {
212     append_indentation();
213     append_token("@error", error);
214     append_mandatory_space();
215     error->message()->perform(this);
216     append_delimiter();
217   }
218
219   void Inspect::operator()(Debug_Ptr debug)
220   {
221     append_indentation();
222     append_token("@debug", debug);
223     append_mandatory_space();
224     debug->value()->perform(this);
225     append_delimiter();
226   }
227
228   void Inspect::operator()(Comment_Ptr comment)
229   {
230     in_comment = true;
231     comment->text()->perform(this);
232     in_comment = false;
233   }
234
235   void Inspect::operator()(If_Ptr cond)
236   {
237     append_indentation();
238     append_token("@if", cond);
239     append_mandatory_space();
240     cond->predicate()->perform(this);
241     cond->block()->perform(this);
242     if (cond->alternative()) {
243       append_optional_linefeed();
244       append_indentation();
245       append_string("else");
246       cond->alternative()->perform(this);
247     }
248   }
249
250   void Inspect::operator()(For_Ptr loop)
251   {
252     append_indentation();
253     append_token("@for", loop);
254     append_mandatory_space();
255     append_string(loop->variable());
256     append_string(" from ");
257     loop->lower_bound()->perform(this);
258     append_string(loop->is_inclusive() ? " through " : " to ");
259     loop->upper_bound()->perform(this);
260     loop->block()->perform(this);
261   }
262
263   void Inspect::operator()(Each_Ptr loop)
264   {
265     append_indentation();
266     append_token("@each", loop);
267     append_mandatory_space();
268     append_string(loop->variables()[0]);
269     for (size_t i = 1, L = loop->variables().size(); i < L; ++i) {
270       append_comma_separator();
271       append_string(loop->variables()[i]);
272     }
273     append_string(" in ");
274     loop->list()->perform(this);
275     loop->block()->perform(this);
276   }
277
278   void Inspect::operator()(While_Ptr loop)
279   {
280     append_indentation();
281     append_token("@while", loop);
282     append_mandatory_space();
283     loop->predicate()->perform(this);
284     loop->block()->perform(this);
285   }
286
287   void Inspect::operator()(Return_Ptr ret)
288   {
289     append_indentation();
290     append_token("@return", ret);
291     append_mandatory_space();
292     ret->value()->perform(this);
293     append_delimiter();
294   }
295
296   void Inspect::operator()(Extension_Ptr extend)
297   {
298     append_indentation();
299     append_token("@extend", extend);
300     append_mandatory_space();
301     extend->selector()->perform(this);
302     append_delimiter();
303   }
304
305   void Inspect::operator()(Definition_Ptr def)
306   {
307     append_indentation();
308     if (def->type() == Definition::MIXIN) {
309       append_token("@mixin", def);
310       append_mandatory_space();
311     } else {
312       append_token("@function", def);
313       append_mandatory_space();
314     }
315     append_string(def->name());
316     def->parameters()->perform(this);
317     def->block()->perform(this);
318   }
319
320   void Inspect::operator()(Mixin_Call_Ptr call)
321   {
322     append_indentation();
323     append_token("@include", call);
324     append_mandatory_space();
325     append_string(call->name());
326     if (call->arguments()) {
327       call->arguments()->perform(this);
328     }
329     if (call->block()) {
330       append_optional_space();
331       call->block()->perform(this);
332     }
333     if (!call->block()) append_delimiter();
334   }
335
336   void Inspect::operator()(Content_Ptr content)
337   {
338     append_indentation();
339     append_token("@content", content);
340     append_delimiter();
341   }
342
343   void Inspect::operator()(Map_Ptr map)
344   {
345     if (output_style() == TO_SASS && map->empty()) {
346       append_string("()");
347       return;
348     }
349     if (map->empty()) return;
350     if (map->is_invisible()) return;
351     bool items_output = false;
352     append_string("(");
353     for (auto key : map->keys()) {
354       if (items_output) append_comma_separator();
355       key->perform(this);
356       append_colon_separator();
357       map->at(key)->perform(this);
358       items_output = true;
359     }
360     append_string(")");
361   }
362
363   std::string Inspect::lbracket(List_Ptr list) {
364     return list->is_bracketed() ? "[" : "(";
365   }
366
367   std::string Inspect::rbracket(List_Ptr list) {
368     return list->is_bracketed() ? "]" : ")";
369   }
370
371   void Inspect::operator()(List_Ptr list)
372   {
373     if (list->empty() && (output_style() == TO_SASS || list->is_bracketed())) {
374       append_string(lbracket(list));
375       append_string(rbracket(list));
376       return;
377     }
378     std::string sep(list->separator() == SASS_SPACE ? " " : ",");
379     if ((output_style() != COMPRESSED) && sep == ",") sep += " ";
380     else if (in_media_block && sep != " ") sep += " "; // verified
381     if (list->empty()) return;
382     bool items_output = false;
383
384     bool was_space_array = in_space_array;
385     bool was_comma_array = in_comma_array;
386     // if the list is bracketed, always include the left bracket
387     if (list->is_bracketed()) {
388       append_string(lbracket(list));
389     }
390     // probably ruby sass eqivalent of element_needs_parens
391     else if (output_style() == TO_SASS &&
392         list->length() == 1 &&
393         !list->from_selector() &&
394         !Cast<List>(list->at(0)) &&
395         !Cast<Selector_List>(list->at(0))
396     ) {
397       append_string(lbracket(list));
398     }
399     else if (!in_declaration && (list->separator() == SASS_HASH ||
400         (list->separator() == SASS_SPACE && in_space_array) ||
401         (list->separator() == SASS_COMMA && in_comma_array)
402     )) {
403       append_string(lbracket(list));
404     }
405
406     if (list->separator() == SASS_SPACE) in_space_array = true;
407     else if (list->separator() == SASS_COMMA) in_comma_array = true;
408
409     for (size_t i = 0, L = list->size(); i < L; ++i) {
410       if (list->separator() == SASS_HASH)
411       { sep[0] = i % 2 ? ':' : ','; }
412       Expression_Obj list_item = list->at(i);
413       if (output_style() != TO_SASS) {
414         if (list_item->is_invisible()) {
415           // this fixes an issue with "" in a list
416           if (!Cast<String_Constant>(list_item)) {
417             continue;
418           }
419         }
420       }
421       if (items_output) {
422         append_string(sep);
423       }
424       if (items_output && sep != " ")
425         append_optional_space();
426       list_item->perform(this);
427       items_output = true;
428     }
429
430     in_comma_array = was_comma_array;
431     in_space_array = was_space_array;
432
433     // if the list is bracketed, always include the right bracket
434     if (list->is_bracketed()) {
435       if (list->separator() == SASS_COMMA && list->size() == 1) {
436         append_string(",");
437       }
438       append_string(rbracket(list));
439     }
440     // probably ruby sass eqivalent of element_needs_parens
441     else if (output_style() == TO_SASS &&
442         list->length() == 1 &&
443         !list->from_selector() &&
444         !Cast<List>(list->at(0)) &&
445         !Cast<Selector_List>(list->at(0))
446     ) {
447       append_string(",");
448       append_string(rbracket(list));
449     }
450     else if (!in_declaration && (list->separator() == SASS_HASH ||
451         (list->separator() == SASS_SPACE && in_space_array) ||
452         (list->separator() == SASS_COMMA && in_comma_array)
453     )) {
454       append_string(rbracket(list));
455     }
456
457   }
458
459   void Inspect::operator()(Binary_Expression_Ptr expr)
460   {
461     expr->left()->perform(this);
462     if ( in_media_block ||
463          (output_style() == INSPECT) || (
464           expr->op().ws_before
465           && (!expr->is_interpolant())
466           && (expr->is_left_interpolant() ||
467               expr->is_right_interpolant())
468
469     )) append_string(" ");
470     switch (expr->optype()) {
471       case Sass_OP::AND: append_string("&&"); break;
472       case Sass_OP::OR:  append_string("||");  break;
473       case Sass_OP::EQ:  append_string("==");  break;
474       case Sass_OP::NEQ: append_string("!=");  break;
475       case Sass_OP::GT:  append_string(">");   break;
476       case Sass_OP::GTE: append_string(">=");  break;
477       case Sass_OP::LT:  append_string("<");   break;
478       case Sass_OP::LTE: append_string("<=");  break;
479       case Sass_OP::ADD: append_string("+");   break;
480       case Sass_OP::SUB: append_string("-");   break;
481       case Sass_OP::MUL: append_string("*");   break;
482       case Sass_OP::DIV: append_string("/"); break;
483       case Sass_OP::MOD: append_string("%");   break;
484       default: break; // shouldn't get here
485     }
486     if ( in_media_block ||
487          (output_style() == INSPECT) || (
488           expr->op().ws_after
489           && (!expr->is_interpolant())
490           && (expr->is_left_interpolant() ||
491               expr->is_right_interpolant())
492     )) append_string(" ");
493     expr->right()->perform(this);
494   }
495
496   void Inspect::operator()(Unary_Expression_Ptr expr)
497   {
498     if (expr->optype() == Unary_Expression::PLUS) append_string("+");
499     else                                          append_string("-");
500     expr->operand()->perform(this);
501   }
502
503   void Inspect::operator()(Function_Call_Ptr call)
504   {
505     append_token(call->name(), call);
506     call->arguments()->perform(this);
507   }
508
509   void Inspect::operator()(Function_Call_Schema_Ptr call)
510   {
511     call->name()->perform(this);
512     call->arguments()->perform(this);
513   }
514
515   void Inspect::operator()(Variable_Ptr var)
516   {
517     append_token(var->name(), var);
518   }
519
520   void Inspect::operator()(Textual_Ptr txt)
521   {
522     append_token(txt->value(), txt);
523   }
524
525   void Inspect::operator()(Number_Ptr n)
526   {
527
528     std::string res;
529
530     // check if the fractional part of the value equals to zero
531     // neat trick from http://stackoverflow.com/a/1521682/1550314
532     // double int_part; bool is_int = modf(value, &int_part) == 0.0;
533
534     // this all cannot be done with one run only, since fixed
535     // output differs from normal output and regular output
536     // can contain scientific notation which we do not want!
537
538     // first sample
539     std::stringstream ss;
540     ss.precision(12);
541     ss << n->value();
542
543     // check if we got scientific notation in result
544     if (ss.str().find_first_of("e") != std::string::npos) {
545       ss.clear(); ss.str(std::string());
546       ss.precision(std::max(12, opt.precision));
547       ss << std::fixed << n->value();
548     }
549
550     std::string tmp = ss.str();
551     size_t pos_point = tmp.find_first_of(".,");
552     size_t pos_fract = tmp.find_last_not_of("0");
553     bool is_int = pos_point == pos_fract ||
554                   pos_point == std::string::npos;
555
556     // reset stream for another run
557     ss.clear(); ss.str(std::string());
558
559     // take a shortcut for integers
560     if (is_int)
561     {
562       ss.precision(0);
563       ss << std::fixed << n->value();
564       res = std::string(ss.str());
565     }
566     // process floats
567     else
568     {
569       // do we have have too much precision?
570       if (pos_fract < opt.precision + pos_point)
571       { ss.precision((int)(pos_fract - pos_point)); }
572       else { ss.precision(opt.precision); }
573       // round value again
574       ss << std::fixed << n->value();
575       res = std::string(ss.str());
576       // maybe we truncated up to decimal point
577       size_t pos = res.find_last_not_of("0");
578       // handle case where we have a "0"
579       if (pos == std::string::npos) {
580         res = "0.0";
581       } else {
582         bool at_dec_point = res[pos] == '.' ||
583                             res[pos] == ',';
584         // don't leave a blank point
585         if (at_dec_point) ++ pos;
586         res.resize (pos + 1);
587       }
588     }
589
590     // some final cosmetics
591     if (res == "0.0") res = "0";
592     else if (res == "") res = "0";
593     else if (res == "-0") res = "0";
594     else if (res == "-0.0") res = "0";
595     else if (opt.output_style == COMPRESSED)
596     {
597       // check if handling negative nr
598       size_t off = res[0] == '-' ? 1 : 0;
599       // remove leading zero from floating point in compressed mode
600       if (n->zero() && res[off] == '0' && res[off+1] == '.') res.erase(off, 1);
601     }
602
603     // add unit now
604     res += n->unit();
605
606     // output the final token
607     append_token(res, n);
608   }
609
610   // helper function for serializing colors
611   template <size_t range>
612   static double cap_channel(double c) {
613     if      (c > range) return range;
614     else if (c < 0)     return 0;
615     else                return c;
616   }
617
618   void Inspect::operator()(Color_Ptr c)
619   {
620     // output the final token
621     std::stringstream ss;
622
623     // original color name
624     // maybe an unknown token
625     std::string name = c->disp();
626
627     // resolved color
628     std::string res_name = name;
629
630     double r = Sass::round(cap_channel<0xff>(c->r()), opt.precision);
631     double g = Sass::round(cap_channel<0xff>(c->g()), opt.precision);
632     double b = Sass::round(cap_channel<0xff>(c->b()), opt.precision);
633     double a = cap_channel<1>   (c->a());
634
635     // get color from given name (if one was given at all)
636     if (name != "" && name_to_color(name)) {
637       Color_Ptr_Const n = name_to_color(name);
638       r = Sass::round(cap_channel<0xff>(n->r()), opt.precision);
639       g = Sass::round(cap_channel<0xff>(n->g()), opt.precision);
640       b = Sass::round(cap_channel<0xff>(n->b()), opt.precision);
641       a = cap_channel<1>   (n->a());
642     }
643     // otherwise get the possible resolved color name
644     else {
645       double numval = r * 0x10000 + g * 0x100 + b;
646       if (color_to_name(numval))
647         res_name = color_to_name(numval);
648     }
649
650     std::stringstream hexlet;
651     bool compressed = opt.output_style == COMPRESSED;
652     hexlet << '#' << std::setw(1) << std::setfill('0');
653     // create a short color hexlet if there is any need for it
654     if (compressed && is_color_doublet(r, g, b) && a == 1) {
655       hexlet << std::hex << std::setw(1) << (static_cast<unsigned long>(r) >> 4);
656       hexlet << std::hex << std::setw(1) << (static_cast<unsigned long>(g) >> 4);
657       hexlet << std::hex << std::setw(1) << (static_cast<unsigned long>(b) >> 4);
658     } else {
659       hexlet << std::hex << std::setw(2) << static_cast<unsigned long>(r);
660       hexlet << std::hex << std::setw(2) << static_cast<unsigned long>(g);
661       hexlet << std::hex << std::setw(2) << static_cast<unsigned long>(b);
662     }
663
664     if (compressed && !c->is_delayed()) name = "";
665     if (opt.output_style == INSPECT && a >= 1) {
666       append_token(hexlet.str(), c);
667       return;
668     }
669
670     // retain the originally specified color definition if unchanged
671     if (name != "") {
672       ss << name;
673     }
674     else if (r == 0 && g == 0 && b == 0 && a == 0) {
675         ss << "transparent";
676     }
677     else if (a >= 1) {
678       if (res_name != "") {
679         if (compressed && hexlet.str().size() < res_name.size()) {
680           ss << hexlet.str();
681         } else {
682           ss << res_name;
683         }
684       }
685       else {
686         ss << hexlet.str();
687       }
688     }
689     else {
690       ss << "rgba(";
691       ss << static_cast<unsigned long>(r) << ",";
692       if (!compressed) ss << " ";
693       ss << static_cast<unsigned long>(g) << ",";
694       if (!compressed) ss << " ";
695       ss << static_cast<unsigned long>(b) << ",";
696       if (!compressed) ss << " ";
697       ss << a << ')';
698     }
699
700     append_token(ss.str(), c);
701
702   }
703
704   void Inspect::operator()(Boolean_Ptr b)
705   {
706     // output the final token
707     append_token(b->value() ? "true" : "false", b);
708   }
709
710   void Inspect::operator()(String_Schema_Ptr ss)
711   {
712     // Evaluation should turn these into String_Constants,
713     // so this method is only for inspection purposes.
714     for (size_t i = 0, L = ss->length(); i < L; ++i) {
715       if ((*ss)[i]->is_interpolant()) append_string("#{");
716       (*ss)[i]->perform(this);
717       if ((*ss)[i]->is_interpolant()) append_string("}");
718     }
719   }
720
721   void Inspect::operator()(String_Constant_Ptr s)
722   {
723     append_token(s->value(), s);
724   }
725
726   void Inspect::operator()(String_Quoted_Ptr s)
727   {
728     if (const char q = s->quote_mark()) {
729       append_token(quote(s->value(), q), s);
730     } else {
731       append_token(s->value(), s);
732     }
733   }
734
735   void Inspect::operator()(Custom_Error_Ptr e)
736   {
737     append_token(e->message(), e);
738   }
739
740   void Inspect::operator()(Custom_Warning_Ptr w)
741   {
742     append_token(w->message(), w);
743   }
744
745   void Inspect::operator()(Supports_Operator_Ptr so)
746   {
747
748     if (so->needs_parens(so->left())) append_string("(");
749     so->left()->perform(this);
750     if (so->needs_parens(so->left())) append_string(")");
751
752     if (so->operand() == Supports_Operator::AND) {
753       append_mandatory_space();
754       append_token("and", so);
755       append_mandatory_space();
756     } else if (so->operand() == Supports_Operator::OR) {
757       append_mandatory_space();
758       append_token("or", so);
759       append_mandatory_space();
760     }
761
762     if (so->needs_parens(so->right())) append_string("(");
763     so->right()->perform(this);
764     if (so->needs_parens(so->right())) append_string(")");
765   }
766
767   void Inspect::operator()(Supports_Negation_Ptr sn)
768   {
769     append_token("not", sn);
770     append_mandatory_space();
771     if (sn->needs_parens(sn->condition())) append_string("(");
772     sn->condition()->perform(this);
773     if (sn->needs_parens(sn->condition())) append_string(")");
774   }
775
776   void Inspect::operator()(Supports_Declaration_Ptr sd)
777   {
778     append_string("(");
779     sd->feature()->perform(this);
780     append_string(": ");
781     sd->value()->perform(this);
782     append_string(")");
783   }
784
785   void Inspect::operator()(Supports_Interpolation_Ptr sd)
786   {
787     sd->value()->perform(this);
788   }
789
790   void Inspect::operator()(Media_Query_Ptr mq)
791   {
792     size_t i = 0;
793     if (mq->media_type()) {
794       if      (mq->is_negated())    append_string("not ");
795       else if (mq->is_restricted()) append_string("only ");
796       mq->media_type()->perform(this);
797     }
798     else {
799       (*mq)[i++]->perform(this);
800     }
801     for (size_t L = mq->length(); i < L; ++i) {
802       append_string(" and ");
803       (*mq)[i]->perform(this);
804     }
805   }
806
807   void Inspect::operator()(Media_Query_Expression_Ptr mqe)
808   {
809     if (mqe->is_interpolated()) {
810       mqe->feature()->perform(this);
811     }
812     else {
813       append_string("(");
814       mqe->feature()->perform(this);
815       if (mqe->value()) {
816         append_string(": "); // verified
817         mqe->value()->perform(this);
818       }
819       append_string(")");
820     }
821   }
822
823   void Inspect::operator()(At_Root_Query_Ptr ae)
824   {
825     append_string("(");
826     ae->feature()->perform(this);
827     if (ae->value()) {
828       append_colon_separator();
829       ae->value()->perform(this);
830     }
831     append_string(")");
832   }
833
834   void Inspect::operator()(Null_Ptr n)
835   {
836     // output the final token
837     append_token("null", n);
838   }
839
840   // parameters and arguments
841   void Inspect::operator()(Parameter_Ptr p)
842   {
843     append_token(p->name(), p);
844     if (p->default_value()) {
845       append_colon_separator();
846       p->default_value()->perform(this);
847     }
848     else if (p->is_rest_parameter()) {
849       append_string("...");
850     }
851   }
852
853   void Inspect::operator()(Parameters_Ptr p)
854   {
855     append_string("(");
856     if (!p->empty()) {
857       (*p)[0]->perform(this);
858       for (size_t i = 1, L = p->length(); i < L; ++i) {
859         append_comma_separator();
860         (*p)[i]->perform(this);
861       }
862     }
863     append_string(")");
864   }
865
866   void Inspect::operator()(Argument_Ptr a)
867   {
868     if (!a->name().empty()) {
869       append_token(a->name(), a);
870       append_colon_separator();
871     }
872     if (!a->value()) return;
873     // Special case: argument nulls can be ignored
874     if (a->value()->concrete_type() == Expression::NULL_VAL) {
875       return;
876     }
877     if (a->value()->concrete_type() == Expression::STRING) {
878       String_Constant_Ptr s = Cast<String_Constant>(a->value());
879       if (s) s->perform(this);
880     } else {
881       a->value()->perform(this);
882     }
883     if (a->is_rest_argument()) {
884       append_string("...");
885     }
886   }
887
888   void Inspect::operator()(Arguments_Ptr a)
889   {
890     append_string("(");
891     if (!a->empty()) {
892       (*a)[0]->perform(this);
893       for (size_t i = 1, L = a->length(); i < L; ++i) {
894         append_string(", "); // verified
895         // Sass Bug? append_comma_separator();
896         (*a)[i]->perform(this);
897       }
898     }
899     append_string(")");
900   }
901
902   void Inspect::operator()(Selector_Schema_Ptr s)
903   {
904     s->contents()->perform(this);
905   }
906
907   void Inspect::operator()(Parent_Selector_Ptr p)
908   {
909     if (p->is_real_parent_ref()) append_string("&");
910   }
911
912   void Inspect::operator()(Placeholder_Selector_Ptr s)
913   {
914     append_token(s->name(), s);
915     if (s->has_line_break()) append_optional_linefeed();
916     if (s->has_line_break()) append_indentation();
917
918   }
919
920   void Inspect::operator()(Element_Selector_Ptr s)
921   {
922     append_token(s->ns_name(), s);
923   }
924
925   void Inspect::operator()(Class_Selector_Ptr s)
926   {
927     append_token(s->ns_name(), s);
928     if (s->has_line_break()) append_optional_linefeed();
929     if (s->has_line_break()) append_indentation();
930   }
931
932   void Inspect::operator()(Id_Selector_Ptr s)
933   {
934     append_token(s->ns_name(), s);
935     if (s->has_line_break()) append_optional_linefeed();
936     if (s->has_line_break()) append_indentation();
937   }
938
939   void Inspect::operator()(Attribute_Selector_Ptr s)
940   {
941     append_string("[");
942     add_open_mapping(s);
943     append_token(s->ns_name(), s);
944     if (!s->matcher().empty()) {
945       append_string(s->matcher());
946       if (s->value() && *s->value()) {
947         s->value()->perform(this);
948       }
949     }
950     add_close_mapping(s);
951     append_string("]");
952   }
953
954   void Inspect::operator()(Pseudo_Selector_Ptr s)
955   {
956     append_token(s->ns_name(), s);
957     if (s->expression()) {
958       append_string("(");
959       s->expression()->perform(this);
960       append_string(")");
961     }
962   }
963
964   void Inspect::operator()(Wrapped_Selector_Ptr s)
965   {
966     bool was = in_wrapped;
967     in_wrapped = true;
968     append_token(s->name(), s);
969     append_string("(");
970     bool was_comma_array = in_comma_array;
971     in_comma_array = false;
972     s->selector()->perform(this);
973     in_comma_array = was_comma_array;
974     append_string(")");
975     in_wrapped = was;
976   }
977
978   void Inspect::operator()(Compound_Selector_Ptr s)
979   {
980     for (size_t i = 0, L = s->length(); i < L; ++i) {
981       (*s)[i]->perform(this);
982     }
983     if (s->has_line_break()) {
984       if (output_style() != COMPACT) {
985         append_optional_linefeed();
986       }
987     }
988   }
989
990   void Inspect::operator()(Complex_Selector_Ptr c)
991   {
992     Compound_Selector_Obj      head = c->head();
993     Complex_Selector_Obj            tail = c->tail();
994     Complex_Selector::Combinator comb = c->combinator();
995
996     if (comb == Complex_Selector::ANCESTOR_OF && (!head || head->empty())) {
997       if (tail) tail->perform(this);
998       return;
999     }
1000
1001     if (c->has_line_feed()) {
1002       if (!(c->has_parent_ref())) {
1003         append_optional_linefeed();
1004         append_indentation();
1005       }
1006     }
1007
1008     if (head && head->length() != 0) head->perform(this);
1009     bool is_empty = !head || head->length() == 0 || head->is_empty_reference();
1010     bool is_tail = head && !head->is_empty_reference() && tail;
1011     if (output_style() == COMPRESSED && comb != Complex_Selector::ANCESTOR_OF) scheduled_space = 0;
1012
1013     switch (comb) {
1014       case Complex_Selector::ANCESTOR_OF:
1015         if (is_tail) append_mandatory_space();
1016       break;
1017       case Complex_Selector::PARENT_OF:
1018         append_optional_space();
1019         append_string(">");
1020         append_optional_space();
1021       break;
1022       case Complex_Selector::ADJACENT_TO:
1023         append_optional_space();
1024         append_string("+");
1025         append_optional_space();
1026       break;
1027       case Complex_Selector::REFERENCE:
1028         append_mandatory_space();
1029         append_string("/");
1030         c->reference()->perform(this);
1031         append_string("/");
1032         append_mandatory_space();
1033       break;
1034       case Complex_Selector::PRECEDES:
1035         if (is_empty) append_optional_space();
1036         else append_mandatory_space();
1037         append_string("~");
1038         if (tail) append_mandatory_space();
1039         else append_optional_space();
1040       break;
1041     }
1042     if (tail && comb != Complex_Selector::ANCESTOR_OF) {
1043       if (c->has_line_break()) append_optional_linefeed();
1044     }
1045     if (tail) tail->perform(this);
1046     if (!tail && c->has_line_break()) {
1047       if (output_style() == COMPACT) {
1048         append_mandatory_space();
1049       }
1050     }
1051   }
1052
1053   void Inspect::operator()(Selector_List_Ptr g)
1054   {
1055
1056     if (g->empty()) {
1057       if (output_style() == TO_SASS) {
1058         append_token("()", g);
1059       }
1060       return;
1061     }
1062
1063
1064     bool was_comma_array = in_comma_array;
1065     // probably ruby sass eqivalent of element_needs_parens
1066     if (output_style() == TO_SASS && g->length() == 1 &&
1067       (!Cast<List>((*g)[0]) &&
1068        !Cast<Selector_List>((*g)[0]))) {
1069       append_string("(");
1070     }
1071     else if (!in_declaration && in_comma_array) {
1072       append_string("(");
1073     }
1074
1075     if (in_declaration) in_comma_array = true;
1076
1077     for (size_t i = 0, L = g->length(); i < L; ++i) {
1078       if (!in_wrapped && i == 0) append_indentation();
1079       if ((*g)[i] == 0) continue;
1080       schedule_mapping(g->at(i)->last());
1081       // add_open_mapping((*g)[i]->last());
1082       (*g)[i]->perform(this);
1083       // add_close_mapping((*g)[i]->last());
1084       if (i < L - 1) {
1085         scheduled_space = 0;
1086         append_comma_separator();
1087       }
1088     }
1089
1090     in_comma_array = was_comma_array;
1091     // probably ruby sass eqivalent of element_needs_parens
1092     if (output_style() == TO_SASS && g->length() == 1 &&
1093       (!Cast<List>((*g)[0]) &&
1094        !Cast<Selector_List>((*g)[0]))) {
1095       append_string(",)");
1096     }
1097     else if (!in_declaration && in_comma_array) {
1098       append_string(")");
1099     }
1100
1101   }
1102
1103   void Inspect::fallback_impl(AST_Node_Ptr n)
1104   {
1105   }
1106
1107 }