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

Documentation of CSS Preprocessor.

This document is part of the Snap! Websites Project.

Copyright by Made to Order Software Corp.