Current Version: 1.0.20
Project Name: csspp
compiler.cpp
Go to the documentation of this file.
1 // CSS Preprocessor
2 // Copyright (c) 2015-2018 Made to Order Software Corp. All Rights Reserved
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  {
80  f_state = f_state_copy;
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  case node_type_t::FRAME:
612  // transparent item, just compile all the children
613  {
614  size_t idx(0);
615  while(idx < n->size() && !f_return_result)
616  {
617  node::pointer_t child(n->get_child(idx));
618  compile(child);
619 
620  // the child may replace itself with something else
621  // in which case we do not want the ++idx
622  if(idx < n->size()
623  && n->get_child(idx) == child)
624  {
625  ++idx;
626  }
627  }
628  // TODO: remove LIST if it now is empty or has 1 item
629  }
630  break;
631 
633  // because of lists of compolent values this can happen...
634  // we just ignore those since it is already compiled
635  //
636  // (we get it to happen with @framekeys ... { ... } within the list
637  // of declarations at a given position)
638  //
639  break;
640 
643  break;
644 
647  break;
648 
650  // passthrough tokens
651  break;
652 
653  default:
654  {
655  std::stringstream ss;
656  ss << "unexpected token (type: " << n->get_type() << ") in compile().";
657  throw csspp_exception_unexpected_token(ss.str());
658  }
659 
660  }
661 }
662 
664 {
665  // already compiled?
666  if(n->is(node_type_t::DECLARATION))
667  {
668  // This is really double ugly, I'll have to look into getting
669  // my loops straighten up because having to test such in
670  // various places is bad!
671  //
672  // We may want to find a better way to skip these entries...
673  // we replace a COMPONENT_VALUE with a DECLARATION and return
674  // and the loops think that the COMPONENT_VALUE was "replaced"
675  // by new code that needs to be compiled; only we replaced the
676  // entry with already compiled data! The best way may be to have
677  // a state with a position that we pass around...
678  return;
679  }
680 
681  // there are quite a few cases to handle here:
682  //
683  // $variable ':' '{' ... '}'
684  // <field-prefix> ':' '{' ... '}'
685  // <selector-list> '{' ... '}'
686  // $variable ':' ...
687  // <field-name> ':' ...
688  //
689 
690  if(n->empty())
691  {
692  // we have a problem, we should already have had an error
693  // somewhere?
694  return; // LCOV_EXCL_LINE
695  }
696 
697  if(n->get_child(0)->is(node_type_t::COMMENT))
698  {
699  // XXX: verify that this is the right location to chek this
700  // special case, we may want to do it only in the loop
701  // that also accepts plain comments instead of here
702  // which is a function that can get called from deep
703  // inside...
704 
705  // get parent of n, remove n from there, replace it by
706  // the comment
708  size_t pos(parent->child_position(n));
709  parent->remove_child(pos);
710  parent->insert_child(pos, n->get_child(0));
711  return;
712  }
713 
714  // was that COMPONENT_VALUE already compiled?
715  if(n->get_child(0)->is(node_type_t::ARG))
716  {
717  // the following fix prevents this from happening so at this time
718  // I mark this as a problem; we could just return otherwise
719  // (like the case the list is a declaration)
720  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
721  }
722 
723  size_t const max_children(n->size());
724  size_t count_cv(0);
725  for(size_t idx(0); idx < max_children; ++idx)
726  {
727  node::pointer_t child(n->get_child(idx));
728  if(child->is(node_type_t::COMPONENT_VALUE))
729  {
730  ++count_cv;
731  }
732  }
733  if(count_cv == max_children)
734  {
735  // this happens when we add elements from a sub {}-block
736  // for example, a verbatim:
737  //
738  // @if (true) { foo { a: b; } blah { c: d; } }
739  //
741  size_t pos(parent->child_position(n));
742  parent->remove_child(pos);
743  for(size_t idx(0); idx < max_children; ++idx, ++pos)
744  {
745  parent->insert_child(pos, n->get_child(idx));
746  }
747  // the caller will call us again with the new list of
748  // COMPONENT_VALUE nodes as expected
749  return;
750  }
751  else if(count_cv != 0)
752  {
753  std::cerr << "Invalid node:\n" << *n; // LCOV_EXCL_LINE
754  throw csspp_exception_logic("compiler.cpp: found a COMPONENT_VALUE with a mix of children."); // LCOV_EXCL_LINE
755  }
756 
757  // this may be only temporary (until I fix the parser) but at this
758  // point we may get an @-keyword inside a COMPONENT_VALUE
759  if(n->get_child(0)->is(node_type_t::AT_KEYWORD))
760  {
761  {
762  safe_parents_t safe_parents(f_state, n->get_child(0));
763  compile_at_keyword(n->get_child(0));
764  }
765  if(n->empty())
766  {
767  // the @-keyword was removed and now the COMPONENT_VALUE is
768  // empty so we can just get rid of it
769  f_state.get_previous_parent()->remove_child(n);
770  }
771  return;
772  }
773 
774  // $variable ':' '{' ... '}'
775  if(parser::is_variable_set(n, true))
776  {
777  throw csspp_exception_logic("compiler.cpp: somehow a variable definition was found while compiling (1)."); // LCOV_EXCL_LINE
778  }
779 
780  // <field-prefix> ':' '{' ... '}'
782  {
784  return;
785  }
786 
787  // <selector-list> '{' ... '}'
788  if(n->get_last_child()->is(node_type_t::OPEN_CURLYBRACKET))
789  {
790  // this is a selector list followed by a block of
791  // definitions and sub-blocks
793  return;
794  }
795 
796  // $variable ':' ... ';'
797  if(parser::is_variable_set(n, false))
798  {
799  throw csspp_exception_logic("compiler.cpp: somehow a variable definition was found while compiling (1)."); // LCOV_EXCL_LINE
800  }
801 
802  // <field-name> ':' ...
804 }
805 
807 {
808  // so here we have a list of selectors, that means we can verify
809  // that said list is valid (i.e. binary operators are used properly,
810  // only valid operators were used, etc.)
811 
812  // any selectors?
813  if(n->size() <= 1)
814  {
815  error::instance() << n->get_position()
816  << "a qualified rule without selectors is not valid."
818  return;
819  }
820 
821  // compile the selectors using a node parser
822  // \ref selectors_rules#grammar
823  if(!parse_selector(n))
824  {
825  // an error occurred, forget this entry and move on
826  return;
827  }
828 
829  // compile the block of contents
830  node::pointer_t brackets(n->get_last_child());
831  if(brackets->empty())
832  {
833  // an empty block is perfectly valid, it means the whole rule
834  // "exists" but is really useless; in SCSS it could be useful
835  // for @extends and %placeholder to have such empty rules
836  //error::instance() << n->get_position()
837  // << "the {}-block of a qualified rule is missing."
838  // << error_mode_t::ERROR_ERROR;
839  return;
840  }
841 
842  safe_parents_t safe_parents(f_state, brackets);
843 
844  for(size_t b(0); b < brackets->size();)
845  {
846  node::pointer_t child(brackets->get_child(b));
847  safe_parents_t safe_list_parents(f_state, child);
848  if(child->is(node_type_t::LIST))
849  {
850  for(size_t idx(0); idx < child->size();)
851  {
852  node::pointer_t item(child->get_child(idx));
853  safe_parents_t safe_sub_parents(f_state, item);
855  if(idx < child->size()
856  && item == child->get_child(idx))
857  {
858  ++idx;
859  }
860  }
861  }
862  else if(child->is(node_type_t::COMPONENT_VALUE))
863  {
865  }
866  if(b < brackets->size()
867  && child == brackets->get_child(b))
868  {
869  ++b;
870  }
871  }
872 }
873 
875 {
876  // already compiled?
877  if(n->is(node_type_t::DECLARATION))
878  {
879  // We may want to find a better way to skip these entries...
880  // we replace a COMPONENT_VALUE with a DECLARATION and return
881  // and the loops think that the COMPONENT_VALUE was "replaced"
882  // by new code that needs to be compiled; only we replaced the
883  // entry with already compiled data! The best way may be to have
884  // a state with a position that we pass around...
885  return;
886  }
887  if(n->size() < 2)
888  {
889  // A "declaration" without the ':' and values reaches here:
890  // font-style; italic; (notice the ';' instead of ':')
891  error::instance() << n->get_position()
892  << "somehow a declaration list is missing a field name or ':'."
894  return;
895  }
896 
897  // first make sure we have a declaration
898  // (i.e. IDENTIFIER WHITESPACE ':' ...)
899  //
900  node::pointer_t identifier(n->get_child(0));
901  if(!identifier->is(node_type_t::IDENTIFIER))
902  {
903  if((identifier->is(node_type_t::MULTIPLY)
904  || identifier->is(node_type_t::PERIOD))
905  && n->get_child(1)->is(node_type_t::IDENTIFIER))
906  {
907  // get rid of the asterisk or period
908  // This was an IE 7 and earlier web browser trick to allow
909  // various CSS entries only for IE...
910  n->remove_child(0);
911  identifier = n->get_child(0);
912  error::instance() << identifier->get_position()
913  << "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."
915  }
916  else if(identifier->is(node_type_t::HASH)
917  || identifier->is(node_type_t::EXCLAMATION))
918  {
919  // the !<id> and #<id> will be converted to a declaration so
920  // we do not need to do anything more about it
921  //
922  // This was an IE 7 and earlier web browser trick to allow
923  // various CSS entries only for IE...
924  error::instance() << identifier->get_position()
925  << "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."
927  }
928  else
929  {
930  error::instance() << identifier->get_position()
931  << "expected an identifier to start a declaration value; got a: " << identifier->get_type() << " instead."
933  return;
934  }
935  }
936 
937  // the WHITESPACE is optional, if present, remove it
938  node::pointer_t next(n->get_child(1));
939  if(next->is(node_type_t::WHITESPACE))
940  {
941  n->remove_child(1);
942  next = n->get_child(1);
943  }
944 
945  // now we must have a COLON, also remove that COLON
946  if(!next->is(node_type_t::COLON))
947  {
948  error::instance() << n->get_position()
949  << "expected a ':' after the identifier of this declaration value; got a: " << n->get_type() << " instead."
951  return;
952  }
953  n->remove_child(1);
954 
955  if(n->size() < 2)
956  {
957  error::instance() << n->get_position()
958  << "somehow a declaration list is missing fields, this happens if you used an invalid variable."
960  return;
961  }
962 
963  // no need to keep the next whitespace if there is one,
964  // plus we often do not expect such at the start of a
965  // list like we are about to generate.
966  if(n->get_child(1)->is(node_type_t::WHITESPACE))
967  {
968  n->remove_child(1);
969  }
970 
971  if(n->size() < 2)
972  {
973  error::instance() << n->get_position()
974  << "somehow a declaration list is missing fields, this happens if you used an invalid variable."
976  return;
977  }
978 
979  // create a declaration to replace the identifier
980  node::pointer_t declaration(new node(node_type_t::DECLARATION, n->get_position()));
981  declaration->set_string(identifier->get_string());
982 
983  // copy the following children as the children of the declaration
984  // (i.e. identifier is element 0, so we copy elements 1 to n)
985  size_t const max_children(n->size());
986  for(size_t i(1); i < max_children; ++i)
987  {
988  // since we are removing the children, we always seemingly
989  // copy child 1...
990  declaration->add_child(n->get_child(1));
991  n->remove_child(1);
992  }
993 
994  // now replace that identifier by its declaration in the parent
996  {
997  // replace the COMPONENT_VALUE instead of the identifier
998  // (this happens when a component value has multiple entries)
999  f_state.get_previous_parent()->replace_child(n, declaration);
1000  }
1001  else
1002  {
1003  throw csspp_exception_logic("compiler.cpp: got a node which was not of type COMPONENT_VALUE to replace with the DECLARATION."); // LCOV_EXCL_LINE
1004  //n->replace_child(identifier, declaration);
1005  }
1006  // now the declaration is part of the stack of parents
1007  safe_parents_t safe_declaration_parent(f_state, declaration);
1008 
1009  // apply the expression parser on the parameters
1010  // TODO: test that stuff a lot better, right now it does not look correct...
1011  bool compile_declaration_items(!declaration->empty() && !declaration->get_child(0)->is(node_type_t::OPEN_CURLYBRACKET));
1012  if(compile_declaration_items)
1013  {
1014  for(size_t i(0); i < declaration->size(); ++i)
1015  {
1016  node::pointer_t item(declaration->get_child(i));
1017  switch(item->get_type())
1018  {
1019  //case node_type_t::OPEN_CURLYBRACKET:
1020  case node_type_t::LIST:
1022  // This means we have a cascade, a field declaration which has
1023  // sub-fields (font-<name>, border-<name>, etc.)
1024  compile_declaration_items = false;
1025  break;
1026 
1027  default:
1028  break;
1029 
1030  }
1031  }
1032  }
1033 
1034  if(compile_declaration_items
1035  && !declaration->empty())
1036  {
1037  node::pointer_t child(declaration->get_child(0));
1038 
1039  bool const ignore(
1040  (child->is(node_type_t::FUNCTION)
1041  && (child->get_string() == "alpha" || child->get_string() == "chroma" || child->get_string() == "gray" || child->get_string() == "opacity")
1042  && (declaration->get_string() == "filter" || declaration->get_string() == "-filter"))
1043  ||
1044  (child->is(node_type_t::IDENTIFIER)
1045  && child->get_string() == "progid"
1046  && (declaration->get_string() == "filter" || declaration->get_string() == "-filter"))
1047  );
1048 
1049  if(ignore)
1050  {
1051  // Note: the progid does not mean that the function used is
1052  // alpha(), but its fairly likely
1053  //
1054  // we probably should remove such declarations, but if we want
1055  // to have functions that output such things, it is important to
1056  // support this horrible field...
1057  //
1058  // alpha() was for IE8 and earlier, now opacity works
1059  error::instance() << child->get_position()
1060  << "the alpha(), chroma() and similar functions of the filter field are Internet Explorer specific extensions which are not supported across browsers."
1062  }
1063 
1064  if(!ignore)
1065  {
1066  // ':' IDENTIFIER
1067 
1068  node::pointer_t declaration_name(new node(node_type_t::STRING, declaration->get_position()));
1069  declaration_name->set_string(declaration->get_string());
1070 
1071  // check the identifier, if "has-font-metrics" is true, then
1072  // slashes are viewed as the font metrics separator
1073  //
1074  set_validation_script("validation/has-font-metrics");
1075  add_validation_variable("field_name", declaration_name);
1076  bool const divide_font_metrics(run_validation(true));
1077 
1078  // if slash-separator returns true then slash (if present)
1079  // is a separator like a comma in a list of arguments
1080  set_validation_script("validation/slash-separator");
1081  add_validation_variable("field_name", declaration_name);
1082  bool const slash_separators(run_validation(true));
1083 
1084  parser::argify(declaration, slash_separators ? node_type_t::DIVIDE : node_type_t::COMMA);
1085  expression args_expr(declaration);
1086  args_expr.set_variable_handler(&f_state);
1087  args_expr.compile_args(divide_font_metrics);
1088  }
1089  }
1090 
1091  compile_declaration_values(declaration);
1092 }
1093 
1095 {
1096  // finally compile the parameters of the declaration
1097  for(size_t i(0); i < declaration->size(); ++i)
1098  {
1099  node::pointer_t item(declaration->get_child(i));
1100  safe_parents_t safe_parents(f_state, item);
1101  switch(item->get_type())
1102  {
1103  case node_type_t::LIST:
1104  // handle lists recursively
1106  break;
1107 
1109  // nested declarations, this {}-block includes sub-field names
1110  // (i.e. names that will be added this this declaration after a
1111  // dash (-) and with the name of the fields appearing here)
1112  for(size_t j(0); j < item->size();)
1113  {
1114  node::pointer_t component(item->get_child(j));
1115  safe_parents_t safe_grand_parents(f_state, component);
1116  if(component->is(node_type_t::LIST))
1117  {
1118  compile_declaration_values(component);
1119  }
1120  else if(component->is(node_type_t::COMPONENT_VALUE))
1121  {
1122  compile_component_value(component);
1123  }
1124  else if(component->is(node_type_t::DECLARATION))
1125  {
1126  // this was compiled, ignore
1127  }
1128  else
1129  {
1130  // it looks like I cannot get here anymore
1131  std::stringstream errmsg; // LCOV_EXCL_LINE
1132  errmsg << "compiler.cpp: found unexpected node type " // LCOV_EXCL_LINE
1133  << component->get_type() // LCOV_EXCL_LINE
1134  << ", expected a LIST."; // LCOV_EXCL_LINE
1135  throw csspp_exception_logic(errmsg.str()); // LCOV_EXCL_LINE
1136  }
1137  if(j < item->size()
1138  && component == item->get_child(j))
1139  {
1140  ++j;
1141  }
1142  }
1143  break;
1144 
1147  break;
1148 
1149  default:
1150  //error::instance() << n->get_position()
1151  // << "found a node of type " << item->get_type() << " in a declaration."
1152  // << error_mode_t::ERROR_ERROR;
1153  break;
1154 
1155  }
1156  }
1157 }
1158 
1160 {
1161  std::string const at(n->get_string());
1162 
1164  node::pointer_t expr(!n->empty() && !n->get_child(0)->is(node_type_t::OPEN_CURLYBRACKET) ? n->get_child(0) : node::pointer_t());
1165 
1166  if(at == "error")
1167  {
1168  parent->remove_child(n);
1169 
1170  error::instance() << n->get_position()
1171  << (expr ? expr->to_string(0) : std::string("@error reached"))
1173  return;
1174  }
1175 
1176  if(at == "warning")
1177  {
1178  parent->remove_child(n);
1179 
1180  error::instance() << n->get_position()
1181  << (expr ? expr->to_string(0) : std::string("@warning reached"))
1183  return;
1184  }
1185 
1186  if(at == "info"
1187  || at == "message")
1188  {
1189  parent->remove_child(n);
1190 
1191  error::instance() << n->get_position()
1192  << (expr ? expr->to_string(0) : std::string("@message reached"))
1194  return;
1195  }
1196 
1197  if(at == "debug")
1198  {
1199  parent->remove_child(n);
1200 
1201  error::instance() << n->get_position()
1202  << (expr ? expr->to_string(0) : std::string("@debug reached"))
1204  return;
1205  }
1206 
1207  if(at == "charset")
1208  {
1209  // we do not keep the @charset, we always default to UTF-8
1210  // on all sides (i.e. HTML, XHTML, XML, CSS, even JS...)
1211  // the assembler could re-introduce such, but again, not required
1212  parent->remove_child(n);
1213 
1214  if(n->size() != 1
1215  || !n->get_child(0)->is(node_type_t::STRING))
1216  {
1217  error::instance() << n->get_position()
1218  << "the @charset is expected to be followed by exactly one string."
1220  return;
1221  }
1222 
1223  std::string charset(n->get_child(0)->get_string());
1224  while(!charset.empty() && std::isspace(charset[0]))
1225  {
1226  charset.erase(charset.begin(), charset.begin() + 1);
1227  }
1228  while(!charset.empty() && std::isspace(charset.back()))
1229  {
1230  charset.erase(charset.end() - 1, charset.end());
1231  }
1232  for(auto & c : charset)
1233  {
1234  c = std::tolower(c);
1235  }
1236  if(charset != "utf-8")
1237  {
1238  error::instance() << n->get_position()
1239  << "we only support @charset \"utf-8\";, any other encoding is refused."
1241  return;
1242  }
1243  return;
1244  }
1245 
1246  // TODO: use a validation to determine the list of @-keyword that need
1247  // parsing as a qualified rule, a component value, or no parsing
1248  // and others are unsupported/unknown (i.e. generate an error)
1249  if(at == "document"
1250  || at == "media"
1251  || at == "supports")
1252  {
1253  if(!n->empty())
1254  {
1255  node::pointer_t last(n->get_last_child());
1256  if(last->is(node_type_t::OPEN_CURLYBRACKET))
1257  {
1258  if(n->size() > 1)
1259  {
1260  parser::argify(n);
1261  }
1262  safe_parents_t safe_list_parents(f_state, last);
1263  for(size_t idx(0); idx < last->size();)
1264  {
1265  node::pointer_t child(last->get_child(idx));
1266  safe_parents_t safe_parents(f_state, child);
1267  if(child->is(node_type_t::AT_KEYWORD))
1268  {
1269  // at this point @-keywords are added inside a
1270  // COMPONENT_VALUE so this does not happen...
1271  // if we change the parser at some point, this
1272  // may happen again so I keep it here
1273  compile_at_keyword(child); // LCOV_EXCL_LINE
1274  }
1275  else
1276  {
1277  compile_component_value(child);
1278  }
1279  if(idx < last->size()
1280  && child == last->get_child(idx))
1281  {
1282  ++idx;
1283  }
1284  }
1285  }
1286  }
1287  return;
1288  }
1289 
1290  if(at == "font-face"
1291  || at == "page"
1292  || at == "viewport"
1293  || at == "-ms-viewport")
1294  {
1295  if(!n->empty())
1296  {
1297  node::pointer_t last(n->get_last_child());
1298  if(last->is(node_type_t::OPEN_CURLYBRACKET))
1299  {
1300  if(last->size() > 0)
1301  {
1302  node::pointer_t list(last);
1303  if(last->get_child(0)->is(node_type_t::LIST))
1304  {
1305  list = last->get_child(0);
1306  }
1307  // we may be stacking the same node twice...
1308  safe_parents_t safe_grand_parents(f_state, last);
1309  safe_parents_t safe_parents(f_state, list);
1310  for(size_t idx(0); idx < list->size();)
1311  {
1312  node::pointer_t child(list->get_child(idx));
1313  // TODO: add support for other node types?
1314  if(child->is(node_type_t::COMPONENT_VALUE))
1315  {
1316  safe_parents_t safe_component_parents(f_state, child);
1317  compile_component_value(child);
1318  if(idx < list->size()
1319  && child == list->get_child(idx))
1320  {
1321  ++idx;
1322  }
1323  }
1324  else
1325  {
1326  ++idx;
1327  }
1328  }
1329  }
1330  }
1331  }
1332  return;
1333  }
1334 
1335  if(at == "return")
1336  {
1337  if(!expr)
1338  {
1339  error::instance() << n->get_position()
1340  << "@return must be followed by a valid expression."
1342  return;
1343  }
1344 
1345  // transform the @return <expr> in a one node result
1346  //
1347  expression return_expr(n);
1348  return_expr.set_variable_handler(&f_state);
1349  f_return_result = return_expr.compile();
1350  if(!f_return_result)
1351  {
1352  // the expression was erroneous but we cannot return
1353  // without a valid node otherwise we could end up
1354  // returning another value "legally"
1355  //
1356  // return a NULL as the result
1357  f_return_result.reset(new node(node_type_t::NULL_TOKEN, n->get_position()));
1358  }
1359 
1360  return;
1361  }
1362 
1363  if(at == "keyframes"
1364  || at == "-o-keyframes"
1365  || at == "-webkit-keyframes")
1366  {
1367  // the format of this @-at keyword is rather peculiar since
1368  // it expects the identifier "from" or "to" or a percentage
1369  // followed by a set of components defined between curly
1370  // brackets
1371  //
1372  // '@keyframes' <name> '{'
1373  // 'from' | 'to' | <number>'%' '{'
1374  // ... /* regular components */
1375  // '}'
1376  // ... /* repeat any number of key frames */
1377  // '}';
1378  //
1379  // the node tree looks like this:
1380  //
1381  // AT_KEYWORD "keyframes" I:0
1382  // IDENTIFIER "progress-bar-stripes"
1383  // OPEN_CURLYBRACKET B:true
1384  // COMPONENT_VALUE
1385  // IDENTIFIER "from"
1386  // OPEN_CURLYBRACKET B:true
1387  // LIST
1388  // COMPONENT_VALUE
1389  // IDENTIFIER "background-position"
1390  // COLON
1391  // WHITESPACE
1392  // INTEGER "px" I:40
1393  // WHITESPACE
1394  // INTEGER "" I:0
1395  // COMPONENT_VALUE
1396  // IDENTIFIER "left"
1397  // COLON
1398  // WHITESPACE
1399  // INTEGER "" I:0
1400  // COMPONENT_VALUE
1401  // PERCENT D:0.3
1402  // OPEN_CURLYBRACKET B:true
1403  // LIST
1404  // COMPONENT_VALUE
1405  // IDENTIFIER "background-position"
1406  // COLON
1407  // WHITESPACE
1408  // INTEGER "px" I:30
1409  // WHITESPACE
1410  // INTEGER "" I:0
1411  // COMPONENT_VALUE
1412  // IDENTIFIER "left"
1413  // COLON
1414  // WHITESPACE
1415  // INTEGER "px" I:20
1416  // COMPONENT_VALUE
1417  // PERCENT D:0.6
1418  // OPEN_CURLYBRACKET B:true
1419  // LIST
1420  // COMPONENT_VALUE
1421  // IDENTIFIER "background-position"
1422  // COLON
1423  // WHITESPACE
1424  // INTEGER "px" I:5
1425  // WHITESPACE
1426  // INTEGER "" I:0
1427  // COMPONENT_VALUE
1428  // IDENTIFIER "left"
1429  // COLON
1430  // WHITESPACE
1431  // INTEGER "px" I:27
1432  // COMPONENT_VALUE
1433  // IDENTIFIER "to"
1434  // OPEN_CURLYBRACKET B:true
1435  // LIST
1436  // COMPONENT_VALUE
1437  // IDENTIFIER "background-position"
1438  // COLON
1439  // WHITESPACE
1440  // INTEGER "" I:0
1441  // WHITESPACE
1442  // INTEGER "" I:0
1443  // COMPONENT_VALUE
1444  // IDENTIFIER "left"
1445  // COLON
1446  // WHITESPACE
1447  // INTEGER "px" I:35
1448 
1449 //std::cerr << "@keyframes before compiling... [" << *n << "]\n";
1450 
1451  if(n->size() != 2)
1452  {
1453  error::instance() << n->get_position()
1454  << "@keyframes must be followed by an identifier and '{' ... '}'."
1456  return;
1457  }
1458 
1459  // TBD: we may be able to write an expression instead?
1460  //
1461  node::pointer_t identifier(n->get_child(0));
1462  if(!identifier->is(node_type_t::IDENTIFIER))
1463  {
1464 //std::cerr << "ERROR compiling keyframes (1)\n";
1465  error::instance() << n->get_position()
1466  << "@keyframes must first be followed by an identifier."
1468  return;
1469  }
1470 
1471  node::pointer_t positions(n->get_child(1));
1472  if(!positions->is(node_type_t::OPEN_CURLYBRACKET))
1473  {
1474 //std::cerr << "ERROR compiling keyframes (2)\n";
1475  error::instance() << n->get_position()
1476  << "@keyframes must be followed by an identifier and '{' ... '}'."
1478  return;
1479  }
1480 
1481  // our list of frame positions
1482  //
1483  node::pointer_t list(new node(node_type_t::LIST, positions->get_position()));
1484 
1485  size_t const max_positions(positions->size());
1486  for(size_t idx(0); idx < max_positions; ++idx)
1487  {
1488  node::pointer_t component_value(positions->get_child(idx));
1489  if(!component_value->is(node_type_t::COMPONENT_VALUE))
1490  {
1491 //std::cerr << "ERROR compiling keyframes (3)\n";
1492  error::instance() << n->get_position()
1493  << "@keyframes is only expecting component values as child entries."
1495  return;
1496  }
1497  if(component_value->size() != 2)
1498  {
1499 //std::cerr << "ERROR compiling keyframes (4)\n";
1500  error::instance() << n->get_position()
1501  << "@keyframes is expected to be followed by an identifier or a percent number and a '{'."
1503  return;
1504  }
1505  node::pointer_t components(component_value->get_child(1));
1506  if(!components->is(node_type_t::OPEN_CURLYBRACKET))
1507  {
1508 //std::cerr << "ERROR compiling keyframes (5)\n";
1509  error::instance() << n->get_position()
1510  << "@keyframes is expected to be followed by an identifier or a percent number and a '{'."
1512  return;
1513  }
1514  if(components->size() != 1)
1515  {
1516 //std::cerr << "ERROR compiling keyframes (6)\n";
1517  error::instance() << n->get_position()
1518  << "@keyframes is expected to be followed by an identifier or a percent number and a '{'."
1520  return;
1521  }
1522  node::pointer_t component_list(components->get_child(0));
1523  if(component_list->is(node_type_t::COMPONENT_VALUE))
1524  {
1525  // if there is only one component value, there won't be a LIST
1526  //
1527  node::pointer_t sub_list(new node(node_type_t::LIST, positions->get_position()));
1528  sub_list->add_child(component_list);
1529  component_list = sub_list;
1530  }
1531  else if(!component_list->is(node_type_t::LIST))
1532  {
1533 //std::cerr << "ERROR compiling keyframes (7)\n";
1534  error::instance() << n->get_position()
1535  << "@keyframes is expected to be followed by an identifier or a percent number and a list of component values '{' ... '}'."
1537  return;
1538  }
1539 
1540  node::pointer_t position(component_value->get_child(0));
1541 
1542  decimal_number_t p(0.0);
1544  {
1545  std::string const l(position->get_string());
1546  if(l == "from")
1547  {
1548  p = 0.0;
1549  }
1550  else if(l == "to")
1551  {
1552  p = 1.0;
1553  }
1554  else
1555  {
1556 //std::cerr << "ERROR compiling keyframes (8)\n";
1557  error::instance() << n->get_position()
1558  << "@keyframes position can be \"from\" or \"to\", other identifiers are not supported."
1560  return;
1561  }
1562  }
1563  else if(position->is(node_type_t::PERCENT))
1564  {
1565  p = position->get_decimal_number();
1566  if(p < 0.0 || p > 1.0)
1567  {
1568 //std::cerr << "ERROR compiling keyframes (9)\n";
1569  error::instance() << n->get_position()
1570  << "@keyframes position must be a percentage between 0% and 100%."
1572  return;
1573  }
1574  }
1575  else
1576  {
1577 //std::cerr << "ERROR compiling keyframes (10)\n";
1578  error::instance() << n->get_position()
1579  << "@keyframes positions must either be \"from\" or \"to\" or a percent number between 0% and 100%."
1581  return;
1582  }
1583 
1584  //compile(component_list);
1585 
1586  // create a frame
1587  //
1588  node::pointer_t frame(new node(node_type_t::FRAME, position->get_position()));
1589  frame->set_decimal_number(p);
1590  frame->take_over_children_of(component_list);
1591 
1592  list->add_child(frame);
1593 
1594 //std::cerr << "frame component list ready? [" << *frame << "]\n";
1595 
1596  // now compile the component list
1597  //
1598  compile(frame);
1599  }
1600 
1601  // first copy the list of frames
1602  //
1603  n->take_over_children_of(list);
1604 
1605  // then re-insert the identifier at the start
1606  //
1607  n->insert_child(0, identifier);
1608 
1609 //std::cerr << "@keyframes in compiler after reorganized: [" << *n << "]\n";
1610 
1611  return;
1612  }
1613 }
1614 
1616 {
1617  static_cast<void>(import);
1618 
1619  //
1620  // WARNING: we do NOT support the SASS extension of multiple entries
1621  // within one @import because it is not CSS 2 or CSS 3
1622  // compatible, not even remotely
1623  //
1624 
1625  // node 'import' is the @import itself
1626  //
1627  // @import string | url() [ media-list ] ';'
1628  //
1629 
1630  node::pointer_t expr(at_keyword_expression(import));
1631 
1632  // we only support arguments with one string
1633  // (@import accepts strings and url() as their first parameter)
1634  //
1635 //std::cerr << "replace @import!?\n";
1636 //if(expr) std::cerr << *expr;
1637 //std::cerr << "----------------------\n";
1638  if(expr
1639  && (expr->is(node_type_t::STRING)
1640  || expr->is(node_type_t::URL)))
1641  {
1642  std::string script_name(expr->get_string());
1643 
1644  if(expr->is(node_type_t::URL))
1645  {
1646  // we only support URIs that start with "file://"
1647  if(script_name.substr(0, 7) == "file://")
1648  {
1649  script_name = script_name.substr(7);
1650  if(script_name.empty()
1651  || script_name[0] != '/')
1652  {
1653  script_name = "/" + script_name;
1654  }
1655  }
1656  else
1657  {
1658  // not a type of URI we support
1659  ++idx;
1660  return;
1661  }
1662  }
1663  else
1664  {
1665  // TODO: add code to avoid testing with filenames that represent URIs
1666  std::string::size_type pos(script_name.find(':'));
1667  if(pos != std::string::npos
1668  && script_name.substr(pos, 3) == "://")
1669  {
1670  std::string const protocol(script_name.substr(0, pos));
1671  auto s(protocol.c_str());
1672  for(; *s != '\0'; ++s)
1673  {
1674  if((*s < 'a' || *s > 'z')
1675  && (*s < 'A' || *s > 'Z'))
1676  {
1677  break;
1678  }
1679  }
1680  if(*s == '\0')
1681  {
1682  if(protocol != "file")
1683  {
1684  ++idx;
1685  return;
1686  }
1687  script_name = script_name.substr(7);
1688  if(script_name.empty()
1689  || script_name[0] != '/')
1690  {
1691  script_name = "/" + script_name;
1692  }
1693  }
1694  //else -- not a valid protocol, so we assume it is
1695  // a weird filename and use it as is
1696  }
1697  }
1698 
1699  // search the corresponding file
1700  std::string filename(find_file(script_name));
1701  if(filename.empty() && script_name.length() > 5)
1702  {
1703  if(script_name.substr(script_name.size() - 5) != ".scss")
1704  {
1705  // try again with the "scss" extension
1706  filename = find_file(script_name + ".scss");
1707  }
1708  }
1709 
1710  // if still not found, we ignore
1711  if(!filename.empty())
1712  {
1713  // found an SCSS include, we remove that @import and replace
1714  // it (see below) with data as loaded from that file
1715  //
1716  // idx will not be incremented as a result
1717  //
1718  parent->remove_child(idx);
1719 
1720  // position object for this file
1721  position pos(filename);
1722 
1723  // TODO: do the necessary to avoid recursive @import
1724 
1725  // we found a file, load it and return it
1726  std::ifstream in;
1727  in.open(filename);
1728  if(!in)
1729  {
1730  // the script may not really allow reading even though
1731  // access() just told us otherwise
1732  error::instance() << pos // LCOV_EXCL_LINE
1733  << "validation script \"" // LCOV_EXCL_LINE
1734  << script_name // LCOV_EXCL_LINE
1735  << "\" could not be opened." // LCOV_EXCL_LINE
1736  << error_mode_t::ERROR_ERROR; // LCOV_EXCL_LINE
1737  }
1738  else
1739  {
1740  // the file got loaded, parse it and return the root node
1741  error_happened_t old_count;
1742 
1743  lexer::pointer_t l(new lexer(in, pos));
1744  parser p(l);
1745  node::pointer_t list(p.stylesheet());
1746 
1747  if(!old_count.error_happened())
1748  {
1749  // copy valid results at 'idx' which will then be
1750  // checked as if it had been part of that script
1751  // all along
1752  //
1753  size_t const max_results(list->size());
1754  for(size_t i(0), j(idx); i < max_results; ++i, ++j)
1755  {
1756  parent->insert_child(j, list->get_child(i));
1757  }
1758  }
1759  }
1760 
1761  // in this case we managed the entry fully
1762  return;
1763  }
1764  else if(script_name.empty())
1765  {
1766  error::instance() << expr->get_position()
1767  << "@import \"\"; and @import url(); are not valid."
1769  }
1770  else if(expr->is(node_type_t::URL))
1771  {
1772  error::instance() << expr->get_position()
1773  << "@import uri("
1774  << script_name
1775  << "); left alone by the CSS Preprocessor, no matching file found."
1777  }
1778  else
1779  {
1780  error::instance() << expr->get_position()
1781  << "@import \""
1782  << script_name
1783  << "\"; left alone by the CSS Preprocessor, no matching file found."
1785  }
1786  }
1787 
1788  ++idx;
1789 }
1790 
1792 {
1793  if(n->size() != 2)
1794  {
1795  error::instance() << n->get_position()
1796  << "a @mixin definition expects exactly two parameters: an identifier or function and a {}-block."
1798  return;
1799  }
1800 
1801  node::pointer_t block(n->get_child(1));
1802  if(!block->is(node_type_t::OPEN_CURLYBRACKET))
1803  {
1804  error::instance() << n->get_position()
1805  << "a @mixin definition expects a {}-block as its second parameter."
1807  return;
1808  }
1809 
1810  node::pointer_t name(n->get_child(0));
1811 
1812  // @mixin and @include do not accept $var[()] as a variable name
1813  // we make the error explicit
1814  if(name->is(node_type_t::VARIABLE)
1815  || name->is(node_type_t::VARIABLE_FUNCTION))
1816  {
1817  error::instance() << n->get_position()
1818  << "a @mixin must use an IDENTIFIER or FUNCTION and no a VARIABLE or VARIABLE_FUNCTION."
1820  return;
1821  }
1822 
1823  // TODO: Are @mixin always global?
1824  if(name->is(node_type_t::IDENTIFIER))
1825  {
1826  // this is just like a variable
1827  //
1828  // Note: here we are creating a variable with "name" IDENTIFIER
1829  // instead of VARIABLE as otherwise expected by the standard
1830  // variable handling
1831  //
1832  f_state.set_variable(name, block, true);
1833  }
1834  else if(name->is(node_type_t::FUNCTION))
1835  {
1836  // parse the arguments and then save the result
1838 
1839  // Note: here we are creating a variable with "name" FUNCTION
1840  // instead of VARIABLE_FUNCTION as otherwise expected by the
1841  // standard variable handling
1842  //
1843  f_state.set_variable(name, block, true);
1844  }
1845  else
1846  {
1847  error::instance() << n->get_position()
1848  << "a @mixin expects either an IDENTIFIER or a FUNCTION as its first parameter."
1850  }
1851 }
1852 
1854 {
1855  safe_parents_t safe_parents(f_state, n);
1856 
1857  switch(n->get_type())
1858  {
1860  //case node_type_t::ARG:
1863  case node_type_t::LIST:
1865  {
1866  // there are the few cases we can have here:
1867  //
1868  // $variable ':' '{' ... '}'
1869  // <field-prefix> ':' '{' ... '}' <-- this is one we're interested in (nested fields)
1870  // <selector-list> '{' ... '}' <-- this is one we're interested in (regular qualified rule)
1871  // $variable ':' ...
1872  // <field-name> ':' ...
1873  //
1874 
1875  if(!n->empty()
1876  && !parser::is_variable_set(n, true) // ! $variable ':' '{' ... '}'
1877  && n->get_last_child()->is(node_type_t::OPEN_CURLYBRACKET)) // <selector-list> '{' ... '}'
1878  {
1879  // this is a selector list followed by a block of
1880  // definitions and sub-blocks
1881  n->get_last_child()->set_boolean(true); // accept variables
1882  }
1883 
1884  // replace all $<var> references with the corresponding value
1885  for(size_t idx(0); idx < n->size(); ++idx)
1886  {
1887  // recursive call to handle all children in the
1888  // entire tree
1889  mark_selectors(n->get_child(idx));
1890  }
1891  }
1892  break;
1893 
1894  default:
1895  // other nodes are not of interest here
1896  break;
1897 
1898  }
1899 }
1900 
1902 {
1903  safe_parents_t safe_parents(f_state, n);
1904 
1905  switch(n->get_type())
1906  {
1908  if(!n->empty()
1909  && n->get_last_child()->is(node_type_t::OPEN_CURLYBRACKET)
1910  && n->get_last_child()->empty())
1911  {
1912  // that's an empty rule such as:
1913  // div {}
1914  // so get rid of it (i.e. optimization in output, no need for
1915  // empty rules, really)
1916  f_state.get_previous_parent()->remove_child(n);
1917  return;
1918  }
1919  /*FALLTHROUGH*/
1921  //case node_type_t::ARG:
1923  case node_type_t::LIST:
1925  // replace all $<var> references with the corresponding value
1926  for(size_t idx(0); idx < n->size();)
1927  {
1928  // recursive call to handle all children in the
1929  // entire tree; we need our special handling in
1930  // case something gets deleted
1931  node::pointer_t child(n->get_child(idx));
1932  remove_empty_rules(child);
1933  if(idx < n->size()
1934  && child == n->get_child(idx))
1935  {
1936  ++idx;
1937  }
1938  }
1939  break;
1940 
1941  default:
1942  // other nodes are not of interest here
1943  break;
1944 
1945  }
1946 }
1947 
1949 {
1950  safe_parents_t safe_parents(f_state, n);
1951 
1952  switch(n->get_type())
1953  {
1954  case node_type_t::LIST:
1955  if(n->empty())
1956  {
1957  // totally ignore empty lists
1958  f_state.get_previous_parent()->remove_child(n);
1959  break;
1960  }
1961  /*FALLTRHOUGH*/
1963  case node_type_t::ARG:
1966  case node_type_t::FUNCTION:
1971  {
1972  // handle a special case which SETs a variable and cannot
1973  // get the first $<var> replaced
1974  bool is_variable_set(n->get_type() == node_type_t::COMPONENT_VALUE
1975  && parser::is_variable_set(n, false));
1976 
1977  // replace all $<var> references with the corresponding value
1978  size_t idx(is_variable_set
1979  ? (n->get_child(0)->is(node_type_t::VARIABLE_FUNCTION)
1980  ? n->size() // completely ignore functions
1981  : 1) // do not replace $<var> in $<var>:
1982  : 0); // replace everything
1983  while(idx < n->size())
1984  {
1985  node::pointer_t child(n->get_child(idx));
1986  if(child->is(node_type_t::VARIABLE))
1987  {
1988  n->remove_child(idx);
1989 
1990  // search for the variable and replace this 'child' with
1991  // the contents of the variable
1992  replace_variable(n, child, idx);
1993  }
1994  else if(child->is(node_type_t::VARIABLE_FUNCTION))
1995  {
1996  // we need to first replace variables in the parameters
1997  // of the function
1998  replace_variables(child);
1999 
2000  n->remove_child(idx);
2001 
2002  // search for the variable and replace this 'child' with
2003  // the contents of the variable
2004  replace_variable(n, child, idx);
2005  }
2006  else
2007  {
2008  // recursive call to handle all children in the
2009  // entire tree
2010  switch(child->get_type())
2011  {
2012  case node_type_t::ARG:
2013  case node_type_t::COMMENT:
2016  case node_type_t::FUNCTION:
2017  case node_type_t::LIST:
2021  replace_variables(child);
2022 
2023  // skip that child if still present
2024  if(idx < n->size()
2025  && child == n->get_child(idx))
2026  {
2027  ++idx;
2028  }
2029  break;
2030 
2032  // handle @import, @mixins, @if, etc.
2033  replace_at_keyword(n, child, idx);
2034  break;
2035 
2036  default:
2037  ++idx;
2038  break;
2039 
2040  }
2041  }
2042  }
2043  // TODO: remove lists that become empty?
2044 
2045  // handle the special case of a variable assignment
2046  if(is_variable_set)
2047  {
2048  // this is enough to get the variable removed
2049  // from COMPONENT_VALUE
2050  set_variable(n);
2051  }
2052  }
2053  break;
2054 
2055  case node_type_t::COMMENT:
2057  break;
2058 
2059  default:
2060  // other nodes are not of interest here
2061  break;
2062 
2063  }
2064 }
2065 
2067 {
2068  std::string const & variable_name(n->get_string());
2069 
2070  // search the parents for the node where the variable will be set
2071  node::pointer_t value(f_state.get_variable(variable_name));
2072  if(!value)
2073  {
2074  // no variable with that name found, generate an error?
2076  {
2077  error::instance() << n->get_position()
2078  << "variable named \""
2079  << variable_name
2080  << "\" is not set."
2082  }
2083  return;
2084  }
2085 
2086  // internal validity check
2087  if(!value->is(node_type_t::LIST)
2088  || value->size() != 2)
2089  {
2090  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
2091  }
2092 
2093  node::pointer_t var(value->get_child(0));
2094  node::pointer_t val(value->get_child(1));
2095 
2096  if(var->is(node_type_t::FUNCTION)
2097  || var->is(node_type_t::VARIABLE_FUNCTION))
2098  {
2100  && !n->is(node_type_t::FUNCTION))
2101  {
2102  error::instance() << n->get_position()
2103  << "variable named \""
2104  << variable_name
2105  << "\" is not a function and it cannot be referenced as such."
2107  return;
2108  }
2109  // we need to apply the function...
2110  parser::argify(n);
2111 
2112  node::pointer_t root(new node(node_type_t::LIST, val->get_position()));
2113  root->add_child(val->clone());
2114  size_t const max_children(var->size());
2115  size_t const max_input(n->size());
2116  for(size_t i(0); i < max_children; ++i)
2117  {
2118  node::pointer_t arg(var->get_child(i));
2119  if(!arg->is(node_type_t::ARG))
2120  {
2121  // function declaration is invalid!
2122  throw csspp_exception_logic("compiler.cpp:compiler::replace_variable(): VARIABLE_FUNCTION children are not all ARG nodes."); // LCOV_EXCL_LINE
2123  }
2124  if(arg->empty())
2125  {
2126  throw csspp_exception_logic("compiler.cpp:compiler::replace_variable(): ARG is empty."); // LCOV_EXCL_LINE
2127  }
2128  node::pointer_t arg_name(arg->get_child(0));
2129  if(!arg_name->is(node_type_t::VARIABLE))
2130  {
2131  // this was already erred when we created the variable
2132  //error::instance() << n->get_position()
2133  // << "function declaration requires all parameters to be variables, "
2134  // << arg_name->get_type()
2135  // << " is not acceptable."
2136  // << error_mode_t::ERROR_ERROR;
2137  return;
2138  }
2139  if(i >= max_input)
2140  {
2141  // user did not specify this value, check whether we have
2142  // an optional value
2143  if(arg->size() > 1)
2144  {
2145  // use default value
2146  node::pointer_t default_param(arg->clone());
2147  default_param->remove_child(0); // remove the variable name
2148  if(default_param->size() == 1)
2149  {
2150  default_param = default_param->get_child(0);
2151  }
2152  else
2153  {
2154  node::pointer_t value_list(new node(node_type_t::LIST, arg->get_position()));
2155  value_list->take_over_children_of(default_param);
2156  default_param = value_list;
2157  }
2158  node::pointer_t param_value(new node(node_type_t::LIST, arg->get_position()));
2159  param_value->add_child(arg_name);
2160  param_value->add_child(default_param);
2161  root->set_variable(arg_name->get_string(), param_value);
2162  }
2163  else
2164  {
2165  // value is missing
2166  error::instance() << n->get_position()
2167  << "missing function variable named \""
2168  << arg_name->get_string()
2169  << "\" when calling "
2170  << variable_name
2171  << "() or using @include "
2172  << variable_name
2173  << "();)."
2175  return;
2176  }
2177  }
2178  else
2179  {
2180  // copy user provided value
2181  node::pointer_t user_param(n->get_child(i));
2182  if(!user_param->is(node_type_t::ARG))
2183  {
2184  throw csspp_exception_logic("compiler.cpp:compiler::replace_variable(): user parameter is not an ARG."); // LCOV_EXCL_LINE
2185  }
2186  if(user_param->size() == 1)
2187  {
2188  user_param = user_param->get_child(0);
2189  }
2190  else
2191  {
2192  // is that really correct?
2193  // we may need a component_value instead...
2194  node::pointer_t list(new node(node_type_t::LIST, user_param->get_position()));
2195  list->take_over_children_of(user_param);
2196  user_param = list;
2197  }
2198  node::pointer_t param_value(new node(node_type_t::LIST, user_param->get_position()));
2199  param_value->add_child(arg_name);
2200  param_value->add_child(user_param->clone());
2201  root->set_variable(arg_name->get_string(), param_value);
2202  }
2203  }
2204 
2205  compiler c(&c.f_state);
2206  c.set_root(root);
2209  c.mark_selectors(root);
2210  c.replace_variables(root);
2211 
2212  // ready to be inserted in the parent
2213  val = root;
2214 
2215  // only keep the curlybracket instead of list + curlybracket
2216  if(val->size() == 1
2217  && val->get_child(0)->is(node_type_t::OPEN_CURLYBRACKET))
2218  {
2219  val = val->get_child(0);
2220  }
2221  }
2222  else
2223  {
2225  || n->is(node_type_t::FUNCTION))
2226  {
2227  error::instance() << n->get_position()
2228  << "variable named \""
2229  << variable_name
2230  << "\" is a function and it can only be referenced with a function ($"
2231  << variable_name
2232  << "() or @include "
2233  << variable_name
2234  << ";)."
2236  return;
2237  }
2238  }
2239 
2240  switch(val->get_type())
2241  {
2242  case node_type_t::LIST:
2244  // check what the content of the list looks like, we may want to
2245  // insert it as a COMPONENT_VALUE instead of directly as is
2246  //
2247  // TODO: the following test is terribly ugly, I'm wondering whether
2248  // a "complex" variable should not instead be recompiled in
2249  // context; one problem being that we do not really know
2250  // what context we're in when we do this transformation...
2251  // none-the-less, I think there would be much better ways
2252  // to handle the situation.
2253  //
2254  if(val->size() == 1
2255  && val->get_child(0)->is(node_type_t::COMPONENT_VALUE)
2256  && parent->is(node_type_t::COMPONENT_VALUE))
2257  {
2258  size_t const max_children(val->get_child(0)->size());
2259  for(size_t j(0), i(idx); j < max_children; ++j, ++i)
2260  {
2261  node::pointer_t child(val->get_child(0)->get_child(j));
2262  parent->insert_child(i, child->clone());
2263  }
2264  break;
2265  }
2266 // else if(val->size() >= 2)
2267 // {
2268 //std::cerr << "----------------- REPLACE WITH VARIABLE CONTENT:\n" << *val << "----------------------------------\n";
2269 // bool component_value(false);
2270 // switch(val->get_child(0)->get_type())
2271 // {
2272 // case node_type_t::IDENTIFIER:
2273 // if(val->get_child(1)->is(node_type_t::WHITESPACE))
2274 // {
2275 // if(val->size() >= 3
2276 // && val->get_child(2)->is(node_type_t::COLON))
2277 // {
2278 // component_value = true;
2279 // }
2280 // }
2281 // else
2282 // {
2283 // if(val->get_child(1)->is(node_type_t::COLON))
2284 // {
2285 // component_value = true;
2286 // }
2287 // }
2288 // if(!component_value)
2289 // {
2290 // component_value = val->get_last_child()->is(node_type_t::OPEN_CURLYBRACKET);
2291 // }
2292 // break;
2293 //
2294 // case node_type_t::MULTIPLY:
2295 // case node_type_t::OPEN_SQUAREBRACKET:
2296 // case node_type_t::PERIOD:
2297 // case node_type_t::REFERENCE:
2298 // case node_type_t::HASH:
2299 // component_value = val->get_last_child()->is(node_type_t::OPEN_CURLYBRACKET);
2300 // break;
2301 //
2302 // default:
2303 // // anything else cannot be defining a component value
2304 // break;
2305 //
2306 // }
2307 //
2308 // if(component_value)
2309 // {
2310 // // in this case we copy the data in a COMPONENT_VALUE instead
2311 // // of directly
2312 // node::pointer_t cv(new node(node_type_t::COMPONENT_VALUE, val->get_position()));
2313 // parent->insert_child(idx, cv);
2314 //
2315 // size_t const max_children(val->size());
2316 // for(size_t j(0); j < max_children; ++j)
2317 // {
2318 // cv->add_child(val->get_child(j)->clone());
2319 // }
2320 // break;
2321 // }
2322 // }
2323  /*FALLTHROUGH*/
2326  // in this case we insert the children of 'val'
2327  // instead of the value itself
2328  {
2329  size_t const max_children(val->size());
2330  for(size_t j(0), i(idx); j < max_children; ++j, ++i)
2331  {
2332  parent->insert_child(i, val->get_child(j)->clone());
2333  }
2334  }
2335  break;
2336 
2339  // whitespaces by themselves do not get re-included,
2340  // which may be a big mistake but at this point
2341  // it seems wise to do so (plus I don't think it can
2342  // happen anyway...)
2343  break;
2344 
2345  default:
2346  parent->insert_child(idx, val->clone());
2347  break;
2348 
2349  }
2350 }
2351 
2353 {
2354  // WARNING: 'n' is still the COMPONENT_VALUE and not the $var
2355 
2356  // a variable gets removed from the tree and its current value
2357  // saved in a parent node that is an OPEN_CURLYBRACKET or the
2358  // root node if no OPEN_CURLYBRACKET is found in the parents
2359  // (note also that only OPEN_CURLYBRACKET marked with 'true'
2360  // are used, those are the only valid '{' for variables, for
2361  // example, an @-keyword '{' does not count...)
2362 
2363  f_state.get_previous_parent()->remove_child(n);
2364 
2365  node::pointer_t var(n->get_child(0));
2366 
2367  n->remove_child(0); // remove the VARIABLE
2368  if(n->get_child(0)->is(node_type_t::WHITESPACE))
2369  {
2370  n->remove_child(0); // remove the WHITESPACE
2371  }
2372  if(!n->get_child(0)->is(node_type_t::COLON))
2373  {
2374  throw csspp_exception_logic("compiler.cpp: somehow a variable set is not exactly IDENTIFIER WHITESPACE* ':'."); // LCOV_EXCL_LINE
2375  }
2376  n->remove_child(0); // remove the COLON
2377  if(!n->empty()
2378  && n->get_child(0)->is(node_type_t::WHITESPACE))
2379  {
2380  // remove WHITESPACE at the beginning of a variable content
2381  n->remove_child(0);
2382  }
2383 
2384  // check whether we have a "!global" at the end of the value
2385  // if so remove it and set global to true
2386  // similarly, handle the "!default"
2387  // we should also support the "!important" but that requires a
2388  // special flag in the variable to know that it cannot be overwritten
2389  bool global(false);
2390  bool set_if_unset(false);
2391  std::string not_at_the_end;
2392  size_t pos(n->size());
2393  while(pos > 0)
2394  {
2395  --pos;
2396  node::pointer_t child(n->get_child(pos));
2397  if(child->is(node_type_t::EXCLAMATION))
2398  {
2399  if(child->get_string() == "global")
2400  {
2401  global = true;
2402  n->remove_child(pos);
2403  if(not_at_the_end.empty()
2404  && pos != n->size())
2405  {
2406  not_at_the_end = child->get_string();
2407  }
2408  }
2409  else if(child->get_string() == "default")
2410  {
2411  set_if_unset = true;
2412  n->remove_child(pos);
2413  if(not_at_the_end.empty()
2414  && pos != n->size())
2415  {
2416  not_at_the_end = child->get_string();
2417  }
2418  }
2419  }
2420  }
2421  if(!not_at_the_end.empty())
2422  {
2423  error::instance() << n->get_position()
2424  << "A special flag, !"
2425  << not_at_the_end
2426  << " in this case, must only appear at the end of a declaration."
2428  }
2429 
2430  // if variable is already set, return immediately
2431  if(set_if_unset)
2432  {
2433  // TODO: verify that the type (i.e. VARIABLE / VARIABLE_FUNCTION)
2434  // would not be changed?
2435  if(f_state.get_variable(var->get_string()))
2436  {
2437  return;
2438  }
2439  }
2440 
2441  // rename the node from COMPONENT_VALUE to a plain LIST
2442  node::pointer_t list;
2443  if(n->size() == 1)
2444  {
2445  list = n->get_child(0);
2446  }
2447  else
2448  {
2449  list.reset(new node(node_type_t::LIST, n->get_position()));
2450  list->take_over_children_of(n);
2451  }
2452 
2453  // we may need to check a little better as null may be in a sub-list
2454  if(list->is(node_type_t::IDENTIFIER)
2455  && list->get_string() == "null")
2456  {
2457  list.reset(new node(node_type_t::NULL_TOKEN, list->get_position()));
2458  }
2459 
2460  // now the value of the variable is 'list'; it will get compiled once in
2461  // context (i.e. not here)
2462 
2463  // search the parents for the node where the variable will be set
2464  if(var->is(node_type_t::VARIABLE_FUNCTION))
2465  {
2467  }
2468 
2469  // now save all of that in the best place
2470  f_state.set_variable(var, list, global);
2471 }
2472 
2474 {
2475  if(!parser::argify(var))
2476  {
2477  return;
2478  }
2479 
2480  // TODO: verify that the list of arguments is valid (i.e. $var
2481  // or $var: <default-value>)
2482  bool optional(false);
2483  size_t const max_children(var->size());
2484  for(size_t i(0); i < max_children; ++i)
2485  {
2486  node::pointer_t arg(var->get_child(i));
2487  if(!arg->is(node_type_t::ARG))
2488  {
2489  throw csspp_exception_logic("compiler.cpp:compiler::set_variable(): an argument is not an ARG node."); // LCOV_EXCL_LINE
2490  }
2491  if(arg->empty())
2492  {
2493  throw csspp_exception_logic("compiler.cpp:compiler::set_variable(): an argument has no children."); // LCOV_EXCL_LINE
2494  }
2495  node::pointer_t param_var(arg->get_child(0));
2496  if(param_var->is(node_type_t::VARIABLE))
2497  {
2498  size_t const arg_size(arg->size());
2499  if(arg_size > 1)
2500  {
2501  if(arg->get_child(1)->is(node_type_t::WHITESPACE))
2502  {
2503  arg->remove_child(1);
2504  }
2505  if(arg->size() > 1
2506  && arg->get_child(1)->is(node_type_t::COLON))
2507  {
2508  optional = true;
2509  arg->remove_child(1);
2510  if(arg->size() > 1
2511  && arg->get_child(1)->is(node_type_t::WHITESPACE))
2512  {
2513  arg->remove_child(1);
2514  }
2515  // so now we have $arg <optional-value> and no whitespaces or colon
2516  }
2517  else
2518  {
2519  error::instance() << arg->get_position()
2520  << "function declarations expect variable with optional parameters to use a ':' after the variable name and before the optional value."
2522  }
2523  }
2524  else
2525  {
2526  // TODO: I think that the last parameter, if it ends with "...", is not required to have an optional value?
2527  if(optional)
2528  {
2529  error::instance() << arg->get_position()
2530  << "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."
2532  }
2533  }
2534  }
2535  else
2536  {
2537  error::instance() << arg->get_position()
2538  << "function declarations expect variables for each of their arguments, not a "
2539  << param_var->get_type()
2540  << "."
2542  }
2543  }
2544 }
2545 
2547 {
2548  // @<id> [expression] '{' ... '}'
2549  //
2550  // Note that the expression is optional. Not only that, in most
2551  // cases we do not attempt to compile it because it is not expected
2552  // to be an SCSS expression (especially in an @support command).
2553  //
2554  // All the @-keyword that are used to control the flow of the
2555  // SCSS file are to be handled here; at this time these include:
2556  //
2557  // @else -- changes what happens (i.e. sets a variable)
2558  // @if -- changes what happens (i.e. sets a variable)
2559  // @import -- changes input code
2560  // @include -- same as $var or $var(args)
2561  // @mixin -- changes variables
2562  //
2563  // To be added are: @for, @while, @each.
2564  //
2565  std::string const at(n->get_string());
2566 
2567  if(at != "mixin")
2568  {
2569  replace_variables(n);
2570  }
2571 
2572  if(at == "import")
2573  {
2574  replace_import(parent, n, idx);
2575  return;
2576  }
2577 
2578  if(at == "mixin")
2579  {
2580  // mixins are handled like variables or
2581  // function declarations, so we always
2582  // remove them
2583  //
2584  parent->remove_child(idx);
2585  handle_mixin(n);
2586  return;
2587  }
2588 
2589  if(at == "if")
2590  {
2591  // get the position of the @if in its parent so we can insert new
2592  // data at that position if necessary
2593  //
2594  parent->remove_child(idx);
2595  replace_if(parent, n, idx);
2596  return;
2597  }
2598 
2599  if(at == "else")
2600  {
2601  // remove the @else from the parent
2602  parent->remove_child(idx);
2603  replace_else(parent, n, idx);
2604  return;
2605  }
2606 
2607  if(at == "include")
2608  {
2609  // this is SASS support, a more explicit way to insert a variable
2610  // I guess...
2611  parent->remove_child(idx);
2612 
2613  if(n->empty())
2614  {
2615  // as far as I can tell, it is not possible to reach these
2616  // lines from a tree created by the parser; we could work
2617  // on creating a "fake" invalid tree too...
2618  error::instance() << n->get_position() // LCOV_EXCL_LINE
2619  << "@include is expected to be followed by an IDENTIFIER or a FUNCTION naming the variable/mixin to include." // LCOV_EXCL_LINE
2620  << error_mode_t::ERROR_ERROR; // LCOV_EXCL_LINE
2621  return; // LCOV_EXCL_LINE
2622  }
2623 
2624  node::pointer_t id(n->get_child(0));
2625  if(!id->is(node_type_t::IDENTIFIER)
2626  && !id->is(node_type_t::FUNCTION))
2627  {
2628  error::instance() << n->get_position()
2629  << "@include is expected to be followed by an IDENTIFIER or a FUNCTION naming the variable/mixin to include."
2631  return;
2632  }
2633 
2634  // search for the variable and replace this 'child' with
2635  // the contents of the variable
2636  replace_variable(parent, id, idx);
2637  return;
2638  }
2639 
2640  if(at == "error"
2641  || at == "warning"
2642  || at == "message"
2643  || at == "info"
2644  || at == "debug")
2645  {
2646  // make sure the expression is calculated for these
2648  }
2649 
2650  // in all other cases the @-keyword is kept as is
2651  ++idx;
2652 }
2653 
2655 {
2656  // calculate the expression if present
2657  if(!n->empty() && !n->get_child(0)->is(node_type_t::OPEN_CURLYBRACKET))
2658  {
2659  expression expr(n);
2661  return expr.compile();
2662  }
2663 
2664  return node::pointer_t();
2665 }
2666 
2668 {
2669  // we want to mark the next block as valid if it is an
2670  // '@else' or '@else if' and can possibly be inserted
2671  node::pointer_t next;
2672  if(idx < parent->size())
2673  {
2674  // note: we deleted the @if so 'idx' represents the position of the
2675  // next node in the parent array
2676  next = parent->get_child(idx);
2677  if(next->is(node_type_t::AT_KEYWORD)
2678  && next->get_string() == "else")
2679  {
2680  // mark that the @else is at the right place
2681  next->set_integer(g_if_or_else_executed);
2682  }
2683  }
2684 
2686 
2687  // make sure that we got a valid syntax
2688  if(n->size() != 2 || !expr)
2689  {
2690  error::instance() << n->get_position()
2691  << "@if is expected to have exactly 2 parameters: an expression and a block. This @if has "
2692  << static_cast<int>(n->size())
2693  << " parameters."
2695  return;
2696  }
2697 
2698  bool const r(expression::boolean(expr));
2699  if(r)
2700  {
2701  // BOOLEAN_TRUE, we need the data which we put in the stream
2702  // at the position of the @if as if the @if and
2703  // expression never existed
2704  node::pointer_t block(n->get_child(1));
2705  size_t const max_children(block->size());
2706  for(size_t j(0); j < max_children; ++j, ++idx)
2707  {
2708  parent->insert_child(idx, block->get_child(j));
2709  }
2710  }
2711  else if(next)
2712  {
2713  // mark the else as not executed if r is false
2714  next->set_integer(g_if_or_else_false_so_far);
2715  }
2716 }
2717 
2719 {
2720  node::pointer_t next;
2721 
2722  // BOOLEAN_FALSE or BOOLEAN_INVALID, we remove the block to avoid
2723  // executing it since we do not know whether it should
2724  // be executed or not; also we mark the next block as
2725  // "true" if it is an '@else' or '@else if'
2726  if(idx < parent->size())
2727  {
2728  next = parent->get_child(idx);
2729  if(next->is(node_type_t::AT_KEYWORD)
2730  && next->get_string() == "else")
2731  {
2732  if(n->size() == 1)
2733  {
2734  error::instance() << n->get_position()
2735  << "'@else { ... }' cannot follow another '@else { ... }'. Maybe you are missing an 'if expr'?"
2737  return;
2738  }
2739 
2740  // at this point we do not know the state that the next
2741  // @else/@else if should have so we use "executed" as a
2742  // safe value
2743  //
2744  next->set_integer(g_if_or_else_executed);
2745  }
2746  else
2747  {
2748  next.reset();
2749  }
2750  }
2751  node::pointer_t expr;
2752 
2753  bool const else_if(n->get_child(0)->is(node_type_t::IDENTIFIER)
2754  && n->get_child(0)->get_string() == "if");
2755 
2756  // in case of an else_if, check the expression
2757  if(else_if)
2758  {
2759  // this is a very special case of the:
2760  //
2761  // @else if expr '{' ... '}'
2762  //
2763  // (this is from SASS, if it had been me, I would have used
2764  // @elseif or @else-if and not @else if ...)
2765  //
2766  n->remove_child(0);
2767  if(!n->empty() && n->get_child(0)->is(node_type_t::WHITESPACE))
2768  {
2769  // this should always happen because otherwise we are missing
2770  // the actual expression!
2771  n->remove_child(0);
2772  }
2773  if(n->size() == 1)
2774  {
2775  error::instance() << n->get_position()
2776  << "'@else if ...' is missing an expression or a block."
2778  return;
2779  }
2780  expr = at_keyword_expression(n);
2781  }
2782 
2783  // if this '@else' is still marked with 'g_if_or_else_undefined'
2784  // then there was no '@if' or '@else if' before it which is an error
2785  //
2786  int status(n->get_integer());
2787  if(status == g_if_or_else_undefined)
2788  {
2789  error::instance() << n->get_position()
2790  << "a standalone @else is not legal, it has to be preceeded by an @if ... or @else if ..."
2792  return;
2793  }
2794 
2795  //
2796  // when the '@if' or any '@else if' all had a 'false' expression,
2797  // we are 'true' here; once one of the '@if' / '@else if' is 'true'
2798  // then we start with 'r = false'
2799  //
2800  bool r(status == g_if_or_else_false_so_far);
2801  if(n->size() != 1)
2802  {
2803  if(n->size() != 2 || !expr)
2804  {
2805  error::instance() << n->get_position()
2806  << "'@else { ... }' is expected to have 1 parameter, '@else if ... { ... }' is expected to have 2 parameters. This @else has "
2807  << static_cast<int>(n->size())
2808  << " parameters."
2810  return;
2811  }
2812 
2813  // as long as 'status == g_if_or_else_false_so_far' we have
2814  // not yet found a match (i.e. the starting '@if' was false
2815  // and any '@else if' were all false so far) so we check the
2816  // expression of this very '@else if' to know whether to go
2817  // on or not; r is BOOLEAN_TRUE when the status allows us to check
2818  // the next expression
2819  if(r)
2820  {
2821  r = expression::boolean(expr);
2822  }
2823  }
2824 
2825  if(r)
2826  {
2827  status = g_if_or_else_executed;
2828 
2829  // BOOLEAN_TRUE, we need the data which we put in the stream
2830  // at the position of the @if as if the @if and
2831  // expression never existed
2832  node::pointer_t block(n->get_child(n->size() == 1 ? 0 : 1));
2833  size_t const max_children(block->size());
2834  for(size_t j(0); j < max_children; ++j, ++idx)
2835  {
2836  parent->insert_child(idx, block->get_child(j));
2837  }
2838  }
2839 
2840  if(next)
2841  {
2842  next->set_integer(status);
2843  }
2844 }
2845 
2847 {
2848  std::string comment(n->get_string());
2849 
2850  std::string::size_type pos(comment.find('{')); // } for vim % functionality
2851  while(pos != std::string::npos)
2852  {
2853  if(pos + 2 < comment.length()
2854  && comment[pos + 1] == '$')
2855  {
2856  std::string::size_type start_var(pos);
2857  std::string::size_type start_name(start_var + 2);
2858  // include the preceeding '#' if present (SASS compatibility)
2859  if(start_var > 0
2860  && comment[start_var - 1] == '#')
2861  {
2862  --start_var;
2863  }
2864  // we do +1 because it definitely cannot start at '{$'
2865  std::string::size_type end_var(comment.find('}', start_var + 2));
2866  if(end_var != std::string::npos)
2867  {
2868  // we got a variable, replace with the content
2869  std::string const full_name(comment.substr(start_name, end_var - start_name));
2870 
2871  // check whether the user referenced a function or not
2872  bool is_function(false);
2873  std::string variable_name(full_name);
2874  std::string::size_type func_pos(full_name.find('('));
2875  if(func_pos != std::string::npos)
2876  {
2877  // only keep the name part, ignore the parameters
2878  variable_name = full_name.substr(0, func_pos);
2879  is_function = true;
2880  }
2881 
2882  node::pointer_t var_content(f_state.get_variable(variable_name));
2883  if(var_content)
2884  {
2885  // internal validity check
2886  if(!var_content->is(node_type_t::LIST)
2887  || var_content->size() != 2)
2888  {
2889  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
2890  }
2891 
2892  node::pointer_t var(var_content->get_child(0));
2893  if(var->is(node_type_t::FUNCTION)
2894  || var->is(node_type_t::VARIABLE_FUNCTION))
2895  {
2896  // TODO: add the support?
2897  error::instance() << n->get_position()
2898  << "variable named \""
2899  << variable_name
2900  << "\", is a function which is not supported in a comment."
2902  }
2903  else
2904  {
2905  if(is_function)
2906  {
2907  error::instance() << n->get_position()
2908  << "variable named \""
2909  << variable_name
2910  << "\", is not a function, yet you referenced it as such (and functions are not yet supported in comments)."
2912  }
2913 
2914  node::pointer_t val(var_content->get_child(1));
2915 
2916  comment.erase(start_var, end_var + 1 - start_var);
2917  // TODO: use the assembler instead of to_string()?
2918  std::string const value(val->to_string(0));
2919 
2920  comment.insert(start_var, value);
2921 
2922  // adjust pos to continue checking from after the
2923  // variable (i.e. if inserting a variable that includes
2924  // a {$var} it won't be replaced...)
2925  pos = start_var + value.length() - 1;
2926  }
2927  }
2928  else
2929  {
2931  {
2932  error::instance() << n->get_position()
2933  << "variable named \""
2934  << variable_name
2935  << "\", used in a comment, is not set."
2937  }
2938  }
2939  }
2940  }
2941 
2942  pos = comment.find('{', pos + 1); // } for vim % functionality
2943  }
2944 
2945  n->set_string(comment);
2946 }
2947 
2949 {
2950  // use a for() as a 'goto exit;' on a 'break'
2951  for(;;)
2952  {
2953  size_t pos(0);
2954  node::pointer_t term(n->get_child(pos));
2955  if(term->is(node_type_t::WHITESPACE))
2956  {
2957  // I'm keeping this here, although there should be no WHITESPACE
2958  // at the start of a '[' block
2959  n->remove_child(term); // LCOV_EXCL_LINE
2960  if(pos >= n->size()) // LCOV_EXCL_LINE
2961  {
2962  break; // LCOV_EXCL_LINE
2963  }
2964  term = n->get_child(pos); // LCOV_EXCL_LINE
2965  }
2966 
2967  if(!term->is(node_type_t::IDENTIFIER))
2968  {
2969  error::instance() << n->get_position()
2970  << "an attribute selector expects to first find an identifier."
2972  return false;
2973  }
2974 
2975  ++pos;
2976  if(pos >= n->size())
2977  {
2978  // just IDENTIFIER is valid
2979  ++parent_pos;
2980  return true;
2981  }
2982 
2983  term = n->get_child(pos);
2984  if(term->is(node_type_t::WHITESPACE))
2985  {
2986  n->remove_child(pos);
2987  if(pos >= n->size())
2988  {
2989  // just IDENTIFIER is valid, although we should never
2990  // reach this line because WHITESPACE are removed from
2991  // the end of lists
2992  ++parent_pos; // LCOV_EXCL_LINE
2993  return true; // LCOV_EXCL_LINE
2994  }
2995  term = n->get_child(pos);
2996  }
2997 
2998  if(!term->is(node_type_t::EQUAL) // '='
2999  && !term->is(node_type_t::NOT_EQUAL) // '!=' -- extension
3000  && !term->is(node_type_t::INCLUDE_MATCH) // '~='
3001  && !term->is(node_type_t::PREFIX_MATCH) // '^='
3002  && !term->is(node_type_t::SUFFIX_MATCH) // '$='
3003  && !term->is(node_type_t::SUBSTRING_MATCH) // '*='
3004  && !term->is(node_type_t::DASH_MATCH)) // '|='
3005  {
3006  error::instance() << n->get_position()
3007  << "expected attribute operator missing, supported operators are '=', '!=', '~=', '^=', '$=', '*=', and '|='."
3009  return false;
3010  }
3011  node::pointer_t op(term);
3012 
3013  ++pos;
3014  if(pos >= n->size())
3015  {
3016  break;
3017  }
3018 
3019  term = n->get_child(pos);
3020  if(term->is(node_type_t::WHITESPACE))
3021  {
3022  n->remove_child(pos);
3023  if(pos >= n->size())
3024  {
3025  // we actually are not expected to ever have a WHITESPACE
3026  // at the end of a block so we cannot hit this line, but
3027  // we keep it, just in case we were wrong...
3028  break; // LCOV_EXCL_LINE
3029  }
3030  term = n->get_child(pos);
3031  }
3032 
3033  if(!term->is(node_type_t::IDENTIFIER)
3034  && !term->is(node_type_t::STRING)
3035  && !term->is(node_type_t::INTEGER)
3036  && !term->is(node_type_t::DECIMAL_NUMBER))
3037  {
3038  error::instance() << n->get_position()
3039  << "attribute selector value must be an identifier, a string, an integer, or a decimal number, a "
3040  << term->get_type() << " is not acceptable."
3042  return false;
3043  }
3044 
3045  ++pos;
3046  if(pos < n->size()) // <<-- inverted test!
3047  {
3048  error::instance() << n->get_position()
3049  << "attribute selector cannot be followed by more than one value, found "
3050  << n->get_child(pos)->get_type() << " after the value, missing quotes?"
3052  return false;
3053  }
3054 
3055  // if the operator was '!=', we have to make changes from:
3056  // [a!=b]
3057  // to
3058  // :not([a=b])
3059  if(op->is(node_type_t::NOT_EQUAL))
3060  {
3061  // remove the [a!=b] from parent
3062  parent->remove_child(parent_pos);
3063 
3064  // add the ':'
3065  node::pointer_t colon(new node(node_type_t::COLON, n->get_position()));
3066  parent->insert_child(parent_pos, colon);
3067  ++parent_pos;
3068 
3069  // add the not()
3070  node::pointer_t not_func(new node(node_type_t::FUNCTION, n->get_position()));
3071  not_func->set_string("not");
3072  parent->insert_child(parent_pos, not_func);
3073 
3074  // in the not() add the [a!=b]
3075  not_func->add_child(n);
3076 
3077  // remove the '!='
3078  n->remove_child(1);
3079 
3080  // replace with the '='
3081  node::pointer_t equal(new node(node_type_t::EQUAL, n->get_position()));
3082  n->insert_child(1, equal);
3083  }
3084 
3085  ++parent_pos;
3086 
3087  return true;
3088  }
3089 
3090  error::instance() << n->get_position()
3091  << "the attribute selector is expected to be an IDENTIFIER optionally followed by an operator and a value."
3093  return false;
3094 }
3095 
3097 {
3098  if(pos >= n->size())
3099  {
3100  throw csspp_exception_logic("compiler.cpp:compiler::selector_term(): selector_simple_term() called when not enough selectors are available."); // LCOV_EXCL_LINE
3101  }
3102 
3103  node::pointer_t term(n->get_child(pos));
3104  switch(term->get_type())
3105  {
3106  case node_type_t::HASH:
3107  // valid term as is
3108  break;
3109 
3111  case node_type_t::MULTIPLY:
3112  // IDENTIFIER
3113  // IDENTIFIER '|' IDENTIFIER
3114  // IDENTIFIER '|' '*'
3115  // '*'
3116  // '*' '|' IDENTIFIER
3117  // '*' '|' '*'
3118  if(pos + 1 < n->size())
3119  {
3120  if(n->get_child(pos + 1)->is(node_type_t::SCOPE))
3121  {
3122  if(pos + 2 >= n->size())
3123  {
3124  error::instance() << n->get_position()
3125  << "the scope operator (|) requires a right hand side identifier or '*'."
3127  return false;
3128  }
3129  pos += 2;
3130  term = n->get_child(pos);
3131  if(!term->is(node_type_t::IDENTIFIER)
3132  && !term->is(node_type_t::MULTIPLY))
3133  {
3134  error::instance() << n->get_position()
3135  << "the right hand side of a scope operator (|) must be an identifier or '*'."
3137  return false;
3138  }
3139  }
3140  else if(term->is(node_type_t::MULTIPLY)
3141  && (n->get_child(pos + 1)->is(node_type_t::OPEN_SQUAREBRACKET)
3142  || n->get_child(pos + 1)->is(node_type_t::PERIOD)))
3143  {
3144  // this asterisk is not required, get rid of it
3145  n->remove_child(term);
3146  return true; // return immediately to avoid the ++pos
3147  }
3148  }
3149  break;
3150 
3151  case node_type_t::SCOPE:
3152  ++pos;
3153  if(pos >= n->size())
3154  {
3155  error::instance() << n->get_position()
3156  << "a scope selector (|) must be followed by an identifier or '*'."
3158  return false;
3159  }
3160  term = n->get_child(pos);
3161  if(!term->is(node_type_t::IDENTIFIER)
3162  && !term->is(node_type_t::MULTIPLY))
3163  {
3164  error::instance() << n->get_position()
3165  << "the right hand side of a scope operator (|) must be an identifier or '*'."
3167  return false;
3168  }
3169  break;
3170 
3171  case node_type_t::COLON:
3172  ++pos;
3173  if(pos >= n->size())
3174  {
3175  // this is caught by the selector_term() when reading the '::'
3176  // so we cannot reach this time; keeping just in case though...
3177  error::instance() << n->get_position()
3178  << "a selector list cannot end with a standalone ':'."
3180  return false;
3181  }
3182  term = n->get_child(pos);
3183  switch(term->get_type())
3184  {
3186  {
3187  // ':' IDENTIFIER
3188  // validate the identifier as only a small number can be used
3189  set_validation_script("validation/pseudo-classes");
3190  node::pointer_t str(new node(node_type_t::STRING, term->get_position()));
3191  str->set_string(term->get_string());
3192  add_validation_variable("pseudo_name", str);
3193  if(!run_validation(false))
3194  {
3195  return false;
3196  }
3197  }
3198  break;
3199 
3200  case node_type_t::FUNCTION:
3201  {
3202  // ':' FUNCTION component-value-list ')'
3203  //
3204  // create a temporary identifier to run the validation
3205  // checks, because the FUNCTION is a list of nodes!
3206  node::pointer_t function_name(new node(node_type_t::STRING, term->get_position()));
3207  function_name->set_string(term->get_string());
3208  set_validation_script("validation/pseudo-nth-functions");
3209  add_validation_variable("pseudo_name", function_name);
3210  if(run_validation(true))
3211  {
3212  // this is a valid nth function, print out its parameters
3213  // and reparse as 'An+B'
3214  size_t const max_children(term->size());
3215  std::string an_b;
3216  for(size_t idx(0); idx < max_children; ++idx)
3217  {
3218  an_b += term->get_child(idx)->to_string(node::g_to_string_flag_show_quotes);
3219  }
3220  // TODO...
3221  nth_child nc;
3222  if(nc.parse(an_b))
3223  {
3224  // success, save the compiled An+B in this object
3225  node::pointer_t an_b_node(new node(node_type_t::AN_PLUS_B, term->get_position()));
3226  an_b_node->set_integer(nc.get_nth());
3227  term->clear();
3228  term->add_child(an_b_node);
3229  }
3230  else
3231  {
3232  // get the error and display it
3233  error::instance() << term->get_position()
3234  << nc.get_error()
3236  return false;
3237  }
3238  }
3239  else
3240  {
3241  set_validation_script("validation/pseudo-functions");
3242  add_validation_variable("pseudo_name", function_name);
3243  if(!run_validation(false))
3244  {
3245  return false;
3246  }
3247  // this is a standard function, check the parameters
3248  if(term->get_string() == "not")
3249  {
3250  // :not(:not(...)) is illegal
3251  error::instance() << n->get_position()
3252  << "the :not() selector does not accept an inner :not()."
3254  return false;
3255  }
3256  else if(term->get_string() == "lang")
3257  {
3258  // the language must be an identifier with no dashes
3259  if(term->size() != 1)
3260  {
3261  error::instance() << term->get_position()
3262  << "a lang() function selector must have exactly one identifier as its parameter."
3264  return false;
3265  }
3266  term = term->get_child(0);
3267  if(term->is(node_type_t::IDENTIFIER))
3268  {
3269  std::string lang(term->get_string());
3270  std::string country;
3271  std::string::size_type char_pos(lang.find('-'));
3272  if(char_pos != std::string::npos)
3273  {
3274  country = lang.substr(char_pos + 1);
3275  lang = lang.substr(0, char_pos);
3276  char_pos = country.find('-');
3277  if(char_pos != std::string::npos)
3278  {
3279  // remove whatever other information that
3280  // we will ignore in our validations
3281  country = country.substr(0, char_pos);
3282  }
3283  }
3284  // check the language (mandatory)
3285  node::pointer_t language_name(new node(node_type_t::STRING, term->get_position()));
3286  language_name->set_string(lang);
3287  set_validation_script("validation/languages");
3288  add_validation_variable("language_name", language_name);
3289  if(!run_validation(false))
3290  {
3291  return false;
3292  }
3293  if(!country.empty())
3294  {
3295  // check the country (optional)
3296  node::pointer_t country_name(new node(node_type_t::STRING, term->get_position()));
3297  country_name->set_string(country);
3298  set_validation_script("validation/countries");
3299  add_validation_variable("country_name", country_name);
3300  if(!run_validation(false))
3301  {
3302  return false;
3303  }
3304  }
3305  }
3306  else
3307  {
3308  error::instance() << term->get_position()
3309  << "a lang() function selector expects an identifier as its parameter."
3311  return false;
3312  }
3313  }
3314  }
3315  }
3316  break;
3317 
3318  default:
3319  // invalid selector list
3320  error::instance() << n->get_position()
3321  << "a ':' selector must be followed by an identifier or a function, a " << n->get_type() << " was found instead."
3323  return false;
3324 
3325  }
3326  break;
3327 
3328  case node_type_t::PERIOD:
3329  // '.' IDENTIFIER -- class (special attribute check)
3330  ++pos;
3331  if(pos >= n->size())
3332  {
3333  error::instance() << n->get_position()
3334  << "a selector list cannot end with a standalone '.'."
3336  return false;
3337  }
3338  term = n->get_child(pos);
3339  if(!term->is(node_type_t::IDENTIFIER))
3340  {
3341  error::instance() << n->get_position()
3342  << "a class selector (after a period: '.') must be an identifier."
3344  return false;
3345  }
3346  break;
3347 
3349  // '[' WHITESPACE attribute-check WHITESPACE ']' -- attributes check
3350  return selector_attribute_check(n, pos, term);
3351 
3353  case node_type_t::ADD:
3354  case node_type_t::PRECEDED:
3355  error::instance() << n->get_position()
3356  << "found token " << term->get_type() << ", which cannot be used to start a selector expression."
3358  return false;
3359 
3360  case node_type_t::FUNCTION:
3361  error::instance() << n->get_position()
3362  << "found function \"" << term->get_string() << "()\", which may be a valid selector token but only if immediately preceeded by one ':' (simple term)."
3364  return false;
3365 
3366  default:
3367  error::instance() << n->get_position()
3368  << "found token " << term->get_type() << ", which is not a valid selector token (simple term)."
3370  return false;
3371 
3372  }
3373 
3374  // move on to the next term
3375  ++pos;
3376 
3377  return true;
3378 }
3379 
3381 {
3382  if(pos >= n->size())
3383  {
3384  throw csspp_exception_logic("compiler.cpp:compiler::selector_term(): selector_term() called when not enough selectors are available."); // LCOV_EXCL_LINE
3385  }
3386 
3387  node::pointer_t term(n->get_child(pos));
3388  switch(term->get_type())
3389  {
3391  // valid complex term as is
3392  break;
3393 
3395  // valid complex term only if pos == 0
3396  if(pos != 0)
3397  {
3398  error::instance() << n->get_position()
3399  << "a selector reference (&) can only appear as the very first item in a list of selectors."
3401  return false;
3402  }
3403  break;
3404 
3405  case node_type_t::COLON:
3406  // ':' FUNCTION (="not") is a term and has to be managed here
3407  // '::' IDENTIFIER is a term and not a simple term (it cannot
3408  // appear inside a :not() function.)
3409  ++pos;
3410  if(pos >= n->size())
3411  {
3412  error::instance() << n->get_position()
3413  << "a selector list cannot end with a standalone ':'."
3415  return false;
3416  }
3417  term = n->get_child(pos);
3418  switch(term->get_type())
3419  {
3421  --pos;
3422  return selector_simple_term(n, pos);
3423 
3424  case node_type_t::FUNCTION:
3425  // ':' FUNCTION component-value-list ')'
3426  if(term->get_string() == "not")
3427  {
3428  // special handling, the :not() is considered to be
3429  // a complex selector and as such has to be handled
3430  // right here; the parameters must represent one valid
3431  // simple term
3432  //
3433  // TODO: still got to take care of WHITESPACE?
3434  size_t sub_pos(0);
3435  if(!selector_simple_term(term, sub_pos))
3436  {
3437  return false;
3438  }
3439  if(sub_pos < term->size())
3440  {
3441  // we did not reach the end of that list so something
3442  // is wrong (i.e. the :not() can only include one
3443  // element)
3444  error::instance() << term->get_position()
3445  << "the :not() function accepts at most one simple term."
3447  return false;
3448  }
3449  }
3450  else
3451  {
3452  --pos;
3453  return selector_simple_term(n, pos);
3454  }
3455  break;
3456 
3457  case node_type_t::COLON:
3458  {
3459  // '::' IDENTIFIER -- pseudo elements
3460  ++pos;
3461  if(pos >= n->size())
3462  {
3463  error::instance() << n->get_position()
3464  << "a selector list cannot end with a '::' without an identifier after it."
3466  return false;
3467  }
3468  term = n->get_child(pos);
3469  if(!term->is(node_type_t::IDENTIFIER))
3470  {
3471  error::instance() << n->get_position()
3472  << "a pseudo element name (defined after a '::' in a list of selectors) must be defined using an identifier."
3474  return false;
3475  }
3476  // only a few pseudo element names exist, do a validation
3477  node::pointer_t pseudo_element(new node(node_type_t::STRING, term->get_position()));
3478  pseudo_element->set_string(term->get_string());
3479  set_validation_script("validation/pseudo-elements");
3480  add_validation_variable("pseudo_name", pseudo_element);
3481  if(!run_validation(false))
3482  {
3483  return false;
3484  }
3485  if(pos + 1 < n->size())
3486  {
3487  error::instance() << n->get_position()
3488  << "a pseudo element name (defined after a '::' in a list of selectors) must be defined as the last element in the list of selectors."
3490  return false;
3491  }
3492  }
3493  break;
3494 
3495  default:
3496  // invalid selector list
3497  error::instance() << n->get_position()
3498  << "a ':' selector must be followed by an identifier or a function, a " << term->get_type() << " was found instead."
3500  return false;
3501 
3502  }
3503  break;
3504 
3505  case node_type_t::HASH:
3507  case node_type_t::MULTIPLY:
3509  case node_type_t::PERIOD:
3510  case node_type_t::SCOPE:
3511  return selector_simple_term(n, pos);
3512 
3514  case node_type_t::ADD:
3515  case node_type_t::PRECEDED:
3516  error::instance() << n->get_position()
3517  << "found token " << term->get_type() << ", which cannot be used to start a selector expression."
3519  return false;
3520 
3521  case node_type_t::FUNCTION:
3522  // we can reach this case if we have a token in the selector list
3523  // which immediately returns false in is_nested_declaration()
3524  error::instance() << n->get_position()
3525  << "found function \"" << term->get_string() << "()\", which may be a valid selector token but only if immediately preceeded by one ':' (term)."
3527  return false;
3528 
3529  default:
3530  error::instance() << n->get_position()
3531  << "found token " << term->get_type() << ", which is not a valid selector token (term)."
3533  return false;
3534 
3535  }
3536 
3537  // move on to the next term
3538  ++pos;
3539 
3540  return true;
3541 }
3542 
3544 {
3545  // we must have a term first
3546  if(!selector_term(n, pos))
3547  {
3548  return false;
3549  }
3550 
3551  for(;;)
3552  {
3553  if(pos >= n->size())
3554  {
3555  return true;
3556  }
3557 
3558  // skip whitespaces between terms
3559  // this also works for binary operators
3560  node::pointer_t term(n->get_child(pos));
3561  if(term->is(node_type_t::WHITESPACE))
3562  {
3563  ++pos;
3564 
3565  // end of list too soon?
3566  if(pos >= n->size())
3567  {
3568  // this should not happen since we remove leading/trailing
3569  // white space tokens
3570  throw csspp_exception_logic("compiler.cpp: a component value has a WHITESPACE token before the OPEN_CURLYBRACKET."); // LCOV_EXCL_LINE
3571  }
3572  term = n->get_child(pos);
3573  }
3574 
3575  if(term->is(node_type_t::GREATER_THAN)
3576  || term->is(node_type_t::ADD)
3577  || term->is(node_type_t::PRECEDED))
3578  {
3579  // if we had a WHITESPACE just before the binary operator,
3580  // remove it as it is not necessary
3581  if(n->get_child(pos - 1)->is(node_type_t::WHITESPACE))
3582  {
3583  n->remove_child(pos - 1);
3584  }
3585  else
3586  {
3587  // otherwise just go over that operator
3588  ++pos;
3589  }
3590 
3591  // it is mandatory for these tokens to be followed by another
3592  // term (i.e. binary operators)
3593  if(pos >= n->size())
3594  {
3595  error::instance() << n->get_position()
3596  << "found token " << term->get_type() << ", which is expected to be followed by another selector term."
3598  return false;
3599  }
3600 
3601  // we may have a WHITESPACE first, if so skip it
3602  term = n->get_child(pos);
3603  if(term->is(node_type_t::WHITESPACE))
3604  {
3605  // no need before/after binary operators
3606  n->remove_child(term);
3607 
3608  // end of list too soon?
3609  if(pos >= n->size())
3610  {
3611  // this should not happen since we remove leading/trailing
3612  // white space tokens
3613  throw csspp_exception_logic("compiler.cpp: a component value has a WHITESPACE token before the OPEN_CURLYBRACKET."); // LCOV_EXCL_LINE
3614  }
3615  }
3616  }
3617 
3618  if(!selector_term(n, pos))
3619  {
3620  return false;
3621  }
3622  }
3623 }
3624 
3626 {
3627  if(!parser::argify(n))
3628  {
3629  return false;
3630  }
3631 
3632  size_t const max_children(n->size());
3633  for(size_t idx(0); idx < max_children; ++idx)
3634  {
3635  node::pointer_t arg(n->get_child(idx));
3636  if(arg->is(node_type_t::OPEN_CURLYBRACKET))
3637  {
3638  // this is at the end of the list, so we're done
3639  break;
3640  }
3641  if(!arg->is(node_type_t::ARG))
3642  {
3643  throw csspp_exception_logic("compiler.cpp: parse_selector() just called argify() and yet a child is not an ARG."); // LCOV_EXCL_LINE
3644  }
3645  size_t pos(0);
3646  if(!selector_list(arg, pos))
3647  {
3648  return false;
3649  }
3650 
3651  // check and make sure that #<id> is not repeated in the same
3652  // list, because that's an error (TBD--there may be one exception
3653  // now that we have the ~ operator...)
3654  bool err(false);
3655  std::map<std::string, bool> hash;
3656  for(size_t j(0); j < arg->size(); ++j)
3657  {
3658  node::pointer_t child(arg->get_child(j));
3659  if(child->is(node_type_t::HASH))
3660  {
3661  if(hash.find(child->get_string()) != hash.end())
3662  {
3663  error::instance() << arg->get_position()
3664  << "found #"
3665  << child->get_string()
3666  << " twice in selector: \""
3667  << arg->to_string(0)
3668  << "\"."
3670  err = true;
3671  }
3672  else
3673  {
3674  hash[child->get_string()] = true;
3675  }
3676  }
3677  }
3678  if(!err)
3679  {
3680  if(hash.size() > 1)
3681  {
3682  error::instance() << arg->get_position()
3683  << "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."
3685  }
3686  // This is a valid case... as in:
3687  //
3688  // .settings.active #id
3689  // .settings.inactive #id
3690  //
3691  // In most cases, though, people do it wrong, if you use #id by
3692  // itself, it gives you direct access to exactly the right place.
3693  //
3694  //else if(hash.size() == 1 && !arg->get_child(0)->is(node_type_t::HASH))
3695  //{
3696  // error::instance() << arg->get_position()
3697  // << "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."
3698  // << error_mode_t::ERROR_INFO;
3699  //}
3700  }
3701  }
3702 
3703  return true;
3704 }
3705 
3706 std::string compiler::find_file(std::string const & script_name)
3707 {
3708  return f_state.find_file(script_name);
3709 }
3710 
3711 void compiler::set_validation_script(std::string const & script_name)
3712 {
3713  // try the filename as is first
3714  std::string filename(find_file(script_name));
3715  if(filename.empty())
3716  {
3717  if(script_name.substr(script_name.size() - 5) != ".scss")
3718  {
3719  // try again with the "scss" extension
3720  filename = find_file(script_name + ".scss");
3721  }
3722  }
3723 
3724  if(filename.empty())
3725  {
3726  // a validation script should always be available, right?
3727  position pos(script_name);
3728  error::instance() << pos
3729  << "validation script \""
3730  << script_name
3731  << "\" was not found."
3733  throw csspp_exception_exit(1);
3734  }
3735 
3736  node::pointer_t script;
3737 
3738  // TODO: review whether a cache would be useful, at this point
3739  // it does not work because the compiler is destructive.
3740  // maybe use node::clone() to make a copy of the cache?
3741  //auto cache(f_validator_scripts.find(filename));
3742  //if(cache == f_validator_scripts.end())
3743  {
3744  position pos(filename);
3745 
3746  // the file exists, read it now
3747  std::ifstream in;
3748  in.open(filename);
3749  if(!in)
3750  {
3751  // a validation script should always be available, right?
3752  //
3753  // At this point I do not see how to write a test to hit
3754  // these lines (i.e. have a file that's accessible in
3755  // read mode, but cannot be opened)
3756  error::instance() << pos // LCOV_EXCL_LINE
3757  << "validation script \"" // LCOV_EXCL_LINE
3758  << script_name // LCOV_EXCL_LINE
3759  << "\" could not be opened." // LCOV_EXCL_LINE
3760  << error_mode_t::ERROR_FATAL; // LCOV_EXCL_LINE
3761  throw csspp_exception_exit(1); // LCOV_EXCL_LINE
3762  }
3763 
3764  lexer::pointer_t l(new lexer(in, pos));
3765  parser p(l);
3766  script = p.stylesheet();
3767 
3768  // TODO: test whether errors occurred while reading the script, if
3769  // so then we have to generate a FATAL error here
3770 
3771  // cache the script
3772  //f_validator_scripts[filename] = script;
3773 //std::cerr << "script " << filename << " is:\n" << *script;
3774  }
3775  //else
3776  //{
3777  // script = cache->second;
3778  //}
3779 
3780  f_current_validation_script = script;
3781  script->clear_variables();
3782 }
3783 
3784 void compiler::add_validation_variable(std::string const & variable_name, node::pointer_t value)
3785 {
3787  {
3788  throw csspp_exception_logic("compiler.cpp: somehow add_validation_variable() was called without a current validation script set."); // LCOV_EXCL_LINE
3789  }
3790 
3791  node::pointer_t var(new node(node_type_t::VARIABLE, value->get_position()));
3792  var->set_string(variable_name);
3793  node::pointer_t v(new node(node_type_t::LIST, value->get_position()));
3794  v->add_child(var);
3795  v->add_child(value);
3796 
3797  f_current_validation_script->set_variable(variable_name, v);
3798 }
3799 
3800 bool compiler::run_validation(bool check_only)
3801 {
3802  // forbid validations from within validation scripts
3804  {
3805  throw csspp_exception_logic("compiler.cpp:compiler::run_validation(): already validating, cannot validate from within a validation script."); // LCOV_EXCL_LINE
3806  }
3807 
3808  // save the number of errors so we can test after we ran
3809  // the compile() function
3810  error_happened_t old_count;
3811 
3812  safe_compiler_state_t safe_state(f_state);
3814  if(check_only)
3815  {
3816  // save the current error/warning counters so they do not change
3817  // on this run
3818  safe_error_t safe_error;
3819 
3820  // replace the output stream with a memory buffer so the user
3821  // does not see any of it
3822  std::stringstream ignore;
3823  safe_error_stream_t safe_output(ignore);
3824 
3825  // now compile that true/false check
3826  compile(true);
3827 
3828  // WARNING: this MUST be here (before the closing curly bracket)
3829  // and not after the if() since we restore the error
3830  // state from before the compile() call.
3831  //
3832  bool const result(!old_count.error_happened());
3833 
3834  // now restore the stream and error counters
3835  return result;
3836  }
3837 
3838  compile(true);
3839 
3840  return !old_count.error_happened();
3841 }
3842 
3844 {
3845  safe_parents_t safe_parents(f_state, n);
3846 
3847  switch(n->get_type())
3848  {
3850  {
3851  node::pointer_t rule_last(n);
3852  for(size_t idx(0); idx < n->size();)
3853  {
3854  node::pointer_t child(n->get_child(idx));
3855  expand_nested_rules(f_state.get_previous_parent(), n, rule_last, child);
3856  if(idx < n->size()
3857  && child == n->get_child(idx))
3858  {
3859  ++idx;
3860  }
3861  }
3862  }
3863  break;
3864 
3866  // this is true for all but one case, when @-keyword accepts
3867  // declarations instead of rules (like @font-face); we may want
3868  // to test that and use the correct call in the @-keyword...
3869  //error::instance() << n->get_position()
3870  // << "a declaration can only appears inside a rule."
3871  // << error_mode_t::ERROR_ERROR;
3872  for(size_t idx(0); idx < n->size();)
3873  {
3874  node::pointer_t child(n->get_child(idx));
3875  node::pointer_t declaration_root(n);
3876  expand_nested_declarations(n->get_string(), f_state.get_previous_parent(), declaration_root, child);
3877  if(idx < n->size()
3878  && child == n->get_child(idx))
3879  {
3880  ++idx;
3881  }
3882  }
3883  //if(n->empty())
3884  //{
3885  // f_state.get_previous_parent()->remove_child(n);
3886  //}
3887  break;
3888 
3889  case node_type_t::LIST:
3892  for(size_t idx(0); idx < n->size();)
3893  {
3894  node::pointer_t child(n->get_child(idx));
3895  expand_nested_components(child);
3896  if(idx < n->size()
3897  && child == n->get_child(idx))
3898  {
3899  ++idx;
3900  }
3901  }
3902  break;
3903 
3904  //case node_type_t::ARG: -- we should not have sub-declarations under ARG
3905  default:
3906  break;
3907 
3908  }
3909 }
3910 
3912 {
3913  safe_parents_t safe_parents(f_state, n);
3914 
3915  switch(n->get_type())
3916  {
3918  //
3919  // before this expansion the declarations are like:
3920  //
3921  // COMPONENT_VALUE
3922  // ARG
3923  // ...
3924  // OPEN_CURLYBRACKET
3925  // LIST
3926  // DECLARATION
3927  // ARG
3928  // ...
3929  // DECLARATION
3930  // ARG
3931  // ...
3932  // COMPONENT_VALUE <-- expand this one with the first one
3933  // ARG
3934  // ...
3935  // OPEN_CURLYBRACKET
3936  // ...
3937  //
3938  // so what we do is move the sub-declaration at the same level as the
3939  // parent and prepend the name of the parent + "-".
3940  //
3941  {
3942  // move the rule as a child of the parent node
3943  f_state.get_previous_parent()->remove_child(n);
3944  size_t pos(parent->child_position(last));
3945  parent->insert_child(pos + 1, n);
3946 
3947  // prepend the arguments of root to the arguments of n
3948  // note that this is a product, if root has 3 ARGs and
3949  // n also has 3 ARGs, we end up with 9 ARGs in n
3950  node::pointer_t list(new node(node_type_t::LIST, n->get_position()));
3951  while(!n->empty())
3952  {
3953  node::pointer_t child(n->get_child(0));
3954  if(!child->is(node_type_t::ARG))
3955  {
3956  break;
3957  }
3958  n->remove_child(0);
3959  list->add_child(child);
3960  }
3961  for(size_t idx(0); idx < root->size(); ++idx)
3962  {
3963  node::pointer_t child(root->get_child(idx));
3964  if(!child->is(node_type_t::ARG))
3965  {
3966  break;
3967  }
3968  // we use clone because of the product
3969  node::pointer_t clone(child->clone());
3970  for(size_t l(0); l < list->size(); ++l)
3971  {
3972  node::pointer_t arg(list->get_child(l));
3973  // we use clone because of the product
3974  for(size_t a(0); a < arg->size(); ++a)
3975  {
3976  node::pointer_t item(arg->get_child(a));
3977  if(!item->is(node_type_t::REFERENCE))
3978  {
3979  if(a == 0)
3980  {
3981  node::pointer_t whitespace(new node(node_type_t::WHITESPACE, clone->get_position()));
3982  clone->add_child(whitespace);
3983  }
3984  clone->add_child(item->clone());
3985  }
3986  }
3987  }
3988  n->insert_child(idx, clone);
3989  }
3990 
3991  last = n;
3992 
3993  for(size_t idx(0); idx < n->size();)
3994  {
3995  node::pointer_t child(n->get_child(idx));
3996  node::pointer_t rule_root(n);
3997  expand_nested_rules(parent, rule_root, last, child);
3998  if(idx < n->size()
3999  && child == n->get_child(idx))
4000  {
4001  ++idx;
4002  }
4003  }
4004  }
4005  break;
4006 
4008  for(size_t idx(0); idx < n->size();)
4009  {
4010  node::pointer_t child(n->get_child(idx));
4011  node::pointer_t declaration_root(n);
4012  expand_nested_declarations(n->get_string(), f_state.get_previous_parent(), declaration_root, child);
4013  if(idx < n->size()
4014  && child == n->get_child(idx))
4015  {
4016  ++idx;
4017  }
4018  }
4019  if(n->empty())
4020  {
4021  f_state.get_previous_parent()->remove_child(n);
4022  }
4023  break;
4024 
4026  for(size_t idx(0); idx < n->size();)
4027  {
4028  node::pointer_t child(n->get_child(idx));
4029  expand_nested_components(child);
4030  if(idx < n->size()
4031  && child == n->get_child(idx))
4032  {
4033  ++idx;
4034  }
4035  }
4036  break;
4037 
4038  case node_type_t::LIST:
4040  for(size_t idx(0); idx < n->size();)
4041  {
4042  node::pointer_t child(n->get_child(idx));
4043  expand_nested_rules(parent, root, last, child);
4044  if(idx < n->size()
4045  && child == n->get_child(idx))
4046  {
4047  ++idx;
4048  }
4049  }
4050  break;
4051 
4052  //case node_type_t::ARG: -- we should not have sub-declarations under ARG
4053  default:
4054  break;
4055 
4056  }
4057 }
4058 
4060 {
4061  safe_parents_t safe_parents(f_state, n);
4062 
4063  switch(n->get_type())
4064  {
4066  //
4067  // before this expansion the declarations are like:
4068  //
4069  // DECLARATION
4070  // ARG
4071  // ...
4072  // OPEN_CURLYBRACKET
4073  // LIST
4074  // DECLARATION <-- expand this one with the first one
4075  // ARG
4076  // ...
4077  // DECLARATION <-- expand this too, also with the first one
4078  // ARG
4079  // ...
4080  //
4081  // so what we do is move the sub-declaration at the same level as the
4082  // parent and prepend the name of the parent + "-".
4083  //
4084  {
4085  std::string const sub_name((name == "-csspp-null" ? "" : name + "-") + n->get_string());
4086 
4087  // move this declaration from where it is now to the root
4088  f_state.get_previous_parent()->remove_child(n);
4089  size_t pos(parent->child_position(root));
4090  parent->insert_child(pos + 1, n);
4091  n->set_string(sub_name);
4092  root = n;
4093 
4094  for(size_t idx(0); idx < n->size();)
4095  {
4096  node::pointer_t child(n->get_child(idx));
4097  expand_nested_declarations(sub_name, parent, root, child);
4098  if(idx < n->size()
4099  && child == n->get_child(idx))
4100  {
4101  ++idx;
4102  }
4103  }
4104 
4105  // remove empty declarations
4106  //if(n->empty())
4107  //{
4108  // f_state.get_previous_parent()->remove_child(n);
4109  //}
4110  }
4111  break;
4112 
4114  // we may have to handle declarations within an @-keyword, but
4115  // it is not a sub-expand-nested-declaration
4116  throw csspp_exception_logic("compiler.cpp:compiler::expand_nested_declarations(): @-keyword cannot appear within a declaration."); // LCOV_EXCL_LINE
4117  //for(size_t idx(0); idx < n->size();)
4118  //{
4119  // node::pointer_t child(n->get_child(idx));
4120  // expand_nested_components(child);
4121  // if(idx < n->size()
4122  // && child == n->get_child(idx))
4123  // {
4124  // ++idx;
4125  // }
4126  //}
4127  //break;
4128 
4129  case node_type_t::LIST:
4131  for(size_t idx(0); idx < n->size();)
4132  {
4133  node::pointer_t child(n->get_child(idx));
4134  expand_nested_declarations(name, parent, root, child);
4135  if(idx < n->size()
4136  && child == n->get_child(idx))
4137  {
4138  ++idx;
4139  }
4140  }
4141  if(n->empty())
4142  {
4143  f_state.get_previous_parent()->remove_child(n);
4144  }
4145  break;
4146 
4148  error::instance() << n->get_position()
4149  << "a nested declaration cannot include a rule."
4151  break;
4152 
4153  //case node_type_t::ARG: -- we should not have sub-declarations under ARG
4154  default:
4155  break;
4156 
4157  }
4158 }
4159 
4160 } // namespace csspp
4161 
4162 // Local Variables:
4163 // mode: cpp
4164 // indent-tabs-mode: nil
4165 // c-basic-offset: 4
4166 // tab-width: 4
4167 // End:
4168 
4169 // vim: ts=4 sw=4 et
void prepare_function_arguments(node::pointer_t var)
Definition: compiler.cpp:2473
void replace_else(node::pointer_t parent, node::pointer_t n, size_t idx)
Definition: compiler.cpp:2718
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:2667
void expand_nested_components(node::pointer_t n)
Definition: compiler.cpp:3843
void expand_nested_rules(node::pointer_t parent, node::pointer_t root, node::pointer_t &last, node::pointer_t n)
Definition: compiler.cpp:3911
static bool is_nested_declaration(node::pointer_t n)
Definition: parser.cpp:704
bool run_validation(bool check_only)
Definition: compiler.cpp:3800
std::shared_ptr< node > pointer_t
Definition: node.h:128
int64_t integer_t
Definition: csspp.h:54
void set_variable(node::pointer_t n)
Definition: compiler.cpp:2352
node::pointer_t get_result() const
Definition: compiler.cpp:415
The namespace of all the classes in the CSS Preprocessor.
Definition: assembler.cpp:40
void compile_component_value(node::pointer_t n)
Definition: compiler.cpp:663
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:4059
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:1094
static bool is_variable_set(node::pointer_t n, bool with_block)
Definition: parser.cpp:668
bool selector_term(node::pointer_t n, size_t &pos)
Definition: compiler.cpp:3380
node::pointer_t stylesheet()
Definition: parser.cpp:70
static int const g_to_string_flag_show_quotes
Definition: node.h:131
void remove_empty_rules(node::pointer_t n)
Definition: compiler.cpp:1901
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:3711
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:790
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:1853
bool f_compiler_validating
Definition: compiler.h:128
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:874
bool selector_attribute_check(node::pointer_t parent, size_t &parent_pos, node::pointer_t n)
Definition: compiler.cpp:2948
node::pointer_t f_current_validation_script
Definition: compiler.h:126
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:1948
std::string find_file(std::string const &script_name)
Definition: compiler.cpp:3706
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:2066
double decimal_number_t
Definition: csspp.h:55
bool selector_list(node::pointer_t n, size_t &pos)
Definition: compiler.cpp:3543
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:127
void compile_at_keyword(node::pointer_t n)
Definition: compiler.cpp:1159
node::pointer_t at_keyword_expression(node::pointer_t n)
Definition: compiler.cpp:2654
void replace_variables_in_comment(node::pointer_t n)
Definition: compiler.cpp:2846
void add_validation_variable(std::string const &variable_name, node::pointer_t value)
Definition: compiler.cpp:3784
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:1615
void clear_paths()
Definition: compiler.cpp:504
void handle_mixin(node::pointer_t n)
Definition: compiler.cpp:1791
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:3096
void replace_at_keyword(node::pointer_t parent, node::pointer_t n, size_t &idx)
Definition: compiler.cpp:2546
void compile_qualified_rule(node::pointer_t n)
Definition: compiler.cpp:806
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:123
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:3625

Documentation of CSS Preprocessor.

This document is part of the Snap! Websites Project.

Copyright by Made to Order Software Corp.