Current Version: 1.0.33
Project Name: csspp
expr_multiplicative.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/expression.h"
29
30#include "csspp/exception.h"
31#include "csspp/parser.h"
32#include "csspp/unicode_range.h"
33
34
35// C++
36//
37#include <algorithm>
38#include <cmath>
39#include <iostream>
40
41
42// last include
43//
44#include <snapdev/poison.h>
45
46
47
48namespace csspp
49{
50
52{
53 switch(n->get_type())
54 {
56 if(n->get_string() == "mul")
57 {
59 }
60 if(n->get_string() == "div")
61 {
63 }
64 if(n->get_string() == "mod")
65 {
67 }
68 break;
69
73 return n->get_type();
74
75 default:
76 break;
77
78 }
79
81}
82
83void expression::dimensions_to_vectors(position const & node_pos, std::string const & dimension, dimension_vector_t & dividend, dimension_vector_t & divisor)
84{
85 bool found_slash(false);
86 std::string::size_type pos(0);
87
88 // return early on empty otherwise we generate an error saying that
89 // the dimension is missing (when unitless numbers are valid)
90 if(dimension.empty())
91 {
92 return;
93 }
94
95 // we have a special case when there is no dividend in a dimension
96 // this is defined as a "1" with a slash
97 if(dimension.length() > 2
98 && dimension.substr(0, 2) == "1/") // user defined may not include the space
99 {
100 pos = 2;
101 found_slash = true;
102 }
103 else if(dimension.length() > 3
104 && dimension.substr(0, 3) == "1 /")
105 {
106 pos = 3;
107 found_slash = true;
108 }
109
110 // if it started with "1 /" then we may have yet another space: "1 / "
111 if(found_slash
112 && pos + 1 < dimension.size()
113 && dimension[pos] == ' ')
114 {
115 ++pos;
116 }
117
118 for(;;)
119 {
120 {
121 // get end of current dimension
122 std::string::size_type end(dimension.find_first_of(" */", pos));
123 if(end == std::string::npos)
124 {
125 end = dimension.size();
126 }
127
128 // add the dimension
129 if(end != pos)
130 {
131 std::string const dim(dimension.substr(pos, end - pos));
132 if(found_slash)
133 {
134 divisor.push_back(dim);
135 }
136 else
137 {
138 dividend.push_back(dim);
139 }
140 }
141 else
142 {
143 error::instance() << node_pos
144 << "number dimension is missing a dimension name."
146 return;
147 }
148
149 pos = end;
150 if(pos >= dimension.size())
151 {
152 return;
153 }
154 }
155
156 // check the separator(s)
157 if(pos < dimension.size()
158 && dimension[pos] == ' ')
159 {
160 // this should always be true, but user defined separators
161 // may not include the spaces
162 ++pos;
163 }
164 if(pos >= dimension.size())
165 {
166 // a user dimension may end with a space, just ignore it
167 return;
168 }
169 if(dimension[pos] == '/')
170 {
171 // second slash?!
172 if(found_slash)
173 {
174 // user defined dimensions could have invalid dimension definitions
175 error::instance() << node_pos
176 << "a valid dimension can have any number of '*' operators and a single '/' operator, here we found a second '/'."
178 return;
179 }
180
181 found_slash = true;
182 ++pos;
183 }
184 else if(dimension[pos] == '*')
185 {
186 ++pos;
187 }
188 else
189 {
190 // what is that character?!
191 error::instance() << node_pos
192 << "multiple dimensions can only be separated by '*' or '/' not '"
193 << dimension.substr(pos, 1)
194 << "'."
196 return;
197 }
198 if(pos < dimension.size()
199 && dimension[pos] == ' ')
200 {
201 // this should always be true, but user defined separators
202 // may not include the spaces
203 ++pos;
204 }
205 }
206}
207
208std::string expression::multiplicative_dimension(position const & pos, std::string const & ldim, node_type_t const op, std::string const & rdim)
209{
210 dimension_vector_t dividend;
211 dimension_vector_t divisor;
212
213 // transform the string in one or two vectors
214 dimensions_to_vectors(pos, ldim, dividend, divisor);
215
216 if(op == node_type_t::MULTIPLY)
217 {
218 dimensions_to_vectors(pos, rdim, dividend, divisor);
219 }
220 else
221 {
222 // a division has the dividend / divisor inverted, simple trick
223 dimensions_to_vectors(pos, rdim, divisor, dividend);
224 }
225
226 // optimize the result
227 for(size_t idx(dividend.size()); idx > 0;)
228 {
229 --idx;
230 std::string const dim(dividend[idx]);
231 dimension_vector_t::iterator it(std::find(divisor.begin(), divisor.end(), dim));
232 if(it != divisor.end())
233 {
234 // present in both places? if so remove from both places
235 dividend.erase(dividend.begin() + idx);
236 divisor.erase(it);
237 }
238 }
239
240 // now we rebuild the resulting dimension
241 // if the dividend is empty, then we have to put 1 / ...
242 // if the divisor is empty, we do not put a '/ ...'
243
244 return rebuild_dimension(dividend, divisor);
245}
246
247std::string expression::rebuild_dimension(dimension_vector_t const & dividend, dimension_vector_t const & divisor)
248{
249 std::string result;
250
251 if(!dividend.empty() || !divisor.empty())
252 {
253 if(dividend.empty())
254 {
255 result += "1";
256 }
257 else
258 {
259 for(size_t idx(0); idx < dividend.size(); ++idx)
260 {
261 if(idx != 0)
262 {
263 result += " * ";
264 }
265 result += dividend[idx];
266 }
267 }
268
269 for(size_t idx(0); idx < divisor.size(); ++idx)
270 {
271 if(idx == 0)
272 {
273 result += " / ";
274 }
275 else
276 {
277 result += " * ";
278 }
279 result += divisor[idx];
280 }
281 }
282
283 return result;
284}
285
287{
289 integer_t ai(0);
290 integer_t bi(0);
291 decimal_number_t af(0.0);
292 decimal_number_t bf(0.0);
293
294 switch(mix_node_types(lhs->get_type(), rhs->get_type()))
295 {
297 swap(lhs, rhs);
298#if __cplusplus >= 201700
299 [[fallthrough]];
300#endif
302 if(op != node_type_t::MULTIPLY)
303 {
304 error::instance() << lhs->get_position()
305 << "incompatible types between "
306 << lhs->get_type()
307 << " and "
308 << rhs->get_type()
309 << " for operator '/' or '%'."
311 return node::pointer_t();
312 }
313 else
314 {
315 integer_t count(rhs->get_integer());
316 if(count < 0)
317 {
318 error::instance() << lhs->get_position()
319 << "string * integer requires that the integer not be negative ("
320 << rhs->get_integer()
321 << ")."
323 return node::pointer_t();
324 }
325 std::string result;
326 for(; count > 0; --count)
327 {
328 result += lhs->get_string();
329 }
330 lhs->set_string(result);
331 }
332 return lhs;
333
335 ai = lhs->get_integer();
336 bi = rhs->get_integer();
338 break;
339
341 af = static_cast<decimal_number_t>(lhs->get_integer());
342 bf = rhs->get_decimal_number();
344 break;
345
347 af = lhs->get_decimal_number();
348 bf = static_cast<decimal_number_t>(rhs->get_integer());
350 break;
351
353 af = lhs->get_decimal_number();
354 bf = rhs->get_decimal_number();
356 break;
357
359 af = lhs->get_decimal_number();
360 bf = rhs->get_decimal_number();
362 break;
363
365 af = lhs->get_decimal_number();
366 bf = rhs->get_decimal_number();
368 break;
369
371 af = static_cast<decimal_number_t>(lhs->get_integer());
372 bf = rhs->get_decimal_number();
374 break;
375
377 af = lhs->get_decimal_number();
378 bf = static_cast<decimal_number_t>(rhs->get_integer());
380 break;
381
383 af = lhs->get_decimal_number();
384 bf = rhs->get_decimal_number();
386 break;
387
388 case mix_node_types(node_type_t::NULL_TOKEN, node_type_t::NULL_TOKEN): // could this one support / and %?
390 if(op == node_type_t::MULTIPLY)
391 {
392 return lhs;
393 }
394 error::instance() << f_current->get_position()
395 << "unicode_range * unicode_range is the only multiplicative operator accepted with unicode ranges, '/' and '%' are not allowed."
397 return node::pointer_t();
398
400 if(op == node_type_t::MULTIPLY)
401 {
402 return rhs;
403 }
404 error::instance() << f_current->get_position()
405 << "unicode_range * unicode_range is the only multiplicative operator accepted with unicode ranges, '/' and '%' are not allowed."
407 return node::pointer_t();
408
410 if(op == node_type_t::MULTIPLY)
411 {
412 unicode_range_t const lrange(static_cast<range_value_t>(lhs->get_integer()));
413 unicode_range_t const rrange(static_cast<range_value_t>(rhs->get_integer()));
414 range_value_t const start(std::max(lrange.get_start(), rrange.get_start()));
415 range_value_t const end (std::min(lrange.get_end(), rrange.get_end()));
416 node::pointer_t result;
417 if(start <= end)
418 {
419 result.reset(new node(node_type_t::UNICODE_RANGE, lhs->get_position()));
420 unicode_range_t range(start, end);
421 result->set_integer(range.get_range());
422 }
423 else
424 {
425 // range becomes null (not characters are in common)
426 result.reset(new node(node_type_t::NULL_TOKEN, lhs->get_position()));
427 }
428 return result;
429 }
430 error::instance() << f_current->get_position()
431 << "unicode_range * unicode_range is the only multiplicative operator accepted with unicode ranges, '/' and '%' are not allowed."
433 return node::pointer_t();
434
436 if(op != node_type_t::MULTIPLY)
437 {
438 error::instance() << f_current->get_position()
439 << "'number / color' and 'number % color' are not available."
441 return node::pointer_t();
442 }
443 swap(lhs, rhs);
444#if __cplusplus >= 201700
445 [[fallthrough]];
446#endif
448 bf = static_cast<decimal_number_t>(rhs->get_integer());
449 goto color_multiplicative;
450
453 if(op != node_type_t::MULTIPLY)
454 {
455 error::instance() << f_current->get_position()
456 << "'number / color' and 'number % color' are not available."
458 return node::pointer_t();
459 }
460 swap(lhs, rhs);
461#if __cplusplus >= 201700
462 [[fallthrough]];
463#endif
466 bf = rhs->get_decimal_number();
467color_multiplicative:
468 if(rhs->is(node_type_t::PERCENT)
469 || rhs->get_string() == "")
470 {
471 color c(lhs->get_color());
473 color_component_t green;
475 color_component_t alpha;
476 c.get_color(red, green, blue, alpha);
477 switch(op)
478 {
480 red *= bf;
481 green *= bf;
482 blue *= bf;
483 alpha *= bf;
484 break;
485
487 red /= bf;
488 green /= bf;
489 blue /= bf;
490 alpha /= bf;
491 break;
492
494 red = fmod(red, bf);
495 green = fmod(green, bf);
496 blue = fmod(blue, bf);
497 alpha = fmod(alpha, bf);
498 break;
499
500 default: // LCOV_EXCL_LINE
501 throw csspp_exception_logic("expression.cpp:multiply(): unexpected operator."); // LCOV_EXCL_LINE
502
503 }
504 c.set_color(red, green, blue, alpha);
505 node::pointer_t result(new node(node_type_t::COLOR, lhs->get_position()));
506 result->set_color(c);
507 return result;
508 }
509 error::instance() << f_current->get_position()
510 << "color factors must be unit less values, "
511 << bf
512 << rhs->get_string()
513 << " is not acceptable."
515 return node::pointer_t();
516
518 {
519 color lc(lhs->get_color());
520 color const rc(rhs->get_color());
522 color_component_t lgreen;
523 color_component_t lblue;
524 color_component_t lalpha;
526 color_component_t rgreen;
527 color_component_t rblue;
528 color_component_t ralpha;
529 lc.get_color(lred, lgreen, lblue, lalpha);
530 rc.get_color(rred, rgreen, rblue, ralpha);
531 switch(op)
532 {
534 lred *= rred;
535 lgreen *= rgreen;
536 lblue *= rblue;
537 lalpha *= ralpha;
538 break;
539
541#pragma GCC diagnostic push
542#pragma GCC diagnostic ignored "-Wfloat-equal"
543 if(rred == 0.0
544 || rgreen == 0.0
545 || rblue == 0.0
546 || ralpha == 0.0)
547#pragma GCC diagnostic pop
548 {
549 error::instance() << f_current->get_position()
550 << "color division does not accept any color component set to zero."
552 return node::pointer_t();
553 }
554 lred /= rred;
555 lgreen /= rgreen;
556 lblue /= rblue;
557 lalpha /= ralpha;
558 break;
559
561#pragma GCC diagnostic push
562#pragma GCC diagnostic ignored "-Wfloat-equal"
563 if(rred == 0.0
564 || rgreen == 0.0
565 || rblue == 0.0
566 || ralpha == 0.0)
567#pragma GCC diagnostic pop
568 {
569 error::instance() << f_current->get_position()
570 << "color modulo does not accept any color component set to zero."
572 return node::pointer_t();
573 }
574 lred = fmod(lred , rred );
575 lgreen = fmod(lgreen , rgreen);
576 lblue = fmod(lblue , rblue );
577 lalpha = fmod(lalpha , ralpha);
578 break;
579
580 default: // LCOV_EXCL_LINE
581 throw csspp_exception_logic("expression.cpp:multiply(): unexpected operator."); // LCOV_EXCL_LINE
582
583 }
584 lc.set_color(lred, lgreen, lblue, lalpha);
585 node::pointer_t result(new node(node_type_t::COLOR, lhs->get_position()));
586 result->set_color(lc);
587 return result;
588 }
589
590 default:
591 error::instance() << f_current->get_position()
592 << "incompatible types between "
593 << lhs->get_type()
594 << " and "
595 << rhs->get_type()
596 << " for operator '*', '/', or '%'."
598 return node::pointer_t();
599
600 }
601
603 {
604 // this is that special case of a FONT_METRICS node
605 node::pointer_t result(new node(node_type_t::FONT_METRICS, lhs->get_position()));
606
607 // convert integers
608 // (we don't need to convert to do the set, but I find it cleaner this way)
609 if(type == node_type_t::INTEGER)
610 {
611 af = static_cast<decimal_number_t>(ai);
612 bf = static_cast<decimal_number_t>(bi);
613 }
614
615 result->set_font_size(af);
616 result->set_line_height(bf);
617 if(lhs->is(node_type_t::PERCENT))
618 {
619 result->set_dim1("%");
620 }
621 else
622 {
623 result->set_dim1(lhs->get_string());
624 }
625 if(rhs->is(node_type_t::PERCENT))
626 {
627 result->set_dim2("%");
628 }
629 else
630 {
631 result->set_dim2(rhs->get_string());
632 }
633 return result;
634 }
635
636 node::pointer_t result(new node(type, lhs->get_position()));
637
638 if(type != node_type_t::PERCENT)
639 {
640 // dimensions do not need to be equal
641 //
642 // a * b results in a dimension such as 'px * em'
643 // a / b results in a dimension such as 'px / em'
644 //
645 // multiple products/divisions can occur in which case the '/' becomes
646 // the separator as in:
647 //
648 // a * b / c / d results in a dimension such as 'px * em / cm * vw'
649 //
650 // when the same dimension appears on the left and right of a /
651 // then it can be removed, allowing conversions from one type to
652 // another, so:
653 //
654 // 'px * em / px' == 'em'
655 //
656 // modulo, like additive operators, requires both dimensions to be
657 // exactly equal or both numbers to not have a dimension
658 //
659 std::string ldim(lhs->is(node_type_t::PERCENT) ? "" : lhs->get_string());
660 std::string rdim(rhs->is(node_type_t::PERCENT) ? "" : rhs->get_string());
661 switch(op)
662 {
664 // modulo requires both dimensions to be equal
665 if(ldim != rdim)
666 {
667 error::instance() << lhs->get_position()
668 << "incompatible dimensions (\""
669 << ldim
670 << "\" and \""
671 << rdim
672 << "\") cannot be used with operator '%'."
674 return node::pointer_t();
675 }
676 // set ldim or rdim, they equal each other anyway
677 result->set_string(ldim);
678 break;
679
682 result->set_string(multiplicative_dimension(lhs->get_position(), ldim, op, rdim));
683 break;
684
685 default: // LCOV_EXCL_LINE
686 // that should never happen
687 throw csspp_exception_logic("expression.cpp:multiply(): unexpected operator."); // LCOV_EXCL_LINE
688
689 }
690 }
691
692 switch(type)
693 {
695 switch(op)
696 {
698 result->set_integer(ai * bi);
699 break;
700
702 if(bi == 0)
703 {
704 error::instance() << lhs->get_position()
705 << "division by zero."
707 return node::pointer_t();
708 }
709 result->set_integer(ai / bi);
710 break;
711
713 if(bi == 0)
714 {
715 error::instance() << lhs->get_position()
716 << "modulo by zero."
718 return node::pointer_t();
719 }
720 result->set_integer(ai % bi);
721 break;
722
723 default: // LCOV_EXCL_LINE
724 throw csspp_exception_logic("expression.cpp:multiply(): unexpected operator."); // LCOV_EXCL_LINE
725
726 }
727 break;
728
731 switch(op)
732 {
734 result->set_decimal_number(af * bf);
735 break;
736
738#pragma GCC diagnostic push
739#pragma GCC diagnostic ignored "-Wfloat-equal"
740 if(bf == 0.0)
741#pragma GCC diagnostic pop
742 {
743 error::instance() << lhs->get_position()
744 << "division by zero."
746 return node::pointer_t();
747 }
748 result->set_decimal_number(af / bf);
749 break;
750
752#pragma GCC diagnostic push
753#pragma GCC diagnostic ignored "-Wfloat-equal"
754 if(bf == 0.0)
755#pragma GCC diagnostic pop
756 {
757 error::instance() << lhs->get_position()
758 << "modulo by zero."
760 return node::pointer_t();
761 }
762 result->set_decimal_number(fmod(af, bf));
763 break;
764
765 default: // LCOV_EXCL_LINE
766 throw csspp_exception_logic("expression.cpp:multiply(): unexpected operator."); // LCOV_EXCL_LINE
767
768 }
769 break;
770
771 default: // LCOV_EXCL_LINE
772 throw csspp_exception_logic("expression.cpp:multiply(): 'type' set to a value which is not handled here."); // LCOV_EXCL_LINE
773
774 }
775
776 return result;
777}
778
780{
781 // multiplicative: power
782 // | multiplicative '*' power
783 // | multiplicative '/' power
784 // | multiplicative '%' power
785
786 node::pointer_t result(power());
787 if(!result)
788 {
789 return node::pointer_t();
790 }
791
793 while(op != node_type_t::UNKNOWN)
794 {
795 // skip the multiplicative operator
796 next();
797
798 node::pointer_t rhs(power());
799 if(rhs == nullptr)
800 {
801 return node::pointer_t();
802 }
803
804 // apply the multiplicative operation
805 result = multiply(op, result, rhs);
806 if(!result)
807 {
808 return node::pointer_t();
809 }
810
812 }
813
814 return result;
815}
816
817} // namespace csspp
818
819// Local Variables:
820// mode: cpp
821// indent-tabs-mode: nil
822// c-basic-offset: 4
823// tab-width: 4
824// End:
825
826// vim: ts=4 sw=4 et
rgba_color_t get_color() const
Definition color.cpp:467
void set_color(rgba_color_t const rgba)
Definition color.cpp:228
static error & instance()
Definition error.cpp:77
node::pointer_t f_current
Definition expression.h:155
std::string multiplicative_dimension(position const &pos, std::string const &dim1, node_type_t const op, std::string const &dim2)
node::pointer_t multiplicative()
std::vector< std::string > dimension_vector_t
Definition expression.h:54
node::pointer_t power()
std::string rebuild_dimension(dimension_vector_t const &dividend, dimension_vector_t const &divisor)
void dimensions_to_vectors(position const &pos, std::string const &dimension, dimension_vector_t &dividend, dimension_vector_t &divisor)
node::pointer_t multiply(node_type_t op, node::pointer_t lhs, node::pointer_t rhs)
std::shared_ptr< node > pointer_t
Definition node.h:132
wide_char_t get_end() const
wide_char_t get_start() const
range_value_t get_range() const
The namespace of all the classes in the CSS Preprocessor.
Definition csspp.h:48
uint64_t range_value_t
node_type_t multiplicative_operator(node::pointer_t n)
node_type_t
Definition node.h:41
float color_component_t
Definition color.h:27
int64_t integer_t
Definition csspp.h:58
int32_t constexpr mix_node_types(node_type_t a, node_type_t b)
Definition node.h:119
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.