LCOV - code coverage report
Current view: top level - src/plugins/yambi - lexer.cpp (source / functions) Hit Total Coverage
Test: coverage-filtered.info Lines: 152 154 98.7 %
Date: 2019-09-12 12:28:41 Functions: 24 24 100.0 %

          Line data    Source code
       1             : /**
       2             :  * @file
       3             :  *
       4             :  * @brief This file contains a lexer that scans YAML data.
       5             :  *
       6             :  * @copyright BSD License (see LICENSE.md or https://www.libelektra.org)
       7             :  */
       8             : 
       9             : // -- Imports ------------------------------------------------------------------
      10             : 
      11             : #include <fstream>
      12             : #include <stdexcept>
      13             : 
      14             : #include <kdblogger.h>
      15             : 
      16             : #include "lexer.hpp"
      17             : 
      18             : using std::deque;
      19             : using std::ifstream;
      20             : using std::make_pair;
      21             : using std::runtime_error;
      22             : using std::string;
      23             : using std::unique_ptr;
      24             : 
      25             : using yambi::Parser;
      26             : using location_type = Parser::location_type;
      27             : using token = Parser::token;
      28             : 
      29             : // -- Class --------------------------------------------------------------------
      30             : 
      31             : /**
      32             :  * @brief This method consumes characters from the input stream keeping
      33             :  *        track of line and column numbers.
      34             :  *
      35             :  * @param characters This parameter specifies the number of characters the
      36             :  *                   the function should consume.
      37             :  */
      38        2959 : void Lexer::forward (size_t const characters = 1)
      39             : {
      40             :         ELEKTRA_LOG_DEBUG ("Forward %zu characters", characters);
      41             : 
      42        8448 :         for (size_t charsLeft = characters; charsLeft > 0; charsLeft--)
      43             :         {
      44        5489 :                 if (input.LA (1) == 0)
      45             :                 {
      46             :                         ELEKTRA_LOG_DEBUG ("Hit EOF!");
      47             :                         return;
      48             :                 }
      49             : 
      50       10978 :                 location += 1;
      51        5489 :                 if (input.LA (1) == '\n')
      52             :                 {
      53         282 :                         location.end.column = 1;
      54         282 :                         location.lines ();
      55             :                 }
      56        5489 :                 input.consume ();
      57             :         }
      58             : }
      59             : 
      60             : /**
      61             :  * @brief This function adds an indentation value if the given value is smaller
      62             :  *        than the current indentation.
      63             :  *
      64             :  * @param lineIndex This parameter specifies the indentation value that this
      65             :  *                  function compares to the current indentation.
      66             :  *
      67             :  * @param type This value specifies the block collection type that
      68             :  *             `lineIndex` might start.
      69             :  *
      70             :  * @retval true If the function added an indentation value
      71             :  *         false Otherwise
      72             :  */
      73         205 : bool Lexer::addIndentation (size_t const lineIndex, Level::Type type)
      74             : {
      75         410 :         if (lineIndex > levels.top ().indent)
      76             :         {
      77             :                 ELEKTRA_LOG_DEBUG ("Add indentation %zu", lineIndex);
      78         198 :                 levels.push (Level{ lineIndex, type });
      79          99 :                 return true;
      80             :         }
      81             :         return false;
      82             : }
      83             : 
      84             : /**
      85             :  * @brief This function checks if the lexer needs to scan additional tokens.
      86             :  *
      87             :  * @retval true If the lexer should fetch additional tokens
      88             :  * @retval false Otherwise
      89             :  */
      90        1511 : bool Lexer::needMoreTokens () const
      91             : {
      92        1511 :         if (done)
      93             :         {
      94             :                 return false;
      95             :         }
      96             : 
      97        2288 :         bool keyCandidateExists = simpleKey.first != nullptr;
      98        1927 :         return keyCandidateExists || tokens.empty ();
      99             : }
     100             : 
     101             : /**
     102             :  * @brief This method removes uninteresting characters from the input.
     103             :  */
     104         571 : void Lexer::scanToNextToken ()
     105             : {
     106             :         ELEKTRA_LOG_DEBUG ("Scan to next token");
     107         571 :         bool found = false;
     108        1328 :         while (!found)
     109             :         {
     110        2073 :                 while (input.LA (1) == ' ')
     111             :                 {
     112         658 :                         forward ();
     113             :                 }
     114             :                 ELEKTRA_LOG_DEBUG ("Skipped whitespace");
     115         757 :                 if (input.LA (1) == '\n')
     116             :                 {
     117         186 :                         forward ();
     118             :                         ELEKTRA_LOG_DEBUG ("Skipped newline");
     119             :                 }
     120             :                 else
     121             :                 {
     122             :                         found = true;
     123             :                         ELEKTRA_LOG_DEBUG ("Found next token");
     124             :                 }
     125             :         }
     126         571 : }
     127             : 
     128             : /**
     129             :  * @brief This method adds new tokens to the token queue.
     130             :  */
     131         571 : void Lexer::fetchTokens ()
     132             : {
     133         571 :         scanToNextToken ();
     134        1142 :         location.step ();
     135         571 :         addBlockEnd (location.begin.column);
     136             :         ELEKTRA_LOG_DEBUG ("Fetch new token at location: %u:%u", location.begin.line, location.begin.column);
     137             : 
     138         571 :         if (input.LA (1) == 0)
     139             :         {
     140          63 :                 scanEnd ();
     141          63 :                 return;
     142             :         }
     143         508 :         else if (isValue ())
     144             :         {
     145         108 :                 scanValue ();
     146         108 :                 return;
     147             :         }
     148         400 :         else if (isElement ())
     149             :         {
     150          97 :                 scanElement ();
     151          97 :                 return;
     152             :         }
     153         303 :         else if (input.LA (1) == '"')
     154             :         {
     155          65 :                 scanDoubleQuotedScalar ();
     156          65 :                 return;
     157             :         }
     158         238 :         else if (input.LA (1) == '\'')
     159             :         {
     160           2 :                 scanSingleQuotedScalar ();
     161           2 :                 return;
     162             :         }
     163         236 :         else if (input.LA (1) == '#')
     164             :         {
     165          24 :                 scanComment ();
     166          24 :                 return;
     167             :         }
     168             : 
     169         212 :         scanPlainScalar ();
     170             : }
     171             : 
     172             : /**
     173             :  * @brief This method checks if the input at the specified offset starts a key
     174             :  *        value token.
     175             :  *
     176             :  * @param offset This parameter specifies an offset to the current position,
     177             :  *               where this function will look for a key value token.
     178             :  *
     179             :  * @retval true If the input matches a key value token
     180             :  * @retval false Otherwise
     181             :  */
     182        3462 : bool Lexer::isValue (size_t const offset) const
     183             : {
     184        3462 :         return (input.LA (offset) == ':') && (input.LA (offset + 1) == '\n' || input.LA (offset + 1) == ' ' || input.LA (offset + 1) == 0);
     185             : }
     186             : 
     187             : /**
     188             :  * @brief This method checks if the current input starts a list element.
     189             :  *
     190             :  * @retval true If the input matches a list element token
     191             :  * @retval false Otherwise
     192             :  */
     193         400 : bool Lexer::isElement () const
     194             : {
     195         400 :         return (input.LA (1) == '-') && (input.LA (2) == '\n' || input.LA (2) == ' ');
     196             : }
     197             : 
     198             : /**
     199             :  * @brief This method checks if the input at the specified offset starts a line
     200             :  *        comment.
     201             :  *
     202             :  * @param offset This parameter specifies an offset to the current position,
     203             :  *               where this function will look for a comment token.
     204             :  *
     205             :  * @retval true If the input matches a comment token
     206             :  * @retval false Otherwise
     207             :  */
     208        2746 : bool Lexer::isComment (size_t const offset) const
     209             : {
     210        2746 :         return (input.LA (offset) == '#') && (input.LA (offset + 1) == '\n' || input.LA (offset + 1) == ' ');
     211             : }
     212             : 
     213             : /**
     214             :  * @brief This method saves a token for a simple key candidate located at the
     215             :  *        current input position.
     216             :  */
     217         279 : void Lexer::addSimpleKeyCandidate ()
     218             : {
     219         558 :         size_t position = tokens.size () + tokensEmitted;
     220        2790 :         simpleKey = make_pair (unique_ptr<Symbol> (new Symbol{ token::KEY, location, "KEY" }), position);
     221         279 : }
     222             : 
     223             : /**
     224             :  * @brief This method adds block closing tokens to the token queue, if the
     225             :  *        indentation decreased.
     226             :  *
     227             :  * @param lineIndex This parameter specifies the column (indentation in number
     228             :  *                  of spaces) for which this method should add block end
     229             :  *                  tokens.
     230             :  */
     231         634 : void Lexer::addBlockEnd (size_t const lineIndex)
     232             : {
     233        1466 :         while (lineIndex < levels.top ().indent)
     234             :         {
     235             :                 ELEKTRA_LOG_DEBUG ("Add block end");
     236         636 :                 tokens.push_back (levels.top ().type == Level::Type::MAP ? Symbol (token::MAP_END, location, "MAP END") :
     237          99 :                                                                            Symbol (token::SEQUENCE_END, location, "SEQUENCE END"));
     238          99 :                 levels.pop ();
     239             :         }
     240         634 : }
     241             : 
     242             : /**
     243             :  * @brief This method adds the token for the start of the YAML stream to
     244             :  *        `tokens`.
     245             :  */
     246          63 : void Lexer::scanStart ()
     247             : {
     248             :         ELEKTRA_LOG_DEBUG ("Scan start token");
     249         441 :         tokens.push_back (Symbol (token::STREAM_START, location, "STREAM START"));
     250          63 : }
     251             : 
     252             : /**
     253             :  * @brief This method adds the token for the end of the YAML stream to
     254             :  *        the token queue.
     255             :  */
     256          63 : void Lexer::scanEnd ()
     257             : {
     258             :         ELEKTRA_LOG_DEBUG ("Scan end token");
     259          63 :         addBlockEnd (0);
     260         441 :         tokens.push_back (Symbol (token::STREAM_END, location, "STREAM END"));
     261         441 :         tokens.push_back (Symbol (token::END, location));
     262          63 :         done = true;
     263          63 : }
     264             : 
     265             : /**
     266             :  * @brief This method scans a single quoted scalar and adds it to the token
     267             :  *        queue.
     268             :  */
     269           2 : void Lexer::scanSingleQuotedScalar ()
     270             : {
     271             :         ELEKTRA_LOG_DEBUG ("Scan single quoted scalar");
     272             : 
     273           2 :         size_t start = input.index ();
     274             :         // A single quoted scalar can start a simple key
     275           2 :         addSimpleKeyCandidate ();
     276             : 
     277           2 :         forward (); // Include initial single quote
     278          54 :         while (input.LA (1) != '\'' || input.LA (2) == '\'')
     279             :         {
     280          26 :                 forward ();
     281             :         }
     282           2 :         forward (); // Include closing single quote
     283          10 :         tokens.push_back (Symbol (token::SINGLE_QUOTED_SCALAR, location, input.getText (start)));
     284           2 : }
     285             : 
     286             : /**
     287             :  * @brief This method scans a double quoted scalar and adds it to the token
     288             :  *        queue.
     289             :  */
     290          65 : void Lexer::scanDoubleQuotedScalar ()
     291             : {
     292             :         ELEKTRA_LOG_DEBUG ("Scan double quoted scalar");
     293          65 :         size_t start = input.index ();
     294             : 
     295             :         // A double quoted scalar can start a simple key
     296          65 :         addSimpleKeyCandidate ();
     297             : 
     298          65 :         forward (); // Include initial double quote
     299        1405 :         while (input.LA (1) != '"')
     300             :         {
     301         670 :                 forward ();
     302             :         }
     303          65 :         forward (); // Include closing double quote
     304         325 :         tokens.push_back (Symbol (token::DOUBLE_QUOTED_SCALAR, location, input.getText (start)));
     305          65 : }
     306             : 
     307             : /**
     308             :  * @brief This method scans a plain scalar and adds it to the token queue.
     309             :  */
     310         212 : void Lexer::scanPlainScalar ()
     311             : {
     312             :         ELEKTRA_LOG_DEBUG ("Scan plain scalar");
     313             :         // A plain scalar can start a simple key
     314         212 :         addSimpleKeyCandidate ();
     315             : 
     316         212 :         size_t lengthSpace = 0;
     317         212 :         size_t start = input.index ();
     318             : 
     319             :         size_t lengthNonSpace;
     320        1498 :         while ((lengthNonSpace = countPlainNonSpace (lengthSpace)) > 0)
     321             :         {
     322         643 :                 forward (lengthSpace + lengthNonSpace);
     323         643 :                 lengthSpace = countPlainSpace ();
     324             :         }
     325             : 
     326        1060 :         tokens.push_back (Symbol (token::PLAIN_SCALAR, location, input.getText (start)));
     327         212 : }
     328             : 
     329             : /**
     330             :  * @brief This method counts the number of non space characters that can be part
     331             :  *        of a plain scalar at position `offset`.
     332             :  *
     333             :  * @param offset This parameter specifies an offset to the current input
     334             :  *               position, where this function searches for non space
     335             :  *               characters.
     336             :  *
     337             :  * @return The number of non-space characters at the input position `offset`
     338             :  */
     339         855 : size_t Lexer::countPlainNonSpace (size_t const offset) const
     340             : {
     341             :         ELEKTRA_LOG_DEBUG ("Scan non space characters");
     342        3420 :         string const stop = " \n";
     343             : 
     344         855 :         size_t lookahead = offset + 1;
     345        9085 :         while (stop.find (input.LA (lookahead)) == string::npos && input.LA (lookahead) != 0 && !isValue (lookahead) &&
     346        2746 :                !isComment (lookahead))
     347             :         {
     348        2742 :                 lookahead++;
     349             :         }
     350             : 
     351             :         ELEKTRA_LOG_DEBUG ("Found %zu non-space characters", lookahead - offset - 1);
     352        1710 :         return lookahead - offset - 1;
     353             : }
     354             : 
     355             : /**
     356             :  * @brief This method counts the number of space characters that can be part
     357             :  *        of a plain scalar at the current input position.
     358             :  *
     359             :  * @return The number of space characters at the current input position
     360             :  */
     361         643 : size_t Lexer::countPlainSpace () const
     362             : {
     363             :         ELEKTRA_LOG_DEBUG ("Scan spaces");
     364         643 :         size_t lookahead = 1;
     365        1521 :         while (input.LA (lookahead) == ' ')
     366             :         {
     367         439 :                 lookahead++;
     368             :         }
     369             :         ELEKTRA_LOG_DEBUG ("Found %zu space characters", lookahead - 1);
     370         643 :         return lookahead - 1;
     371             : }
     372             : 
     373             : /**
     374             :  * @brief This method scans a comment and adds it to the token queue.
     375             :  */
     376          24 : void Lexer::scanComment ()
     377             : {
     378             :         ELEKTRA_LOG_DEBUG ("Scan comment");
     379          24 :         size_t start = input.index ();
     380         488 :         while (input.LA (1) != '\n' && input.LA (1) != 0)
     381             :         {
     382         232 :                 forward ();
     383             :         }
     384         120 :         tokens.push_back (Symbol (token::COMMENT, location, input.getText (start)));
     385          24 : }
     386             : 
     387             : /**
     388             :  * @brief This method scans a mapping value token and adds it to the token
     389             :  *        queue.
     390             :  */
     391         108 : void Lexer::scanValue ()
     392             : {
     393             :         ELEKTRA_LOG_DEBUG ("Scan value");
     394         108 :         forward (1);
     395         540 :         tokens.push_back (Symbol (token::VALUE, location, input.getText (input.index () - 1)));
     396         108 :         if (input.LA (1)) forward (1);
     397         216 :         if (simpleKey.first == nullptr)
     398             :         {
     399           0 :                 throw runtime_error ("Unable to locate key for value");
     400             :         }
     401         108 :         size_t offset = simpleKey.second - tokensEmitted;
     402         432 :         tokens.insert (tokens.begin () + offset, *simpleKey.first);
     403         324 :         auto mapStartLocation = simpleKey.first->getLocation ();
     404         216 :         simpleKey.first = nullptr; // Remove key candidate
     405         108 :         if (addIndentation (mapStartLocation.begin.column, Level::Type::MAP))
     406             :         {
     407          57 :                 mapStartLocation.end = mapStartLocation.begin;
     408         513 :                 tokens.insert (tokens.begin () + offset, Symbol (token::MAP_START, mapStartLocation, "MAP START"));
     409             :         }
     410         108 : }
     411             : 
     412             : /**
     413             :  * @brief This method scans a list element token and adds it to the token
     414             :  *        queue.
     415             :  */
     416          97 : void Lexer::scanElement ()
     417             : {
     418             :         ELEKTRA_LOG_DEBUG ("Scan element");
     419          97 :         if (addIndentation (location.end.column, Level::Type::SEQUENCE))
     420             :         {
     421         294 :                 tokens.push_back (Symbol (token::SEQUENCE_START, location, "SEQUENCE START"));
     422             :         }
     423          97 :         forward (1);
     424         485 :         tokens.push_back (Symbol (token::ELEMENT, location, input.getText (input.index () - 1)));
     425          97 :         forward (1);
     426          97 : }
     427             : 
     428             : /**
     429             :  * @brief This constructor initializes a lexer with the given input.
     430             :  *
     431             :  * @param stream This stream specifies the text which this lexer analyzes.
     432             :  */
     433         567 : Lexer::Lexer (ifstream & stream) : input{ stream }
     434             : {
     435             :         ELEKTRA_LOG_DEBUG ("Init lexer");
     436             : 
     437          63 :         scanStart ();
     438          63 :         fetchTokens ();
     439          63 : }
     440             : 
     441             : /**
     442             :  * @brief This method returns the next token the lexer produced from `input`.
     443             :  *
     444             :  * @return The next token the parser has not emitted yet
     445             :  */
     446        1003 : Parser::symbol_type Lexer::nextToken ()
     447             : {
     448        1511 :         while (needMoreTokens ())
     449             :         {
     450         508 :                 fetchTokens ();
     451             :         }
     452             : #ifdef HAVE_LOGGER
     453             :         string output;
     454             :         ELEKTRA_LOG_DEBUG ("Tokens:");
     455             :         for (auto symbol : tokens)
     456             :         {
     457             :                 ELEKTRA_LOG_DEBUG ("\t%s", symbol.toString ().c_str ());
     458             :         }
     459             :         ELEKTRA_LOG_DEBUG ("%s", output.c_str ());
     460             : #endif
     461             : 
     462             :         // If `fetchTokens` was unable to retrieve a token (error condition), we emit
     463             :         // an end token.
     464        2006 :         if (tokens.size () <= 0)
     465             :         {
     466           0 :                 tokens.push_back (Symbol (token::END, location));
     467             :         }
     468        3009 :         Symbol symbol = tokens.front ();
     469        1003 :         tokens.pop_front ();
     470        1003 :         tokensEmitted++;
     471        2006 :         return symbol.get ();
     472             : }
     473             : 
     474             : /**
     475             :  * @brief This method returns the current input of the lexer
     476             :  *
     477             :  * @return A UTF-8 encoded string version of the parser input
     478             :  */
     479          11 : string Lexer::getText ()
     480             : {
     481          11 :         return input.toString ();
     482         156 : }

Generated by: LCOV version 1.13