Current Version: 1.0.33
Project Name: csspp
internal_functions.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#include "csspp/expression.h"
27
28#include "csspp/parser.h"
29
30#include <algorithm>
31#include <cmath>
32#include <climits>
33#include <iostream>
34
35namespace csspp
36{
37
38namespace
39{
40
42
43decimal_number_t dimension_to_radians(position const & pos, decimal_number_t n, std::string const & dimension)
44{
45 if(dimension == "rad")
46 {
47 // all good as is
48 return n;
49 }
50 else if(dimension == "deg"
51 || dimension == "") // no angle dimension, use as if it were degrees (which has been the default in CSS)
52 {
53 // convert degrees to radians
54 return n * M_PI / 180.0;
55 }
56 else if(dimension == "grad")
57 {
58 // convert grads to radians
59 return n * M_PI / 200.0;
60 }
61 else if(dimension == "turn")
62 {
63 // convert turns to radians
64 return n * M_PI * 2.0;
65 }
66 else
67 {
68 error::instance() << pos
69 << "trigonometry functions expect an angle (deg, grad, rad, turn) as a parameter."
71
72 // keep 'n' as is... what else could we do?
73 return n;
74 }
75}
76
77} // no name namespace
78
80{
81 g_unique_id_counter = counter;
82}
83
85{
86 return g_unique_id_counter;
87}
88
90{
91 if(argn >= func->size())
92 {
93 return node::pointer_t(); // LCOV_EXCL_LINE
94 }
95
96 node::pointer_t arg(func->get_child(argn));
97 if(arg->size() != 1)
98 {
99 return node::pointer_t();
100 }
101
102 return arg->get_child(0);
103}
104
106{
107 if(argn >= func->size())
108 {
109 return node::pointer_t(); // LCOV_EXCL_LINE
110 }
111
112 node::pointer_t arg(func->get_child(argn));
113 if(arg->size() != 1)
114 {
115 return node::pointer_t();
116 }
117
118 node::pointer_t value(arg->get_child(0));
120 {
121 col = value->get_color();
122 return value;
123 }
124
125 return node::pointer_t();
126}
127
129{
130 if(argn >= func->size())
131 {
132 return node::pointer_t();
133 }
134
135 node::pointer_t arg(func->get_child(argn));
136 if(arg->size() != 1)
137 {
138 return node::pointer_t();
139 }
140
141 node::pointer_t value(arg->get_child(0));
143 {
144 number = static_cast<decimal_number_t>(value->get_integer());
145 return value;
146 }
147
149 {
150 number = value->get_decimal_number();
151 return value;
152 }
153
154 return node::pointer_t();
155}
156
158{
159 if(argn >= func->size())
160 {
161 return node::pointer_t(); // LCOV_EXCL_LINE
162 }
163
164 node::pointer_t arg(func->get_child(argn));
165 if(arg->size() != 1)
166 {
167 return node::pointer_t();
168 }
169
170 node::pointer_t value(arg->get_child(0));
172 {
173 number = static_cast<decimal_number_t>(value->get_integer());
174 return value;
175 }
176
178 {
179 number = value->get_decimal_number();
180 return value;
181 }
182
184 {
185 number = value->get_decimal_number();
186 return value;
187 }
188
189 return node::pointer_t();
190}
191
193{
194 if(argn >= func->size())
195 {
196 return node::pointer_t(); // LCOV_EXCL_LINE
197 }
198
199 node::pointer_t arg(func->get_child(argn));
200 if(arg->size() != 1)
201 {
202 return node::pointer_t();
203 }
204
205 node::pointer_t value(arg->get_child(0));
207 {
208 str = value->get_string();
209 return value;
210 }
211
212 return node::pointer_t();
213}
214
216{
217 if(argn >= func->size())
218 {
219 return node::pointer_t(); // LCOV_EXCL_LINE
220 }
221
222 node::pointer_t arg(func->get_child(argn));
223 if(arg->size() != 1)
224 {
225 return node::pointer_t();
226 }
227
228 node::pointer_t value(arg->get_child(0));
231 {
232 str = value->get_string();
233 return value;
234 }
235
236 return node::pointer_t();
237}
238
240{
241 // abs(number)
244 if(number)
245 {
246 if(number->is(node_type_t::INTEGER))
247 {
248 number->set_integer(labs(number->get_integer()));
249 }
250 else
251 {
252 number->set_decimal_number(fabs(number->get_decimal_number()));
253 }
254 return number;
255 }
256
257 error::instance() << f_current->get_position()
258 << "abs() expects a number as parameter."
260
261 return node::pointer_t();
262}
263
265{
266 // acos(number)
269 if(number)
270 {
271 if(number->is(node_type_t::INTEGER))
272 {
273 number.reset(new node(node_type_t::DECIMAL_NUMBER, number->get_position()));
274 }
275 // should we return the angle in degrees instead?
276 number->set_decimal_number(acos(n));
277 number->set_string("rad");
278 return number;
279 }
280
281 error::instance() << f_current->get_position()
282 << "acos() expects a number as parameter."
284
285 return node::pointer_t();
286}
287
289{
290 // alpha(color)
291 color c;
293 if(col)
294 {
299 c.get_color(r, g, b, a);
301 component->set_decimal_number(a);
302 return component;
303 }
304
305 error::instance() << f_current->get_position()
306 << "alpha() expects a color as parameter."
308
309 return node::pointer_t();
310}
311
313{
314 // asin(number)
317 if(number)
318 {
319 if(number->is(node_type_t::INTEGER))
320 {
321 number.reset(new node(node_type_t::DECIMAL_NUMBER, number->get_position()));
322 }
323 // should we return the angle in degrees instead?
324 number->set_decimal_number(asin(n));
325 number->set_string("rad");
326 return number;
327 }
328
329 error::instance() << f_current->get_position()
330 << "asin() expects a number as parameter."
332
333 return node::pointer_t();
334}
335
337{
338 // atan(number)
341 if(number)
342 {
343 if(number->is(node_type_t::INTEGER))
344 {
345 number.reset(new node(node_type_t::DECIMAL_NUMBER, number->get_position()));
346 }
347 // should we return the angle in degrees instead?
348 number->set_decimal_number(atan(n));
349 number->set_string("rad");
350 return number;
351 }
352
353 error::instance() << f_current->get_position()
354 << "atan() expects a number as parameter."
356
357 return node::pointer_t();
358}
359
361{
362 // blue(color)
363 color c;
365 if(col)
366 {
371 c.get_color(r, g, b, a);
373 component->set_integer(static_cast<integer_t>(b * 255.0 + 0.5));
374 return component;
375 }
376
377 error::instance() << f_current->get_position()
378 << "blue() expects a color as parameter."
380
381 return node::pointer_t();
382}
383
385{
386 // ceil(number)
389 if(number)
390 {
391 if(number->is(node_type_t::DECIMAL_NUMBER))
392 {
393 number->set_decimal_number(ceil(number->get_decimal_number()));
394 }
395 return number;
396 }
397
398 error::instance() << f_current->get_position()
399 << "ceil() expects a number as parameter."
401
402 return node::pointer_t();
403}
404
406{
407 // cos(number)
410 if(number)
411 {
412 std::string const dimension(number->get_string());
413 if(number->is(node_type_t::INTEGER))
414 {
415 number.reset(new node(node_type_t::DECIMAL_NUMBER, number->get_position()));
416 }
417 else
418 {
419 // we "lose" the dimension
420 number->set_string("");
421 }
422 n = dimension_to_radians(func->get_position(), n, dimension);
423 number->set_decimal_number(cos(n));
424 return number;
425 }
426
427 error::instance() << f_current->get_position()
428 << "cos() expects an angle as parameter."
430
431 return node::pointer_t();
432}
433
435{
436 // decimal_number(expr)
438 if(any)
439 {
440 switch(any->get_type())
441 {
443 // already a decimal number, return as is
444 return any;
445
447 {
448 node::pointer_t number(new node(node_type_t::DECIMAL_NUMBER, any->get_position()));
449 number->set_decimal_number(any->get_decimal_number());
450 return number;
451 }
452
454 {
455 node::pointer_t number(new node(node_type_t::DECIMAL_NUMBER, any->get_position()));
456 number->set_decimal_number(any->get_integer());
457 number->set_string(any->get_string());
458 return number;
459 }
460
463 case node_type_t::URL:
464 {
465 std::stringstream ss;
466 ss << any->get_string();
467 lexer l(ss, any->get_position());
468 node::pointer_t number(l.next_token());
469 if(number->is(node_type_t::WHITESPACE))
470 {
471 number = l.next_token();
472 }
473 switch(number->get_type())
474 {
476 return number;
477
479 {
481 result->set_decimal_number(number->get_decimal_number());
482 return result;
483 }
484
486 {
488 result->set_decimal_number(number->get_integer());
489 result->set_string(number->get_string());
490 return result;
491 }
492
493 default:
494 break;
495
496 }
497 }
498 error::instance() << f_current->get_position()
499 << "decimal_number() expects a string parameter to represent a valid integer, decimal number, or percent value."
501 return node::pointer_t();
502
503 default:
504 break;
505
506 }
507 }
508
509 error::instance() << f_current->get_position()
510 << "decimal_number() expects one value as parameter."
512
513 return node::pointer_t();
514}
515
517{
518 // floor(number)
521 if(number)
522 {
523 if(number->is(node_type_t::DECIMAL_NUMBER))
524 {
525 number->set_decimal_number(floor(number->get_decimal_number()));
526 }
527 return number;
528 }
529
530 error::instance() << f_current->get_position()
531 << "floor() expects a number as parameter."
533
534 return node::pointer_t();
535}
536
538{
539 // frgb(color)
540 // frgb(fred, fgreen, fblue)
541 color c;
543 if(col)
544 {
545 // force alpha to 1.0
550 c.get_color(r, g, b, a);
551 c.set_color(r, g, b, static_cast<color_component_t>(1.0));
552 col->set_color(c);
553 return col;
554 }
555 else
556 {
560
564
565 if(col1 && col2 && col3)
566 {
567 // force alpha to 1.0
568 c.set_color(r, g, b, 1.0);
569 col.reset(new node(node_type_t::COLOR, func->get_position()));
570 col->set_color(c);
571 return col;
572 }
573 }
574
575 error::instance() << f_current->get_position()
576 << "frgb() expects exactly one color parameter or three numbers (Red, Green, Blue)."
578
579 return node::pointer_t();
580}
581
583{
584 // frgba(color, alpha)
585 // frgba(fred, fgreen, fblue, alpha)
586 color c;
590 if(col && alpha)
591 {
592 // replace alpha
597 c.get_color(r, g, b, old_a);
598 c.set_color(r, g, b, static_cast<color_component_t>(a));
599 col->set_color(c);
600 return col;
601 }
602 else
603 {
607
612
613 if(col1 && col2 && col3 && col4)
614 {
615 // set color with alpha
616 c.set_color(r, g, b, a);
617 col.reset(new node(node_type_t::COLOR, func->get_position()));
618 col->set_color(c);
619 return col;
620 }
621 }
622
623 error::instance() << f_current->get_position()
624 << "frgba() expects exactly one color parameter followed by one number (Color, Alpha), or four numbers (Red, Green, Blue, Alpha)."
626
627 return node::pointer_t();
628}
629
631{
632 // function_exists(name)
633 std::string name;
635 if(id && !name.empty())
636 {
637 node::pointer_t result(new node(node_type_t::BOOLEAN, func->get_position()));
638
639 // although variables were already applied they will still be
640 // defined when we reach these lines of code
642 {
644 if(var
645 && var->is(node_type_t::LIST)
646 && !var->empty()
647 && (var->get_child(0)->is(node_type_t::VARIABLE_FUNCTION) // $<name>()
648 || var->get_child(0)->is(node_type_t::FUNCTION))) // @mixin <name>()
649 {
650 result->set_boolean(true);
651 }
652 }
653 // else -- default is already false
654
655 return result;
656 }
657
658 error::instance() << f_current->get_position()
659 << "function_exists() expects a string or an identifier as parameter."
661
662 return node::pointer_t();
663}
664
666{
667 // green(color)
668 color c;
670 if(col)
671 {
676 c.get_color(r, g, b, a);
678 component->set_integer(static_cast<integer_t>(g * 255.0 + 0.5));
679 return component;
680 }
681
682 error::instance() << f_current->get_position()
683 << "green() expects a color as parameter."
685
686 return node::pointer_t();
687}
688
690{
691 // global_variable_exists(name)
692 std::string name;
694 if(id && !name.empty())
695 {
696 node::pointer_t result(new node(node_type_t::BOOLEAN, func->get_position()));
697
698 // although variables were already applied they will still be
699 // defined when we reach these lines of code
701 {
703 if(var
704 && var->is(node_type_t::LIST)
705 && !var->empty()
706 && (var->get_child(0)->is(node_type_t::VARIABLE) // $<name>
707 || var->get_child(0)->is(node_type_t::IDENTIFIER))) // @mixin <name>
708 {
709 result->set_boolean(true);
710 }
711 }
712 // else -- default is already false
713
714 return result;
715 }
716
717 error::instance() << f_current->get_position()
718 << "global_variable_exists() expects a string or an identifier as parameter."
720
721 return node::pointer_t();
722}
723
725{
726 // hsl(red, green, blue)
730
734
735 if(col1 && col2 && col3)
736 {
737 // transform the angle from whatever dimension it is defined as to
738 // radians as expected by set_hsl()
739 h = dimension_to_radians(func->get_position(), h, col1->get_string());
740
741 // force alpha to 1.0
742 color c;
743 c.set_hsl(h, s, l, 1.0);
744 node::pointer_t col(new node(node_type_t::COLOR, func->get_position()));
745 col->set_color(c);
746 return col;
747 }
748
749 error::instance() << f_current->get_position()
750 << "hsl() expects exactly three numbers: Hue (angle), Saturation (%), and Lightness (%)."
752
753 return node::pointer_t();
754}
755
757{
758 // hsla(red, green, blue, alpha)
763
768
769 if(col1 && col2 && col3 && col4)
770 {
771 // transform the angle from whatever dimension it is defined as to
772 // radians as expected by set_hsl()
773 h = dimension_to_radians(func->get_position(), h, col1->get_string());
774
775 // set color with alpha
776 color c;
777 c.set_hsl(h, s, l, a);
778 node::pointer_t col(new node(node_type_t::COLOR, func->get_position()));
779 col->set_color(c);
780 return col;
781 }
782
783 error::instance() << f_current->get_position()
784 << "hsla() expects exactly four numbers: Hue (angle), Saturation (%), Lightness (%), and Alpha (0.0 to 1.0)."
786
787 return node::pointer_t();
788}
789
791{
792 // hue(color)
793 color c;
795 if(col)
796 {
801 c.get_hsl(hue, saturation, lightness, a);
803 component->set_decimal_number(hue * 180.0 / M_PI);
804 component->set_string("deg");
805 return component;
806 }
807
808 error::instance() << f_current->get_position()
809 << "hue() expects a color as parameter."
811
812 return node::pointer_t();
813}
814
816{
817 // identifier(expr)
819 if(any)
820 {
821 switch(any->get_type())
822 {
824 // already an identifier, return as is
825 return any;
826
831 {
832 node::pointer_t id(new node(node_type_t::IDENTIFIER, any->get_position()));
833 id->set_string(any->to_string(0));
834 return id;
835 }
836
838 case node_type_t::URL:
839 {
840 node::pointer_t id(new node(node_type_t::IDENTIFIER, any->get_position()));
841 id->set_string(any->get_string());
842 return id;
843 }
844
845 default:
846 break;
847
848 }
849 }
850
851 error::instance() << f_current->get_position()
852 << "identifier() expects one value as parameter."
854
855 return node::pointer_t();
856}
857
859{
860 // if(condition, if-true, if-false)
861 node::pointer_t arg1(func->get_child(0));
862 if(arg1->size() != 1)
863 {
864 error::instance() << f_current->get_position()
865 << "if() expects a boolean as its first argument."
867 return node::pointer_t();
868 }
869 else
870 {
871 // if boolean() returns true when arg1 is considered true
872 //
873 // Note:
874 // It generates an error and returns false if the node passed in
875 // is not considered to be a valid boolean node.
876 //
877 bool const r(boolean(arg1->get_child(0)));
878 node::pointer_t result(func->get_child(r ? 1 : 2));
879 if(result->size() == 1)
880 {
881 // very simple result, return as is
882 return result->get_child(0);
883 }
884 // complex result (multiple nodes), return in a list
885 node::pointer_t list(new node(node_type_t::LIST, result->get_position()));
886 list->take_over_children_of(result); // TBD: should we use clone() instead?
887 return list;
888 }
889}
890
892{
893 // inspect(expression)
894 //
895 // no need to check whether child 0 exists since we get called only
896 // if the function has exactly 1 argument
897 //
898 node::pointer_t any(func->get_child(0));
899 node::pointer_t result(new node(node_type_t::STRING, any->get_position()));
900 result->set_string(any->to_string(node::g_to_string_flag_show_quotes));
901 return result;
902}
903
905{
906 // integer(expression)
908 if(any)
909 {
910 switch(any->get_type())
911 {
913 // already an integer, return as is
914 return any;
915
917 {
918 node::pointer_t number(new node(node_type_t::INTEGER, any->get_position()));
919 number->set_integer(any->get_decimal_number());
920 number->set_string(any->get_string());
921 return number;
922 }
923
925 {
926 node::pointer_t number(new node(node_type_t::INTEGER, any->get_position()));
927 number->set_integer(any->get_decimal_number());
928 return number;
929 }
930
933 case node_type_t::URL:
934 {
935 std::stringstream ss;
936 ss << any->get_string();
937 lexer l(ss, any->get_position());
938 node::pointer_t number(l.next_token());
939 if(number->is(node_type_t::WHITESPACE))
940 {
941 number = l.next_token();
942 }
943 switch(number->get_type())
944 {
946 return number;
947
949 {
950 node::pointer_t result(new node(node_type_t::INTEGER, any->get_position()));
951 result->set_integer(number->get_decimal_number());
952 result->set_string(number->get_string());
953 return result;
954 }
955
957 {
958 node::pointer_t result(new node(node_type_t::INTEGER, any->get_position()));
959 result->set_integer(number->get_decimal_number());
960 return result;
961 }
962
963 default:
964 break;
965
966 }
967 }
968 error::instance() << f_current->get_position()
969 << "decimal_number() expects a string parameter to represent a valid integer, decimal number, or percent value."
971 return node::pointer_t();
972
973 default:
974 break;
975
976 }
977 }
978
979 error::instance() << f_current->get_position()
980 << "integer() expects one value as parameter."
982
983 return node::pointer_t();
984}
985
987{
988 // lightness(color)
989 color c;
991 if(col)
992 {
997 c.get_hsl(hue, saturation, lightness, a);
999 component->set_decimal_number(lightness);
1000 return component;
1001 }
1002
1003 error::instance() << f_current->get_position()
1004 << "lightness() expects a color as parameter."
1006
1007 return node::pointer_t();
1008}
1009
1011{
1012 // log(number)
1015 if(number)
1016 {
1017 if(!number->get_string().empty())
1018 {
1019 error::instance() << f_current->get_position()
1020 << "log() expects a unit less number as parameter."
1022
1023 return node::pointer_t();
1024 }
1025 if(n <= 0.0)
1026 {
1027 error::instance() << f_current->get_position()
1028 << "log() expects a positive number as parameter."
1030
1031 return node::pointer_t();
1032 }
1033 if(number->is(node_type_t::INTEGER))
1034 {
1035 number.reset(new node(node_type_t::DECIMAL_NUMBER, number->get_position()));
1036 }
1037 number->set_decimal_number(log(n));
1038 return number;
1039 }
1040
1041 error::instance() << f_current->get_position()
1042 << "log() expects a number as parameter."
1044
1045 return node::pointer_t();
1046}
1047
1049{
1050 // max(n1, n2, ...)
1051 node::pointer_t number;
1053 std::string dimension;
1054 size_t const max_children(func->size());
1055 for(size_t idx(0); idx < max_children; ++idx)
1056 {
1059 if(n)
1060 {
1061 if(idx == 0)
1062 {
1063 if(n->is(node_type_t::PERCENT))
1064 {
1065#pragma GCC diagnostic push
1066#pragma GCC diagnostic ignored "-Wrestrict"
1067 dimension = "%";
1068#pragma GCC diagnostic pop
1069 }
1070 else
1071 {
1072 dimension = n->get_string();
1073 }
1074 }
1075 else
1076 {
1077 bool valid_dimension(true);
1078 if(n->is(node_type_t::PERCENT))
1079 {
1080 valid_dimension = dimension == "%";
1081 }
1082 else
1083 {
1084 valid_dimension = dimension == n->get_string();
1085 }
1086 if(!valid_dimension)
1087 {
1088 // all dimensions must be the same
1089 error::instance() << f_current->get_position()
1090 << "max() expects all numbers to have the same dimension."
1092 }
1093 }
1094 if(idx == 0 || r > maximum)
1095 {
1096 number = n;
1097 maximum = r;
1098 }
1099 }
1100 else
1101 {
1102 error::instance() << f_current->get_position()
1103 << "max() expects any number of numbers."
1105 }
1106 }
1107 return number;
1108}
1109
1111{
1112 // min(n1, n2, ...)
1113 node::pointer_t number;
1115 std::string dimension;
1116 size_t const max_children(func->size());
1117 for(size_t idx(0); idx < max_children; ++idx)
1118 {
1121 if(n)
1122 {
1123 if(idx == 0)
1124 {
1125 if(n->is(node_type_t::PERCENT))
1126 {
1127#pragma GCC diagnostic push
1128#pragma GCC diagnostic ignored "-Wrestrict"
1129 dimension = "%";
1130#pragma GCC diagnostic pop
1131 }
1132 else
1133 {
1134 dimension = n->get_string();
1135 }
1136 }
1137 else
1138 {
1139 bool valid_dimension(true);
1140 if(n->is(node_type_t::PERCENT))
1141 {
1142 valid_dimension = dimension == "%";
1143 }
1144 else
1145 {
1146 valid_dimension = dimension == n->get_string();
1147 }
1148 if(!valid_dimension)
1149 {
1150 // all dimensions must be the same
1151 error::instance() << f_current->get_position()
1152 << "min() expects all numbers to have the same dimension."
1154 }
1155 }
1156 if(idx == 0 || r < minimum)
1157 {
1158 number = n;
1159 minimum = r;
1160 }
1161 }
1162 else
1163 {
1164 error::instance() << f_current->get_position()
1165 << "min() expects any number of numbers."
1167 }
1168 }
1169 return number;
1170}
1171
1173{
1174 // not(boolean)
1175 node::pointer_t arg1(func->get_child(0));
1176 if(arg1->size() != 1)
1177 {
1178 error::instance() << f_current->get_position()
1179 << "not() expects a boolean as its first argument."
1181 return node::pointer_t();
1182 }
1183 else
1184 {
1185 bool const r(boolean(arg1->get_child(0)));
1186 node::pointer_t result(new node(node_type_t::BOOLEAN, func->get_position()));
1187 result->set_boolean(!r); // this is 'not()' so false is true and vice versa
1188 return result;
1189 }
1190}
1191
1193{
1194 // percentage(expr)
1196 if(any)
1197 {
1198 switch(any->get_type())
1199 {
1201 {
1202 node::pointer_t number(new node(node_type_t::PERCENT, any->get_position()));
1203 number->set_decimal_number(any->get_decimal_number());
1204 return number;
1205 }
1206
1208 // already a percentage, return as is
1209 return any;
1210
1212 {
1213 node::pointer_t number(new node(node_type_t::PERCENT, any->get_position()));
1214 number->set_decimal_number(any->get_integer());
1215 return number;
1216 }
1217
1220 case node_type_t::URL:
1221 {
1222 std::stringstream ss;
1223 ss << any->get_string();
1224 lexer l(ss, any->get_position());
1225 node::pointer_t number(l.next_token());
1226 if(number->is(node_type_t::WHITESPACE))
1227 {
1228 number = l.next_token();
1229 }
1230 switch(number->get_type())
1231 {
1233 {
1234 node::pointer_t result(new node(node_type_t::PERCENT, any->get_position()));
1235 result->set_decimal_number(number->get_decimal_number());
1236 return result;
1237 }
1238
1240 return number;
1241
1243 {
1244 node::pointer_t result(new node(node_type_t::PERCENT, any->get_position()));
1245 result->set_decimal_number(number->get_integer());
1246 return result;
1247 }
1248
1249 default:
1250 break;
1251
1252 }
1253 }
1254 error::instance() << f_current->get_position()
1255 << "percentage() expects a string parameter to represent a valid integer, decimal number, or percent value."
1257 return node::pointer_t();
1258
1259 default:
1260 break;
1261
1262 }
1263 }
1264
1265 error::instance() << f_current->get_position()
1266 << "percentage() expects one value as parameter."
1268
1269 return node::pointer_t();
1270}
1271
1273{
1274 // red(color)
1275 color c;
1277 if(col)
1278 {
1283 c.get_color(r, g, b, a);
1285 component->set_integer(static_cast<integer_t>(r * 255.0 + 0.5));
1286 return component;
1287 }
1288
1289 error::instance() << f_current->get_position()
1290 << "red() expects a color as parameter."
1292
1293 return node::pointer_t();
1294}
1295
1297{
1298 // rgb(color)
1299 // rgb(red, green, blue)
1300 color c;
1302 if(col)
1303 {
1304 // force alpha to 1.0
1309 c.get_color(r, g, b, a);
1310 c.set_color(r, g, b, static_cast<color_component_t>(1.0));
1311 col->set_color(c);
1312 return col;
1313 }
1314 else
1315 {
1319
1323
1324 if(col1 && col2 && col3)
1325 {
1326 // force alpha to 1.0
1327 c.set_color(r / 255.0, g / 255.0, b / 255.0, 1.0);
1328 col.reset(new node(node_type_t::COLOR, func->get_position()));
1329 col->set_color(c);
1330 return col;
1331 }
1332 }
1333
1334 error::instance() << f_current->get_position()
1335 << "rgb() expects exactly one color parameter (Color) or three numbers (Red, Green, Blue)."
1337
1338 return node::pointer_t();
1339}
1340
1342{
1343 // rgba(color, alpha)
1344 // rgba(red, green, blue, alpha)
1345 color c;
1349 if(col && alpha)
1350 {
1351 // replace alpha
1356 c.get_color(r, g, b, old_a);
1357 c.set_color(r, g, b, static_cast<color_component_t>(a));
1358 col->set_color(c);
1359 return col;
1360 }
1361 else
1362 {
1366
1371
1372 if(col1 && col2 && col3 && col4)
1373 {
1374 // set color with alpha
1375 c.set_color(r / 255.0, g / 255.0, b / 255.0, a);
1376 col.reset(new node(node_type_t::COLOR, func->get_position()));
1377 col->set_color(c);
1378 return col;
1379 }
1380 }
1381
1382 error::instance() << f_current->get_position()
1383 << "rgba() expects exactly one color parameter followed by alpha (Color, Alpha) or four numbers (Red, Green, Blue, Alpha)."
1385
1386 return node::pointer_t();
1387}
1388
1390{
1391 // random()
1392 node::pointer_t number(new node(node_type_t::DECIMAL_NUMBER, func->get_position()));
1393 // rand() is certainly the worst function ever, but I am not even
1394 // sure why anyone would ever want to use random() in a CSS document
1395 // (frankly?! random CSS???)
1396 number->set_decimal_number(static_cast<decimal_number_t>(rand()) / (static_cast<decimal_number_t>(RAND_MAX) + 1.0));
1397 return number;
1398}
1399
1401{
1402 // round(number)
1405 if(number)
1406 {
1407 if(number->is(node_type_t::DECIMAL_NUMBER))
1408 {
1409 number->set_decimal_number(round(number->get_decimal_number()));
1410 }
1411 return number;
1412 }
1413
1414 error::instance() << f_current->get_position()
1415 << "round() expects a number as parameter."
1417
1418 return node::pointer_t();
1419}
1420
1422{
1423 // saturation(color)
1424 color c;
1426 if(col)
1427 {
1432 c.get_hsl(hue, saturation, lightness, a);
1434 component->set_decimal_number(saturation);
1435 return component;
1436 }
1437
1438 error::instance() << f_current->get_position()
1439 << "saturation() expects a color as parameter."
1441
1442 return node::pointer_t();
1443}
1444
1446{
1447 // sign(number)
1450 if(number)
1451 {
1452 if(number->is(node_type_t::INTEGER))
1453 {
1454 number->set_integer(n < 0.0 ? -1 : (n > 0.0 ? 1 : 0));
1455 }
1456 else
1457 {
1458 number->set_decimal_number(n < 0.0 ? -1.0 : (n > 0.0 ? 1.0 : 0.0));
1459 }
1460 return number;
1461 }
1462
1463 error::instance() << f_current->get_position()
1464 << "sign() expects a number as parameter."
1466
1467 return node::pointer_t();
1468}
1469
1471{
1472 // sin(number)
1475 if(number)
1476 {
1477 std::string const dimension(number->get_string());
1478 if(number->is(node_type_t::INTEGER))
1479 {
1480 number.reset(new node(node_type_t::DECIMAL_NUMBER, number->get_position()));
1481 }
1482 else
1483 {
1484 // we "lose" the dimension
1485 number->set_string("");
1486 }
1487 n = dimension_to_radians(func->get_position(), n, dimension);
1488 number->set_decimal_number(sin(n));
1489 return number;
1490 }
1491
1492 error::instance() << f_current->get_position()
1493 << "sin() expects an angle as parameter."
1495
1496 return node::pointer_t();
1497}
1498
1500{
1501 // sqrt(number)
1504 if(number)
1505 {
1506 if(n < 0.0)
1507 {
1508 // an error occured, we cannot handle those dimensions
1509 error::instance() << f_current->get_position()
1510 << "sqrt() expects zero or a positive number."
1512
1513 return node::pointer_t();
1514 }
1515 std::string dimension(number->get_string());
1516 if(!dimension.empty())
1517 {
1518 // the dimension MUST be a square
1519 //
1520 // first get the current dimensions
1523 dimensions_to_vectors(number->get_position(), dimension, dividend, divisor);
1524
1525 if(((dividend.size() & 1) == 0)
1526 && ((divisor.size() & 1) == 0))
1527 {
1529 while(dividend.size() > 0)
1530 {
1531 std::string const dim(*(dividend.end() - 1));
1532 // remove this instance
1533 dividend.erase(dividend.end() - 1);
1534 // make sure there is another instance
1535 dimension_vector_t::iterator it(std::find(dividend.begin(), dividend.end(), dim));
1536 if(it == dividend.end())
1537 {
1538 // it's not valid
1539 dimension.clear();
1540 break;
1541 }
1542 // remove the copy instance
1543 dividend.erase(it);
1544 // keep one instance here instead
1545 new_dividend.push_back(dim);
1546 }
1547
1549 if(!dimension.empty())
1550 {
1551 while(divisor.size() > 0)
1552 {
1553 std::string const dim(*(divisor.end() - 1));
1554 // remove this instance
1555 divisor.erase(divisor.end() - 1);
1556 // make sure there is another instance
1557 dimension_vector_t::iterator it(std::find(divisor.begin(), divisor.end(), dim));
1558 if(it == divisor.end())
1559 {
1560 // it's not valid
1561 dimension.clear();
1562 break;
1563 }
1564 // remove the copy instance
1565 divisor.erase(it);
1566 // keep one instance here instead
1567 new_divisor.push_back(dim);
1568 }
1569 }
1570
1571 if(!dimension.empty())
1572 {
1574 }
1575 }
1576 else
1577 {
1578 dimension.clear();
1579 }
1580
1581 if(dimension.empty())
1582 {
1583 // an error occured, we cannot handle those dimensions
1584 error::instance() << f_current->get_position()
1585 << "sqrt() expects dimensions to be squarely defined (i.e. 'px * px')."
1587
1588 return node::pointer_t();
1589 }
1590 }
1591 if(number->is(node_type_t::INTEGER))
1592 {
1593 number.reset(new node(node_type_t::DECIMAL_NUMBER, number->get_position()));
1594 }
1595 number->set_decimal_number(sqrt(n));
1596 number->set_string(dimension);
1597 return number;
1598 }
1599
1600 error::instance() << f_current->get_position()
1601 << "sqrt() expects a number as parameter."
1603
1604 return node::pointer_t();
1605}
1606
1608{
1609 // string(expr)
1611 if(any)
1612 {
1613 switch(any->get_type())
1614 {
1616 // already a string, return as is
1617 return any;
1618
1619 case node_type_t::COLOR:
1623 {
1624 node::pointer_t id(new node(node_type_t::STRING, any->get_position()));
1625 id->set_string(any->to_string(0));
1626 return id;
1627 }
1628
1630 case node_type_t::URL:
1631 {
1632 node::pointer_t id(new node(node_type_t::STRING, any->get_position()));
1633 id->set_string(any->get_string());
1634 return id;
1635 }
1636
1637 default:
1638 break;
1639
1640 }
1641 }
1642
1643 error::instance() << f_current->get_position()
1644 << "string() expects one value as parameter."
1646
1647 return node::pointer_t();
1648}
1649
1651{
1652 // str_length(string)
1653 std::string copy;
1655 if(str)
1656 {
1657 // make sure to compute the proper UTF-8 length
1658 node::pointer_t length(new node(node_type_t::INTEGER, func->get_position()));
1659 size_t l(0);
1660 for(char const *s(copy.c_str()); *s != '\0'; ++s)
1661 {
1662 if(static_cast<unsigned char>(*s) < 0x80
1663 || static_cast<unsigned char>(*s) >= 0xC0)
1664 {
1665 ++l;
1666 }
1667 }
1668 length->set_integer(l);
1669 return length;
1670 }
1671
1672 error::instance() << f_current->get_position()
1673 << "str_length() expects one string as parameter."
1675
1676 return node::pointer_t();
1677}
1678
1680{
1681 // tan(number)
1684 if(number)
1685 {
1686 std::string const dimension(number->get_string());
1687 if(number->is(node_type_t::INTEGER))
1688 {
1689 number.reset(new node(node_type_t::DECIMAL_NUMBER, number->get_position()));
1690 }
1691 else
1692 {
1693 // we "lose" the dimension
1694 number->set_string("");
1695 }
1696 n = dimension_to_radians(func->get_position(), n, dimension);
1697 number->set_decimal_number(tan(n));
1698 return number;
1699 }
1700
1701 error::instance() << f_current->get_position()
1702 << "tan() expects an angle as parameter."
1704
1705 return node::pointer_t();
1706}
1707
1709{
1710 // unique_id()
1711 // unique_id(identifier)
1712 std::string id;
1713 if(func->size() == 1)
1714 {
1716 if(!user_id)
1717 {
1718 error::instance() << f_current->get_position()
1719 << "unique_id() expects a string or an identifier as its optional parameter."
1721 return node::pointer_t();
1722 }
1723 id = user_id->get_string();
1724 }
1725 if(id.empty())
1726 {
1727 id = "_csspp_unique";
1728 }
1729
1730 // counter increases on each call
1731 // (this is not too good if the library is used by a GUI)
1732 ++g_unique_id_counter;
1733
1734 id += std::to_string(g_unique_id_counter);
1735
1736 node::pointer_t identifier(new node(node_type_t::IDENTIFIER, func->get_position()));
1737 identifier->set_string(id);
1738
1739 return identifier;
1740}
1741
1743{
1744 // type_of(expression)
1746 if(any)
1747 {
1748 node::pointer_t type(new node(node_type_t::STRING, func->get_position()));
1749 switch(any->get_type())
1750 {
1751 case node_type_t::ARRAY:
1752 case node_type_t::LIST:
1753 type->set_string("list");
1754 break;
1755
1757 type->set_string("bool");
1758 break;
1759
1760 case node_type_t::COLOR:
1761 type->set_string("color");
1762 break;
1763
1766 type->set_string("number");
1767 break;
1768
1770 type->set_string("identifier");
1771 break;
1772
1774 type->set_string("integer");
1775 break;
1776
1777 case node_type_t::MAP:
1778 type->set_string("map");
1779 break;
1780
1782 type->set_string("string");
1783 break;
1784
1786 type->set_string("unicode-range");
1787 break;
1788
1789 //case node_type_t::NULL_TOKEN: -- null is like undefined
1790 default:
1791 type->set_string("undefined");
1792 break;
1793
1794 }
1795 return type;
1796 }
1797
1798 error::instance() << f_current->get_position()
1799 << "type_of() expects one value as a parameter."
1801
1802 return node::pointer_t();
1803}
1804
1806{
1807 // unit(number)
1810 if(number != nullptr)
1811 {
1812 node::pointer_t unit(new node(node_type_t::STRING, func->get_position()));
1813 if(number->is(node_type_t::PERCENT))
1814 {
1815 unit->set_string("%");
1816 }
1817 else
1818 {
1819 unit->set_string(number->get_string());
1820 }
1821 return unit;
1822 }
1823
1824 error::instance() << f_current->get_position()
1825 << "unit() expects a number as parameter."
1827
1828 return node::pointer_t();
1829}
1830
1832{
1833 // variable_exists(name)
1834 std::string name;
1836 if(id && !name.empty())
1837 {
1838 node::pointer_t result(new node(node_type_t::BOOLEAN, func->get_position()));
1839
1840 // although variables were already applied they will still be
1841 // defined when we reach these lines of code
1843 {
1845 if(var
1846 && var->is(node_type_t::LIST)
1847 && !var->empty()
1848 && (var->get_child(0)->is(node_type_t::VARIABLE) // $<name>
1849 || var->get_child(0)->is(node_type_t::IDENTIFIER))) // @mixin <name>
1850 {
1851 result->set_boolean(true);
1852 }
1853 }
1854 // else -- default is already false
1855
1856 return result;
1857 }
1858
1859 error::instance() << f_current->get_position()
1860 << "variable_exists() expects a string or an identifier as parameter."
1862
1863 return node::pointer_t();
1864}
1865
1867{
1869
1870 // TODO: maybe add a parser which takes all the ARGs and transform
1871 // them in a list of ready to use nodes for our internal
1872 // functions?
1873 struct function_table_t
1874 {
1875 char const * f_name;
1876 int f_min_params;
1877 int f_max_params;
1879 };
1880
1881 static function_table_t const g_functions[] =
1882 {
1883 {
1884 "abs",
1885 1,
1886 1,
1888 },
1889 {
1890 "acos",
1891 1,
1892 1,
1894 },
1895 {
1896 "alpha",
1897 1,
1898 1,
1900 },
1901 {
1902 "asin",
1903 1,
1904 1,
1906 },
1907 {
1908 "atan",
1909 1,
1910 1,
1912 },
1913 {
1914 "blue",
1915 1,
1916 1,
1918 },
1919 {
1920 "ceil",
1921 1,
1922 1,
1924 },
1925 {
1926 "cos",
1927 1,
1928 1,
1930 },
1931 {
1932 "decimal_number",
1933 1,
1934 1,
1936 },
1937 {
1938 "floor",
1939 1,
1940 1,
1942 },
1943 {
1944 "frgb",
1945 1,
1946 3,
1948 },
1949 {
1950 "frgba",
1951 2,
1952 4,
1954 },
1955 {
1956 "function_exists",
1957 1,
1958 1,
1960 },
1961 {
1962 "global_variable_exists",
1963 1,
1964 1,
1966 },
1967 {
1968 "green",
1969 1,
1970 1,
1972 },
1973 {
1974 "hsl",
1975 3,
1976 3,
1978 },
1979 {
1980 "hsla",
1981 4,
1982 4,
1984 },
1985 {
1986 "hue",
1987 1,
1988 1,
1990 },
1991 {
1992 "identifier",
1993 1,
1994 1,
1996 },
1997 {
1998 "if",
1999 3,
2000 3,
2002 },
2003 {
2004 "integer",
2005 1,
2006 1,
2008 },
2009 {
2010 "inspect",
2011 1,
2012 1,
2014 },
2015 {
2016 "lightness",
2017 1,
2018 1,
2020 },
2021 {
2022 "log",
2023 1,
2024 1,
2026 },
2027 {
2028 "max",
2029 1,
2030 INT_MAX,
2032 },
2033 {
2034 "min",
2035 1,
2036 INT_MAX,
2038 },
2039 {
2040 "not",
2041 1,
2042 1,
2044 },
2045 {
2046 "percentage",
2047 1,
2048 1,
2050 },
2051 {
2052 "random",
2053 0,
2054 0,
2056 },
2057 {
2058 "red",
2059 1,
2060 1,
2062 },
2063 {
2064 "rgb",
2065 1,
2066 3,
2068 },
2069 {
2070 "rgba",
2071 2,
2072 4,
2074 },
2075 {
2076 "round",
2077 1,
2078 1,
2080 },
2081 {
2082 "saturation",
2083 1,
2084 1,
2086 },
2087 {
2088 "sign",
2089 1,
2090 1,
2092 },
2093 {
2094 "sin",
2095 1,
2096 1,
2098 },
2099 {
2100 "sqrt",
2101 1,
2102 1,
2104 },
2105 {
2106 "string",
2107 1,
2108 1,
2110 },
2111 {
2112 "str_length",
2113 1,
2114 1,
2116 },
2117 {
2118 "tan", // WARNING: just 'tan' is a color... 'tan(...)' is a function
2119 1,
2120 1,
2122 },
2123 {
2124 "type_of",
2125 1,
2126 1,
2128 },
2129 {
2130 "unique_id",
2131 0,
2132 1,
2134 },
2135 {
2136 "unit",
2137 1,
2138 1,
2140 },
2141 {
2142 "variable_exists",
2143 1,
2144 1,
2146 }
2147 };
2148
2149 std::string const function_name(func->get_string());
2150
2151 // TODO: (1) verify that functions are properly sorted
2152 // (2) use a binary search
2153 for(size_t idx(0); idx < sizeof(g_functions) / sizeof(g_functions[0]); ++idx)
2154 {
2155 if(function_name == g_functions[idx].f_name)
2156 {
2157 // found the function, it is internal!
2158
2159 // right number of parameters?
2160 if(func->size() >= static_cast<size_t>(g_functions[idx].f_min_params)
2161 && func->size() <= static_cast<size_t>(g_functions[idx].f_max_params))
2162 {
2163 if(function_name == "function_exists")
2164 {
2165 // first check whether the user is checking for an
2166 // internal function! (because the list of internal
2167 // functions is not available anywhere else)
2168 std::string name;
2170 if(id && !name.empty())
2171 {
2172 for(size_t j(0); j < sizeof(g_functions) / sizeof(g_functions[0]); ++j)
2173 {
2174 if(name == g_functions[j].f_name)
2175 {
2176 node::pointer_t result(new node(node_type_t::BOOLEAN, func->get_position()));
2177 result->set_boolean(true);
2178 return result;
2179 }
2180 }
2181 }
2182 }
2183 return (this->*g_functions[idx].f_func)(func);
2184 }
2185
2187 {
2188 error::instance() << f_current->get_position()
2189 << function_name
2190 << "() expects exactly "
2191 << g_functions[idx].f_min_params
2192 << " parameter"
2193 << (g_functions[idx].f_min_params == 1 ? "" : "s")
2194 << "."
2196 }
2197 else
2198 {
2199 error::instance() << f_current->get_position()
2200 << function_name
2201 << "() expects between "
2202 << g_functions[idx].f_min_params
2203 << " and "
2204 << g_functions[idx].f_max_params
2205 << " parameters."
2207 }
2208 return node::pointer_t();
2209 }
2210 }
2211
2212 // if we have a handler then allow the handler to run to execute
2213 // user defined functions (@mixin func() ...)
2215 {
2217 }
2218
2219 // "unknown" functions have to be left alone since these may be
2220 // CSS functions that we do not want to transform (we already
2221 // worked on their arguments, that's the extend of it at this point.)
2222 //
2223 // For now I mark it as unreachable because we should always be
2224 // using expression objects with a variable handler.
2225 return func; // LCOV_EXCL_LINE
2226}
2227
2228} // namespace csspp
2229
2230// Local Variables:
2231// mode: cpp
2232// indent-tabs-mode: nil
2233// c-basic-offset: 4
2234// tab-width: 4
2235// End:
2236
2237// vim: ts=4 sw=4 et
void set_hsl(color_component_t h, color_component_t s, color_component_t l, color_component_t alpha)
Definition color.cpp:354
static error & instance()
Definition error.cpp:77
virtual node::pointer_t get_variable(std::string const &variable_name, bool global_only=false) const =0
virtual node::pointer_t execute_user_function(node::pointer_t func)=0
node::pointer_t internal_function__atan(node::pointer_t func)
node::pointer_t internal_function__log(node::pointer_t func)
node::pointer_t internal_function__acos(node::pointer_t func)
node::pointer_t internal_function__floor(node::pointer_t func)
node::pointer_t internal_function__get_string_or_identifier(node::pointer_t func, size_t argn, std::string &str)
node::pointer_t internal_function__min(node::pointer_t func)
node::pointer_t internal_function__integer(node::pointer_t func)
node::pointer_t excecute_function(node::pointer_t func)
node::pointer_t f_current
Definition expression.h:155
node::pointer_t internal_function__max(node::pointer_t func)
node::pointer_t internal_function__type_of(node::pointer_t func)
node::pointer_t internal_function__variable_exists(node::pointer_t func)
node::pointer_t internal_function__blue(node::pointer_t func)
node::pointer_t internal_function__unique_id(node::pointer_t funct)
node::pointer_t internal_function__get_color(node::pointer_t func, size_t argn, color &col)
node::pointer_t internal_function__tan(node::pointer_t func)
node::pointer_t internal_function__if(node::pointer_t func)
node::pointer_t internal_function__hsla(node::pointer_t func)
node::pointer_t internal_function__asin(node::pointer_t func)
static void set_unique_id_counter(int counter)
node::pointer_t internal_function__identifier(node::pointer_t func)
node::pointer_t internal_function__rgb(node::pointer_t func)
node::pointer_t internal_function__get_number_or_percent(node::pointer_t func, size_t argn, decimal_number_t &number)
std::vector< std::string > dimension_vector_t
Definition expression.h:54
node::pointer_t internal_function__round(node::pointer_t func)
node::pointer_t internal_function__percentage(node::pointer_t func)
node::pointer_t internal_function__lightness(node::pointer_t func)
node::pointer_t internal_function__saturation(node::pointer_t func)
node::pointer_t internal_function__str_length(node::pointer_t func)
expression_variables_interface * f_variable_handler
Definition expression.h:158
node::pointer_t internal_function__abs(node::pointer_t func)
node::pointer_t internal_function__red(node::pointer_t func)
node::pointer_t internal_function__green(node::pointer_t func)
node::pointer_t internal_function__sin(node::pointer_t func)
node::pointer_t internal_function__frgb(node::pointer_t func)
node::pointer_t internal_function__inspect(node::pointer_t func)
node::pointer_t internal_function__decimal_number(node::pointer_t func)
node::pointer_t internal_function__random(node::pointer_t func)
node::pointer_t internal_function__global_variable_exists(node::pointer_t func)
std::string rebuild_dimension(dimension_vector_t const &dividend, dimension_vector_t const &divisor)
node::pointer_t internal_function__get_number(node::pointer_t func, size_t argn, decimal_number_t &number)
node::pointer_t internal_function__not(node::pointer_t func)
node::pointer_t internal_function__hsl(node::pointer_t func)
node::pointer_t internal_function__rgba(node::pointer_t func)
static int get_unique_id_counter()
void dimensions_to_vectors(position const &pos, std::string const &dimension, dimension_vector_t &dividend, dimension_vector_t &divisor)
node::pointer_t internal_function__function_exists(node::pointer_t func)
node::pointer_t internal_function__hue(node::pointer_t func)
node::pointer_t internal_function__frgba(node::pointer_t func)
node::pointer_t internal_function__get_string(node::pointer_t func, size_t argn, std::string &str)
node::pointer_t internal_function__string(node::pointer_t func)
node::pointer_t internal_function__sign(node::pointer_t func)
node::pointer_t internal_function__unit(node::pointer_t func)
node::pointer_t internal_function__sqrt(node::pointer_t func)
node::pointer_t internal_function__cos(node::pointer_t func)
node::pointer_t internal_function__get_any(node::pointer_t func, size_t argn)
node::pointer_t internal_function__ceil(node::pointer_t func)
node::pointer_t internal_function__alpha(node::pointer_t func)
static int const g_to_string_flag_show_quotes
Definition node.h:135
std::shared_ptr< node > pointer_t
Definition node.h:132
#define M_PI
Definition csspp.h:44
decimal_number_t dimension_to_radians(position const &pos, decimal_number_t n, std::string const &dimension)
The namespace of all the classes in the CSS Preprocessor.
Definition csspp.h:48
float color_component_t
Definition color.h:27
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.