Current Version: 1.0.10
Project Name: csspp
compiler.cpp
Go to the documentation of this file.
1 // CSS Preprocessor
2 // Copyright (C) 2015-2016 Made to Order Software Corp.
3 //
4 // This program is free software; you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation; either version 2 of the License, or
7 // (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with this program; if not, write to the Free Software
16 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
17 
27 #include "csspp/compiler.h"
28 
29 #include "csspp/exceptions.h"
30 #include "csspp/nth_child.h"
31 #include "csspp/parser.h"
32 
33 #include <cmath>
34 #include <fstream>
35 #include <iostream>
36 
37 #include <unistd.h>
38 
39 namespace csspp
40 {
41 
42 namespace
43 {
44 
48 
49 } // no name namespace
50 
52 {
53 public:
55  : f_state(state)
56  {
58  }
59 
61  {
63  }
64 
65 private:
67 };
68 
70 {
71 public:
73  : f_state(state)
74  , f_state_copy(state)
75  {
76  }
77 
79  {
81  }
82 
83 private:
86 };
87 
89 {
90  f_root = root;
91  f_parents.clear();
92 }
93 
95 {
96  return f_root;
97 }
98 
100 {
101  f_paths.clear();
102 }
103 
104 void compiler::compiler_state_t::add_path(std::string const & path)
105 {
106  f_paths.push_back(path);
107 }
108 
110 {
111  // replace out paths with another set
112  f_paths = state.f_paths;
113 }
114 
116 {
117  f_parents.push_back(parent);
118 }
119 
121 {
122  f_parents.pop_back();
123 }
124 
126 {
127  return f_parents.empty();
128 }
129 
131 {
132  if(f_parents.size() < 2)
133  {
134  throw csspp_exception_logic("compiler.cpp:compiler::compiler_state_t::get_current_parent(): no previous parents available."); // LCOV_EXCL_LINE
135  }
136 
137  // return the parent before last
138  return f_parents[f_parents.size() - 2];
139 }
140 
142 {
143  // the name is used in a map to quickly save/retrieve variables
144  // so we save the variable / mixin definitions in a list saved
145  // along the value of this variable
146  std::string const variable_name(name->get_string());
147  node::pointer_t v(new node(node_type_t::LIST, value->get_position()));
148  v->add_child(name);
149  v->add_child(value);
150 
151  if(!global)
152  {
153  size_t pos(f_parents.size());
154  while(pos > 0)
155  {
156  --pos;
157  node::pointer_t s(f_parents[pos]);
159  && s->get_boolean())
160  {
161  s->set_variable(variable_name, v);
162  return;
163  }
164  }
165  }
166 
167  // if nothing else make it a global variable
168  f_root->set_variable(variable_name, v);
169 }
170 
171 node::pointer_t compiler::compiler_state_t::get_variable(std::string const & variable_name, bool global_only) const
172 {
173  if(!global_only)
174  {
175  size_t pos(f_parents.size());
176  while(pos > 0)
177  {
178  --pos;
179  node::pointer_t s(f_parents[pos]);
180  switch(s->get_type())
181  {
183  if(s->get_boolean())
184  {
185  node::pointer_t value(s->get_variable(variable_name));
186  if(value)
187  {
188  return value;
189  }
190  }
191  break;
192 
193  default:
194  break;
195 
196  }
197  }
198  }
199 
200  return f_root->get_variable(variable_name);
201 }
202 
204 {
205  // search the parents for the node where the function will be set
206  node::pointer_t value(get_variable(func->get_string()));
207  if(!value)
208  {
209  // no function (or variables) with that name found, return the
210  // input function as is
211  return func;
212  }
213 
214  // internal validity check
215  if(!value->is(node_type_t::LIST)
216  || value->size() != 2)
217  {
218  throw csspp_exception_logic("compiler.cpp:compiler::compiler_state_t::execute_user_function(): all functions must be two sub-values in a LIST, the first item being the variable."); // LCOV_EXCL_LINE
219  }
220 
221  node::pointer_t var(value->get_child(0));
222  node::pointer_t val(value->get_child(1));
223 
224  if(!var->is(node_type_t::FUNCTION))
225  //&& !var->is(node_type_t::VARIABLE_FUNCTION)) -- TBD
226  {
227  // found something, but that is not a @mixin function...
228  return func;
229  }
230 
231  // the function was already argified in expression::unary()
232  //parser::argify(func);
233 
234  // define value of each argument
235  node::pointer_t root(new node(node_type_t::LIST, val->get_position()));
236  if(!val->is(node_type_t::OPEN_CURLYBRACKET))
237  {
238  throw csspp_exception_logic("compiler.cpp:compiler::compiler_state_t::execute_user_function(): @mixin function is not defined inside a {}-block."); // LCOV_EXCL_LINE
239  }
240 
241  // make sure we get a copy of the current global variables
242  root->copy_variable(f_root);
243 
244  size_t max_val_children(val->size());
245  for(size_t j(0); j < max_val_children; ++j)
246  {
247  root->add_child(val->get_child(j)->clone());
248  }
249 
250  size_t const max_children(var->size());
251  size_t const max_input(func->size());
252  for(size_t i(0); i < max_children; ++i)
253  {
254  node::pointer_t arg(var->get_child(i));
255  if(!arg->is(node_type_t::ARG))
256  {
257  // function declaration is invalid!
258  throw csspp_exception_logic("compiler.cpp:compiler::compiler_state_t::execute_user_function(): FUNCTION children are not all ARG nodes."); // LCOV_EXCL_LINE
259  }
260  if(arg->empty())
261  {
262  throw csspp_exception_logic("compiler.cpp:compiler::compiler_state_t::execute_user_function(): ARG is empty."); // LCOV_EXCL_LINE
263  }
264  node::pointer_t arg_name(arg->get_child(0));
265  if(!arg_name->is(node_type_t::VARIABLE))
266  {
267  // this was already erred when we created the variable
268  //error::instance() << val->get_position()
269  // << "function declaration requires all parameters to be variables, "
270  // << arg_name->get_type()
271  // << " is not acceptable."
272  // << error_mode_t::ERROR_ERROR;
273  return func;
274  }
275  if(i >= max_input)
276  {
277  // user did not specify this value, check whether we have
278  // an optional value
279  if(arg->size() > 1)
280  {
281  // use default value
282  node::pointer_t default_param(arg->clone());
283  default_param->remove_child(0); // remove the variable name
284  if(default_param->size() == 1)
285  {
286  default_param = default_param->get_child(0);
287  }
288  else
289  {
290  node::pointer_t value_list(new node(node_type_t::LIST, arg->get_position()));
291  value_list->take_over_children_of(default_param);
292  default_param = value_list;
293  }
294  node::pointer_t param_value(new node(node_type_t::LIST, arg->get_position()));
295  param_value->add_child(arg_name);
296  param_value->add_child(default_param);
297  root->set_variable(arg_name->get_string(), param_value);
298  }
299  else
300  {
301  // value is missing
302  error::instance() << val->get_position()
303  << "missing function variable named \""
304  << arg_name->get_string()
305  << "\" when calling "
306  << func->get_string()
307  << "();."
309  return func;
310  }
311  }
312  else
313  {
314  // copy user provided value
315  node::pointer_t user_param(func->get_child(i));
316  if(!user_param->is(node_type_t::ARG))
317  {
318  throw csspp_exception_logic("compiler.cpp:compiler::replace_variable(): user parameter is not an ARG."); // LCOV_EXCL_LINE
319  }
320  if(user_param->size() == 1)
321  {
322  user_param = user_param->get_child(0);
323  }
324  else
325  {
326  // is that really correct?
327  // we may need a component_value instead...
328  node::pointer_t list(new node(node_type_t::LIST, user_param->get_position()));
329  list->take_over_children_of(user_param);
330  user_param = list;
331  }
332  node::pointer_t param_value(new node(node_type_t::LIST, user_param->get_position()));
333  param_value->add_child(arg_name);
334  param_value->add_child(user_param->clone());
335  root->set_variable(arg_name->get_string(), param_value);
336  }
337  }
338 
339  compiler c(true);
340  c.set_root(root);
341  c.f_state.f_paths = f_paths;
342  c.f_state.f_empty_on_undefined_variable = f_empty_on_undefined_variable;
343  // use 'true' here otherwise it would reload the header/footer each time!
344  c.compile(true);
345 
346  return c.get_result();
347 }
348 
350 {
351  f_empty_on_undefined_variable = empty_on_undefined_variable;
352 }
353 
355 {
356  return f_empty_on_undefined_variable;
357 }
358 
359 std::string compiler::compiler_state_t::find_file(std::string const & script_name)
360 {
361  // no name?!
362  if(script_name.empty())
363  {
364  return std::string();
365  }
366 
367  // an absolute path?
368  if(script_name[0] == '/')
369  {
370  if(access(script_name.c_str(), R_OK) == 0)
371  {
372  return script_name;
373  }
374  // absolute does not mean we can find the file
375  return std::string();
376  }
377 
378  // no paths at all???
379  if(f_paths.empty())
380  {
381  // should this be "." here instead of the default?
382  f_paths.push_back("/usr/lib/csspp/scripts");
383  }
384 
385  // check with each path and return the first match
386  for(auto it : f_paths)
387  {
388  std::string const name(it == "" ? script_name : it + "/" + script_name);
389  if(access(name.c_str(), R_OK) == 0)
390  {
391  return name;
392  }
393  }
394 
395  // in case we cannot find a file
396  return std::string();
397 }
398 
399 compiler::compiler(bool validating)
400  : f_compiler_validating(validating)
401 {
402  f_state.add_path("/usr/lib/csspp/scripts");
403 }
404 
406 {
407  f_state.set_root(root);
408 }
409 
411 {
412  return f_state.get_root();
413 }
414 
416 {
417  return f_return_result;
418 }
419 
421 {
422  // make sure we're ready to setup the date and time
423  node::pointer_t root(get_root());
424  if(!root)
425  {
426  throw csspp_exception_logic("compiler.cpp: compiler::set_date_time_variables(): function called too soon, root not set yet.");
427  }
428 
429  // convert date/time in a string
430  struct tm t;
431  localtime_r(&now, &t);
432  char buf[20];
433  strftime(buf, sizeof(buf), "%m/%d/%Y%T", &t);
434 
435  // save the result in variables
436 
437  // usdate
438  csspp::node::pointer_t var(new csspp::node(csspp::node_type_t::VARIABLE, root->get_position()));
439  var->set_string("_csspp_usdate");
440  csspp::node::pointer_t arg(new csspp::node(csspp::node_type_t::STRING, root->get_position()));
441  arg->set_string(std::string(buf, 10));
442  f_state.set_variable(var, arg, true);
443 
444  // month
445  var.reset(new csspp::node(csspp::node_type_t::VARIABLE, root->get_position()));
446  var->set_string("_csspp_month");
447  arg.reset(new csspp::node(csspp::node_type_t::STRING, root->get_position()));
448  arg->set_string(std::string(buf, 2));
449  f_state.set_variable(var, arg, true);
450 
451  // day
452  var.reset(new csspp::node(csspp::node_type_t::VARIABLE, root->get_position()));
453  var->set_string("_csspp_day");
454  arg.reset(new csspp::node(csspp::node_type_t::STRING, root->get_position()));
455  arg->set_string(std::string(buf + 3, 2));
456  f_state.set_variable(var, arg, true);
457 
458  // year
459  var.reset(new csspp::node(csspp::node_type_t::VARIABLE, root->get_position()));
460  var->set_string("_csspp_year");
461  arg.reset(new csspp::node(csspp::node_type_t::STRING, root->get_position()));
462  arg->set_string(std::string(buf + 6, 4));
463  f_state.set_variable(var, arg, true);
464 
465  // time
466  var.reset(new csspp::node(csspp::node_type_t::VARIABLE, root->get_position()));
467  var->set_string("_csspp_time");
468  arg.reset(new csspp::node(csspp::node_type_t::STRING, root->get_position()));
469  arg->set_string(std::string(buf + 10, 8));
470  f_state.set_variable(var, arg, true);
471 
472  // hour
473  var.reset(new csspp::node(csspp::node_type_t::VARIABLE, root->get_position()));
474  var->set_string("_csspp_hour");
475  arg.reset(new csspp::node(csspp::node_type_t::STRING, root->get_position()));
476  arg->set_string(std::string(buf + 10, 2));
477  f_state.set_variable(var, arg, true);
478 
479  // minute
480  var.reset(new csspp::node(csspp::node_type_t::VARIABLE, root->get_position()));
481  var->set_string("_csspp_minute");
482  arg.reset(new csspp::node(csspp::node_type_t::STRING, root->get_position()));
483  arg->set_string(std::string(buf + 13, 2));
484  f_state.set_variable(var, arg, true);
485 
486  // second
487  var.reset(new csspp::node(csspp::node_type_t::VARIABLE, root->get_position()));
488  var->set_string("_csspp_second");
489  arg.reset(new csspp::node(csspp::node_type_t::STRING, root->get_position()));
490  arg->set_string(std::string(buf + 16, 2));
491  f_state.set_variable(var, arg, true);
492 }
493 
494 void compiler::set_empty_on_undefined_variable(bool empty_on_undefined_variable)
495 {
496  f_state.set_empty_on_undefined_variable(empty_on_undefined_variable);
497 }
498 
499 void compiler::set_no_logo(bool no_logo)
500 {
501  f_no_logo = no_logo;
502 }
503 
505 {
507 }
508 
509 void compiler::add_path(std::string const & path)
510 {
511  f_state.add_path(path);
512 }
513 
514 void compiler::compile(bool bare)
515 {
516  if(!f_state.get_root())
517  {
518  throw csspp_exception_logic("compiler.cpp: compiler::compile(): compile() called without a root node pointer, call set_root() first."); // LCOV_EXCL_LINE
519  }
520 
521  // before we compile anything we want to transform all the variables
522  // with their verbatim contents; otherwise the compiler would be way
523  // more complex for nothing...
524  //
525  // Also for the variables to work properly, we immediately handle
526  // the @import and @mixins since both may define additional variables.
527  // Similarly, we handle control flow (@if, @else, @include, ...)
528  //
529 //std::cerr << "************* COMPILING:\n" << *f_state.get_root() << "-----------------\n";
530  if(!bare)
531  {
533  }
534 
536  if(!f_state.empty_parents())
537  {
538  throw csspp_exception_logic("compiler.cpp: the stack of parents must always be empty before mark_selectors() returns."); // LCOV_EXCL_LINE
539  }
540 
542  if(!f_state.empty_parents())
543  {
544  throw csspp_exception_logic("compiler.cpp: the stack of parents must always be empty before replace_variables() returns."); // LCOV_EXCL_LINE
545  }
546 
548  if(!f_state.empty_parents())
549  {
550  throw csspp_exception_logic("compiler.cpp: the stack of parents must always be empty before compile() returns"); // LCOV_EXCL_LINE
551  }
552 
554  if(!f_state.empty_parents())
555  {
556  throw csspp_exception_logic("compiler.cpp: the stack of parents must always be empty before remove_empty_rules() returns"); // LCOV_EXCL_LINE
557  }
558 
560  if(!f_state.empty_parents())
561  {
562  throw csspp_exception_logic("compiler.cpp: the stack of parents must always be empty before expand_nested_components() returns"); // LCOV_EXCL_LINE
563  }
564 }
565 
567 {
568  // the header is @import "scripts/init.scss"
569  //
570  {
571  position pos("header.scss");
572  node::pointer_t header(new node(node_type_t::AT_KEYWORD, pos));
573  header->set_string("import");
574  node::pointer_t header_string(new node(node_type_t::STRING, pos));
575  header_string->set_string("system/init.scss");
576  header->add_child(header_string);
577  f_state.get_root()->insert_child(0, header);
578  }
579 
580  // the footer is @import "script/close.scss"
581  //
582  {
583  position pos("footer.scss");
584  node::pointer_t footer(new node(node_type_t::AT_KEYWORD, pos));
585  footer->set_string("import");
586  node::pointer_t footer_string(new node(node_type_t::STRING, pos));
587  footer_string->set_string("system/close.scss");
588  footer->add_child(footer_string);
589  f_state.get_root()->add_child(footer);
590  }
591 
592  // the close.scss checks this flag
593  //
594  {
595  position pos("close.scss");
596  node::pointer_t no_logo(new node(node_type_t::VARIABLE, pos));
597  no_logo->set_string("_csspp_no_logo");
598  node::pointer_t value(new node(node_type_t::BOOLEAN, pos));
599  value->set_boolean(f_no_logo);
600  f_state.set_variable(no_logo, value, true);
601  }
602 }
603 
605 {
606  safe_parents_t safe_parents(f_state, n);
607 
608  switch(n->get_type())
609  {
610  case node_type_t::LIST:
611  // transparent item, just compile all the children
612  {
613  size_t idx(0);
614  while(idx < n->size() && !f_return_result)
615  {
616  node::pointer_t child(n->get_child(idx));
617  compile(child);
618 
619  // the child may replace itself with something else
620  // in which case we do not want the ++idx
621  if(idx < n->size()
622  && n->get_child(idx) == child)
623  {
624  ++idx;
625  }
626  }
627  // TODO: remove LIST if it now is empty or has 1 item
628  }
629  break;
630 
633  break;
634 
637  break;
638 
640  // passthrough tokens
641  break;
642 
643  default:
644  {
645  std::stringstream ss;
646  ss << "unexpected token (type: " << n->get_type() << ") in compile().";
647  throw csspp_exception_unexpected_token(ss.str());
648  }
649 
650  }
651 }
652 
654 {
655  // already compiled?
656  if(n->is(node_type_t::DECLARATION))
657  {
658  // This is really double ugly, I'll have to look into getting
659  // my loops straighten up because having to test such in
660  // various places is bad!
661  //
662  // We may want to find a better way to skip these entries...
663  // we replace a COMPONENT_VALUE with a DECLARATION and return
664  // and the loops think that the COMPONENT_VALUE was "replaced"
665  // by new code that needs to be compiled; only we replaced the
666  // entry with already compiled data! The best way may be to have
667  // a state with a position that we pass around...
668  return;
669  }
670 
671  // there are quite a few cases to handle here:
672  //
673  // $variable ':' '{' ... '}'
674  // <field-prefix> ':' '{' ... '}'
675  // <selector-list> '{' ... '}'
676  // $variable ':' ...
677  // <field-name> ':' ...
678  //
679 
680  if(n->empty())
681  {
682  // we have a problem, we should already have had an error
683  // somewhere?
684  return; // LCOV_EXCL_LINE
685  }
686 
687  if(n->get_child(0)->is(node_type_t::COMMENT))
688  {
689  // XXX: verify that this is the right location to chek this
690  // special case, we may want to do it only in the loop
691  // that also accepts plain comments instead of here
692  // which is a function that can get called from deep
693  // inside...
694 
695  // get parent of n, remove n from there, replace it by
696  // the comment
698  size_t pos(parent->child_position(n));
699  parent->remove_child(pos);
700  parent->insert_child(pos, n->get_child(0));
701  return;
702  }
703 
704  // was that COMPONENT_VALUE already compiled?
705  if(n->get_child(0)->is(node_type_t::ARG))
706  {
707  // the following fix prevents this from happening so at this time
708  // I mark this as a problem; we could just return otherwise
709  // (like the case the list is a declaration)
710  throw csspp_exception_logic("compiler.cpp: found an ARG as the first child of COMPONENT_VALUE, compile_component_value() called twice on the same object?"); // LCOV_EXCL_LINE
711  }
712 
713  size_t const max_children(n->size());
714  size_t count_cv(0);
715  for(size_t idx(0); idx < max_children; ++idx)
716  {
717  node::pointer_t child(n->get_child(idx));
718  if(child->is(node_type_t::COMPONENT_VALUE))
719  {
720  ++count_cv;
721  }
722  }
723  if(count_cv == max_children)
724  {
725  // this happens when we add elements from a sub {}-block
726  // for example, a verbatim:
727  //
728  // @if (true) { foo { a: b; } blah { c: d; } }
729  //
731  size_t pos(parent->child_position(n));
732  parent->remove_child(pos);
733  for(size_t idx(0); idx < max_children; ++idx, ++pos)
734  {
735  parent->insert_child(pos, n->get_child(idx));
736  }
737  // the caller will call us again with the new list of
738  // COMPONENT_VALUE nodes as expected
739  return;
740  }
741  else if(count_cv != 0)
742  {
743  std::cerr << "Invalid node:\n" << *n; // LCOV_EXCL_LINE
744  throw csspp_exception_logic("compiler.cpp: found a COMPONENT_VALUE with a mix of children."); // LCOV_EXCL_LINE
745  }
746 
747  // this may be only temporary (until I fix the parser) but at this
748  // point we may get an @-keyword inside a COMPONENT_VALUE
749  if(n->get_child(0)->is(node_type_t::AT_KEYWORD))
750  {
751  {
752  safe_parents_t safe_parents(f_state, n->get_child(0));
753  compile_at_keyword(n->get_child(0));
754  }
755  if(n->empty())
756  {
757  // the @-keyword was removed and now the COMPONENT_VALUE is
758  // empty so we can just get rid of it
759  f_state.get_previous_parent()->remove_child(n);
760  }
761  return;
762  }
763 
764  // $variable ':' '{' ... '}'
765  if(parser::is_variable_set(n, true))
766  {
767  throw csspp_exception_logic("compiler.cpp: somehow a variable definition was found while compiling (1)."); // LCOV_EXCL_LINE
768  }
769 
770  // <field-prefix> ':' '{' ... '}'
772  {
774  return;
775  }
776 
777  // <selector-list> '{' ... '}'
778  if(n->get_last_child()->is(node_type_t::OPEN_CURLYBRACKET))
779  {
780  // this is a selector list followed by a block of
781  // definitions and sub-blocks
783  return;
784  }
785 
786  // $variable ':' ... ';'
787  if(parser::is_variable_set(n, false))
788  {
789  throw csspp_exception_logic("compiler.cpp: somehow a variable definition was found while compiling (1)."); // LCOV_EXCL_LINE
790  }
791 
792  // <field-name> ':' ...
794 }
795 
797 {
798  // so here we have a list of selectors, that means we can verify
799  // that said list is valid (i.e. binary operators are used properly,
800  // only valid operators were used, etc.)
801 
802  // any selectors?
803  if(n->size() <= 1)
804  {
805  error::instance() << n->get_position()
806  << "a qualified rule without selectors is not valid."
808  return;
809  }
810 
811  // compile the selectors using a node parser
812  // \ref selectors_rules#grammar
813  if(!parse_selector(n))
814  {
815  // an error occurred, forget this entry and move on
816  return;
817  }
818 
819  // compile the block of contents
820  node::pointer_t brackets(n->get_last_child());
821  if(brackets->empty())
822  {
823  // an empty block is perfectly valid, it means the whole rule
824  // "exists" but is really useless; in SCSS it could be useful
825  // for @extends and %placeholder to have such empty rules
826  //error::instance() << n->get_position()
827  // << "the {}-block of a qualified rule is missing."
828  // << error_mode_t::ERROR_ERROR;
829  return;
830  }
831 
832  safe_parents_t safe_parents(f_state, brackets);
833 
834  for(size_t b(0); b < brackets->size();)
835  {
836  node::pointer_t child(brackets->get_child(b));
837  safe_parents_t safe_list_parents(f_state, child);
838  if(child->is(node_type_t::LIST))
839  {
840  for(size_t idx(0); idx < child->size();)
841  {
842  node::pointer_t item(child->get_child(idx));
843  safe_parents_t safe_sub_parents(f_state, item);
845  if(idx < child->size()
846  && item == child->get_child(idx))
847  {
848  ++idx;
849  }
850  }
851  }
852  else if(child->is(node_type_t::COMPONENT_VALUE))
853  {
855  }
856  if(b < brackets->size()
857  && child == brackets->get_child(b))
858  {
859  ++b;
860  }
861  }
862 }
863 
865 {
866  // already compiled?
867  if(n->is(node_type_t::DECLARATION))
868  {
869  // We may want to find a better way to skip these entries...
870  // we replace a COMPONENT_VALUE with a DECLARATION and return
871  // and the loops think that the COMPONENT_VALUE was "replaced"
872  // by new code that needs to be compiled; only we replaced the
873  // entry with already compiled data! The best way may be to have
874  // a state with a position that we pass around...
875  return;
876  }
877  if(n->size() < 2)
878  {
879  // A "declaration" without the ':' and values reaches here:
880  // font-style; italic; (notice the ';' instead of ':')
881  error::instance() << n->get_position()
882  << "somehow a declaration list is missing a field name or ':'."
884  return;
885  }
886 
887  // first make sure we have a declaration
888  // (i.e. IDENTIFIER WHITESPACE ':' ...)
889  //
890  node::pointer_t identifier(n->get_child(0));
891  if(!identifier->is(node_type_t::IDENTIFIER))
892  {
893  if((identifier->is(node_type_t::MULTIPLY)
894  || identifier->is(node_type_t::PERIOD))
895  && n->get_child(1)->is(node_type_t::IDENTIFIER))
896  {
897  // get rid of the asterisk or period
898  // This was an IE 7 and earlier web browser trick to allow
899  // various CSS entries only for IE...
900  n->remove_child(0);
901  identifier = n->get_child(0);
902  error::instance() << identifier->get_position()
903  << "the '[*|.|!]<field-name>: ...' syntax is not allowed in csspp, we offer other ways to control field names per browser and do not allow such tricks."
905  }
906  else if(identifier->is(node_type_t::HASH)
907  || identifier->is(node_type_t::EXCLAMATION))
908  {
909  // the !<id> and #<id> will be converted to a declaration so
910  // we do not need to do anything more about it
911  //
912  // This was an IE 7 and earlier web browser trick to allow
913  // various CSS entries only for IE...
914  error::instance() << identifier->get_position()
915  << "the '#<field-name>: ...' syntax is not allowed in csspp, we offer other ways to control field names per browser and do not allow such tricks."
917  }
918  else
919  {
920  error::instance() << identifier->get_position()
921  << "expected an identifier to start a declaration value; got a: " << identifier->get_type() << " instead."
923  return;
924  }
925  }
926 
927  // the WHITESPACE is optional, if present, remove it
928  node::pointer_t next(n->get_child(1));
929  if(next->is(node_type_t::WHITESPACE))
930  {
931  n->remove_child(1);
932  next = n->get_child(1);
933  }
934 
935  // now we must have a COLON, also remove that COLON
936  if(!next->is(node_type_t::COLON))
937  {
938  error::instance() << n->get_position()
939  << "expected a ':' after the identifier of this declaration value; got a: " << n->get_type() << " instead."
941  return;
942  }
943  n->remove_child(1);
944 
945  if(n->size() < 2)
946  {
947  error::instance() << n->get_position()
948  << "somehow a declaration list is missing fields, this happens if you used an invalid variable."
950  return;
951  }
952 
953  // no need to keep the next whitespace if there is one,
954  // plus we often do not expect such at the start of a
955  // list like we are about to generate.
956  if(n->get_child(1)->is(node_type_t::WHITESPACE))
957  {
958  n->remove_child(1);
959  }
960 
961  if(n->size() < 2)
962  {
963  error::instance() << n->get_position()
964  << "somehow a declaration list is missing fields, this happens if you used an invalid variable."
966  return;
967  }
968 
969  // create a declaration to replace the identifier
970  node::pointer_t declaration(new node(node_type_t::DECLARATION, n->get_position()));
971  declaration->set_string(identifier->get_string());
972 
973  // copy the following children as the children of the declaration
974  // (i.e. identifier is element 0, so we copy elements 1 to n)
975  size_t const max_children(n->size());
976  for(size_t i(1); i < max_children; ++i)
977  {
978  // since we are removing the children, we always seemingly
979  // copy child 1...
980  declaration->add_child(n->get_child(1));
981  n->remove_child(1);
982  }
983 
984  // now replace that identifier by its declaration in the parent
986  {
987  // replace the COMPONENT_VALUE instead of the identifier
988  // (this happens when a component value has multiple entries)
989  f_state.get_previous_parent()->replace_child(n, declaration);
990  }
991  else
992  {
993  throw csspp_exception_logic("compiler.cpp: got a node which was not of type COMPONENT_VALUE to replace with the DECLARATION."); // LCOV_EXCL_LINE
994  //n->replace_child(identifier, declaration);
995  }
996  // now the declaration is part of the stack of parents
997  safe_parents_t safe_declaration_parent(f_state, declaration);
998 
999  // apply the expression parser on the parameters
1000  // TODO: test that stuff a lot better, right now it does not look correct...
1001  bool compile_declaration_items(!declaration->empty() && !declaration->get_child(0)->is(node_type_t::OPEN_CURLYBRACKET));
1002  if(compile_declaration_items)
1003  {
1004  for(size_t i(0); i < declaration->size(); ++i)
1005  {
1006  node::pointer_t item(declaration->get_child(i));
1007  switch(item->get_type())
1008  {
1009  //case node_type_t::OPEN_CURLYBRACKET:
1010  case node_type_t::LIST:
1012  // This means we have a cascade, a field declaration which has
1013  // sub-fields (font-<name>, border-<name>, etc.)
1014  compile_declaration_items = false;
1015  break;
1016 
1017  default:
1018  break;
1019 
1020  }
1021  }
1022  }
1023 
1024  if(compile_declaration_items
1025  && !declaration->empty())
1026  {
1027  node::pointer_t child(declaration->get_child(0));
1028 
1029  bool const ignore(
1030  (child->is(node_type_t::FUNCTION)
1031  && (child->get_string() == "alpha" || child->get_string() == "chroma" || child->get_string() == "gray" || child->get_string() == "opacity")
1032  && (declaration->get_string() == "filter" || declaration->get_string() == "-filter"))
1033  ||
1034  (child->is(node_type_t::IDENTIFIER)
1035  && child->get_string() == "progid"
1036  && (declaration->get_string() == "filter" || declaration->get_string() == "-filter"))
1037  );
1038 
1039  if(ignore)
1040  {
1041  // Note: the progid does not mean that the function used is
1042  // alpha(), but its fairly likely
1043  //
1044  // we probably should remove such declarations, but if we want
1045  // to have functions that output such things, it is important to
1046  // support this horrible field...
1047  //
1048  // alpha() was for IE8 and earlier, now opacity works
1049  error::instance() << child->get_position()
1050  << "the alpha(), chroma() and similar functions of the filter field are Internet Explorer specific extensions which are not supported across browsers."
1052  }
1053 
1054  if(!ignore)
1055  {
1056  // ':' IDENTIFIER
1057 
1058  node::pointer_t declaration_name(new node(node_type_t::STRING, declaration->get_position()));
1059  declaration_name->set_string(declaration->get_string());
1060 
1061  // check the identifier, if "has-font-metrics" is true, then
1062  // slashes are viewed as the font metrics separator
1063  //
1064  set_validation_script("validation/has-font-metrics");
1065  add_validation_variable("field_name", declaration_name);
1066  bool const divide_font_metrics(run_validation(true));
1067 
1068  // if slash-separator returns true then slash (if present)
1069  // is a separator like a comma in a list of arguments
1070  set_validation_script("validation/slash-separator");
1071  add_validation_variable("field_name", declaration_name);
1072  bool const slash_separators(run_validation(true));
1073 
1074  parser::argify(declaration, slash_separators ? node_type_t::DIVIDE : node_type_t::COMMA);
1075  expression args_expr(declaration);
1076  args_expr.set_variable_handler(&f_state);
1077  args_expr.compile_args(divide_font_metrics);
1078  }
1079  }
1080 
1081  compile_declaration_values(declaration);
1082 }
1083 
1085 {
1086  // finally compile the parameters of the declaration
1087  for(size_t i(0); i < declaration->size(); ++i)
1088  {
1089  node::pointer_t item(declaration->get_child(i));
1090  safe_parents_t safe_parents(f_state, item);
1091  switch(item->get_type())
1092  {
1093  case node_type_t::LIST:
1094  // handle lists recursively
1096  break;
1097 
1099  // nested declarations, this {}-block includes sub-field names
1100  // (i.e. names that will be added this this declaration after a
1101  // dash (-) and with the name of the fields appearing here)
1102  for(size_t j(0); j < item->size();)
1103  {
1104  node::pointer_t component(item->get_child(j));
1105  safe_parents_t safe_grand_parents(f_state, component);
1106  if(component->is(node_type_t::LIST))
1107  {
1108  compile_declaration_values(component);
1109  }
1110  else if(component->is(node_type_t::COMPONENT_VALUE))
1111  {
1112  compile_component_value(component);
1113  }
1114  else if(component->is(node_type_t::DECLARATION))
1115  {
1116  // this was compiled, ignore
1117  }
1118  else
1119  {
1120  // it looks like I cannot get here anymore
1121  std::stringstream errmsg; // LCOV_EXCL_LINE
1122  errmsg << "compiler.cpp: found unexpected node type " // LCOV_EXCL_LINE
1123  << component->get_type() // LCOV_EXCL_LINE
1124  << ", expected a LIST."; // LCOV_EXCL_LINE
1125  throw csspp_exception_logic(errmsg.str()); // LCOV_EXCL_LINE
1126  }
1127  if(j < item->size()
1128  && component == item->get_child(j))
1129  {
1130  ++j;
1131  }
1132  }
1133  break;
1134 
1137  break;
1138 
1139  default:
1140  //error::instance() << n->get_position()
1141  // << "found a node of type " << item->get_type() << " in a declaration."
1142  // << error_mode_t::ERROR_ERROR;
1143  break;
1144 
1145  }
1146  }
1147 }
1148 
1150 {
1151  std::string const at(n->get_string());
1152 
1154  node::pointer_t expr(!n->empty() && !n->get_child(0)->is(node_type_t::OPEN_CURLYBRACKET) ? n->get_child(0) : node::pointer_t());
1155 
1156  if(at == "error")
1157  {
1158  parent->remove_child(n);
1159 
1160  error::instance() << n->get_position()
1161  << (expr ? expr->to_string(0) : std::string("@error reached"))
1163  return;
1164  }
1165 
1166  if(at == "warning")
1167  {
1168  parent->remove_child(n);
1169 
1170  error::instance() << n->get_position()
1171  << (expr ? expr->to_string(0) : std::string("@warning reached"))
1173  return;
1174  }
1175 
1176  if(at == "info"
1177  || at == "message")
1178  {
1179  parent->remove_child(n);
1180 
1181  error::instance() << n->get_position()
1182  << (expr ? expr->to_string(0) : std::string("@message reached"))
1184  return;
1185  }
1186 
1187  if(at == "debug")
1188  {
1189  parent->remove_child(n);
1190 
1191  error::instance() << n->get_position()
1192  << (expr ? expr->to_string(0) : std::string("@debug reached"))
1194  return;
1195  }
1196 
1197  if(at == "charset")
1198  {
1199  // we do not keep the @charset, we always default to UTF-8
1200  // on all sides (i.e. HTML, XHTML, XML, CSS, even JS...)
1201  // the assembler could re-introduce such, but again, not required
1202  parent->remove_child(n);
1203 
1204  if(n->size() != 1
1205  || !n->get_child(0)->is(node_type_t::STRING))
1206  {
1207  error::instance() << n->get_position()
1208  << "the @charset is expected to be followed by exactly one string."
1210  return;
1211  }
1212 
1213  std::string charset(n->get_child(0)->get_string());
1214  while(!charset.empty() && std::isspace(charset[0]))
1215  {
1216  charset.erase(charset.begin(), charset.begin() + 1);
1217  }
1218  while(!charset.empty() && std::isspace(charset.back()))
1219  {
1220  charset.erase(charset.end() - 1, charset.end());
1221  }
1222  for(auto & c : charset)
1223  {
1224  c = std::tolower(c);
1225  }
1226  if(charset != "utf-8")
1227  {
1228  error::instance() << n->get_position()
1229  << "we only support @charset \"utf-8\";, any other encoding is refused."
1231  return;
1232  }
1233  return;
1234  }
1235 
1236  // TODO: use a validation to determine the list of @-keyword that need
1237  // parsing as a qualified rule, a component value, or no parsing
1238  // and others are unsupported/unknown (i.e. generate an error)
1239  if(at == "document"
1240  || at == "media"
1241  || at == "supports")
1242  {
1243  if(!n->empty())
1244  {
1245  node::pointer_t last(n->get_last_child());
1246  if(last->is(node_type_t::OPEN_CURLYBRACKET))
1247  {
1248  if(n->size() > 1)
1249  {
1250  parser::argify(n);
1251  }
1252  safe_parents_t safe_list_parents(f_state, last);
1253  for(size_t idx(0); idx < last->size();)
1254  {
1255  node::pointer_t child(last->get_child(idx));
1256  safe_parents_t safe_parents(f_state, child);
1257  if(child->is(node_type_t::AT_KEYWORD))
1258  {
1259  // at this point @-keywords are added inside a
1260  // COMPONENT_VALUE so this does not happen...
1261  // if we change the parser at some point, this
1262  // may happen again so I keep it here
1263  compile_at_keyword(child); // LCOV_EXCL_LINE
1264  }
1265  else
1266  {
1267  compile_component_value(child);
1268  }
1269  if(idx < last->size()
1270  && child == last->get_child(idx))
1271  {
1272  ++idx;
1273  }
1274  }
1275  }
1276  }
1277  return;
1278  }
1279 
1280  if(at == "font-face"
1281  || at == "page")
1282  {
1283  if(!n->empty())
1284  {
1285  node::pointer_t last(n->get_last_child());
1286  if(last->is(node_type_t::OPEN_CURLYBRACKET))
1287  {
1288  if(last->size() > 0)
1289  {
1290  node::pointer_t list(last);
1291  if(last->get_child(0)->is(node_type_t::LIST))
1292  {
1293  list = last->get_child(0);
1294  }
1295  // we may be stacking the same node twice...
1296  safe_parents_t safe_grand_parents(f_state, last);
1297  safe_parents_t safe_parents(f_state, list);
1298  for(size_t idx(0); idx < list->size();)
1299  {
1300  node::pointer_t child(list->get_child(idx));
1301  // TODO: add support for other node types?
1302  if(child->is(node_type_t::COMPONENT_VALUE))
1303  {
1304  safe_parents_t safe_component_parents(f_state, child);
1305  compile_component_value(child);
1306  if(idx < list->size()
1307  && child == list->get_child(idx))
1308  {
1309  ++idx;
1310  }
1311  }
1312  else
1313  {
1314  ++idx;
1315  }
1316  }
1317  }
1318  }
1319  }
1320  return;
1321  }
1322 
1323  if(at == "return")
1324  {
1325  if(!expr)
1326  {
1327  error::instance() << n->get_position()
1328  << "@return must be followed by a valid expression."
1330  return;
1331  }
1332 
1333  // transform the @return <expr> in a one node result
1334  expression return_expr(n);
1335  return_expr.set_variable_handler(&f_state);
1336  f_return_result = return_expr.compile();
1337  if(!f_return_result)
1338  {
1339  // the expression was erroneous but we cannot return
1340  // without a valid node otherwise we could end up
1341  // returning another value "legally"
1342  //
1343  // return a NULL as the result
1344  f_return_result.reset(new node(node_type_t::NULL_TOKEN, n->get_position()));
1345  }
1346 
1347  return;
1348  }
1349 }
1350 
1352 {
1353  static_cast<void>(import);
1354 
1355  //
1356  // WARNING: we do NOT support the SASS extension of multiple entries
1357  // within one @import because it is not CSS 2 or CSS 3
1358  // compatible, not even remotely
1359  //
1360 
1361  // node 'import' is the @import itself
1362  //
1363  // @import string | url() [ media-list ] ';'
1364  //
1365 
1366  node::pointer_t expr(at_keyword_expression(import));
1367 
1368  // we only support arguments with one string
1369  // (@import accepts strings and url() as their first parameter)
1370  //
1371 //std::cerr << "replace @import!?\n";
1372 //if(expr) std::cerr << *expr;
1373 //std::cerr << "----------------------\n";
1374  if(expr
1375  && (expr->is(node_type_t::STRING)
1376  || expr->is(node_type_t::URL)))
1377  {
1378  std::string script_name(expr->get_string());
1379 
1380  if(expr->is(node_type_t::URL))
1381  {
1382  // we only support URIs that start with "file://"
1383  if(script_name.substr(0, 7) == "file://")
1384  {
1385  script_name = script_name.substr(7);
1386  if(script_name.empty()
1387  || script_name[0] != '/')
1388  {
1389  script_name = "/" + script_name;
1390  }
1391  }
1392  else
1393  {
1394  // not a type of URI we support
1395  ++idx;
1396  return;
1397  }
1398  }
1399  else
1400  {
1401  // TODO: add code to avoid testing with filenames that represent URIs
1402  std::string::size_type pos(script_name.find(':'));
1403  if(pos != std::string::npos
1404  && script_name.substr(pos, 3) == "://")
1405  {
1406  std::string const protocol(script_name.substr(0, pos));
1407  auto s(protocol.c_str());
1408  for(; *s != '\0'; ++s)
1409  {
1410  if((*s < 'a' || *s > 'z')
1411  && (*s < 'A' || *s > 'Z'))
1412  {
1413  break;
1414  }
1415  }
1416  if(*s == '\0')
1417  {
1418  if(protocol != "file")
1419  {
1420  ++idx;
1421  return;
1422  }
1423  script_name = script_name.substr(7);
1424  if(script_name.empty()
1425  || script_name[0] != '/')
1426  {
1427  script_name = "/" + script_name;
1428  }
1429  }
1430  //else -- not a valid protocol, so we assume it is
1431  // a weird filename and use it as is
1432  }
1433  }
1434 
1435  // search the corresponding file
1436  std::string filename(find_file(script_name));
1437  if(filename.empty() && script_name.length() > 5)
1438  {
1439  if(script_name.substr(script_name.size() - 5) != ".scss")
1440  {
1441  // try again with the "scss" extension
1442  filename = find_file(script_name + ".scss");
1443  }
1444  }
1445 
1446  // if still not found, we ignore
1447  if(!filename.empty())
1448  {
1449  // found an SCSS include, we remove that @import and replace
1450  // it (see below) with data as loaded from that file
1451  //
1452  // idx will not be incremented as a result
1453  //
1454  parent->remove_child(idx);
1455 
1456  // position object for this file
1457  position pos(filename);
1458 
1459  // TODO: do the necessary to avoid recursive @import
1460 
1461  // we found a file, load it and return it
1462  std::ifstream in;
1463  in.open(filename);
1464  if(!in)
1465  {
1466  // the script may not really allow reading even though
1467  // access() just told us otherwise
1468  error::instance() << pos // LCOV_EXCL_LINE
1469  << "validation script \"" // LCOV_EXCL_LINE
1470  << script_name // LCOV_EXCL_LINE
1471  << "\" could not be opened." // LCOV_EXCL_LINE
1472  << error_mode_t::ERROR_ERROR; // LCOV_EXCL_LINE
1473  }
1474  else
1475  {
1476  // the file got loaded, parse it and return the root node
1477  error_happened_t old_count;
1478 
1479  lexer::pointer_t l(new lexer(in, pos));
1480  parser p(l);
1481  node::pointer_t list(p.stylesheet());
1482 
1483  if(!old_count.error_happened())
1484  {
1485  // copy valid results at 'idx' which will then be
1486  // checked as if it had been part of that script
1487  // all along
1488  //
1489  size_t const max_results(list->size());
1490  for(size_t i(0), j(idx); i < max_results; ++i, ++j)
1491  {
1492  parent->insert_child(j, list->get_child(i));
1493  }
1494  }
1495  }
1496 
1497  // in this case we managed the entry fully
1498  return;
1499  }
1500  else if(script_name.empty())
1501  {
1502  error::instance() << expr->get_position()
1503  << "@import \"\"; and @import url(); are not valid."
1505  }
1506  else if(expr->is(node_type_t::URL))
1507  {
1508  error::instance() << expr->get_position()
1509  << "@import uri("
1510  << script_name
1511  << "); left alone by the CSS Preprocessor, no matching file found."
1513  }
1514  else
1515  {
1516  error::instance() << expr->get_position()
1517  << "@import \""
1518  << script_name
1519  << "\"; left alone by the CSS Preprocessor, no matching file found."
1521  }
1522  }
1523 
1524  ++idx;
1525 }
1526 
1528 {
1529  if(n->size() != 2)
1530  {
1531  error::instance() << n->get_position()
1532  << "a @mixin definition expects exactly two parameters: an identifier or function and a {}-block."
1534  return;
1535  }
1536 
1537  node::pointer_t block(n->get_child(1));
1538  if(!block->is(node_type_t::OPEN_CURLYBRACKET))
1539  {
1540  error::instance() << n->get_position()
1541  << "a @mixin definition expects a {}-block as its second parameter."
1543  return;
1544  }
1545 
1546  node::pointer_t name(n->get_child(0));
1547 
1548  // @mixin and @include do not accept $var[()] as a variable name
1549  // we make the error explicit
1550  if(name->is(node_type_t::VARIABLE)
1551  || name->is(node_type_t::VARIABLE_FUNCTION))
1552  {
1553  error::instance() << n->get_position()
1554  << "a @mixin must use an IDENTIFIER or FUNCTION and no a VARIABLE or VARIABLE_FUNCTION."
1556  return;
1557  }
1558 
1559  // TODO: Are @mixin always global?
1560  if(name->is(node_type_t::IDENTIFIER))
1561  {
1562  // this is just like a variable
1563  //
1564  // Note: here we are creating a variable with "name" IDENTIFIER
1565  // instead of VARIABLE as otherwise expected by the standard
1566  // variable handling
1567  //
1568  f_state.set_variable(name, block, true);
1569  }
1570  else if(name->is(node_type_t::FUNCTION))
1571  {
1572  // parse the arguments and then save the result
1574 
1575  // Note: here we are creating a variable with "name" FUNCTION
1576  // instead of VARIABLE_FUNCTION as otherwise expected by the
1577  // standard variable handling
1578  //
1579  f_state.set_variable(name, block, true);
1580  }
1581  else
1582  {
1583  error::instance() << n->get_position()
1584  << "a @mixin expects either an IDENTIFIER or a FUNCTION as its first parameter."
1586  }
1587 }
1588 
1590 {
1591  safe_parents_t safe_parents(f_state, n);
1592 
1593  switch(n->get_type())
1594  {
1596  //case node_type_t::ARG:
1599  case node_type_t::LIST:
1601  {
1602  // there are the few cases we can have here:
1603  //
1604  // $variable ':' '{' ... '}'
1605  // <field-prefix> ':' '{' ... '}' <-- this is one we're interested in (nested fields)
1606  // <selector-list> '{' ... '}' <-- this is one we're interested in (regular qualified rule)
1607  // $variable ':' ...
1608  // <field-name> ':' ...
1609  //
1610 
1611  if(!n->empty()
1612  && !parser::is_variable_set(n, true) // ! $variable ':' '{' ... '}'
1613  && n->get_last_child()->is(node_type_t::OPEN_CURLYBRACKET)) // <selector-list> '{' ... '}'
1614  {
1615  // this is a selector list followed by a block of
1616  // definitions and sub-blocks
1617  n->get_last_child()->set_boolean(true); // accept variables
1618  }
1619 
1620  // replace all $<var> references with the corresponding value
1621  for(size_t idx(0); idx < n->size(); ++idx)
1622  {
1623  // recursive call to handle all children in the
1624  // entire tree
1625  mark_selectors(n->get_child(idx));
1626  }
1627  }
1628  break;
1629 
1630  default:
1631  // other nodes are not of interest here
1632  break;
1633 
1634  }
1635 }
1636 
1638 {
1639  safe_parents_t safe_parents(f_state, n);
1640 
1641  switch(n->get_type())
1642  {
1644  if(!n->empty()
1645  && n->get_last_child()->is(node_type_t::OPEN_CURLYBRACKET)
1646  && n->get_last_child()->empty())
1647  {
1648  // that's an empty rule such as:
1649  // div {}
1650  // so get rid of it (i.e. optimization in output, no need for
1651  // empty rules, really)
1652  f_state.get_previous_parent()->remove_child(n);
1653  return;
1654  }
1655  /*FALLTHROUGH*/
1657  //case node_type_t::ARG:
1659  case node_type_t::LIST:
1661  // replace all $<var> references with the corresponding value
1662  for(size_t idx(0); idx < n->size();)
1663  {
1664  // recursive call to handle all children in the
1665  // entire tree; we need our special handling in
1666  // case something gets deleted
1667  node::pointer_t child(n->get_child(idx));
1668  remove_empty_rules(child);
1669  if(idx < n->size()
1670  && child == n->get_child(idx))
1671  {
1672  ++idx;
1673  }
1674  }
1675  break;
1676 
1677  default:
1678  // other nodes are not of interest here
1679  break;
1680 
1681  }
1682 }
1683 
1685 {
1686  safe_parents_t safe_parents(f_state, n);
1687 
1688  switch(n->get_type())
1689  {
1690  case node_type_t::LIST:
1691  if(n->empty())
1692  {
1693  // totally ignore empty lists
1694  f_state.get_previous_parent()->remove_child(n);
1695  break;
1696  }
1697  /*FALLTRHOUGH*/
1699  case node_type_t::ARG:
1702  case node_type_t::FUNCTION:
1707  {
1708  // handle a special case which SETs a variable and cannot
1709  // get the first $<var> replaced
1710  bool is_variable_set(n->get_type() == node_type_t::COMPONENT_VALUE
1711  && parser::is_variable_set(n, false));
1712 
1713  // replace all $<var> references with the corresponding value
1714  size_t idx(is_variable_set
1715  ? (n->get_child(0)->is(node_type_t::VARIABLE_FUNCTION)
1716  ? n->size() // completely ignore functions
1717  : 1) // do not replace $<var> in $<var>:
1718  : 0); // replace everything
1719  while(idx < n->size())
1720  {
1721  node::pointer_t child(n->get_child(idx));
1722  if(child->is(node_type_t::VARIABLE))
1723  {
1724  n->remove_child(idx);
1725 
1726  // search for the variable and replace this 'child' with
1727  // the contents of the variable
1728  replace_variable(n, child, idx);
1729  }
1730  else if(child->is(node_type_t::VARIABLE_FUNCTION))
1731  {
1732  // we need to first replace variables in the parameters
1733  // of the function
1734  replace_variables(child);
1735 
1736  n->remove_child(idx);
1737 
1738  // search for the variable and replace this 'child' with
1739  // the contents of the variable
1740  replace_variable(n, child, idx);
1741  }
1742  else
1743  {
1744  // recursive call to handle all children in the
1745  // entire tree
1746  switch(child->get_type())
1747  {
1748  case node_type_t::ARG:
1749  case node_type_t::COMMENT:
1752  case node_type_t::FUNCTION:
1753  case node_type_t::LIST:
1757  replace_variables(child);
1758 
1759  // skip that child if still present
1760  if(idx < n->size()
1761  && child == n->get_child(idx))
1762  {
1763  ++idx;
1764  }
1765  break;
1766 
1768  // handle @import, @mixins, @if, etc.
1769  replace_at_keyword(n, child, idx);
1770  break;
1771 
1772  default:
1773  ++idx;
1774  break;
1775 
1776  }
1777  }
1778  }
1779  // TODO: remove lists that become empty?
1780 
1781  // handle the special case of a variable assignment
1782  if(is_variable_set)
1783  {
1784  // this is enough to get the variable removed
1785  // from COMPONENT_VALUE
1786  set_variable(n);
1787  }
1788  }
1789  break;
1790 
1791  case node_type_t::COMMENT:
1793  break;
1794 
1795  default:
1796  // other nodes are not of interest here
1797  break;
1798 
1799  }
1800 }
1801 
1803 {
1804  std::string const & variable_name(n->get_string());
1805 
1806  // search the parents for the node where the variable will be set
1807  node::pointer_t value(f_state.get_variable(variable_name));
1808  if(!value)
1809  {
1810  // no variable with that name found, generate an error?
1812  {
1813  error::instance() << n->get_position()
1814  << "variable named \""
1815  << variable_name
1816  << "\" is not set."
1818  }
1819  return;
1820  }
1821 
1822  // internal validity check
1823  if(!value->is(node_type_t::LIST)
1824  || value->size() != 2)
1825  {
1826  throw csspp_exception_logic("compiler.cpp:compiler::replace_variable(): all variable values must be two sub-values in a LIST, the first item being the variable."); // LCOV_EXCL_LINE
1827  }
1828 
1829  node::pointer_t var(value->get_child(0));
1830  node::pointer_t val(value->get_child(1));
1831 
1832  if(var->is(node_type_t::FUNCTION)
1833  || var->is(node_type_t::VARIABLE_FUNCTION))
1834  {
1836  && !n->is(node_type_t::FUNCTION))
1837  {
1838  error::instance() << n->get_position()
1839  << "variable named \""
1840  << variable_name
1841  << "\" is not a function and it cannot be referenced as such."
1843  return;
1844  }
1845  // we need to apply the function...
1846  parser::argify(n);
1847 
1848  node::pointer_t root(new node(node_type_t::LIST, val->get_position()));
1849  root->add_child(val->clone());
1850  size_t const max_children(var->size());
1851  size_t const max_input(n->size());
1852  for(size_t i(0); i < max_children; ++i)
1853  {
1854  node::pointer_t arg(var->get_child(i));
1855  if(!arg->is(node_type_t::ARG))
1856  {
1857  // function declaration is invalid!
1858  throw csspp_exception_logic("compiler.cpp:compiler::replace_variable(): VARIABLE_FUNCTION children are not all ARG nodes."); // LCOV_EXCL_LINE
1859  }
1860  if(arg->empty())
1861  {
1862  throw csspp_exception_logic("compiler.cpp:compiler::replace_variable(): ARG is empty."); // LCOV_EXCL_LINE
1863  }
1864  node::pointer_t arg_name(arg->get_child(0));
1865  if(!arg_name->is(node_type_t::VARIABLE))
1866  {
1867  // this was already erred when we created the variable
1868  //error::instance() << n->get_position()
1869  // << "function declaration requires all parameters to be variables, "
1870  // << arg_name->get_type()
1871  // << " is not acceptable."
1872  // << error_mode_t::ERROR_ERROR;
1873  return;
1874  }
1875  if(i >= max_input)
1876  {
1877  // user did not specify this value, check whether we have
1878  // an optional value
1879  if(arg->size() > 1)
1880  {
1881  // use default value
1882  node::pointer_t default_param(arg->clone());
1883  default_param->remove_child(0); // remove the variable name
1884  if(default_param->size() == 1)
1885  {
1886  default_param = default_param->get_child(0);
1887  }
1888  else
1889  {
1890  node::pointer_t value_list(new node(node_type_t::LIST, arg->get_position()));
1891  value_list->take_over_children_of(default_param);
1892  default_param = value_list;
1893  }
1894  node::pointer_t param_value(new node(node_type_t::LIST, arg->get_position()));
1895  param_value->add_child(arg_name);
1896  param_value->add_child(default_param);
1897  root->set_variable(arg_name->get_string(), param_value);
1898  }
1899  else
1900  {
1901  // value is missing
1902  error::instance() << n->get_position()
1903  << "missing function variable named \""
1904  << arg_name->get_string()
1905  << "\" when calling "
1906  << variable_name
1907  << "() or using @include "
1908  << variable_name
1909  << "();)."
1911  return;
1912  }
1913  }
1914  else
1915  {
1916  // copy user provided value
1917  node::pointer_t user_param(n->get_child(i));
1918  if(!user_param->is(node_type_t::ARG))
1919  {
1920  throw csspp_exception_logic("compiler.cpp:compiler::replace_variable(): user parameter is not an ARG."); // LCOV_EXCL_LINE
1921  }
1922  if(user_param->size() == 1)
1923  {
1924  user_param = user_param->get_child(0);
1925  }
1926  else
1927  {
1928  // is that really correct?
1929  // we may need a component_value instead...
1930  node::pointer_t list(new node(node_type_t::LIST, user_param->get_position()));
1931  list->take_over_children_of(user_param);
1932  user_param = list;
1933  }
1934  node::pointer_t param_value(new node(node_type_t::LIST, user_param->get_position()));
1935  param_value->add_child(arg_name);
1936  param_value->add_child(user_param->clone());
1937  root->set_variable(arg_name->get_string(), param_value);
1938  }
1939  }
1940 
1941  compiler c(&c.f_state);
1942  c.set_root(root);
1945  c.mark_selectors(root);
1946  c.replace_variables(root);
1947 
1948  // ready to be inserted in the parent
1949  val = root;
1950 
1951  // only keep the curlybracket instead of list + curlybracket
1952  if(val->size() == 1
1953  && val->get_child(0)->is(node_type_t::OPEN_CURLYBRACKET))
1954  {
1955  val = val->get_child(0);
1956  }
1957  }
1958  else
1959  {
1961  || n->is(node_type_t::FUNCTION))
1962  {
1963  error::instance() << n->get_position()
1964  << "variable named \""
1965  << variable_name
1966  << "\" is a function and it can only be referenced with a function ($"
1967  << variable_name
1968  << "() or @include "
1969  << variable_name
1970  << ";)."
1972  return;
1973  }
1974  }
1975 
1976  switch(val->get_type())
1977  {
1978  case node_type_t::LIST:
1980  // check what the content of the list looks like, we may want to
1981  // insert it as a COMPONENT_VALUE instead of directly as is
1982  //
1983  // TODO: the following test is terribly ugly, I'm wondering whether
1984  // a "complex" variable should not instead be recompiled in
1985  // context; one problem being that we do not really know
1986  // what context we're in when we do this transformation...
1987  // none-the-less, I think there would be much better ways
1988  // to handle the situation.
1989  //
1990  if(val->size() == 1
1991  && val->get_child(0)->is(node_type_t::COMPONENT_VALUE)
1992  && parent->is(node_type_t::COMPONENT_VALUE))
1993  {
1994  size_t const max_children(val->get_child(0)->size());
1995  for(size_t j(0), i(idx); j < max_children; ++j, ++i)
1996  {
1997  node::pointer_t child(val->get_child(0)->get_child(j));
1998  parent->insert_child(i, child->clone());
1999  }
2000  break;
2001  }
2002 // else if(val->size() >= 2)
2003 // {
2004 //std::cerr << "----------------- REPLACE WITH VARIABLE CONTENT:\n" << *val << "----------------------------------\n";
2005 // bool component_value(false);
2006 // switch(val->get_child(0)->get_type())
2007 // {
2008 // case node_type_t::IDENTIFIER:
2009 // if(val->get_child(1)->is(node_type_t::WHITESPACE))
2010 // {
2011 // if(val->size() >= 3
2012 // && val->get_child(2)->is(node_type_t::COLON))
2013 // {
2014 // component_value = true;
2015 // }
2016 // }
2017 // else
2018 // {
2019 // if(val->get_child(1)->is(node_type_t::COLON))
2020 // {
2021 // component_value = true;
2022 // }
2023 // }
2024 // if(!component_value)
2025 // {
2026 // component_value = val->get_last_child()->is(node_type_t::OPEN_CURLYBRACKET);
2027 // }
2028 // break;
2029 //
2030 // case node_type_t::MULTIPLY:
2031 // case node_type_t::OPEN_SQUAREBRACKET:
2032 // case node_type_t::PERIOD:
2033 // case node_type_t::REFERENCE:
2034 // case node_type_t::HASH:
2035 // component_value = val->get_last_child()->is(node_type_t::OPEN_CURLYBRACKET);
2036 // break;
2037 //
2038 // default:
2039 // // anything else cannot be defining a component value
2040 // break;
2041 //
2042 // }
2043 //
2044 // if(component_value)
2045 // {
2046 // // in this case we copy the data in a COMPONENT_VALUE instead
2047 // // of directly
2048 // node::pointer_t cv(new node(node_type_t::COMPONENT_VALUE, val->get_position()));
2049 // parent->insert_child(idx, cv);
2050 //
2051 // size_t const max_children(val->size());
2052 // for(size_t j(0); j < max_children; ++j)
2053 // {
2054 // cv->add_child(val->get_child(j)->clone());
2055 // }
2056 // break;
2057 // }
2058 // }
2059  /*FALLTHROUGH*/
2062  // in this case we insert the children of 'val'
2063  // instead of the value itself
2064  {
2065  size_t const max_children(val->size());
2066  for(size_t j(0), i(idx); j < max_children; ++j, ++i)
2067  {
2068  parent->insert_child(i, val->get_child(j)->clone());
2069  }
2070  }
2071  break;
2072 
2075  // whitespaces by themselves do not get re-included,
2076  // which may be a big mistake but at this point
2077  // it seems wise to do so (plus I don't think it can
2078  // happen anyway...)
2079  break;
2080 
2081  default:
2082  parent->insert_child(idx, val->clone());
2083  break;
2084 
2085  }
2086 }
2087 
2089 {
2090  // WARNING: 'n' is still the COMPONENT_VALUE and not the $var
2091 
2092  // a variable gets removed from the tree and its current value
2093  // saved in a parent node that is an OPEN_CURLYBRACKET or the
2094  // root node if no OPEN_CURLYBRACKET is found in the parents
2095  // (note also that only OPEN_CURLYBRACKET marked with 'true'
2096  // are used, those are the only valid '{' for variables, for
2097  // example, an @-keyword '{' does not count...)
2098 
2099  f_state.get_previous_parent()->remove_child(n);
2100 
2101  node::pointer_t var(n->get_child(0));
2102 
2103  n->remove_child(0); // remove the VARIABLE
2104  if(n->get_child(0)->is(node_type_t::WHITESPACE))
2105  {
2106  n->remove_child(0); // remove the WHITESPACE
2107  }
2108  if(!n->get_child(0)->is(node_type_t::COLON))
2109  {
2110  throw csspp_exception_logic("compiler.cpp: somehow a variable set is not exactly IDENTIFIER WHITESPACE* ':'."); // LCOV_EXCL_LINE
2111  }
2112  n->remove_child(0); // remove the COLON
2113  if(!n->empty()
2114  && n->get_child(0)->is(node_type_t::WHITESPACE))
2115  {
2116  // remove WHITESPACE at the beginning of a variable content
2117  n->remove_child(0);
2118  }
2119 
2120  // check whether we have a "!global" at the end of the value
2121  // if so remove it and set global to true
2122  // similarly, handle the "!default"
2123  // we should also support the "!important" but that requires a
2124  // special flag in the variable to know that it cannot be overwritten
2125  bool global(false);
2126  bool set_if_unset(false);
2127  std::string not_at_the_end;
2128  size_t pos(n->size());
2129  while(pos > 0)
2130  {
2131  --pos;
2132  node::pointer_t child(n->get_child(pos));
2133  if(child->is(node_type_t::EXCLAMATION))
2134  {
2135  if(child->get_string() == "global")
2136  {
2137  global = true;
2138  n->remove_child(pos);
2139  if(not_at_the_end.empty()
2140  && pos != n->size())
2141  {
2142  not_at_the_end = child->get_string();
2143  }
2144  }
2145  else if(child->get_string() == "default")
2146  {
2147  set_if_unset = true;
2148  n->remove_child(pos);
2149  if(not_at_the_end.empty()
2150  && pos != n->size())
2151  {
2152  not_at_the_end = child->get_string();
2153  }
2154  }
2155  }
2156  }
2157  if(!not_at_the_end.empty())
2158  {
2159  error::instance() << n->get_position()
2160  << "A special flag, !"
2161  << not_at_the_end
2162  << " in this case, must only appear at the end of a declaration."
2164  }
2165 
2166  // if variable is already set, return immediately
2167  if(set_if_unset)
2168  {
2169  // TODO: verify that the type (i.e. VARIABLE / VARIABLE_FUNCTION)
2170  // would not be changed?
2171  if(f_state.get_variable(var->get_string()))
2172  {
2173  return;
2174  }
2175  }
2176 
2177  // rename the node from COMPONENT_VALUE to a plain LIST
2178  node::pointer_t list;
2179  if(n->size() == 1)
2180  {
2181  list = n->get_child(0);
2182  }
2183  else
2184  {
2185  list.reset(new node(node_type_t::LIST, n->get_position()));
2186  list->take_over_children_of(n);
2187  }
2188 
2189  // we may need to check a little better as null may be in a sub-list
2190  if(list->is(node_type_t::IDENTIFIER)
2191  && list->get_string() == "null")
2192  {
2193  list.reset(new node(node_type_t::NULL_TOKEN, list->get_position()));
2194  }
2195 
2196  // now the value of the variable is 'list'; it will get compiled once in
2197  // context (i.e. not here)
2198 
2199  // search the parents for the node where the variable will be set
2200  if(var->is(node_type_t::VARIABLE_FUNCTION))
2201  {
2203  }
2204 
2205  // now save all of that in the best place
2206  f_state.set_variable(var, list, global);
2207 }
2208 
2210 {
2211  if(!parser::argify(var))
2212  {
2213  return;
2214  }
2215 
2216  // TODO: verify that the list of arguments is valid (i.e. $var
2217  // or $var: <default-value>)
2218  bool optional(false);
2219  size_t const max_children(var->size());
2220  for(size_t i(0); i < max_children; ++i)
2221  {
2222  node::pointer_t arg(var->get_child(i));
2223  if(!arg->is(node_type_t::ARG))
2224  {
2225  throw csspp_exception_logic("compiler.cpp:compiler::set_variable(): an argument is not an ARG node."); // LCOV_EXCL_LINE
2226  }
2227  if(arg->empty())
2228  {
2229  throw csspp_exception_logic("compiler.cpp:compiler::set_variable(): an argument has no children."); // LCOV_EXCL_LINE
2230  }
2231  node::pointer_t param_var(arg->get_child(0));
2232  if(param_var->is(node_type_t::VARIABLE))
2233  {
2234  size_t const arg_size(arg->size());
2235  if(arg_size > 1)
2236  {
2237  if(arg->get_child(1)->is(node_type_t::WHITESPACE))
2238  {
2239  arg->remove_child(1);
2240  }
2241  if(arg->size() > 1
2242  && arg->get_child(1)->is(node_type_t::COLON))
2243  {
2244  optional = true;
2245  arg->remove_child(1);
2246  if(arg->size() > 1
2247  && arg->get_child(1)->is(node_type_t::WHITESPACE))
2248  {
2249  arg->remove_child(1);
2250  }
2251  // so now we have $arg <optional-value> and no whitespaces or colon
2252  }
2253  else
2254  {
2255  error::instance() << arg->get_position()
2256  << "function declarations expect variable with optional parameters to use a ':' after the variable name and before the optional value."
2258  }
2259  }
2260  else
2261  {
2262  // TODO: I think that the last parameter, if it ends with "...", is not required to have an optional value?
2263  if(optional)
2264  {
2265  error::instance() << arg->get_position()
2266  << "function declarations with optional parameters must make all parameters optional from the first one that is given an optional value up to the end of the list of arguments."
2268  }
2269  }
2270  }
2271  else
2272  {
2273  error::instance() << arg->get_position()
2274  << "function declarations expect variables for each of their arguments, not a "
2275  << param_var->get_type()
2276  << "."
2278  }
2279  }
2280 }
2281 
2283 {
2284  // @<id> [expression] '{' ... '}'
2285  //
2286  // Note that the expression is optional. Not only that, in most
2287  // cases we do not attempt to compile it because it is not expected
2288  // to be an SCSS expression (especially in an @support command).
2289  //
2290  // All the @-keyword that are used to control the flow of the
2291  // SCSS file are to be handled here; at this time these include:
2292  //
2293  // @else -- changes what happens (i.e. sets a variable)
2294  // @if -- changes what happens (i.e. sets a variable)
2295  // @import -- changes input code
2296  // @include -- same as $var or $var(args)
2297  // @mixin -- changes variables
2298  //
2299  // To be added are: @for, @while, @each.
2300  //
2301  std::string const at(n->get_string());
2302 
2303  if(at != "mixin")
2304  {
2305  replace_variables(n);
2306  }
2307 
2308  if(at == "import")
2309  {
2310  replace_import(parent, n, idx);
2311  return;
2312  }
2313 
2314  if(at == "mixin")
2315  {
2316  // mixins are handled like variables or
2317  // function declarations, so we always
2318  // remove them
2319  //
2320  parent->remove_child(idx);
2321  handle_mixin(n);
2322  return;
2323  }
2324 
2325  if(at == "if")
2326  {
2327  // get the position of the @if in its parent so we can insert new
2328  // data at that position if necessary
2329  //
2330  parent->remove_child(idx);
2331  replace_if(parent, n, idx);
2332  return;
2333  }
2334 
2335  if(at == "else")
2336  {
2337  // remove the @else from the parent
2338  parent->remove_child(idx);
2339  replace_else(parent, n, idx);
2340  return;
2341  }
2342 
2343  if(at == "include")
2344  {
2345  // this is SASS support, a more explicit way to insert a variable
2346  // I guess...
2347  parent->remove_child(idx);
2348 
2349  if(n->empty())
2350  {
2351  // as far as I can tell, it is not possible to reach these
2352  // lines from a tree created by the parser; we could work
2353  // on creating a "fake" invalid tree too...
2354  error::instance() << n->get_position() // LCOV_EXCL_LINE
2355  << "@include is expected to be followed by an IDENTIFIER or a FUNCTION naming the variable/mixin to include." // LCOV_EXCL_LINE
2356  << error_mode_t::ERROR_ERROR; // LCOV_EXCL_LINE
2357  return; // LCOV_EXCL_LINE
2358  }
2359 
2360  node::pointer_t id(n->get_child(0));
2361  if(!id->is(node_type_t::IDENTIFIER)
2362  && !id->is(node_type_t::FUNCTION))
2363  {
2364  error::instance() << n->get_position()
2365  << "@include is expected to be followed by an IDENTIFIER or a FUNCTION naming the variable/mixin to include."
2367  return;
2368  }
2369 
2370  // search for the variable and replace this 'child' with
2371  // the contents of the variable
2372  replace_variable(parent, id, idx);
2373  return;
2374  }
2375 
2376  if(at == "error"
2377  || at == "warning"
2378  || at == "message"
2379  || at == "info"
2380  || at == "debug")
2381  {
2382  // make sure the expression is calculated for these
2384  }
2385 
2386  // in all other cases the @-keyword is kept as is
2387  ++idx;
2388 }
2389 
2391 {
2392  // calculate the expression if present
2393  if(!n->empty() && !n->get_child(0)->is(node_type_t::OPEN_CURLYBRACKET))
2394  {
2395  expression expr(n);
2397  return expr.compile();
2398  }
2399 
2400  return node::pointer_t();
2401 }
2402 
2404 {
2405  // we want to mark the next block as valid if it is an
2406  // '@else' or '@else if' and can possibly be inserted
2407  node::pointer_t next;
2408  if(idx < parent->size())
2409  {
2410  // note: we deleted the @if so 'idx' represents the position of the
2411  // next node in the parent array
2412  next = parent->get_child(idx);
2413  if(next->is(node_type_t::AT_KEYWORD)
2414  && next->get_string() == "else")
2415  {
2416  // mark that the @else is at the right place
2417  next->set_integer(g_if_or_else_executed);
2418  }
2419  }
2420 
2422 
2423  // make sure that we got a valid syntax
2424  if(n->size() != 2 || !expr)
2425  {
2426  error::instance() << n->get_position()
2427  << "@if is expected to have exactly 2 parameters: an expression and a block. This @if has "
2428  << static_cast<int>(n->size())
2429  << " parameters."
2431  return;
2432  }
2433 
2434  bool const r(expression::boolean(expr));
2435  if(r)
2436  {
2437  // BOOLEAN_TRUE, we need the data which we put in the stream
2438  // at the position of the @if as if the @if and
2439  // expression never existed
2440  node::pointer_t block(n->get_child(1));
2441  size_t const max_children(block->size());
2442  for(size_t j(0); j < max_children; ++j, ++idx)
2443  {
2444  parent->insert_child(idx, block->get_child(j));
2445  }
2446  }
2447  else if(next)
2448  {
2449  // mark the else as not executed if r is false
2450  next->set_integer(g_if_or_else_false_so_far);
2451  }
2452 }
2453 
2455 {
2456  node::pointer_t next;
2457 
2458  // BOOLEAN_FALSE or BOOLEAN_INVALID, we remove the block to avoid
2459  // executing it since we do not know whether it should
2460  // be executed or not; also we mark the next block as
2461  // "true" if it is an '@else' or '@else if'
2462  if(idx < parent->size())
2463  {
2464  next = parent->get_child(idx);
2465  if(next->is(node_type_t::AT_KEYWORD)
2466  && next->get_string() == "else")
2467  {
2468  if(n->size() == 1)
2469  {
2470  error::instance() << n->get_position()
2471  << "'@else { ... }' cannot follow another '@else { ... }'. Maybe you are missing an 'if expr'?"
2473  return;
2474  }
2475 
2476  // at this point we do not know the state that the next
2477  // @else/@else if should have so we use "executed" as a
2478  // safe value
2479  //
2480  next->set_integer(g_if_or_else_executed);
2481  }
2482  else
2483  {
2484  next.reset();
2485  }
2486  }
2487  node::pointer_t expr;
2488 
2489  bool const else_if(n->get_child(0)->is(node_type_t::IDENTIFIER)
2490  && n->get_child(0)->get_string() == "if");
2491 
2492  // in case of an else_if, check the expression
2493  if(else_if)
2494  {
2495  // this is a very special case of the:
2496  //
2497  // @else if expr '{' ... '}'
2498  //
2499  // (this is from SASS, if it had been me, I would have used
2500  // @elseif or @else-if and not @else if ...)
2501  //
2502  n->remove_child(0);
2503  if(!n->empty() && n->get_child(0)->is(node_type_t::WHITESPACE))
2504  {
2505  // this should always happen because otherwise we are missing
2506  // the actual expression!
2507  n->remove_child(0);
2508  }
2509  if(n->size() == 1)
2510  {
2511  error::instance() << n->get_position()
2512  << "'@else if ...' is missing an expression or a block."
2514  return;
2515  }
2516  expr = at_keyword_expression(n);
2517  }
2518 
2519  // if this '@else' is still marked with 'g_if_or_else_undefined'
2520  // then there was no '@if' or '@else if' before it which is an error
2521  //
2522  int status(n->get_integer());
2523  if(status == g_if_or_else_undefined)
2524  {
2525  error::instance() << n->get_position()
2526  << "a standalone @else is not legal, it has to be preceeded by an @if ... or @else if ..."
2528  return;
2529  }
2530 
2531  //
2532  // when the '@if' or any '@else if' all had a 'false' expression,
2533  // we are 'true' here; once one of the '@if' / '@else if' is 'true'
2534  // then we start with 'r = false'
2535  //
2536  bool r(status == g_if_or_else_false_so_far);
2537  if(n->size() != 1)
2538  {
2539  if(n->size() != 2 || !expr)
2540  {
2541  error::instance() << n->get_position()
2542  << "'@else { ... }' is expected to have 1 parameter, '@else if ... { ... }' is expected to have 2 parameters. This @else has "
2543  << static_cast<int>(n->size())
2544  << " parameters."
2546  return;
2547  }
2548 
2549  // as long as 'status == g_if_or_else_false_so_far' we have
2550  // not yet found a match (i.e. the starting '@if' was false
2551  // and any '@else if' were all false so far) so we check the
2552  // expression of this very '@else if' to know whether to go
2553  // on or not; r is BOOLEAN_TRUE when the status allows us to check
2554  // the next expression
2555  if(r)
2556  {
2557  r = expression::boolean(expr);
2558  }
2559  }
2560 
2561  if(r)
2562  {
2563  status = g_if_or_else_executed;
2564 
2565  // BOOLEAN_TRUE, we need the data which we put in the stream
2566  // at the position of the @if as if the @if and
2567  // expression never existed
2568  node::pointer_t block(n->get_child(n->size() == 1 ? 0 : 1));
2569  size_t const max_children(block->size());
2570  for(size_t j(0); j < max_children; ++j, ++idx)
2571  {
2572  parent->insert_child(idx, block->get_child(j));
2573  }
2574  }
2575 
2576  if(next)
2577  {
2578  next->set_integer(status);
2579  }
2580 }
2581 
2583 {
2584  std::string comment(n->get_string());
2585 
2586  std::string::size_type pos(comment.find('{')); // } for vim % functionality
2587  while(pos != std::string::npos)
2588  {
2589  if(pos + 2 < comment.length()
2590  && comment[pos + 1] == '$')
2591  {
2592  std::string::size_type start_var(pos);
2593  std::string::size_type start_name(start_var + 2);
2594  // include the preceeding '#' if present (SASS compatibility)
2595  if(start_var > 0
2596  && comment[start_var - 1] == '#')
2597  {
2598  --start_var;
2599  }
2600  // we do +1 because it definitely cannot start at '{$'
2601  std::string::size_type end_var(comment.find('}', start_var + 2));
2602  if(end_var != std::string::npos)
2603  {
2604  // we got a variable, replace with the content
2605  std::string const full_name(comment.substr(start_name, end_var - start_name));
2606 
2607  // check whether the user referenced a function or not
2608  bool is_function(false);
2609  std::string variable_name(full_name);
2610  std::string::size_type func_pos(full_name.find('('));
2611  if(func_pos != std::string::npos)
2612  {
2613  // only keep the name part, ignore the parameters
2614  variable_name = full_name.substr(0, func_pos);
2615  is_function = true;
2616  }
2617 
2618  node::pointer_t var_content(f_state.get_variable(variable_name));
2619  if(var_content)
2620  {
2621  // internal validity check
2622  if(!var_content->is(node_type_t::LIST)
2623  || var_content->size() != 2)
2624  {
2625  throw csspp_exception_logic("compiler.cpp:compiler::replace_variables_in_comment(): all variable values must be two sub-values in a LIST, the first item being the variable."); // LCOV_EXCL_LINE
2626  }
2627 
2628  node::pointer_t var(var_content->get_child(0));
2629  if(var->is(node_type_t::FUNCTION)
2630  || var->is(node_type_t::VARIABLE_FUNCTION))
2631  {
2632  // TODO: add the support?
2633  error::instance() << n->get_position()
2634  << "variable named \""
2635  << variable_name
2636  << "\", is a function which is not supported in a comment."
2638  }
2639  else
2640  {
2641  if(is_function)
2642  {
2643  error::instance() << n->get_position()
2644  << "variable named \""
2645  << variable_name
2646  << "\", is not a function, yet you referenced it as such (and functions are not yet supported in comments)."
2648  }
2649 
2650  node::pointer_t val(var_content->get_child(1));
2651 
2652  comment.erase(start_var, end_var + 1 - start_var);
2653  // TODO: use the assembler instead of to_string()?
2654  std::string const value(val->to_string(0));
2655 
2656  comment.insert(start_var, value);
2657 
2658  // adjust pos to continue checking from after the
2659  // variable (i.e. if inserting a variable that includes
2660  // a {$var} it won't be replaced...)
2661  pos = start_var + value.length() - 1;
2662  }
2663  }
2664  else
2665  {
2667  {
2668  error::instance() << n->get_position()
2669  << "variable named \""
2670  << variable_name
2671  << "\", used in a comment, is not set."
2673  }
2674  }
2675  }
2676  }
2677 
2678  pos = comment.find('{', pos + 1); // } for vim % functionality
2679  }
2680 
2681  n->set_string(comment);
2682 }
2683 
2685 {
2686  // use a for() as a 'goto exit;' on a 'break'
2687  for(;;)
2688  {
2689  size_t pos(0);
2690  node::pointer_t term(n->get_child(pos));
2691  if(term->is(node_type_t::WHITESPACE))
2692  {
2693  // I'm keeping this here, although there should be no WHITESPACE
2694  // at the start of a '[' block
2695  n->remove_child(term); // LCOV_EXCL_LINE
2696  if(pos >= n->size()) // LCOV_EXCL_LINE
2697  {
2698  break; // LCOV_EXCL_LINE
2699  }
2700  term = n->get_child(pos); // LCOV_EXCL_LINE
2701  }
2702 
2703  if(!term->is(node_type_t::IDENTIFIER))
2704  {
2705  error::instance() << n->get_position()
2706  << "an attribute selector expects to first find an identifier."
2708  return false;
2709  }
2710 
2711  ++pos;
2712  if(pos >= n->size())
2713  {
2714  // just IDENTIFIER is valid
2715  ++parent_pos;
2716  return true;
2717  }
2718 
2719  term = n->get_child(pos);
2720  if(term->is(node_type_t::WHITESPACE))
2721  {
2722  n->remove_child(pos);
2723  if(pos >= n->size())
2724  {
2725  // just IDENTIFIER is valid, although we should never
2726  // reach this line because WHITESPACE are removed from
2727  // the end of lists
2728  ++parent_pos; // LCOV_EXCL_LINE
2729  return true; // LCOV_EXCL_LINE
2730  }
2731  term = n->get_child(pos);
2732  }
2733 
2734  if(!term->is(node_type_t::EQUAL) // '='
2735  && !term->is(node_type_t::NOT_EQUAL) // '!=' -- extension
2736  && !term->is(node_type_t::INCLUDE_MATCH) // '~='
2737  && !term->is(node_type_t::PREFIX_MATCH) // '^='
2738  && !term->is(node_type_t::SUFFIX_MATCH) // '$='
2739  && !term->is(node_type_t::SUBSTRING_MATCH) // '*='
2740  && !term->is(node_type_t::DASH_MATCH)) // '|='
2741  {
2742  error::instance() << n->get_position()
2743  << "expected attribute operator missing, supported operators are '=', '!=', '~=', '^=', '$=', '*=', and '|='."
2745  return false;
2746  }
2747  node::pointer_t op(term);
2748 
2749  ++pos;
2750  if(pos >= n->size())
2751  {
2752  break;
2753  }
2754 
2755  term = n->get_child(pos);
2756  if(term->is(node_type_t::WHITESPACE))
2757  {
2758  n->remove_child(pos);
2759  if(pos >= n->size())
2760  {
2761  // we actually are not expected to ever have a WHITESPACE
2762  // at the end of a block so we cannot hit this line, but
2763  // we keep it, just in case we were wrong...
2764  break; // LCOV_EXCL_LINE
2765  }
2766  term = n->get_child(pos);
2767  }
2768 
2769  if(!term->is(node_type_t::IDENTIFIER)
2770  && !term->is(node_type_t::STRING)
2771  && !term->is(node_type_t::INTEGER)
2772  && !term->is(node_type_t::DECIMAL_NUMBER))
2773  {
2774  error::instance() << n->get_position()
2775  << "attribute selector value must be an identifier, a string, an integer, or a decimal number, a "
2776  << term->get_type() << " is not acceptable."
2778  return false;
2779  }
2780 
2781  ++pos;
2782  if(pos < n->size()) // <<-- inverted test!
2783  {
2784  error::instance() << n->get_position()
2785  << "attribute selector cannot be followed by more than one value, found "
2786  << n->get_child(pos)->get_type() << " after the value, missing quotes?"
2788  return false;
2789  }
2790 
2791  // if the operator was '!=', we have to make changes from:
2792  // [a!=b]
2793  // to
2794  // :not([a=b])
2795  if(op->is(node_type_t::NOT_EQUAL))
2796  {
2797  // remove the [a!=b] from parent
2798  parent->remove_child(parent_pos);
2799 
2800  // add the ':'
2801  node::pointer_t colon(new node(node_type_t::COLON, n->get_position()));
2802  parent->insert_child(parent_pos, colon);
2803  ++parent_pos;
2804 
2805  // add the not()
2806  node::pointer_t not_func(new node(node_type_t::FUNCTION, n->get_position()));
2807  not_func->set_string("not");
2808  parent->insert_child(parent_pos, not_func);
2809 
2810  // in the not() add the [a!=b]
2811  not_func->add_child(n);
2812 
2813  // remove the '!='
2814  n->remove_child(1);
2815 
2816  // replace with the '='
2817  node::pointer_t equal(new node(node_type_t::EQUAL, n->get_position()));
2818  n->insert_child(1, equal);
2819  }
2820 
2821  ++parent_pos;
2822 
2823  return true;
2824  }
2825 
2826  error::instance() << n->get_position()
2827  << "the attribute selector is expected to be an IDENTIFIER optionally followed by an operator and a value."
2829  return false;
2830 }
2831 
2833 {
2834  if(pos >= n->size())
2835  {
2836  throw csspp_exception_logic("compiler.cpp:compiler::selector_term(): selector_simple_term() called when not enough selectors are available."); // LCOV_EXCL_LINE
2837  }
2838 
2839  node::pointer_t term(n->get_child(pos));
2840  switch(term->get_type())
2841  {
2842  case node_type_t::HASH:
2843  // valid term as is
2844  break;
2845 
2847  case node_type_t::MULTIPLY:
2848  // IDENTIFIER
2849  // IDENTIFIER '|' IDENTIFIER
2850  // IDENTIFIER '|' '*'
2851  // '*'
2852  // '*' '|' IDENTIFIER
2853  // '*' '|' '*'
2854  if(pos + 1 < n->size())
2855  {
2856  if(n->get_child(pos + 1)->is(node_type_t::SCOPE))
2857  {
2858  if(pos + 2 >= n->size())
2859  {
2860  error::instance() << n->get_position()
2861  << "the scope operator (|) requires a right hand side identifier or '*'."
2863  return false;
2864  }
2865  pos += 2;
2866  term = n->get_child(pos);
2867  if(!term->is(node_type_t::IDENTIFIER)
2868  && !term->is(node_type_t::MULTIPLY))
2869  {
2870  error::instance() << n->get_position()
2871  << "the right hand side of a scope operator (|) must be an identifier or '*'."
2873  return false;
2874  }
2875  }
2876  else if(term->is(node_type_t::MULTIPLY)
2877  && (n->get_child(pos + 1)->is(node_type_t::OPEN_SQUAREBRACKET)
2878  || n->get_child(pos + 1)->is(node_type_t::PERIOD)))
2879  {
2880  // this asterisk is not required, get rid of it
2881  n->remove_child(term);
2882  return true; // return immediately to avoid the ++pos
2883  }
2884  }
2885  break;
2886 
2887  case node_type_t::SCOPE:
2888  ++pos;
2889  if(pos >= n->size())
2890  {
2891  error::instance() << n->get_position()
2892  << "a scope selector (|) must be followed by an identifier or '*'."
2894  return false;
2895  }
2896  term = n->get_child(pos);
2897  if(!term->is(node_type_t::IDENTIFIER)
2898  && !term->is(node_type_t::MULTIPLY))
2899  {
2900  error::instance() << n->get_position()
2901  << "the right hand side of a scope operator (|) must be an identifier or '*'."
2903  return false;
2904  }
2905  break;
2906 
2907  case node_type_t::COLON:
2908  ++pos;
2909  if(pos >= n->size())
2910  {
2911  // this is caught by the selector_term() when reading the '::'
2912  // so we cannot reach this time; keeping just in case though...
2913  error::instance() << n->get_position()
2914  << "a selector list cannot end with a standalone ':'."
2916  return false;
2917  }
2918  term = n->get_child(pos);
2919  switch(term->get_type())
2920  {
2922  {
2923  // ':' IDENTIFIER
2924  // validate the identifier as only a small number can be used
2925  set_validation_script("validation/pseudo-classes");
2926  node::pointer_t str(new node(node_type_t::STRING, term->get_position()));
2927  str->set_string(term->get_string());
2928  add_validation_variable("pseudo_name", str);
2929  if(!run_validation(false))
2930  {
2931  return false;
2932  }
2933  }
2934  break;
2935 
2936  case node_type_t::FUNCTION:
2937  {
2938  // ':' FUNCTION component-value-list ')'
2939  //
2940  // create a temporary identifier to run the validation
2941  // checks, because the FUNCTION is a list of nodes!
2942  node::pointer_t function_name(new node(node_type_t::STRING, term->get_position()));
2943  function_name->set_string(term->get_string());
2944  set_validation_script("validation/pseudo-nth-functions");
2945  add_validation_variable("pseudo_name", function_name);
2946  if(run_validation(true))
2947  {
2948  // this is a valid nth function, print out its parameters
2949  // and reparse as 'An+B'
2950  size_t const max_children(term->size());
2951  std::string an_b;
2952  for(size_t idx(0); idx < max_children; ++idx)
2953  {
2954  an_b += term->get_child(idx)->to_string(node::g_to_string_flag_show_quotes);
2955  }
2956  // TODO...
2957  nth_child nc;
2958  if(nc.parse(an_b))
2959  {
2960  // success, save the compiled An+B in this object
2961  node::pointer_t an_b_node(new node(node_type_t::AN_PLUS_B, term->get_position()));
2962  an_b_node->set_integer(nc.get_nth());
2963  term->clear();
2964  term->add_child(an_b_node);
2965  }
2966  else
2967  {
2968  // get the error and display it
2969  error::instance() << term->get_position()
2970  << nc.get_error()
2972  return false;
2973  }
2974  }
2975  else
2976  {
2977  set_validation_script("validation/pseudo-functions");
2978  add_validation_variable("pseudo_name", function_name);
2979  if(!run_validation(false))
2980  {
2981  return false;
2982  }
2983  // this is a standard function, check the parameters
2984  if(term->get_string() == "not")
2985  {
2986  // :not(:not(...)) is illegal
2987  error::instance() << n->get_position()
2988  << "the :not() selector does not accept an inner :not()."
2990  return false;
2991  }
2992  else if(term->get_string() == "lang")
2993  {
2994  // the language must be an identifier with no dashes
2995  if(term->size() != 1)
2996  {
2997  error::instance() << term->get_position()
2998  << "a lang() function selector must have exactly one identifier as its parameter."
3000  return false;
3001  }
3002  term = term->get_child(0);
3003  if(term->is(node_type_t::IDENTIFIER))
3004  {
3005  std::string lang(term->get_string());
3006  std::string country;
3007  std::string::size_type char_pos(lang.find('-'));
3008  if(char_pos != std::string::npos)
3009  {
3010  country = lang.substr(char_pos + 1);
3011  lang = lang.substr(0, char_pos);
3012  char_pos = country.find('-');
3013  if(char_pos != std::string::npos)
3014  {
3015  // remove whatever other information that
3016  // we will ignore in our validations
3017  country = country.substr(0, char_pos);
3018  }
3019  }
3020  // check the language (mandatory)
3021  node::pointer_t language_name(new node(node_type_t::STRING, term->get_position()));
3022  language_name->set_string(lang);
3023  set_validation_script("validation/languages");
3024  add_validation_variable("language_name", language_name);
3025  if(!run_validation(false))
3026  {
3027  return false;
3028  }
3029  if(!country.empty())
3030  {
3031  // check the country (optional)
3032  node::pointer_t country_name(new node(node_type_t::STRING, term->get_position()));
3033  country_name->set_string(country);
3034  set_validation_script("validation/countries");
3035  add_validation_variable("country_name", country_name);
3036  if(!run_validation(false))
3037  {
3038  return false;
3039  }
3040  }
3041  }
3042  else
3043  {
3044  error::instance() << term->get_position()
3045  << "a lang() function selector expects an identifier as its parameter."
3047  return false;
3048  }
3049  }
3050  }
3051  }
3052  break;
3053 
3054  default:
3055  // invalid selector list
3056  error::instance() << n->get_position()
3057  << "a ':' selector must be followed by an identifier or a function, a " << n->get_type() << " was found instead."
3059  return false;
3060 
3061  }
3062  break;
3063 
3064  case node_type_t::PERIOD:
3065  // '.' IDENTIFIER -- class (special attribute check)
3066  ++pos;
3067  if(pos >= n->size())
3068  {
3069  error::instance() << n->get_position()
3070  << "a selector list cannot end with a standalone '.'."
3072  return false;
3073  }
3074  term = n->get_child(pos);
3075  if(!term->is(node_type_t::IDENTIFIER))
3076  {
3077  error::instance() << n->get_position()
3078  << "a class selector (after a period: '.') must be an identifier."
3080  return false;
3081  }
3082  break;
3083 
3085  // '[' WHITESPACE attribute-check WHITESPACE ']' -- attributes check
3086  return selector_attribute_check(n, pos, term);
3087 
3089  case node_type_t::ADD:
3090  case node_type_t::PRECEDED:
3091  error::instance() << n->get_position()
3092  << "found token " << term->get_type() << ", which cannot be used to start a selector expression."
3094  return false;
3095 
3096  case node_type_t::FUNCTION:
3097  error::instance() << n->get_position()
3098  << "found function \"" << term->get_string() << "()\", which may be a valid selector token but only if immediately preceeded by one ':' (simple term)."
3100  return false;
3101 
3102  default:
3103  error::instance() << n->get_position()
3104  << "found token " << term->get_type() << ", which is not a valid selector token (simple term)."
3106  return false;
3107 
3108  }
3109 
3110  // move on to the next term
3111  ++pos;
3112 
3113  return true;
3114 }
3115 
3117 {
3118  if(pos >= n->size())
3119  {
3120  throw csspp_exception_logic("compiler.cpp:compiler::selector_term(): selector_term() called when not enough selectors are available."); // LCOV_EXCL_LINE
3121  }
3122 
3123  node::pointer_t term(n->get_child(pos));
3124  switch(term->get_type())
3125  {
3127  // valid complex term as is
3128  break;
3129 
3131  // valid complex term only if pos == 0
3132  if(pos != 0)
3133  {
3134  error::instance() << n->get_position()
3135  << "a selector reference (&) can only appear as the very first item in a list of selectors."
3137  return false;
3138  }
3139  break;
3140 
3141  case node_type_t::COLON:
3142  // ':' FUNCTION (="not") is a term and has to be managed here
3143  // '::' IDENTIFIER is a term and not a simple term (it cannot
3144  // appear inside a :not() function.)
3145  ++pos;
3146  if(pos >= n->size())
3147  {
3148  error::instance() << n->get_position()
3149  << "a selector list cannot end with a standalone ':'."
3151  return false;
3152  }
3153  term = n->get_child(pos);
3154  switch(term->get_type())
3155  {
3157  --pos;
3158  return selector_simple_term(n, pos);
3159 
3160  case node_type_t::FUNCTION:
3161  // ':' FUNCTION component-value-list ')'
3162  if(term->get_string() == "not")
3163  {
3164  // special handling, the :not() is considered to be
3165  // a complex selector and as such has to be handled
3166  // right here; the parameters must represent one valid
3167  // simple term
3168  //
3169  // TODO: still got to take care of WHITESPACE?
3170  size_t sub_pos(0);
3171  if(!selector_simple_term(term, sub_pos))
3172  {
3173  return false;
3174  }
3175  if(sub_pos < term->size())
3176  {
3177  // we did not reach the end of that list so something
3178  // is wrong (i.e. the :not() can only include one
3179  // element)
3180  error::instance() << term->get_position()
3181  << "the :not() function accepts at most one simple term."
3183  return false;
3184  }
3185  }
3186  else
3187  {
3188  --pos;
3189  return selector_simple_term(n, pos);
3190  }
3191  break;
3192 
3193  case node_type_t::COLON:
3194  {
3195  // '::' IDENTIFIER -- pseudo elements
3196  ++pos;
3197  if(pos >= n->size())
3198  {
3199  error::instance() << n->get_position()
3200  << "a selector list cannot end with a '::' without an identifier after it."
3202  return false;
3203  }
3204  term = n->get_child(pos);
3205  if(!term->is(node_type_t::IDENTIFIER))
3206  {
3207  error::instance() << n->get_position()
3208  << "a pseudo element name (defined after a '::' in a list of selectors) must be defined using an identifier."
3210  return false;
3211  }
3212  // only a few pseudo element names exist, do a validation
3213  node::pointer_t pseudo_element(new node(node_type_t::STRING, term->get_position()));
3214  pseudo_element->set_string(term->get_string());
3215  set_validation_script("validation/pseudo-elements");
3216  add_validation_variable("pseudo_name", pseudo_element);
3217  if(!run_validation(false))
3218  {
3219  return false;
3220  }
3221  if(pos + 1 < n->size())
3222  {
3223  error::instance() << n->get_position()
3224  << "a pseudo element name (defined after a '::' in a list of selectors) must be defined as the last element in the list of selectors."
3226  return false;
3227  }
3228  }
3229  break;
3230 
3231  default:
3232  // invalid selector list
3233  error::instance() << n->get_position()
3234  << "a ':' selector must be followed by an identifier or a function, a " << term->get_type() << " was found instead."
3236  return false;
3237 
3238  }
3239  break;
3240 
3241  case node_type_t::HASH:
3243  case node_type_t::MULTIPLY:
3245  case node_type_t::PERIOD:
3246  case node_type_t::SCOPE:
3247  return selector_simple_term(n, pos);
3248 
3250  case node_type_t::ADD:
3251  case node_type_t::PRECEDED:
3252  error::instance() << n->get_position()
3253  << "found token " << term->get_type() << ", which cannot be used to start a selector expression."
3255  return false;
3256 
3257  case node_type_t::FUNCTION:
3258  // we can reach this case if we have a token in the selector list
3259  // which immediately returns false in is_nested_declaration()
3260  error::instance() << n->get_position()
3261  << "found function \"" << term->get_string() << "()\", which may be a valid selector token but only if immediately preceeded by one ':' (term)."
3263  return false;
3264 
3265  default:
3266  error::instance() << n->get_position()
3267  << "found token " << term->get_type() << ", which is not a valid selector token (term)."
3269  return false;
3270 
3271  }
3272 
3273  // move on to the next term
3274  ++pos;
3275 
3276  return true;
3277 }
3278 
3280 {
3281  // we must have a term first
3282  if(!selector_term(n, pos))
3283  {
3284  return false;
3285  }
3286 
3287  for(;;)
3288  {
3289  if(pos >= n->size())
3290  {
3291  return true;
3292  }
3293 
3294  // skip whitespaces between terms
3295  // this also works for binary operators
3296  node::pointer_t term(n->get_child(pos));
3297  if(term->is(node_type_t::WHITESPACE))
3298  {
3299  ++pos;
3300 
3301  // end of list too soon?
3302  if(pos >= n->size())
3303  {
3304  // this should not happen since we remove leading/trailing
3305  // white space tokens
3306  throw csspp_exception_logic("compiler.cpp: a component value has a WHITESPACE token before the OPEN_CURLYBRACKET."); // LCOV_EXCL_LINE
3307  }
3308  term = n->get_child(pos);
3309  }
3310 
3311  if(term->is(node_type_t::GREATER_THAN)
3312  || term->is(node_type_t::ADD)
3313  || term->is(node_type_t::PRECEDED))
3314  {
3315  // if we had a WHITESPACE just before the binary operator,
3316  // remove it as it is not necessary
3317  if(n->get_child(pos - 1)->is(node_type_t::WHITESPACE))
3318  {
3319  n->remove_child(pos - 1);
3320  }
3321  else
3322  {
3323  // otherwise just go over that operator
3324  ++pos;
3325  }
3326 
3327  // it is mandatory for these tokens to be followed by another
3328  // term (i.e. binary operators)
3329  if(pos >= n->size())
3330  {
3331  error::instance() << n->get_position()
3332  << "found token " << term->get_type() << ", which is expected to be followed by another selector term."
3334  return false;
3335  }
3336 
3337  // we may have a WHITESPACE first, if so skip it
3338  term = n->get_child(pos);
3339  if(term->is(node_type_t::WHITESPACE))
3340  {
3341  // no need before/after binary operators
3342  n->remove_child(term);
3343 
3344  // end of list too soon?
3345  if(pos >= n->size())
3346  {
3347  // this should not happen since we remove leading/trailing
3348  // white space tokens
3349  throw csspp_exception_logic("compiler.cpp: a component value has a WHITESPACE token before the OPEN_CURLYBRACKET."); // LCOV_EXCL_LINE
3350  }
3351  }
3352  }
3353 
3354  if(!selector_term(n, pos))
3355  {
3356  return false;
3357  }
3358  }
3359 }
3360 
3362 {
3363  if(!parser::argify(n))
3364  {
3365  return false;
3366  }
3367 
3368  size_t const max_children(n->size());
3369  for(size_t idx(0); idx < max_children; ++idx)
3370  {
3371  node::pointer_t arg(n->get_child(idx));
3372  if(arg->is(node_type_t::OPEN_CURLYBRACKET))
3373  {
3374  // this is at the end of the list, so we're done
3375  break;
3376  }
3377  if(!arg->is(node_type_t::ARG))
3378  {
3379  throw csspp_exception_logic("compiler.cpp: parse_selector() just called argify() and yet a child is not an ARG."); // LCOV_EXCL_LINE
3380  }
3381  size_t pos(0);
3382  if(!selector_list(arg, pos))
3383  {
3384  return false;
3385  }
3386 
3387  // check and make sure that #<id> is not repeated in the same
3388  // list, because that's an error (TBD--there may be one exception
3389  // now that we have the ~ operator...)
3390  bool err(false);
3391  std::map<std::string, bool> hash;
3392  for(size_t j(0); j < arg->size(); ++j)
3393  {
3394  node::pointer_t child(arg->get_child(j));
3395  if(child->is(node_type_t::HASH))
3396  {
3397  if(hash.find(child->get_string()) != hash.end())
3398  {
3399  error::instance() << arg->get_position()
3400  << "found #"
3401  << child->get_string()
3402  << " twice in selector: \""
3403  << arg->to_string(0)
3404  << "\"."
3406  err = true;
3407  }
3408  else
3409  {
3410  hash[child->get_string()] = true;
3411  }
3412  }
3413  }
3414  if(!err)
3415  {
3416  if(hash.size() > 1)
3417  {
3418  error::instance() << arg->get_position()
3419  << "found multiple #id entries, note that in most cases, assuming your HTML is proper (identifiers are not repeated) then only the last #id is necessary."
3421  }
3422  else if(hash.size() == 1 && !arg->get_child(0)->is(node_type_t::HASH))
3423  {
3424  error::instance() << arg->get_position()
3425  << "found an #id entry which is not at the beginning of the list of selectors; unless your HTML changes that much, #id should be the first selector only."
3427  }
3428  }
3429  }
3430 
3431  return true;
3432 }
3433 
3434 std::string compiler::find_file(std::string const & script_name)
3435 {
3436  return f_state.find_file(script_name);
3437 }
3438 
3439 void compiler::set_validation_script(std::string const & script_name)
3440 {
3441  // try the filename as is first
3442  std::string filename(find_file(script_name));
3443  if(filename.empty())
3444  {
3445  if(script_name.substr(script_name.size() - 5) != ".scss")
3446  {
3447  // try again with the "scss" extension
3448  filename = find_file(script_name + ".scss");
3449  }
3450  }
3451 
3452  if(filename.empty())
3453  {
3454  // a validation script should always be available, right?
3455  position pos(script_name);
3456  error::instance() << pos
3457  << "validation script \""
3458  << script_name
3459  << "\" was not found."
3461  throw csspp_exception_exit(1);
3462  }
3463 
3464  node::pointer_t script;
3465 
3466  // TODO: review whether a cache would be useful, at this point
3467  // it does not work because the compiler is destructive.
3468  // maybe use node::clone() to make a copy of the cache?
3469  //auto cache(f_validator_scripts.find(filename));
3470  //if(cache == f_validator_scripts.end())
3471  {
3472  position pos(filename);
3473 
3474  // the file exists, read it now
3475  std::ifstream in;
3476  in.open(filename);
3477  if(!in)
3478  {
3479  // a validation script should always be available, right?
3480  //
3481  // At this point I do not see how to write a test to hit
3482  // these lines (i.e. have a file that's accessible in
3483  // read mode, but cannot be opened)
3484  error::instance() << pos // LCOV_EXCL_LINE
3485  << "validation script \"" // LCOV_EXCL_LINE
3486  << script_name // LCOV_EXCL_LINE
3487  << "\" could not be opened." // LCOV_EXCL_LINE
3488  << error_mode_t::ERROR_FATAL; // LCOV_EXCL_LINE
3489  throw csspp_exception_exit(1); // LCOV_EXCL_LINE
3490  }
3491 
3492  lexer::pointer_t l(new lexer(in, pos));
3493  parser p(l);
3494  script = p.stylesheet();
3495 
3496  // TODO: test whether errors occurred while reading the script, if
3497  // so then we have to generate a FATAL error here
3498 
3499  // cache the script
3500  //f_validator_scripts[filename] = script;
3501 //std::cerr << "script " << filename << " is:\n" << *script;
3502  }
3503  //else
3504  //{
3505  // script = cache->second;
3506  //}
3507 
3508  f_current_validation_script = script;
3509  script->clear_variables();
3510 }
3511 
3512 void compiler::add_validation_variable(std::string const & variable_name, node::pointer_t value)
3513 {
3515  {
3516  throw csspp_exception_logic("compiler.cpp: somehow add_validation_variable() was called without a current validation script set."); // LCOV_EXCL_LINE
3517  }
3518 
3519  node::pointer_t var(new node(node_type_t::VARIABLE, value->get_position()));
3520  var->set_string(variable_name);
3521  node::pointer_t v(new node(node_type_t::LIST, value->get_position()));
3522  v->add_child(var);
3523  v->add_child(value);
3524 
3525  f_current_validation_script->set_variable(variable_name, v);
3526 }
3527 
3528 bool compiler::run_validation(bool check_only)
3529 {
3530  // forbid validations from within validation scripts
3532  {
3533  throw csspp_exception_logic("compiler.cpp:compiler::run_validation(): already validating, cannot validate from within a validation script."); // LCOV_EXCL_LINE
3534  }
3535 
3536  // save the number of errors so we can test after we ran
3537  // the compile() function
3538  error_happened_t old_count;
3539 
3540  safe_compiler_state_t safe_state(f_state);
3542  if(check_only)
3543  {
3544  // save the current error/warning counters so they do not change
3545  // on this run
3546  safe_error_t safe_error;
3547 
3548  // replace the output stream with a memory buffer so the user
3549  // does not see any of it
3550  std::stringstream ignore;
3551  safe_error_stream_t safe_output(ignore);
3552 
3553  // now compile that true/false check
3554  compile(true);
3555 
3556  // WARNING: this MUST be here (before the closing curly bracket)
3557  // and not after the if() since we restore the error
3558  // state from before the compile() call.
3559  //
3560  bool const result(!old_count.error_happened());
3561 
3562  // now restore the stream and error counters
3563  return result;
3564  }
3565 
3566  compile(true);
3567 
3568  return !old_count.error_happened();
3569 }
3570 
3572 {
3573  safe_parents_t safe_parents(f_state, n);
3574 
3575  switch(n->get_type())
3576  {
3578  {
3579  node::pointer_t rule_last(n);
3580  for(size_t idx(0); idx < n->size();)
3581  {
3582  node::pointer_t child(n->get_child(idx));
3583  expand_nested_rules(f_state.get_previous_parent(), n, rule_last, child);
3584  if(idx < n->size()
3585  && child == n->get_child(idx))
3586  {
3587  ++idx;
3588  }
3589  }
3590  }
3591  break;
3592 
3594  // this is true for all but one case, when @-keyword accepts
3595  // declarations instead of rules (like @font-face); we may want
3596  // to test that and use the correct call in the @-keyword...
3597  //error::instance() << n->get_position()
3598  // << "a declaration can only appears inside a rule."
3599  // << error_mode_t::ERROR_ERROR;
3600  for(size_t idx(0); idx < n->size();)
3601  {
3602  node::pointer_t child(n->get_child(idx));
3603  node::pointer_t declaration_root(n);
3604  expand_nested_declarations(n->get_string(), f_state.get_previous_parent(), declaration_root, child);
3605  if(idx < n->size()
3606  && child == n->get_child(idx))
3607  {
3608  ++idx;
3609  }
3610  }
3611  //if(n->empty())
3612  //{
3613  // f_state.get_previous_parent()->remove_child(n);
3614  //}
3615  break;
3616 
3617  case node_type_t::LIST:
3620  for(size_t idx(0); idx < n->size();)
3621  {
3622  node::pointer_t child(n->get_child(idx));
3623  expand_nested_components(child);
3624  if(idx < n->size()
3625  && child == n->get_child(idx))
3626  {
3627  ++idx;
3628  }
3629  }
3630  break;
3631 
3632  //case node_type_t::ARG: -- we should not have sub-declarations under ARG
3633  default:
3634  break;
3635 
3636  }
3637 }
3638 
3640 {
3641  safe_parents_t safe_parents(f_state, n);
3642 
3643  switch(n->get_type())
3644  {
3646  //
3647  // before this expansion the declarations are like:
3648  //
3649  // COMPONENT_VALUE
3650  // ARG
3651  // ...
3652  // OPEN_CURLYBRACKET
3653  // LIST
3654  // DECLARATION
3655  // ARG
3656  // ...
3657  // DECLARATION
3658  // ARG
3659  // ...
3660  // COMPONENT_VALUE <-- expand this one with the first one
3661  // ARG
3662  // ...
3663  // OPEN_CURLYBRACKET
3664  // ...
3665  //
3666  // so what we do is move the sub-declaration at the same level as the
3667  // parent and prepend the name of the parent + "-".
3668  //
3669  {
3670  // move the rule as a child of the parent node
3671  f_state.get_previous_parent()->remove_child(n);
3672  size_t pos(parent->child_position(last));
3673  parent->insert_child(pos + 1, n);
3674 
3675  // prepend the arguments of root to the arguments of n
3676  // note that this is a product, if root has 3 ARGs and
3677  // n also has 3 ARGs, we end up with 9 ARGs in n
3678  node::pointer_t list(new node(node_type_t::LIST, n->get_position()));
3679  while(!n->empty())
3680  {
3681  node::pointer_t child(n->get_child(0));
3682  if(!child->is(node_type_t::ARG))
3683  {
3684  break;
3685  }
3686  n->remove_child(0);
3687  list->add_child(child);
3688  }
3689  for(size_t idx(0); idx < root->size(); ++idx)
3690  {
3691  node::pointer_t child(root->get_child(idx));
3692  if(!child->is(node_type_t::ARG))
3693  {
3694  break;
3695  }
3696  // we use clone because of the product
3697  node::pointer_t clone(child->clone());
3698  for(size_t l(0); l < list->size(); ++l)
3699  {
3700  node::pointer_t arg(list->get_child(l));
3701  // we use clone because of the product
3702  for(size_t a(0); a < arg->size(); ++a)
3703  {
3704  node::pointer_t item(arg->get_child(a));
3705  if(!item->is(node_type_t::REFERENCE))
3706  {
3707  if(a == 0)
3708  {
3709  node::pointer_t whitespace(new node(node_type_t::WHITESPACE, clone->get_position()));
3710  clone->add_child(whitespace);
3711  }
3712  clone->add_child(item->clone());
3713  }
3714  }
3715  }
3716  n->insert_child(idx, clone);
3717  }
3718 
3719  last = n;
3720 
3721  for(size_t idx(0); idx < n->size();)
3722  {
3723  node::pointer_t child(n->get_child(idx));
3724  node::pointer_t rule_root(n);
3725  expand_nested_rules(parent, rule_root, last, child);
3726  if(idx < n->size()
3727  && child == n->get_child(idx))
3728  {
3729  ++idx;
3730  }
3731  }
3732  }
3733  break;
3734 
3736  for(size_t idx(0); idx < n->size();)
3737  {
3738  node::pointer_t child(n->get_child(idx));
3739  node::pointer_t declaration_root(n);
3740  expand_nested_declarations(n->get_string(), f_state.get_previous_parent(), declaration_root, child);
3741  if(idx < n->size()
3742  && child == n->get_child(idx))
3743  {
3744  ++idx;
3745  }
3746  }
3747  if(n->empty())
3748  {
3749  f_state.get_previous_parent()->remove_child(n);
3750  }
3751  break;
3752 
3754  for(size_t idx(0); idx < n->size();)
3755  {
3756  node::pointer_t child(n->get_child(idx));
3757  expand_nested_components(child);
3758  if(idx < n->size()
3759  && child == n->get_child(idx))
3760  {
3761  ++idx;
3762  }
3763  }
3764  break;
3765 
3766  case node_type_t::LIST:
3768  for(size_t idx(0); idx < n->size();)
3769  {
3770  node::pointer_t child(n->get_child(idx));
3771  expand_nested_rules(parent, root, last, child);
3772  if(idx < n->size()
3773  && child == n->get_child(idx))
3774  {
3775  ++idx;
3776  }
3777  }
3778  break;
3779 
3780  //case node_type_t::ARG: -- we should not have sub-declarations under ARG
3781  default:
3782  break;
3783 
3784  }
3785 }
3786 
3788 {
3789  safe_parents_t safe_parents(f_state, n);
3790 
3791  switch(n->get_type())
3792  {
3794  //
3795  // before this expansion the declarations are like:
3796  //
3797  // DECLARATION
3798  // ARG
3799  // ...
3800  // OPEN_CURLYBRACKET
3801  // LIST
3802  // DECLARATION <-- expand this one with the first one
3803  // ARG
3804  // ...
3805  // DECLARATION <-- expand this too, also with the first one
3806  // ARG
3807  // ...
3808  //
3809  // so what we do is move the sub-declaration at the same level as the
3810  // parent and prepend the name of the parent + "-".
3811  //
3812  {
3813  std::string const sub_name((name == "-csspp-null" ? "" : name + "-") + n->get_string());
3814 
3815  // move this declaration from where it is now to the root
3816  f_state.get_previous_parent()->remove_child(n);
3817  size_t pos(parent->child_position(root));
3818  parent->insert_child(pos + 1, n);
3819  n->set_string(sub_name);
3820  root = n;
3821 
3822  for(size_t idx(0); idx < n->size();)
3823  {
3824  node::pointer_t child(n->get_child(idx));
3825  expand_nested_declarations(sub_name, parent, root, child);
3826  if(idx < n->size()
3827  && child == n->get_child(idx))
3828  {
3829  ++idx;
3830  }
3831  }
3832 
3833  // remove empty declarations
3834  //if(n->empty())
3835  //{
3836  // f_state.get_previous_parent()->remove_child(n);
3837  //}
3838  }
3839  break;
3840 
3842  // we may have to handle declarations within an @-keyword, but
3843  // it is not a sub-expand-nested-declaration
3844  throw csspp_exception_logic("compiler.cpp:compiler::expand_nested_declarations(): @-keyword cannot appear within a declaration."); // LCOV_EXCL_LINE
3845  //for(size_t idx(0); idx < n->size();)
3846  //{
3847  // node::pointer_t child(n->get_child(idx));
3848  // expand_nested_components(child);
3849  // if(idx < n->size()
3850  // && child == n->get_child(idx))
3851  // {
3852  // ++idx;
3853  // }
3854  //}
3855  //break;
3856 
3857  case node_type_t::LIST:
3859  for(size_t idx(0); idx < n->size();)
3860  {
3861  node::pointer_t child(n->get_child(idx));
3862  expand_nested_declarations(name, parent, root, child);
3863  if(idx < n->size()
3864  && child == n->get_child(idx))
3865  {
3866  ++idx;
3867  }
3868  }
3869  if(n->empty())
3870  {
3871  f_state.get_previous_parent()->remove_child(n);
3872  }
3873  break;
3874 
3876  error::instance() << n->get_position()
3877  << "a nested declaration cannot include a rule."
3879  break;
3880 
3881  //case node_type_t::ARG: -- we should not have sub-declarations under ARG
3882  default:
3883  break;
3884 
3885  }
3886 }
3887 
3888 } // namespace csspp
3889 
3890 // Local Variables:
3891 // mode: cpp
3892 // indent-tabs-mode: nil
3893 // c-basic-offset: 4
3894 // tab-width: 4
3895 // End:
3896 
3897 // vim: ts=4 sw=4 et
void prepare_function_arguments(node::pointer_t var)
Definition: compiler.cpp:2209
void replace_else(node::pointer_t parent, node::pointer_t n, size_t idx)
Definition: compiler.cpp:2454
void push_parent(node::pointer_t parent)
Definition: compiler.cpp:115
node::pointer_t get_previous_parent() const
Definition: compiler.cpp:130
void set_no_logo(bool no_logo=true)
Definition: compiler.cpp:499
void replace_if(node::pointer_t parent, node::pointer_t n, size_t idx)
Definition: compiler.cpp:2403
void expand_nested_components(node::pointer_t n)
Definition: compiler.cpp:3571
void expand_nested_rules(node::pointer_t parent, node::pointer_t root, node::pointer_t &last, node::pointer_t n)
Definition: compiler.cpp:3639
static bool is_nested_declaration(node::pointer_t n)
Definition: parser.cpp:700
bool run_validation(bool check_only)
Definition: compiler.cpp:3528
std::shared_ptr< node > pointer_t
Definition: node.h:122
int64_t integer_t
Definition: csspp.h:52
void set_variable(node::pointer_t n)
Definition: compiler.cpp:2088
node::pointer_t get_result() const
Definition: compiler.cpp:415
void compile_component_value(node::pointer_t n)
Definition: compiler.cpp:653
void add_path(std::string const &path)
Definition: compiler.cpp:509
void expand_nested_declarations(std::string const &name, node::pointer_t parent, node::pointer_t &root, node::pointer_t n)
Definition: compiler.cpp:3787
compiler::compiler_state_t & f_state
Definition: compiler.cpp:66
integer_t get_nth() const
Definition: nth_child.cpp:102
void compile_declaration_values(node::pointer_t declaration)
Definition: compiler.cpp:1084
static bool is_variable_set(node::pointer_t n, bool with_block)
Definition: parser.cpp:664
bool selector_term(node::pointer_t n, size_t &pos)
Definition: compiler.cpp:3116
node::pointer_t stylesheet()
Definition: parser.cpp:70
static int const g_to_string_flag_show_quotes
Definition: node.h:125
void remove_empty_rules(node::pointer_t n)
Definition: compiler.cpp:1637
void compile(bool bare)
Definition: compiler.cpp:514
bool parse(std::string const &an_plus_b)
Definition: nth_child.cpp:112
void set_validation_script(std::string const &script_name)
Definition: compiler.cpp:3439
virtual node::pointer_t execute_user_function(node::pointer_t func)
Definition: compiler.cpp:203
void set_root(node::pointer_t root)
Definition: compiler.cpp:405
static bool argify(node::pointer_t n, node_type_t const separator=node_type_t::COMMA)
Definition: parser.cpp:786
void set_empty_on_undefined_variable(bool const empty_on_undefined_variable)
Definition: compiler.cpp:349
virtual node::pointer_t get_variable(std::string const &variable_name, bool global_only=false) const
Definition: compiler.cpp:171
void mark_selectors(node::pointer_t n)
Definition: compiler.cpp:1589
bool f_compiler_validating
Definition: compiler.h:127
void set_paths(compiler_state_t const &state)
Definition: compiler.cpp:109
void add_path(std::string const &path)
Definition: compiler.cpp:104
std::string find_file(std::string const &script_name)
Definition: compiler.cpp:359
void compile_declaration(node::pointer_t n)
Definition: compiler.cpp:864
bool selector_attribute_check(node::pointer_t parent, size_t &parent_pos, node::pointer_t n)
Definition: compiler.cpp:2684
node::pointer_t f_current_validation_script
Definition: compiler.h:125
std::shared_ptr< lexer > pointer_t
Definition: lexer.h:28
node::pointer_t get_root() const
Definition: compiler.cpp:94
void replace_variables(node::pointer_t n)
Definition: compiler.cpp:1684
std::string find_file(std::string const &script_name)
Definition: compiler.cpp:3434
void set_root(node::pointer_t root)
Definition: compiler.cpp:88
void add_header_and_footer()
Definition: compiler.cpp:566
static bool boolean(node::pointer_t n)
Check whether a node represents true or false.
void replace_variable(node::pointer_t parent, node::pointer_t n, size_t &idx)
Definition: compiler.cpp:1802
bool selector_list(node::pointer_t n, size_t &pos)
Definition: compiler.cpp:3279
void set_empty_on_undefined_variable(bool const empty_on_undefined_variable)
Definition: compiler.cpp:494
bool get_empty_on_undefined_variable() const
Definition: compiler.cpp:354
node::pointer_t f_return_result
Definition: compiler.h:126
void compile_at_keyword(node::pointer_t n)
Definition: compiler.cpp:1149
node::pointer_t at_keyword_expression(node::pointer_t n)
Definition: compiler.cpp:2390
void replace_variables_in_comment(node::pointer_t n)
Definition: compiler.cpp:2582
void add_validation_variable(std::string const &variable_name, node::pointer_t value)
Definition: compiler.cpp:3512
safe_compiler_state_t(compiler::compiler_state_t &state)
Definition: compiler.cpp:72
compiler::compiler_state_t & f_state
Definition: compiler.cpp:84
void set_variable(node::pointer_t variable, node::pointer_t value, bool global) const
Definition: compiler.cpp:141
compiler(bool validating=false)
Definition: compiler.cpp:399
safe_parents_t(compiler::compiler_state_t &state, node::pointer_t n)
Definition: compiler.cpp:54
void compile_args(bool divide_font_metrics)
Definition: expr_unary.cpp:40
node::pointer_t get_root() const
Definition: compiler.cpp:410
void replace_import(node::pointer_t parent, node::pointer_t import, size_t &idx)
Definition: compiler.cpp:1351
void clear_paths()
Definition: compiler.cpp:504
void handle_mixin(node::pointer_t n)
Definition: compiler.cpp:1527
compiler::compiler_state_t f_state_copy
Definition: compiler.cpp:85
bool selector_simple_term(node::pointer_t n, size_t &pos)
Definition: compiler.cpp:2832
void replace_at_keyword(node::pointer_t parent, node::pointer_t n, size_t &idx)
Definition: compiler.cpp:2282
void compile_qualified_rule(node::pointer_t n)
Definition: compiler.cpp:796
void set_variable_handler(expression_variables_interface *handler)
Definition: expression.cpp:49
node::pointer_t compile()
Definition: expression.cpp:54
std::string get_error() const
Definition: nth_child.cpp:107
compiler_state_t f_state
Definition: compiler.h:122
static error & instance()
Definition: error.cpp:78
void set_date_time_variables(time_t now)
Definition: compiler.cpp:420
bool error_happened() const
Definition: error.cpp:285
bool parse_selector(node::pointer_t n)
Definition: compiler.cpp:3361

Documentation of CSS Preprocessor.

This document is part of the Snap! Websites Project.

Copyright by Made to Order Software Corp.