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

Documentation of CSS Preprocessor.

This document is part of the Snap! Websites Project.

Copyright by Made to Order Software Corp.