LCOV - code coverage report
Current view: top level - lib - expr_list.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 151 151 100.0 %
Date: 2017-03-23 22:48:20 Functions: 6 6 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : // CSS Preprocessor
       2             : // Copyright (C) 2015-2017  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             : 
      18             : /** \file
      19             :  * \brief Implementation of the CSS Preprocessor expression.
      20             :  *
      21             :  * The CSS Preprocessor expression class is used to reduce a list
      22             :  * of nodes by applying expressions to the various values.
      23             :  *
      24             :  * \sa \ref expression_rules
      25             :  */
      26             : 
      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             : 
      40        1989 : bool expression::is_label() const
      41             : {
      42             :     // we have a label if we have:
      43             :     //    <identifier> <ws>* ':'
      44        3978 :     if(!f_current->is(node_type_t::IDENTIFIER)
      45        1989 :     || f_pos >= f_node->size())
      46             :     {
      47         727 :         return false;
      48             :     }
      49             : 
      50        1262 :     return f_node->get_child(f_pos)->is(node_type_t::COLON);
      51             : }
      52             : 
      53         982 : node::pointer_t expression::expression_list()
      54             : {
      55             :     // expression-list: array
      56             :     //                | map
      57             :     //
      58             :     // array: assignment
      59             :     //      | array ',' assignment
      60             :     //
      61             :     // map: IDENTIFIER ':' assignment
      62             :     //    | IDENTIFIER ':'
      63             :     //    | map ',' IDENTIFIER ':' assignment
      64             :     //    | map ',' IDENTIFIER ':'
      65             :     //
      66             : 
      67         982 :     if(is_label())
      68             :     {
      69         554 :         node::pointer_t map(new node(node_type_t::MAP, f_current->get_position()));
      70         277 :         bool found_end(false);
      71        2241 :         while(!found_end && is_label())
      72             :         {
      73        1964 :             node::pointer_t name(f_current);
      74             : 
      75             :             // skip the IDENTIFIER (f_current == ':')
      76         982 :             next();
      77             : 
      78             :             // skip the ':'
      79         982 :             next();
      80             : 
      81        1964 :             node::pointer_t result;
      82         982 :             if(f_current->is(node_type_t::COMMA))
      83             :             {
      84             :                 // empty entries are viewed as valid and set to NULL
      85             :                 // (see below for the NULL_TOKEN allocation)
      86             : 
      87             :                 // skip the ','
      88          25 :                 next();
      89             :             }
      90         957 :             else if(f_current->is(node_type_t::EOF_TOKEN))
      91             :             {
      92             :                 // map ends with just a label, make sure we add a NULL too
      93          25 :                 result.reset();
      94             :             }
      95             :             else
      96             :             {
      97         932 :                 result = assignment();
      98             : 
      99         932 :                 if(f_current->is(node_type_t::COMMA))
     100             :                 {
     101         680 :                     next();
     102             :                 }
     103             :                 else
     104             :                 {
     105             :                     // no comma, we must have reached the end of the list
     106         252 :                     found_end = true;
     107             :                 }
     108             :             }
     109             : 
     110         982 :             if(!result)
     111             :             {
     112             :                 // maps need to have an even number of entries, but
     113             :                 // the value of an entry does not need to be provided
     114             :                 // in which case we want to put NULL in there
     115          51 :                 result.reset(new node(node_type_t::NULL_TOKEN, f_current->get_position()));
     116             :             }
     117             : 
     118             :             // add both at the same time
     119         982 :             map->add_child(name);
     120         982 :             map->add_child(result);
     121             :         }
     122         277 :         return map;
     123             :     }
     124             :     else
     125             :     {
     126        1410 :         node::pointer_t result(assignment());
     127             : 
     128         705 :         if(result
     129         705 :         && f_current->is(node_type_t::COMMA))
     130             :         {
     131             :             // in CSS Preprocessor, a list of expressions is an ARRAY
     132             :             // (contrary to C/C++ which just return the last expression)
     133         426 :             node::pointer_t array(new node(node_type_t::ARRAY, f_current->get_position()));
     134         213 :             array->add_child(result);
     135             : 
     136        1107 :             while(f_current->is(node_type_t::COMMA))
     137             :             {
     138             :                 // skip the ','
     139         448 :                 next();
     140             : 
     141         448 :                 result = assignment();
     142         448 :                 if(!result)
     143             :                 {
     144           1 :                     break;
     145             :                 }
     146         447 :                 array->add_child(result);
     147             :             }
     148             : 
     149             :             // the result is the array in this case
     150         213 :             return array;
     151             :         }
     152             : 
     153         492 :         return result;
     154             :     }
     155             : }
     156             : 
     157        2085 : node::pointer_t expression::assignment()
     158             : {
     159             :     // assignment: conditional
     160             :     //           | IDENTIFIER ':=' conditional
     161             : 
     162        4170 :     node::pointer_t result(conditional());
     163        2085 :     if(!result)
     164             :     {
     165           4 :         return node::pointer_t();
     166             :     }
     167             : 
     168        4162 :     if(result->is(node_type_t::IDENTIFIER)
     169        2081 :     && f_current->is(node_type_t::ASSIGNMENT))
     170             :     {
     171           4 :         next();
     172             : 
     173           8 :         node::pointer_t value(conditional());
     174           4 :         f_variables[result->get_string()] = value;
     175             : 
     176             :         // the return value of an assignment is the value of
     177             :         // the variable
     178           4 :         result = value;
     179             :     }
     180             : 
     181        2081 :     return result;
     182             : }
     183             : 
     184      752867 : node::pointer_t expression::post()
     185             : {
     186             :     // post: unary
     187             :     //     | post '[' expression ']'
     188             :     //     | post '.' IDENTIFIER
     189             : 
     190             :     // TODO: add support to access color members (i.e. $c.red <=> red($c))
     191             : 
     192     1505734 :     node::pointer_t result(unary());
     193      752867 :     if(!result)
     194             :     {
     195         109 :         return node::pointer_t();
     196             :     }
     197             : 
     198     1505516 :     node::pointer_t index;
     199          87 :     for(;;)
     200             :     {
     201      752845 :         if(f_current->is(node_type_t::OPEN_SQUAREBRACKET))
     202             :         {
     203             :             // compile the index expression
     204         510 :             expression index_expr(f_current);
     205         468 :             index_expr.set_variable_handler(f_variable_handler);
     206         468 :             index_expr.next();
     207         510 :             node::pointer_t i(index_expr.expression_list());
     208         468 :             if(!i)
     209             :             {
     210           2 :                 return node::pointer_t();
     211             :             }
     212             : 
     213             :             // skip the '['
     214         466 :             next();
     215             : 
     216         466 :             if(i->is(node_type_t::INTEGER))
     217             :             {
     218         866 :                 if(result->is(node_type_t::ARRAY)
     219         433 :                 || result->is(node_type_t::LIST))
     220             :                 {
     221             :                     // index is 1 based (not like in C/C++)
     222         207 :                     integer_t idx(i->get_integer());
     223         207 :                     if(idx < 0)
     224             :                     {
     225             :                         // negative numbers get items from the item
     226         104 :                         idx = result->size() + idx;
     227             :                     }
     228             :                     else
     229             :                     {
     230         103 :                         --idx;
     231             :                     }
     232         207 :                     if(static_cast<size_t>(idx) >= result->size())
     233             :                     {
     234         195 :                         error::instance() << f_current->get_position()
     235         195 :                                 << "index "
     236         390 :                                 << i->get_integer()
     237         195 :                                 << " is out of range. The allowed range is 1 to "
     238         390 :                                 << static_cast<int>(result->size())
     239         195 :                                 << "."
     240         195 :                                 << error_mode_t::ERROR_ERROR;
     241         195 :                         return node::pointer_t();
     242             :                     }
     243          12 :                     result = result->get_child(idx);
     244             :                 }
     245         226 :                 else if(result->is(node_type_t::MAP))
     246             :                 {
     247             :                     // index is 1 based (not like in C/C++)
     248             :                     // maps are defined as <property name> ':' <property value>
     249             :                     // so the numeric index being used to access the property
     250             :                     // value it has to be x 2 + 1 (C index: 1, 3, 5...)
     251             :                     // if negative we first have to "invert" the index
     252         225 :                     integer_t idx(i->get_integer());
     253         225 :                     if(idx < 0)
     254             :                     {
     255             :                         // negative numbers get items from the item
     256         112 :                         idx = result->size() / 2 + idx;
     257             :                     }
     258             :                     else
     259             :                     {
     260         113 :                         --idx;
     261             :                     }
     262         225 :                     idx = idx * 2 + 1;
     263         225 :                     if(static_cast<size_t>(idx) >= result->size())
     264             :                     {
     265         195 :                         error::instance() << f_current->get_position()
     266         195 :                                 << "index "
     267         390 :                                 << i->get_integer()
     268         195 :                                 << " is out of range. The allowed range is 1 to "
     269         390 :                                 << static_cast<int>(result->size()) / 2
     270         195 :                                 << "."
     271         195 :                                 << error_mode_t::ERROR_ERROR;
     272         195 :                         return node::pointer_t();
     273             :                     }
     274          30 :                     result = result->get_child(idx);
     275             :                 }
     276             :                 else
     277             :                 {
     278           1 :                     error::instance() << f_current->get_position()
     279           1 :                             << "unsupported type "
     280           2 :                             << result->get_type()
     281           1 :                             << " for the 'array[<index>]' operation."
     282           1 :                             << error_mode_t::ERROR_ERROR;
     283           1 :                     return node::pointer_t();
     284             :                 }
     285             :             }
     286          66 :             else if(i->is(node_type_t::STRING)
     287          33 :                  || i->is(node_type_t::IDENTIFIER))
     288             :             {
     289             :                 // nothing more to skip, the string is a child in
     290             :                 // a separate list
     291          31 :                 index = i;
     292          31 :                 goto field_index;
     293             :             }
     294             :             else
     295             :             {
     296           2 :                 error::instance() << f_current->get_position()
     297           2 :                         << "an integer, an identifier, or a string was expected as the index (defined in '[ ... ]'). A "
     298           4 :                         << i->get_type()
     299           2 :                         << " was not expected."
     300           2 :                         << error_mode_t::ERROR_ERROR;
     301           2 :                 return node::pointer_t();
     302             :             }
     303             :         }
     304      752377 :         else if(f_current->is(node_type_t::PERIOD))
     305             :         {
     306             :             // skip the '.'
     307          17 :             next();
     308             : 
     309          17 :             if(!f_current->is(node_type_t::IDENTIFIER))
     310             :             {
     311           1 :                 error::instance() << f_current->get_position()
     312           1 :                         << "only an identifier is expected after a '.'."
     313           1 :                         << error_mode_t::ERROR_ERROR;
     314           1 :                 return node::pointer_t();
     315             :             }
     316          16 :             index = f_current;
     317             : 
     318             :             // skip the index (identifier)
     319          16 :             next();
     320             : 
     321             : field_index:
     322          47 :             if(result->is(node_type_t::MAP))
     323             :             {
     324             :                 // in this case the index is a string or an identifier
     325          91 :                 std::string const idx(index->get_string());
     326          46 :                 size_t const max_item(result->size());
     327          46 :                 if((max_item & 1) != 0)
     328             :                 {
     329             :                     throw csspp_exception_logic("expression.cpp:expression::post(): number of items in a map has to be even."); // LCOV_EXCL_LINE
     330             :                 }
     331          46 :                 bool found(false);
     332         139 :                 for(size_t j(0); j < max_item; j += 2)
     333             :                 {
     334         231 :                     node::pointer_t item_name(result->get_child(j));
     335         138 :                     if(!item_name->is(node_type_t::IDENTIFIER))
     336             :                     {
     337             :                         throw csspp_exception_logic("expression.cpp:expression::post(): a map has the name of an entry which is not an identifier."); // LCOV_EXCL_LINE
     338             :                     }
     339         138 :                     if(item_name->get_string() == idx)
     340             :                     {
     341          45 :                         result = result->get_child(j + 1);
     342          45 :                         found = true;
     343          45 :                         break;
     344             :                     }
     345             :                 }
     346          46 :                 if(!found)
     347             :                 {
     348             :                     // TBD: should this be acceptable and we return NULL instead?
     349           1 :                     error::instance() << f_current->get_position()
     350           1 :                             << "'map[\""
     351           1 :                             << idx
     352           1 :                             << "\"]' is not set."
     353           1 :                             << error_mode_t::ERROR_ERROR;
     354           1 :                     return node::pointer_t();
     355             :                 }
     356             :             }
     357             :             else
     358             :             {
     359           1 :                 error::instance() << f_current->get_position()
     360           1 :                         << "unsupported left handside type "
     361           2 :                         << result->get_type()
     362           1 :                         << " for the '<map>.<identifier>' operation."
     363           1 :                         << error_mode_t::ERROR_ERROR;
     364           1 :                 return node::pointer_t();
     365             :             }
     366             :         }
     367             :         else
     368             :         {
     369      752360 :             break;
     370             :         }
     371             :     }
     372             : 
     373      752360 :     return result;
     374             : }
     375             : 
     376           9 : } // namespace csspp
     377             : 
     378             : // Local Variables:
     379             : // mode: cpp
     380             : // indent-tabs-mode: nil
     381             : // c-basic-offset: 4
     382             : // tab-width: 4
     383             : // End:
     384             : 
     385             : // vim: ts=4 sw=4 et

Generated by: LCOV version 1.12