]> git.armaanb.net Git - gen-shell.git/blob - src/libshared/src/Pig.cpp
added install instructions
[gen-shell.git] / src / libshared / src / Pig.cpp
1 ////////////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright 2015 - 2017, Paul Beckingham, Federico Hernandez.
4 //
5 // Permission is hereby granted, free of charge, to any person obtaining a copy
6 // of this software and associated documentation files (the "Software"), to deal
7 // in the Software without restriction, including without limitation the rights
8 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 // copies of the Software, and to permit persons to whom the Software is
10 // furnished to do so, subject to the following conditions:
11 //
12 // The above copyright notice and this permission notice shall be included
13 // in all copies or substantial portions of the Software.
14 //
15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16 // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 // SOFTWARE.
22 //
23 // http://www.opensource.org/licenses/mit-license.php
24 //
25 ////////////////////////////////////////////////////////////////////////////////
26
27 #include <cmake.h>
28 #include <Pig.h>
29 #include <shared.h>
30 #include <unicode.h>
31 #include <utf8.h>
32 #include <algorithm>
33 #include <sstream>
34 #include <cinttypes>
35 #include <cstdlib>
36
37 ////////////////////////////////////////////////////////////////////////////////
38 Pig::Pig (const std::string& text)
39 : _text {std::make_shared <std::string> (text)}
40 {
41 }
42
43 ////////////////////////////////////////////////////////////////////////////////
44 bool Pig::skip (int c)
45 {
46   if ((*_text)[_cursor] == c)
47   {
48     ++_cursor;
49     return true;
50   }
51
52   return false;
53 }
54
55 ////////////////////////////////////////////////////////////////////////////////
56 bool Pig::skipN (const int quantity)
57 {
58   auto save = _cursor;
59
60   auto count = 0;
61   while (count++ < quantity)
62   {
63     if (! utf8_next_char (*_text, _cursor))
64     {
65       _cursor = save;
66       return false;
67     }
68   }
69
70   return true;
71 }
72
73 ////////////////////////////////////////////////////////////////////////////////
74 bool Pig::skipWS ()
75 {
76   auto save = _cursor;
77
78   int c;
79   auto prev = _cursor;
80   while ((c = utf8_next_char (*_text, _cursor)))
81   {
82     if (! unicodeWhitespace (c))
83     {
84       _cursor = prev;
85       break;
86     }
87     prev = _cursor;
88   }
89
90   return _cursor > save;
91 }
92
93 ////////////////////////////////////////////////////////////////////////////////
94 bool Pig::skipLiteral (const std::string& literal)
95 {
96   if (_text->find (literal, _cursor) == _cursor)
97   {
98     _cursor += literal.length ();
99     return true;
100   }
101
102   return false;
103 }
104
105 ////////////////////////////////////////////////////////////////////////////////
106 bool Pig::skipPartial (const std::string& reference, std::string& result)
107 {
108   // Walk the common substring.
109   auto pos = 0;
110   while (reference[pos] &&
111          (*_text)[_cursor + pos] &&
112          reference[pos] == (*_text)[_cursor + pos])
113     ++pos;
114
115   if (pos > 0)
116   {
117     result = _text->substr (_cursor, pos);
118     _cursor += pos;
119     return true;
120   }
121
122   return false;
123 }
124
125 ////////////////////////////////////////////////////////////////////////////////
126 bool Pig::getUntil (int end, std::string& result)
127 {
128   auto save = _cursor;
129
130   int c;
131   auto prev = _cursor;
132   while ((c = utf8_next_char (*_text, _cursor)))
133   {
134     if (c == end)
135     {
136       _cursor = prev;
137       result = _text->substr (save, _cursor - save);
138       return true;
139     }
140
141     else if (eos ())
142     {
143       result = _text->substr (save, _cursor - save);
144       return true;
145     }
146
147     prev = _cursor;
148   }
149
150   return _cursor > save;
151 }
152
153 ////////////////////////////////////////////////////////////////////////////////
154 bool Pig::getUntilWS (std::string& result)
155 {
156   auto save = _cursor;
157
158   int c;
159   auto prev = _cursor;
160   while ((c = utf8_next_char (*_text, _cursor)))
161   {
162     if (unicodeWhitespace (c))
163     {
164       _cursor = prev;
165       result = _text->substr (save, _cursor - save);
166       return true;
167     }
168
169     // Note: This test must follow the above unicodeWhitespace(c) test because
170     //       it is testing the value of 'c', and eos() is testing _cursor,
171     //       which has already been advanced.
172     else if (eos ())
173     {
174       result = _text->substr (save, _cursor - save);
175       return true;
176     }
177
178     prev = _cursor;
179   }
180
181   return _cursor > save;
182 }
183
184 ////////////////////////////////////////////////////////////////////////////////
185 bool Pig::getCharacter (int& result)
186 {
187   int c = (*_text)[_cursor];
188   if (c)
189   {
190     result = c;
191     ++_cursor;
192     return true;
193   }
194
195   return false;
196 }
197
198 ////////////////////////////////////////////////////////////////////////////////
199 bool Pig::getDigit (int& result)
200 {
201   int c = (*_text)[_cursor];
202   if (c &&
203       unicodeLatinDigit (c))
204   {
205     result = c - '0';
206     ++_cursor;
207     return true;
208   }
209
210   return false;
211 }
212
213 ////////////////////////////////////////////////////////////////////////////////
214 bool Pig::getDigit2 (int& result)
215 {
216   if (unicodeLatinDigit ((*_text)[_cursor + 0]))
217   {
218     if (unicodeLatinDigit ((*_text)[_cursor + 1]))
219     {
220       result = strtoimax (_text->substr (_cursor, 2).c_str (), NULL, 10);
221       _cursor += 2;
222       return true;
223     }
224   }
225
226   return false;
227 }
228
229 ////////////////////////////////////////////////////////////////////////////////
230 bool Pig::getDigit3 (int& result)
231 {
232   if (unicodeLatinDigit ((*_text)[_cursor + 0]))
233   {
234     if (unicodeLatinDigit ((*_text)[_cursor + 1]))
235     {
236       if (unicodeLatinDigit ((*_text)[_cursor + 2]))
237       {
238         result = strtoimax (_text->substr (_cursor, 3).c_str (), NULL, 10);
239         _cursor += 3;
240         return true;
241       }
242     }
243   }
244
245   return false;
246 }
247
248 ////////////////////////////////////////////////////////////////////////////////
249 bool Pig::getDigit4 (int& result)
250 {
251   if (unicodeLatinDigit ((*_text)[_cursor + 0]))
252   {
253     if (unicodeLatinDigit ((*_text)[_cursor + 1]))
254     {
255       if (unicodeLatinDigit ((*_text)[_cursor + 2]))
256       {
257         if (unicodeLatinDigit ((*_text)[_cursor + 3]))
258         {
259           result = strtoimax (_text->substr (_cursor, 4).c_str (), NULL, 10);
260           _cursor += 4;
261           return true;
262         }
263       }
264     }
265   }
266
267   return false;
268 }
269
270 ////////////////////////////////////////////////////////////////////////////////
271 bool Pig::getDigits (int& result)
272 {
273   auto save = _cursor;
274
275   int c;
276   auto prev = _cursor;
277   while ((c = utf8_next_char (*_text, _cursor)))
278   {
279     if (! unicodeLatinDigit (c))
280     {
281       _cursor = prev;
282       break;
283     }
284
285     prev = _cursor;
286   }
287
288   if (_cursor > save)
289   {
290     result = strtoimax (_text->substr (save, _cursor - save).c_str (), NULL, 10);
291     return true;
292   }
293
294   return false;
295 }
296
297 ////////////////////////////////////////////////////////////////////////////////
298 bool Pig::getHexDigit (int& result)
299 {
300   int c = (*_text)[_cursor];
301   if (c &&
302       unicodeHexDigit (c))
303   {
304     if (c >= '0' && c <= '9')
305     {
306       result = c - '0';
307       ++_cursor;
308       return true;
309     }
310     else if (c >= 'A' && c <= 'F')
311     {
312       result = c - 'A' + 10;
313       ++_cursor;
314       return true;
315     }
316     else if (c >= 'a' && c <= 'f')
317     {
318       result = c - 'a' + 10;
319       ++_cursor;
320       return true;
321     }
322   }
323
324   return false;
325 }
326
327 ////////////////////////////////////////////////////////////////////////////////
328 // number:
329 //   int frac? exp?
330 //
331 // int:
332 //   (-|+)? digit+
333 //
334 // frac:
335 //   . digit+
336 //
337 // exp:
338 //   e digit+
339 //
340 // e:
341 //   e|E (+|-)?
342 //
343 bool Pig::getNumber (std::string& result)
344 {
345   auto i = _cursor;
346
347   // [+-]?
348   if ((*_text)[i] &&
349       ((*_text)[i] == '-' ||
350        (*_text)[i] == '+'))
351     ++i;
352
353   // digit+
354   if ((*_text)[i] &&
355       unicodeLatinDigit ((*_text)[i]))
356   {
357     ++i;
358
359     while ((*_text)[i] && unicodeLatinDigit ((*_text)[i]))
360       ++i;
361
362     // ( . digit+ )?
363     if ((*_text)[i] && (*_text)[i] == '.')
364     {
365       ++i;
366
367       while ((*_text)[i] && unicodeLatinDigit ((*_text)[i]))
368         ++i;
369     }
370
371     // ( [eE] [+-]? digit+ )?
372     if ((*_text)[i] &&
373         ((*_text)[i] == 'e' ||
374          (*_text)[i] == 'E'))
375     {
376       ++i;
377
378       if ((*_text)[i] &&
379           ((*_text)[i] == '+' ||
380            (*_text)[i] == '-'))
381         ++i;
382
383       if ((*_text)[i] && unicodeLatinDigit ((*_text)[i]))
384       {
385         ++i;
386
387         while ((*_text)[i] && unicodeLatinDigit ((*_text)[i]))
388           ++i;
389
390         result = _text->substr (_cursor, i - _cursor);
391         _cursor = i;
392         return true;
393       }
394
395       return false;
396     }
397
398     result = _text->substr (_cursor, i - _cursor);
399     _cursor = i;
400     return true;
401   }
402
403   return false;
404 }
405
406 ////////////////////////////////////////////////////////////////////////////////
407 bool Pig::getNumber (double& result)
408 {
409   std::string s;
410   if (getNumber (s))
411   {
412     result = std::strtod (s.c_str (), NULL);
413     return true;
414   }
415
416   return false;
417 }
418
419 ////////////////////////////////////////////////////////////////////////////////
420 // [ + | - ] \d+ [ . [ \d+ ]]
421 bool Pig::getDecimal (std::string& result)
422 {
423   auto i = _cursor;
424
425   // [+-]?
426   if ((*_text)[i] &&
427       ((*_text)[i] == '-' ||
428        (*_text)[i] == '+'))
429     ++i;
430
431   // digit+
432   if ((*_text)[i] && unicodeLatinDigit ((*_text)[i]))
433   {
434     ++i;
435
436     while ((*_text)[i] && unicodeLatinDigit ((*_text)[i]))
437       ++i;
438
439     // ( . digit+ )?
440     if ((*_text)[i] && (*_text)[i] == '.')
441     {
442       ++i;
443
444       while ((*_text)[i] && unicodeLatinDigit ((*_text)[i]))
445         ++i;
446     }
447
448     result = _text->substr (_cursor, i - _cursor);
449     _cursor = i;
450     return true;
451   }
452
453   return false;
454 }
455
456 ////////////////////////////////////////////////////////////////////////////////
457 bool Pig::getDecimal (double& result)
458 {
459   std::string s;
460   if (getDecimal (s))
461   {
462     result = std::strtod (s.c_str (), NULL);
463     return true;
464   }
465
466   return false;
467 }
468
469 ////////////////////////////////////////////////////////////////////////////////
470 // Gets quote content:      "foobar" -> foobar      (for c = '"')
471 // Handles escaped quotes:  "foo\"bar" -> foo\"bar  (for c = '"')
472 // Returns false if first character is not c, or if there is no closing c.
473 // Does not modify content between quotes.
474 bool Pig::getQuoted (int quote, std::string& result)
475 {
476   if (! (*_text)[_cursor] ||
477       (*_text)[_cursor] != quote)
478     return false;
479
480   auto start = _cursor + utf8_sequence (quote);
481   auto i = start;
482
483   while ((*_text)[i])
484   {
485     i = _text->find (quote, i);
486     if (i == std::string::npos)
487       return false;  // Unclosed quote.  Short cut, not definitive.
488
489     if (i == start)
490     {
491       // Empty quote
492       _cursor += 2 * utf8_sequence (quote);  // Skip both quote chars
493       result = "";
494       return true;
495     }
496
497     if ((*_text)[i - 1] == '\\')
498     {
499       // Check for escaped backslashes.  Backtracking like this is not very
500       // efficient, but is only done in extreme corner cases.
501
502       auto j = i - 2;  // Start one character further left
503       bool is_escaped_quote = true;
504       while (j >= start && (*_text)[j] == '\\')
505       {
506         // Toggle flag for each further backslash encountered.
507         is_escaped_quote = is_escaped_quote ? false : true;
508         --j;
509       }
510
511       if (is_escaped_quote)
512       {
513         // Keep searching
514         ++i;
515         continue;
516       }
517     }
518
519     // None of the above applied, we must have found the closing quote char.
520     result.assign (*_text, start, i - start);
521     _cursor = i + utf8_sequence (quote);  // Skip closing quote char
522     return true;
523   }
524
525   // This should never be reached.  We could throw here instead.
526   return false;
527 }
528
529 ////////////////////////////////////////////////////////////////////////////////
530 // Assumes that the options are sorted by decreasing length, so that if the
531 // options contain 'fourteen' and 'four', the stream is first matched against
532 // the longer entry.
533 bool Pig::getOneOf (
534   const std::vector <std::string>& options,
535   std::string& found)
536 {
537   for (const auto& option : options)
538   {
539     if (skipLiteral (option))
540     {
541       found = option;
542       return true;
543     }
544   }
545
546   return false;
547 }
548
549 ////////////////////////////////////////////////////////////////////////////////
550 bool Pig::getHMS (int& hours, int& minutes, int& seconds)
551 {
552   auto save = _cursor;
553
554   if ((getDigit2 (hours) || getDigit (hours)) &&
555       skip (':')                              &&
556       getDigit2 (minutes))
557   {
558     seconds = 0;
559     if (skip (':') &&
560         ! getDigit2 (seconds))
561       return false;
562
563     return true;
564   }
565
566   _cursor = save;
567   return false;
568 }
569
570 ////////////////////////////////////////////////////////////////////////////////
571 bool Pig::getRemainder (std::string& result)
572 {
573   if ((*_text)[_cursor])
574   {
575     result = _text->substr (_cursor);
576     _cursor += result.length ();
577     return true;
578   }
579
580   return false;
581 }
582
583 ////////////////////////////////////////////////////////////////////////////////
584 bool Pig::eos () const
585 {
586   return (*_text)[_cursor] == '\0';
587 }
588
589 ////////////////////////////////////////////////////////////////////////////////
590 // Peeks ahead - does not move cursor.
591 int Pig::peek () const
592 {
593   return (*_text)[_cursor];
594 }
595
596 ////////////////////////////////////////////////////////////////////////////////
597 // Peeks ahead - does not move cursor.
598 std::string Pig::peek (const int quantity) const
599 {
600   std::string::size_type adjusted = std::min (static_cast <std::string::size_type> (quantity), _text->length () - _cursor);
601   if ((*_text)[_cursor])
602     return _text->substr (_cursor, adjusted);
603
604   return "";
605 }
606
607 ////////////////////////////////////////////////////////////////////////////////
608 std::string::size_type Pig::cursor () const
609 {
610   return _cursor;
611 }
612
613 ////////////////////////////////////////////////////////////////////////////////
614 // Note: never called internally, otherwise the client cannot rely on iṫ.
615 std::string::size_type Pig::save ()
616 {
617   return _saved = _cursor;
618 }
619
620 ////////////////////////////////////////////////////////////////////////////////
621 // Note: never called internally, otherwise the client cannot rely on iṫ.
622 std::string::size_type Pig::restore ()
623 {
624   return _cursor = _saved;
625 }
626
627 ////////////////////////////////////////////////////////////////////////////////
628 std::string::size_type Pig::restoreTo (std::string::size_type previous)
629 {
630   return _cursor = previous;
631 }
632
633 ////////////////////////////////////////////////////////////////////////////////
634 std::string Pig::substr (
635   std::string::size_type start,
636   std::string::size_type end) const
637 {
638   return _text->substr (start, end - start);
639 }
640
641 ////////////////////////////////////////////////////////////////////////////////
642 std::string Pig::str () const
643 {
644   return _text->substr (_cursor);
645 }
646
647 ////////////////////////////////////////////////////////////////////////////////
648 // Show the text, with the matched part in white on green, and the unmatched
649 // part white on red, followed by the index equivalent.
650 std::string Pig::dump () const
651 {
652   std::stringstream out;
653   if (_cursor)
654     out << "\e[37;42m"
655         << _text->substr (0, _cursor)
656         << "\e[0m";
657
658   out << "\e[37;41m"
659       << _text->substr (_cursor)
660       << "\e[0m "
661       << _cursor
662       << '/'
663       << _text->length ();
664
665   return str_replace (out.str (), "\n", "\\n");
666 }
667
668 ////////////////////////////////////////////////////////////////////////////////