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