LCOV - code coverage report
Current view: top level - src/plugins/yambi - driver.cpp (source / functions) Hit Total Coverage
Test: coverage-filtered.info Lines: 90 92 97.8 %
Date: 2019-09-12 12:28:41 Functions: 17 17 100.0 %

          Line data    Source code
       1             : /**
       2             :  * @file
       3             :  *
       4             :  * @brief This file specifies auxiliary functions and data used by a Bison
       5             :  *        parser to convert YAML data to a key set.
       6             :  *
       7             :  * @copyright BSD License (see LICENSE.md or https://www.libelektra.org)
       8             :  */
       9             : 
      10             : // -- Imports ------------------------------------------------------------------
      11             : 
      12             : #include <cerrno>
      13             : #include <fstream>
      14             : #include <stdexcept>
      15             : 
      16             : #include <kdbconfig.h>
      17             : 
      18             : #include "driver.hpp"
      19             : 
      20             : using std::ifstream;
      21             : using std::overflow_error;
      22             : using std::string;
      23             : using std::to_string;
      24             : 
      25             : using kdb::Key;
      26             : using kdb::KeySet;
      27             : 
      28             : using yambi::Parser;
      29             : 
      30             : // -- Functions ----------------------------------------------------------------
      31             : 
      32             : namespace
      33             : {
      34             : 
      35             : /**
      36             :  * @brief This function converts a given number to an array base name.
      37             :  *
      38             :  * @param index This number specifies the index of the array entry.
      39             :  *
      40             :  * @return A string representing the given indices as Elektra array name.
      41             :  */
      42          81 : string indexToArrayBaseName (uintmax_t const index)
      43             : {
      44          81 :         size_t digits = 1;
      45             : 
      46          81 :         for (uintmax_t value = index; value > 9; digits++)
      47             :         {
      48           0 :                 value /= 10;
      49             :         }
      50             : 
      51         567 :         return "#" + string (digits - 1, '_') + to_string (index);
      52             : }
      53             : 
      54             : /**
      55             :  * @brief This function converts a YAML scalar to a string.
      56             :  *
      57             :  * @param text This string contains a YAML scalar (including quote
      58             :  *             characters).
      59             :  *
      60             :  * @return A string without leading and trailing quote characters
      61             :  */
      62         259 : string scalarToText (string const & text)
      63             : {
      64         259 :         if (text.length () == 0)
      65             :         {
      66             :                 return text;
      67             :         }
      68         712 :         if (*(text.begin ()) == '"' || *(text.begin ()) == '\'')
      69             :         {
      70          67 :                 return text.substr (1, text.length () - 2);
      71             :         }
      72             :         return text;
      73             : }
      74             : 
      75             : /**
      76             :  * @brief This function returns a Clang-like error message for a given error.
      77             :  *
      78             :  * @param location This parameter stores the location of the error.
      79             :  * @param input This value stores the textual input where the error occurred.
      80             :  * @param prefix This variable stores as prefix that this function prepends
      81             :  *               to every line of the visualized error message.
      82             :  *
      83             :  * @return A string representation of the error
      84             :  */
      85          11 : string visualizeError (location_type const & location, string const & input, string const & prefix)
      86             : {
      87          11 :         string::size_type start = 0;
      88          11 :         string::size_type end = 0;
      89          58 :         for (size_t currentLine = 1; currentLine <= location.begin.line; currentLine++)
      90             :         {
      91          47 :                 size_t offset = (end == 0 ? 0 : 1);
      92          47 :                 start = end + offset;
      93          94 :                 end = input.find ("\n", end + offset);
      94             :         }
      95             : 
      96          11 :         string errorLine = input.substr (start, end - start);
      97             : 
      98          99 :         errorLine = prefix + errorLine + "\n" + prefix + string (location.begin.column - 1, ' ');
      99             :         // We assume that an error does not span more than one line
     100          11 :         start = location.begin.column;
     101          11 :         end = location.end.column - 1;
     102             :         errorLine += "^"; // Show at least one caret, even if the token is 0 characters long
     103          11 :         for (size_t current = start; current < end; current++)
     104             :         {
     105           0 :                 errorLine += "^";
     106             :         }
     107             : 
     108          11 :         return errorLine;
     109             : }
     110             : 
     111             : 
     112             : } // namespace
     113             : 
     114             : // -- Class --------------------------------------------------------------------
     115             : 
     116             : /**
     117             :  * This constructor creates a new driver for the given parent key.
     118             :  *
     119             :  * @param parent This key specifies the parent of the key set the parser
     120             :  *               creates.
     121             :  */
     122         567 : Driver::Driver (Key const & parent)
     123             : {
     124         252 :         parents.push (parent.dup ());
     125          63 : }
     126             : 
     127             : /**
     128             :  * @brief This function parses the current YAML file.
     129             :  *
     130             :  * @param filename This parameter stores the path of the file the driver
     131             :  *                 should parse.
     132             :  *
     133             :  * @retval -3 if the given file could not be opened
     134             :  * @retval -2 if parsing was unsuccessful due to memory exhaustion
     135             :  * @retval -1 if the given file contains a syntax error
     136             :  * @retval  0 if parsing was successful
     137             :  */
     138          63 : int Driver::parse (const string & filepath)
     139             : {
     140         126 :         filename = filepath;
     141             : 
     142         126 :         ifstream input{ filename };
     143          63 :         if (!input.good ()) return -3;
     144             : 
     145         126 :         Lexer lexer{ input };
     146         126 :         Parser parser{ lexer, *this };
     147             : 
     148             : #if DEBUG
     149          63 :         parser.set_debug_level (1);
     150             : #endif
     151          63 :         numberOfErrors = 0;
     152          63 :         auto status = parser.parse ();
     153          63 :         if (status == 0 && numberOfErrors > 0)
     154             :         {
     155           8 :                 status = 1;
     156             :         }
     157             : 
     158          63 :         return -status;
     159             : }
     160             : 
     161             : /**
     162             :  * @brief This method retrieves the current key set produced by the driver.
     163             :  *
     164             :  * @return A key set representing the YAML data produced by the last call of
     165             :  *         the method `parse`
     166             :  */
     167          55 : KeySet Driver::getKeySet () const
     168             : {
     169         110 :         return keys;
     170             : }
     171             : 
     172             : /**
     173             :  * @brief This function will be called by the Bison parser to indicate an error.
     174             :  *
     175             :  * @param location This value specifies the location of the erroneous input.
     176             :  * @param message This value stores the error message emitted by the Bison
     177             :  *                parser.
     178             :  * @param input This value stores the current input of the lexer/parser as text
     179             :  */
     180          11 : void Driver::error (const location_type & location, const string & message, string const & input)
     181             : {
     182          11 :         numberOfErrors++;
     183         110 :         auto position = filename + ":" + to_string (location.begin.line) + ":" + to_string (location.begin.column) + ": ";
     184          55 :         auto indent = string (position.length (), ' ');
     185             : 
     186          55 :         errorMessage += "\n" + position + message + "\n";
     187          33 :         errorMessage += visualizeError (location, input, indent);
     188          11 : }
     189             : 
     190             : /**
     191             :  * @brief This function returns the last error message produced by the parser.
     192             :  *
     193             :  * @return A string containing an error message describing a syntax error
     194             :  */
     195           8 : string Driver::getErrorMessage ()
     196             : {
     197          16 :         return errorMessage;
     198             : }
     199             : 
     200             : // ===========
     201             : // = Actions =
     202             : // ===========
     203             : 
     204             : /**
     205             :  * @brief This function will be called before the parser enters an empty file (that might contain comments).
     206             :  */
     207           4 : void Driver::enterEmpty ()
     208             : {
     209             :         // We add a parent key that stores nothing representing an empty file.
     210          20 :         keys.append (Key{ parents.top ().getName (), KEY_BINARY, KEY_END });
     211           4 : }
     212             : 
     213             : /**
     214             :  * @brief This function will be called after the parser exits a value.
     215             :  *
     216             :  * @param text This variable contains the text stored in the value.
     217             :  */
     218         153 : void Driver::exitValue (string const & text)
     219             : {
     220         612 :         Key key = parents.top ();
     221         306 :         if (text == "true" || text == "false")
     222             :         {
     223           2 :                 key.set<bool> (text == "true");
     224             :         }
     225             :         else
     226             :         {
     227         302 :                 key.set<string> (scalarToText (text));
     228             :         }
     229         306 :         keys.append (key);
     230         153 : }
     231             : 
     232             : /**
     233             :  * @brief This function will be called after the parser found a key.
     234             :  *
     235             :  * @param text This variable contains the text of the key.
     236             :  */
     237         108 : void Driver::exitKey (string const & text)
     238             : {
     239             :         // Entering a mapping such as `part: …` means that we need to add `part` to
     240             :         // the key name
     241         432 :         Key child{ parents.top ().getName (), KEY_END };
     242         216 :         child.addBaseName (scalarToText (text));
     243         216 :         parents.push (child);
     244         108 : }
     245             : 
     246             : /**
     247             :  * @brief This function will be called after the parser exits a key-value
     248             :  *        pair.
     249             :  *
     250             :  * @param matchedValue This variable specifies if the pair contains a value
     251             :  *                     or not.
     252             :  */
     253         108 : void Driver::exitPair (bool const matchedValue)
     254             : {
     255         108 :         if (!matchedValue)
     256             :         {
     257             :                 // Add key with empty value
     258          15 :                 parents.top ().setBinary (NULL, 0);
     259          10 :                 keys.append (parents.top ());
     260             :         }
     261             :         // Returning from a mapping such as `part: …` means that we need need to
     262             :         // remove the key for `part` from the stack.
     263         216 :         parents.pop ();
     264         108 : }
     265             : 
     266             : /**
     267             :  * @brief This function will be called after the parser enters a sequence.
     268             :  */
     269          33 : void Driver::enterSequence ()
     270             : {
     271          66 :         indices.push (0);
     272         165 :         parents.top ().setMeta ("array", ""); // We start with an empty array
     273          33 : }
     274             : 
     275             : /**
     276             :  * @brief This function will be called after the parser exits a sequence.
     277             :  */
     278          33 : void Driver::exitSequence ()
     279             : {
     280             :         // We add the parent key of all array elements after we leave the sequence
     281          99 :         keys.append (parents.top ());
     282          66 :         indices.pop ();
     283          33 : }
     284             : 
     285             : /**
     286             :  * @brief This function will be called after the parser recognizes an element
     287             :  *        of a sequence.
     288             :  */
     289          81 : void Driver::enterElement ()
     290             : {
     291             : 
     292         324 :         Key key{ parents.top ().getName (), KEY_END };
     293         162 :         if (indices.top () >= UINTMAX_MAX) throw overflow_error ("Unable to increase array index for array “" + key.getName () + "”");
     294             : 
     295         243 :         key.addBaseName (indexToArrayBaseName (indices.top ()));
     296             : 
     297         162 :         uintmax_t index = indices.top ();
     298         162 :         indices.pop ();
     299          81 :         index++;
     300         162 :         indices.push (index);
     301             : 
     302         486 :         parents.top ().setMeta ("array", key.getBaseName ());
     303         162 :         parents.push (key);
     304          81 : }
     305             : 
     306             : /**
     307             :  * @brief This function will be called after the parser read an element of a
     308             :  *        sequence.
     309             :  */
     310          81 : void Driver::exitElement ()
     311             : {
     312         162 :         parents.pop (); // Remove the key for the current array entry
     313         237 : }

Generated by: LCOV version 1.13