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 : }
|