c3ee6959e4d0d64c359aa61c57c6243a8dd9debd
[platform/upstream/cmake.git] / Source / cmStringCommand.cxx
1 /* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
2    file Copyright.txt or https://cmake.org/licensing for details.  */
3 // NOLINTNEXTLINE(bugprone-reserved-identifier)
4 #define _SCL_SECURE_NO_WARNINGS
5
6 #include "cmStringCommand.h"
7
8 #include <algorithm>
9 #include <cctype>
10 #include <cstdio>
11 #include <cstdlib>
12 #include <initializer_list>
13 #include <limits>
14 #include <memory>
15 #include <stdexcept>
16 #include <utility>
17
18 #include <cm/iterator>
19 #include <cm/optional>
20 #include <cm/string_view>
21 #include <cmext/string_view>
22
23 #include <cm3p/json/reader.h>
24 #include <cm3p/json/value.h>
25 #include <cm3p/json/writer.h>
26
27 #include "cmsys/RegularExpression.hxx"
28
29 #include "cmCryptoHash.h"
30 #include "cmExecutionStatus.h"
31 #include "cmGeneratorExpression.h"
32 #include "cmMakefile.h"
33 #include "cmMessageType.h"
34 #include "cmRange.h"
35 #include "cmStringAlgorithms.h"
36 #include "cmStringReplaceHelper.h"
37 #include "cmSubcommandTable.h"
38 #include "cmSystemTools.h"
39 #include "cmTimestamp.h"
40 #include "cmUuid.h"
41 #include "cmValue.h"
42
43 namespace {
44
45 bool RegexMatch(std::vector<std::string> const& args,
46                 cmExecutionStatus& status);
47 bool RegexMatchAll(std::vector<std::string> const& args,
48                    cmExecutionStatus& status);
49 bool RegexReplace(std::vector<std::string> const& args,
50                   cmExecutionStatus& status);
51
52 bool joinImpl(std::vector<std::string> const& args, std::string const& glue,
53               size_t varIdx, cmMakefile& makefile);
54
55 bool HandleHashCommand(std::vector<std::string> const& args,
56                        cmExecutionStatus& status)
57 {
58   if (args.size() != 3) {
59     status.SetError(
60       cmStrCat(args[0], " requires an output variable and an input string"));
61     return false;
62   }
63
64   std::unique_ptr<cmCryptoHash> hash(cmCryptoHash::New(args[0]));
65   if (hash) {
66     std::string out = hash->HashString(args[2]);
67     status.GetMakefile().AddDefinition(args[1], out);
68     return true;
69   }
70   return false;
71 }
72
73 bool HandleToUpperLowerCommand(std::vector<std::string> const& args,
74                                bool toUpper, cmExecutionStatus& status)
75 {
76   if (args.size() < 3) {
77     status.SetError("no output variable specified");
78     return false;
79   }
80
81   std::string const& outvar = args[2];
82   std::string output;
83
84   if (toUpper) {
85     output = cmSystemTools::UpperCase(args[1]);
86   } else {
87     output = cmSystemTools::LowerCase(args[1]);
88   }
89
90   // Store the output in the provided variable.
91   status.GetMakefile().AddDefinition(outvar, output);
92   return true;
93 }
94
95 bool HandleToUpperCommand(std::vector<std::string> const& args,
96                           cmExecutionStatus& status)
97 {
98   return HandleToUpperLowerCommand(args, true, status);
99 }
100
101 bool HandleToLowerCommand(std::vector<std::string> const& args,
102                           cmExecutionStatus& status)
103 {
104   return HandleToUpperLowerCommand(args, false, status);
105 }
106
107 bool HandleAsciiCommand(std::vector<std::string> const& args,
108                         cmExecutionStatus& status)
109 {
110   if (args.size() < 3) {
111     status.SetError("No output variable specified");
112     return false;
113   }
114   std::string::size_type cc;
115   std::string const& outvar = args.back();
116   std::string output;
117   for (cc = 1; cc < args.size() - 1; cc++) {
118     int ch = atoi(args[cc].c_str());
119     if (ch > 0 && ch < 256) {
120       output += static_cast<char>(ch);
121     } else {
122       std::string error =
123         cmStrCat("Character with code ", args[cc], " does not exist.");
124       status.SetError(error);
125       return false;
126     }
127   }
128   // Store the output in the provided variable.
129   status.GetMakefile().AddDefinition(outvar, output);
130   return true;
131 }
132
133 bool HandleHexCommand(std::vector<std::string> const& args,
134                       cmExecutionStatus& status)
135 {
136   if (args.size() != 3) {
137     status.SetError("Incorrect number of arguments");
138     return false;
139   }
140   auto const& instr = args[1];
141   auto const& outvar = args[2];
142   std::string output(instr.size() * 2, ' ');
143
144   std::string::size_type hexIndex = 0;
145   for (auto const& c : instr) {
146     sprintf(&output[hexIndex], "%.2x", static_cast<unsigned char>(c) & 0xFF);
147     hexIndex += 2;
148   }
149
150   status.GetMakefile().AddDefinition(outvar, output);
151   return true;
152 }
153
154 bool HandleConfigureCommand(std::vector<std::string> const& args,
155                             cmExecutionStatus& status)
156 {
157   if (args.size() < 2) {
158     status.SetError("No input string specified.");
159     return false;
160   }
161   if (args.size() < 3) {
162     status.SetError("No output variable specified.");
163     return false;
164   }
165
166   // Parse options.
167   bool escapeQuotes = false;
168   bool atOnly = false;
169   for (unsigned int i = 3; i < args.size(); ++i) {
170     if (args[i] == "@ONLY") {
171       atOnly = true;
172     } else if (args[i] == "ESCAPE_QUOTES") {
173       escapeQuotes = true;
174     } else {
175       status.SetError(cmStrCat("Unrecognized argument \"", args[i], "\""));
176       return false;
177     }
178   }
179
180   // Configure the string.
181   std::string output;
182   status.GetMakefile().ConfigureString(args[1], output, atOnly, escapeQuotes);
183
184   // Store the output in the provided variable.
185   status.GetMakefile().AddDefinition(args[2], output);
186
187   return true;
188 }
189
190 bool HandleRegexCommand(std::vector<std::string> const& args,
191                         cmExecutionStatus& status)
192 {
193   if (args.size() < 2) {
194     status.SetError("sub-command REGEX requires a mode to be specified.");
195     return false;
196   }
197   std::string const& mode = args[1];
198   if (mode == "MATCH") {
199     if (args.size() < 5) {
200       status.SetError("sub-command REGEX, mode MATCH needs "
201                       "at least 5 arguments total to command.");
202       return false;
203     }
204     return RegexMatch(args, status);
205   }
206   if (mode == "MATCHALL") {
207     if (args.size() < 5) {
208       status.SetError("sub-command REGEX, mode MATCHALL needs "
209                       "at least 5 arguments total to command.");
210       return false;
211     }
212     return RegexMatchAll(args, status);
213   }
214   if (mode == "REPLACE") {
215     if (args.size() < 6) {
216       status.SetError("sub-command REGEX, mode REPLACE needs "
217                       "at least 6 arguments total to command.");
218       return false;
219     }
220     return RegexReplace(args, status);
221   }
222
223   std::string e = "sub-command REGEX does not recognize mode " + mode;
224   status.SetError(e);
225   return false;
226 }
227
228 bool RegexMatch(std::vector<std::string> const& args,
229                 cmExecutionStatus& status)
230 {
231   //"STRING(REGEX MATCH <regular_expression> <output variable>
232   // <input> [<input>...])\n";
233   std::string const& regex = args[2];
234   std::string const& outvar = args[3];
235
236   status.GetMakefile().ClearMatches();
237   // Compile the regular expression.
238   cmsys::RegularExpression re;
239   if (!re.compile(regex)) {
240     std::string e =
241       "sub-command REGEX, mode MATCH failed to compile regex \"" + regex +
242       "\".";
243     status.SetError(e);
244     return false;
245   }
246
247   // Concatenate all the last arguments together.
248   std::string input = cmJoin(cmMakeRange(args).advance(4), std::string());
249
250   // Scan through the input for all matches.
251   std::string output;
252   if (re.find(input)) {
253     status.GetMakefile().StoreMatches(re);
254     std::string::size_type l = re.start();
255     std::string::size_type r = re.end();
256     if (r - l == 0) {
257       std::string e = "sub-command REGEX, mode MATCH regex \"" + regex +
258         "\" matched an empty string.";
259       status.SetError(e);
260       return false;
261     }
262     output = input.substr(l, r - l);
263   }
264
265   // Store the output in the provided variable.
266   status.GetMakefile().AddDefinition(outvar, output);
267   return true;
268 }
269
270 bool RegexMatchAll(std::vector<std::string> const& args,
271                    cmExecutionStatus& status)
272 {
273   //"STRING(REGEX MATCHALL <regular_expression> <output variable> <input>
274   // [<input>...])\n";
275   std::string const& regex = args[2];
276   std::string const& outvar = args[3];
277
278   status.GetMakefile().ClearMatches();
279   // Compile the regular expression.
280   cmsys::RegularExpression re;
281   if (!re.compile(regex)) {
282     std::string e =
283       "sub-command REGEX, mode MATCHALL failed to compile regex \"" + regex +
284       "\".";
285     status.SetError(e);
286     return false;
287   }
288
289   // Concatenate all the last arguments together.
290   std::string input = cmJoin(cmMakeRange(args).advance(4), std::string());
291
292   // Scan through the input for all matches.
293   std::string output;
294   const char* p = input.c_str();
295   while (re.find(p)) {
296     status.GetMakefile().ClearMatches();
297     status.GetMakefile().StoreMatches(re);
298     std::string::size_type l = re.start();
299     std::string::size_type r = re.end();
300     if (r - l == 0) {
301       std::string e = "sub-command REGEX, mode MATCHALL regex \"" + regex +
302         "\" matched an empty string.";
303       status.SetError(e);
304       return false;
305     }
306     if (!output.empty()) {
307       output += ";";
308     }
309     output += std::string(p + l, r - l);
310     p += r;
311   }
312
313   // Store the output in the provided variable.
314   status.GetMakefile().AddDefinition(outvar, output);
315   return true;
316 }
317
318 bool RegexReplace(std::vector<std::string> const& args,
319                   cmExecutionStatus& status)
320 {
321   //"STRING(REGEX REPLACE <regular_expression> <replace_expression>
322   // <output variable> <input> [<input>...])\n"
323   std::string const& regex = args[2];
324   std::string const& replace = args[3];
325   std::string const& outvar = args[4];
326   cmStringReplaceHelper replaceHelper(regex, replace, &status.GetMakefile());
327
328   if (!replaceHelper.IsReplaceExpressionValid()) {
329     status.SetError(
330       "sub-command REGEX, mode REPLACE: " + replaceHelper.GetError() + ".");
331     return false;
332   }
333
334   status.GetMakefile().ClearMatches();
335
336   if (!replaceHelper.IsRegularExpressionValid()) {
337     std::string e =
338       "sub-command REGEX, mode REPLACE failed to compile regex \"" + regex +
339       "\".";
340     status.SetError(e);
341     return false;
342   }
343
344   // Concatenate all the last arguments together.
345   const std::string input =
346     cmJoin(cmMakeRange(args).advance(5), std::string());
347   std::string output;
348
349   if (!replaceHelper.Replace(input, output)) {
350     status.SetError(
351       "sub-command REGEX, mode REPLACE: " + replaceHelper.GetError() + ".");
352     return false;
353   }
354
355   // Store the output in the provided variable.
356   status.GetMakefile().AddDefinition(outvar, output);
357   return true;
358 }
359
360 bool HandleFindCommand(std::vector<std::string> const& args,
361                        cmExecutionStatus& status)
362 {
363   // check if all required parameters were passed
364   if (args.size() < 4 || args.size() > 5) {
365     status.SetError("sub-command FIND requires 3 or 4 parameters.");
366     return false;
367   }
368
369   // check if the reverse flag was set or not
370   bool reverseMode = false;
371   if (args.size() == 5 && args[4] == "REVERSE") {
372     reverseMode = true;
373   }
374
375   // if we have 5 arguments the last one must be REVERSE
376   if (args.size() == 5 && args[4] != "REVERSE") {
377     status.SetError("sub-command FIND: unknown last parameter");
378     return false;
379   }
380
381   // local parameter names.
382   const std::string& sstring = args[1];
383   const std::string& schar = args[2];
384   const std::string& outvar = args[3];
385
386   // ensure that the user cannot accidentally specify REVERSE as a variable
387   if (outvar == "REVERSE") {
388     status.SetError("sub-command FIND does not allow one to select REVERSE as "
389                     "the output variable.  "
390                     "Maybe you missed the actual output variable?");
391     return false;
392   }
393
394   // try to find the character and return its position
395   size_t pos;
396   if (!reverseMode) {
397     pos = sstring.find(schar);
398   } else {
399     pos = sstring.rfind(schar);
400   }
401   if (std::string::npos != pos) {
402     status.GetMakefile().AddDefinition(outvar, std::to_string(pos));
403     return true;
404   }
405
406   // the character was not found, but this is not really an error
407   status.GetMakefile().AddDefinition(outvar, "-1");
408   return true;
409 }
410
411 bool HandleCompareCommand(std::vector<std::string> const& args,
412                           cmExecutionStatus& status)
413 {
414   if (args.size() < 2) {
415     status.SetError("sub-command COMPARE requires a mode to be specified.");
416     return false;
417   }
418   std::string const& mode = args[1];
419   if ((mode == "EQUAL") || (mode == "NOTEQUAL") || (mode == "LESS") ||
420       (mode == "LESS_EQUAL") || (mode == "GREATER") ||
421       (mode == "GREATER_EQUAL")) {
422     if (args.size() < 5) {
423       std::string e =
424         cmStrCat("sub-command COMPARE, mode ", mode,
425                  " needs at least 5 arguments total to command.");
426       status.SetError(e);
427       return false;
428     }
429
430     const std::string& left = args[2];
431     const std::string& right = args[3];
432     const std::string& outvar = args[4];
433     bool result;
434     if (mode == "LESS") {
435       result = (left < right);
436     } else if (mode == "LESS_EQUAL") {
437       result = (left <= right);
438     } else if (mode == "GREATER") {
439       result = (left > right);
440     } else if (mode == "GREATER_EQUAL") {
441       result = (left >= right);
442     } else if (mode == "EQUAL") {
443       result = (left == right);
444     } else // if(mode == "NOTEQUAL")
445     {
446       result = !(left == right);
447     }
448     if (result) {
449       status.GetMakefile().AddDefinition(outvar, "1");
450     } else {
451       status.GetMakefile().AddDefinition(outvar, "0");
452     }
453     return true;
454   }
455   std::string e = "sub-command COMPARE does not recognize mode " + mode;
456   status.SetError(e);
457   return false;
458 }
459
460 bool HandleReplaceCommand(std::vector<std::string> const& args,
461                           cmExecutionStatus& status)
462 {
463   if (args.size() < 5) {
464     status.SetError("sub-command REPLACE requires at least four arguments.");
465     return false;
466   }
467
468   const std::string& matchExpression = args[1];
469   const std::string& replaceExpression = args[2];
470   const std::string& variableName = args[3];
471
472   std::string input = cmJoin(cmMakeRange(args).advance(4), std::string());
473
474   cmsys::SystemTools::ReplaceString(input, matchExpression.c_str(),
475                                     replaceExpression.c_str());
476
477   status.GetMakefile().AddDefinition(variableName, input);
478   return true;
479 }
480
481 bool HandleSubstringCommand(std::vector<std::string> const& args,
482                             cmExecutionStatus& status)
483 {
484   if (args.size() != 5) {
485     status.SetError("sub-command SUBSTRING requires four arguments.");
486     return false;
487   }
488
489   const std::string& stringValue = args[1];
490   int begin = atoi(args[2].c_str());
491   int end = atoi(args[3].c_str());
492   const std::string& variableName = args[4];
493
494   size_t stringLength = stringValue.size();
495   int intStringLength = static_cast<int>(stringLength);
496   if (begin < 0 || begin > intStringLength) {
497     status.SetError(
498       cmStrCat("begin index: ", begin, " is out of range 0 - ", stringLength));
499     return false;
500   }
501   if (end < -1) {
502     status.SetError(cmStrCat("end index: ", end, " should be -1 or greater"));
503     return false;
504   }
505
506   status.GetMakefile().AddDefinition(variableName,
507                                      stringValue.substr(begin, end));
508   return true;
509 }
510
511 bool HandleLengthCommand(std::vector<std::string> const& args,
512                          cmExecutionStatus& status)
513 {
514   if (args.size() != 3) {
515     status.SetError("sub-command LENGTH requires two arguments.");
516     return false;
517   }
518
519   const std::string& stringValue = args[1];
520   const std::string& variableName = args[2];
521
522   size_t length = stringValue.size();
523   char buffer[1024];
524   snprintf(buffer, sizeof(buffer), "%d", static_cast<int>(length));
525
526   status.GetMakefile().AddDefinition(variableName, buffer);
527   return true;
528 }
529
530 bool HandleAppendCommand(std::vector<std::string> const& args,
531                          cmExecutionStatus& status)
532 {
533   if (args.size() < 2) {
534     status.SetError("sub-command APPEND requires at least one argument.");
535     return false;
536   }
537
538   // Skip if nothing to append.
539   if (args.size() < 3) {
540     return true;
541   }
542
543   auto const& variableName = args[1];
544
545   cm::string_view oldView{ status.GetMakefile().GetSafeDefinition(
546     variableName) };
547
548   auto const newValue = cmJoin(cmMakeRange(args).advance(2), {}, oldView);
549   status.GetMakefile().AddDefinition(variableName, newValue);
550
551   return true;
552 }
553
554 bool HandlePrependCommand(std::vector<std::string> const& args,
555                           cmExecutionStatus& status)
556 {
557   if (args.size() < 2) {
558     status.SetError("sub-command PREPEND requires at least one argument.");
559     return false;
560   }
561
562   // Skip if nothing to prepend.
563   if (args.size() < 3) {
564     return true;
565   }
566
567   const std::string& variable = args[1];
568
569   std::string value = cmJoin(cmMakeRange(args).advance(2), std::string());
570   cmValue oldValue = status.GetMakefile().GetDefinition(variable);
571   if (oldValue) {
572     value += *oldValue;
573   }
574   status.GetMakefile().AddDefinition(variable, value);
575   return true;
576 }
577
578 bool HandleConcatCommand(std::vector<std::string> const& args,
579                          cmExecutionStatus& status)
580 {
581   if (args.size() < 2) {
582     status.SetError("sub-command CONCAT requires at least one argument.");
583     return false;
584   }
585
586   return joinImpl(args, std::string(), 1, status.GetMakefile());
587 }
588
589 bool HandleJoinCommand(std::vector<std::string> const& args,
590                        cmExecutionStatus& status)
591 {
592   if (args.size() < 3) {
593     status.SetError("sub-command JOIN requires at least two arguments.");
594     return false;
595   }
596
597   return joinImpl(args, args[1], 2, status.GetMakefile());
598 }
599
600 bool joinImpl(std::vector<std::string> const& args, std::string const& glue,
601               const size_t varIdx, cmMakefile& makefile)
602 {
603   std::string const& variableName = args[varIdx];
604   // NOTE Items to concat/join placed right after the variable for
605   // both `CONCAT` and `JOIN` sub-commands.
606   std::string value = cmJoin(cmMakeRange(args).advance(varIdx + 1), glue);
607
608   makefile.AddDefinition(variableName, value);
609   return true;
610 }
611
612 bool HandleMakeCIdentifierCommand(std::vector<std::string> const& args,
613                                   cmExecutionStatus& status)
614 {
615   if (args.size() != 3) {
616     status.SetError("sub-command MAKE_C_IDENTIFIER requires two arguments.");
617     return false;
618   }
619
620   const std::string& input = args[1];
621   const std::string& variableName = args[2];
622
623   status.GetMakefile().AddDefinition(variableName,
624                                      cmSystemTools::MakeCidentifier(input));
625   return true;
626 }
627
628 bool HandleGenexStripCommand(std::vector<std::string> const& args,
629                              cmExecutionStatus& status)
630 {
631   if (args.size() != 3) {
632     status.SetError("sub-command GENEX_STRIP requires two arguments.");
633     return false;
634   }
635
636   const std::string& input = args[1];
637
638   std::string result = cmGeneratorExpression::Preprocess(
639     input, cmGeneratorExpression::StripAllGeneratorExpressions);
640
641   const std::string& variableName = args[2];
642
643   status.GetMakefile().AddDefinition(variableName, result);
644   return true;
645 }
646
647 bool HandleStripCommand(std::vector<std::string> const& args,
648                         cmExecutionStatus& status)
649 {
650   if (args.size() != 3) {
651     status.SetError("sub-command STRIP requires two arguments.");
652     return false;
653   }
654
655   const std::string& stringValue = args[1];
656   const std::string& variableName = args[2];
657   size_t inStringLength = stringValue.size();
658   size_t startPos = inStringLength + 1;
659   size_t endPos = 0;
660   const char* ptr = stringValue.c_str();
661   size_t cc;
662   for (cc = 0; cc < inStringLength; ++cc) {
663     if (!isspace(*ptr)) {
664       if (startPos > inStringLength) {
665         startPos = cc;
666       }
667       endPos = cc;
668     }
669     ++ptr;
670   }
671
672   size_t outLength = 0;
673
674   // if the input string didn't contain any non-space characters, return
675   // an empty string
676   if (startPos > inStringLength) {
677     outLength = 0;
678     startPos = 0;
679   } else {
680     outLength = endPos - startPos + 1;
681   }
682
683   status.GetMakefile().AddDefinition(variableName,
684                                      stringValue.substr(startPos, outLength));
685   return true;
686 }
687
688 bool HandleRepeatCommand(std::vector<std::string> const& args,
689                          cmExecutionStatus& status)
690 {
691   cmMakefile& makefile = status.GetMakefile();
692
693   // `string(REPEAT "<str>" <times> OUTPUT_VARIABLE)`
694   enum ArgPos : std::size_t
695   {
696     SUB_COMMAND,
697     VALUE,
698     TIMES,
699     OUTPUT_VARIABLE,
700     TOTAL_ARGS
701   };
702
703   if (args.size() != ArgPos::TOTAL_ARGS) {
704     makefile.IssueMessage(MessageType::FATAL_ERROR,
705                           "sub-command REPEAT requires three arguments.");
706     return true;
707   }
708
709   unsigned long times;
710   if (!cmStrToULong(args[ArgPos::TIMES], &times)) {
711     makefile.IssueMessage(MessageType::FATAL_ERROR,
712                           "repeat count is not a positive number.");
713     return true;
714   }
715
716   const auto& stringValue = args[ArgPos::VALUE];
717   const auto& variableName = args[ArgPos::OUTPUT_VARIABLE];
718   const auto inStringLength = stringValue.size();
719
720   std::string result;
721   switch (inStringLength) {
722     case 0u:
723       // Nothing to do for zero length input strings
724       break;
725     case 1u:
726       // NOTE If the string to repeat consists of the only character,
727       // use the appropriate constructor.
728       result = std::string(times, stringValue[0]);
729       break;
730     default:
731       result = std::string(inStringLength * times, char{});
732       for (auto i = 0u; i < times; ++i) {
733         std::copy(cm::cbegin(stringValue), cm::cend(stringValue),
734                   &result[i * inStringLength]);
735       }
736       break;
737   }
738
739   makefile.AddDefinition(variableName, result);
740   return true;
741 }
742
743 bool HandleRandomCommand(std::vector<std::string> const& args,
744                          cmExecutionStatus& status)
745 {
746   if (args.size() < 2 || args.size() == 3 || args.size() == 5) {
747     status.SetError("sub-command RANDOM requires at least one argument.");
748     return false;
749   }
750
751   static bool seeded = false;
752   bool force_seed = false;
753   unsigned int seed = 0;
754   int length = 5;
755   const char cmStringCommandDefaultAlphabet[] = "qwertyuiopasdfghjklzxcvbnm"
756                                                 "QWERTYUIOPASDFGHJKLZXCVBNM"
757                                                 "0123456789";
758   std::string alphabet;
759
760   if (args.size() > 3) {
761     size_t i = 1;
762     size_t stopAt = args.size() - 2;
763
764     for (; i < stopAt; ++i) {
765       if (args[i] == "LENGTH") {
766         ++i;
767         length = atoi(args[i].c_str());
768       } else if (args[i] == "ALPHABET") {
769         ++i;
770         alphabet = args[i];
771       } else if (args[i] == "RANDOM_SEED") {
772         ++i;
773         seed = static_cast<unsigned int>(atoi(args[i].c_str()));
774         force_seed = true;
775       }
776     }
777   }
778   if (alphabet.empty()) {
779     alphabet = cmStringCommandDefaultAlphabet;
780   }
781
782   double sizeofAlphabet = static_cast<double>(alphabet.size());
783   if (sizeofAlphabet < 1) {
784     status.SetError("sub-command RANDOM invoked with bad alphabet.");
785     return false;
786   }
787   if (length < 1) {
788     status.SetError("sub-command RANDOM invoked with bad length.");
789     return false;
790   }
791   const std::string& variableName = args.back();
792
793   std::vector<char> result;
794
795   if (!seeded || force_seed) {
796     seeded = true;
797     srand(force_seed ? seed : cmSystemTools::RandomSeed());
798   }
799
800   const char* alphaPtr = alphabet.c_str();
801   for (int cc = 0; cc < length; cc++) {
802     int idx = static_cast<int>(sizeofAlphabet * rand() / (RAND_MAX + 1.0));
803     result.push_back(*(alphaPtr + idx));
804   }
805   result.push_back(0);
806
807   status.GetMakefile().AddDefinition(variableName, result.data());
808   return true;
809 }
810
811 bool HandleTimestampCommand(std::vector<std::string> const& args,
812                             cmExecutionStatus& status)
813 {
814   if (args.size() < 2) {
815     status.SetError("sub-command TIMESTAMP requires at least one argument.");
816     return false;
817   }
818   if (args.size() > 4) {
819     status.SetError("sub-command TIMESTAMP takes at most three arguments.");
820     return false;
821   }
822
823   unsigned int argsIndex = 1;
824
825   const std::string& outputVariable = args[argsIndex++];
826
827   std::string formatString;
828   if (args.size() > argsIndex && args[argsIndex] != "UTC") {
829     formatString = args[argsIndex++];
830   }
831
832   bool utcFlag = false;
833   if (args.size() > argsIndex) {
834     if (args[argsIndex] == "UTC") {
835       utcFlag = true;
836     } else {
837       std::string e = " TIMESTAMP sub-command does not recognize option " +
838         args[argsIndex] + ".";
839       status.SetError(e);
840       return false;
841     }
842   }
843
844   cmTimestamp timestamp;
845   std::string result = timestamp.CurrentTime(formatString, utcFlag);
846   status.GetMakefile().AddDefinition(outputVariable, result);
847
848   return true;
849 }
850
851 bool HandleUuidCommand(std::vector<std::string> const& args,
852                        cmExecutionStatus& status)
853 {
854 #if !defined(CMAKE_BOOTSTRAP)
855   unsigned int argsIndex = 1;
856
857   if (args.size() < 2) {
858     status.SetError("UUID sub-command requires an output variable.");
859     return false;
860   }
861
862   const std::string& outputVariable = args[argsIndex++];
863
864   std::string uuidNamespaceString;
865   std::string uuidName;
866   std::string uuidType;
867   bool uuidUpperCase = false;
868
869   while (args.size() > argsIndex) {
870     if (args[argsIndex] == "NAMESPACE") {
871       ++argsIndex;
872       if (argsIndex >= args.size()) {
873         status.SetError("UUID sub-command, NAMESPACE requires a value.");
874         return false;
875       }
876       uuidNamespaceString = args[argsIndex++];
877     } else if (args[argsIndex] == "NAME") {
878       ++argsIndex;
879       if (argsIndex >= args.size()) {
880         status.SetError("UUID sub-command, NAME requires a value.");
881         return false;
882       }
883       uuidName = args[argsIndex++];
884     } else if (args[argsIndex] == "TYPE") {
885       ++argsIndex;
886       if (argsIndex >= args.size()) {
887         status.SetError("UUID sub-command, TYPE requires a value.");
888         return false;
889       }
890       uuidType = args[argsIndex++];
891     } else if (args[argsIndex] == "UPPER") {
892       ++argsIndex;
893       uuidUpperCase = true;
894     } else {
895       std::string e =
896         "UUID sub-command does not recognize option " + args[argsIndex] + ".";
897       status.SetError(e);
898       return false;
899     }
900   }
901
902   std::string uuid;
903   cmUuid uuidGenerator;
904
905   std::vector<unsigned char> uuidNamespace;
906   if (!uuidGenerator.StringToBinary(uuidNamespaceString, uuidNamespace)) {
907     status.SetError("UUID sub-command, malformed NAMESPACE UUID.");
908     return false;
909   }
910
911   if (uuidType == "MD5") {
912     uuid = uuidGenerator.FromMd5(uuidNamespace, uuidName);
913   } else if (uuidType == "SHA1") {
914     uuid = uuidGenerator.FromSha1(uuidNamespace, uuidName);
915   } else {
916     std::string e = "UUID sub-command, unknown TYPE '" + uuidType + "'.";
917     status.SetError(e);
918     return false;
919   }
920
921   if (uuid.empty()) {
922     status.SetError("UUID sub-command, generation failed.");
923     return false;
924   }
925
926   if (uuidUpperCase) {
927     uuid = cmSystemTools::UpperCase(uuid);
928   }
929
930   status.GetMakefile().AddDefinition(outputVariable, uuid);
931   return true;
932 #else
933   status.SetError(cmStrCat(args[0], " not available during bootstrap"));
934   return false;
935 #endif
936 }
937
938 #if !defined(CMAKE_BOOTSTRAP)
939
940 // Helpers for string(JSON ...)
941 struct Args : cmRange<typename std::vector<std::string>::const_iterator>
942 {
943   using cmRange<typename std::vector<std::string>::const_iterator>::cmRange;
944
945   auto PopFront(cm::string_view error) -> const std::string&;
946   auto PopBack(cm::string_view error) -> const std::string&;
947 };
948
949 class json_error : public std::runtime_error
950 {
951 public:
952   json_error(std::initializer_list<cm::string_view> message,
953              cm::optional<Args> errorPath = cm::nullopt)
954     : std::runtime_error(cmCatViews(message))
955     , ErrorPath{
956       std::move(errorPath) // NOLINT(performance-move-const-arg)
957     }
958   {
959   }
960   cm::optional<Args> ErrorPath;
961 };
962
963 const std::string& Args::PopFront(cm::string_view error)
964 {
965   if (this->empty()) {
966     throw json_error({ error });
967   }
968   const std::string& res = *this->begin();
969   this->advance(1);
970   return res;
971 }
972
973 const std::string& Args::PopBack(cm::string_view error)
974 {
975   if (this->empty()) {
976     throw json_error({ error });
977   }
978   const std::string& res = *(this->end() - 1);
979   this->retreat(1);
980   return res;
981 }
982
983 cm::string_view JsonTypeToString(Json::ValueType type)
984 {
985   switch (type) {
986     case Json::ValueType::nullValue:
987       return "NULL"_s;
988     case Json::ValueType::intValue:
989     case Json::ValueType::uintValue:
990     case Json::ValueType::realValue:
991       return "NUMBER"_s;
992     case Json::ValueType::stringValue:
993       return "STRING"_s;
994     case Json::ValueType::booleanValue:
995       return "BOOLEAN"_s;
996     case Json::ValueType::arrayValue:
997       return "ARRAY"_s;
998     case Json::ValueType::objectValue:
999       return "OBJECT"_s;
1000   }
1001   throw json_error({ "invalid JSON type found"_s });
1002 }
1003
1004 int ParseIndex(
1005   const std::string& str, cm::optional<Args> const& progress = cm::nullopt,
1006   Json::ArrayIndex max = std::numeric_limits<Json::ArrayIndex>::max())
1007 {
1008   unsigned long lindex;
1009   if (!cmStrToULong(str, &lindex)) {
1010     throw json_error({ "expected an array index, got: '"_s, str, "'"_s },
1011                      progress);
1012   }
1013   Json::ArrayIndex index = static_cast<Json::ArrayIndex>(lindex);
1014   if (index >= max) {
1015     cmAlphaNum sizeStr{ max };
1016     throw json_error({ "expected an index less then "_s, sizeStr.View(),
1017                        " got '"_s, str, "'"_s },
1018                      progress);
1019   }
1020   return index;
1021 }
1022
1023 Json::Value& ResolvePath(Json::Value& json, Args path)
1024 {
1025   Json::Value* search = &json;
1026
1027   for (auto curr = path.begin(); curr != path.end(); ++curr) {
1028     const std::string& field = *curr;
1029     Args progress{ path.begin(), curr + 1 };
1030
1031     if (search->isArray()) {
1032       auto index = ParseIndex(field, progress, search->size());
1033       search = &(*search)[index];
1034
1035     } else if (search->isObject()) {
1036       if (!search->isMember(field)) {
1037         const auto progressStr = cmJoin(progress, " "_s);
1038         throw json_error({ "member '"_s, progressStr, "' not found"_s },
1039                          progress);
1040       }
1041       search = &(*search)[field];
1042     } else {
1043       const auto progressStr = cmJoin(progress, " "_s);
1044       throw json_error(
1045         { "invalid path '"_s, progressStr,
1046           "', need element of OBJECT or ARRAY type to lookup '"_s, field,
1047           "' got "_s, JsonTypeToString(search->type()) },
1048         progress);
1049     }
1050   }
1051   return *search;
1052 }
1053
1054 Json::Value ReadJson(const std::string& jsonstr)
1055 {
1056   Json::CharReaderBuilder builder;
1057   builder["collectComments"] = false;
1058   auto jsonReader = std::unique_ptr<Json::CharReader>(builder.newCharReader());
1059   Json::Value json;
1060   std::string error;
1061   if (!jsonReader->parse(jsonstr.data(), jsonstr.data() + jsonstr.size(),
1062                          &json, &error)) {
1063     throw json_error({ "failed parsing json string: "_s, error });
1064   }
1065   return json;
1066 }
1067 std::string WriteJson(const Json::Value& value)
1068 {
1069   Json::StreamWriterBuilder writer;
1070   writer["indentation"] = "  ";
1071   writer["commentStyle"] = "None";
1072   return Json::writeString(writer, value);
1073 }
1074
1075 #endif
1076
1077 bool HandleJSONCommand(std::vector<std::string> const& arguments,
1078                        cmExecutionStatus& status)
1079 {
1080 #if !defined(CMAKE_BOOTSTRAP)
1081
1082   auto& makefile = status.GetMakefile();
1083   Args args{ arguments.begin() + 1, arguments.end() };
1084
1085   const std::string* errorVariable = nullptr;
1086   const std::string* outputVariable = nullptr;
1087   bool success = true;
1088
1089   try {
1090     outputVariable = &args.PopFront("missing out-var argument"_s);
1091
1092     if (!args.empty() && *args.begin() == "ERROR_VARIABLE"_s) {
1093       args.PopFront("");
1094       errorVariable = &args.PopFront("missing error-var argument"_s);
1095       makefile.AddDefinition(*errorVariable, "NOTFOUND"_s);
1096     }
1097
1098     const auto& mode = args.PopFront("missing mode argument"_s);
1099     if (mode != "GET"_s && mode != "TYPE"_s && mode != "MEMBER"_s &&
1100         mode != "LENGTH"_s && mode != "REMOVE"_s && mode != "SET"_s &&
1101         mode != "EQUAL"_s) {
1102       throw json_error(
1103         { "got an invalid mode '"_s, mode,
1104           "', expected one of GET, TYPE, MEMBER, LENGTH, REMOVE, SET, "
1105           " EQUAL"_s });
1106     }
1107
1108     const auto& jsonstr = args.PopFront("missing json string argument"_s);
1109     Json::Value json = ReadJson(jsonstr);
1110
1111     if (mode == "GET"_s) {
1112       const auto& value = ResolvePath(json, args);
1113       if (value.isObject() || value.isArray()) {
1114         makefile.AddDefinition(*outputVariable, WriteJson(value));
1115       } else if (value.isBool()) {
1116         makefile.AddDefinitionBool(*outputVariable, value.asBool());
1117       } else {
1118         makefile.AddDefinition(*outputVariable, value.asString());
1119       }
1120
1121     } else if (mode == "TYPE"_s) {
1122       const auto& value = ResolvePath(json, args);
1123       makefile.AddDefinition(*outputVariable, JsonTypeToString(value.type()));
1124
1125     } else if (mode == "MEMBER"_s) {
1126       const auto& indexStr = args.PopBack("missing member index"_s);
1127       const auto& value = ResolvePath(json, args);
1128       if (!value.isObject()) {
1129         throw json_error({ "MEMBER needs to be called with an element of "
1130                            "type OBJECT, got "_s,
1131                            JsonTypeToString(value.type()) },
1132                          args);
1133       }
1134       const auto index = ParseIndex(
1135         indexStr, Args{ args.begin(), args.end() + 1 }, value.size());
1136       const auto memIt = std::next(value.begin(), index);
1137       makefile.AddDefinition(*outputVariable, memIt.name());
1138
1139     } else if (mode == "LENGTH"_s) {
1140       const auto& value = ResolvePath(json, args);
1141       if (!value.isArray() && !value.isObject()) {
1142         throw json_error({ "LENGTH needs to be called with an "
1143                            "element of type ARRAY or OBJECT, got "_s,
1144                            JsonTypeToString(value.type()) },
1145                          args);
1146       }
1147
1148       cmAlphaNum sizeStr{ value.size() };
1149       makefile.AddDefinition(*outputVariable, sizeStr.View());
1150
1151     } else if (mode == "REMOVE"_s) {
1152       const auto& toRemove =
1153         args.PopBack("missing member or index to remove"_s);
1154       auto& value = ResolvePath(json, args);
1155
1156       if (value.isArray()) {
1157         const auto index = ParseIndex(
1158           toRemove, Args{ args.begin(), args.end() + 1 }, value.size());
1159         Json::Value removed;
1160         value.removeIndex(index, &removed);
1161
1162       } else if (value.isObject()) {
1163         Json::Value removed;
1164         value.removeMember(toRemove, &removed);
1165
1166       } else {
1167         throw json_error({ "REMOVE needs to be called with an "
1168                            "element of type ARRAY or OBJECT, got "_s,
1169                            JsonTypeToString(value.type()) },
1170                          args);
1171       }
1172       makefile.AddDefinition(*outputVariable, WriteJson(json));
1173
1174     } else if (mode == "SET"_s) {
1175       const auto& newValueStr = args.PopBack("missing new value remove"_s);
1176       const auto& toAdd = args.PopBack("missing member name to add"_s);
1177       auto& value = ResolvePath(json, args);
1178
1179       Json::Value newValue = ReadJson(newValueStr);
1180       if (value.isObject()) {
1181         value[toAdd] = newValue;
1182       } else if (value.isArray()) {
1183         const auto index =
1184           ParseIndex(toAdd, Args{ args.begin(), args.end() + 1 });
1185         if (value.isValidIndex(index)) {
1186           value[static_cast<int>(index)] = newValue;
1187         } else {
1188           value.append(newValue);
1189         }
1190       } else {
1191         throw json_error({ "SET needs to be called with an "
1192                            "element of type OBJECT or ARRAY, got "_s,
1193                            JsonTypeToString(value.type()) });
1194       }
1195
1196       makefile.AddDefinition(*outputVariable, WriteJson(json));
1197
1198     } else if (mode == "EQUAL"_s) {
1199       const auto& jsonstr2 =
1200         args.PopFront("missing second json string argument"_s);
1201       Json::Value json2 = ReadJson(jsonstr2);
1202       makefile.AddDefinitionBool(*outputVariable, json == json2);
1203     }
1204
1205   } catch (const json_error& e) {
1206     if (outputVariable && e.ErrorPath) {
1207       const auto errorPath = cmJoin(*e.ErrorPath, "-");
1208       makefile.AddDefinition(*outputVariable,
1209                              cmCatViews({ errorPath, "-NOTFOUND"_s }));
1210     } else if (outputVariable) {
1211       makefile.AddDefinition(*outputVariable, "NOTFOUND"_s);
1212     }
1213
1214     if (errorVariable) {
1215       makefile.AddDefinition(*errorVariable, e.what());
1216     } else {
1217       status.SetError(cmCatViews({ "sub-command JSON "_s, e.what(), "."_s }));
1218       success = false;
1219     }
1220   }
1221   return success;
1222 #else
1223   status.SetError(cmStrCat(arguments[0], " not available during bootstrap"_s));
1224   return false;
1225 #endif
1226 }
1227
1228 } // namespace
1229
1230 bool cmStringCommand(std::vector<std::string> const& args,
1231                      cmExecutionStatus& status)
1232 {
1233   if (args.empty()) {
1234     status.SetError("must be called with at least one argument.");
1235     return false;
1236   }
1237
1238   static cmSubcommandTable const subcommand{
1239     { "REGEX"_s, HandleRegexCommand },
1240     { "REPLACE"_s, HandleReplaceCommand },
1241     { "MD5"_s, HandleHashCommand },
1242     { "SHA1"_s, HandleHashCommand },
1243     { "SHA224"_s, HandleHashCommand },
1244     { "SHA256"_s, HandleHashCommand },
1245     { "SHA384"_s, HandleHashCommand },
1246     { "SHA512"_s, HandleHashCommand },
1247     { "SHA3_224"_s, HandleHashCommand },
1248     { "SHA3_256"_s, HandleHashCommand },
1249     { "SHA3_384"_s, HandleHashCommand },
1250     { "SHA3_512"_s, HandleHashCommand },
1251     { "TOLOWER"_s, HandleToLowerCommand },
1252     { "TOUPPER"_s, HandleToUpperCommand },
1253     { "COMPARE"_s, HandleCompareCommand },
1254     { "ASCII"_s, HandleAsciiCommand },
1255     { "HEX"_s, HandleHexCommand },
1256     { "CONFIGURE"_s, HandleConfigureCommand },
1257     { "LENGTH"_s, HandleLengthCommand },
1258     { "APPEND"_s, HandleAppendCommand },
1259     { "PREPEND"_s, HandlePrependCommand },
1260     { "CONCAT"_s, HandleConcatCommand },
1261     { "JOIN"_s, HandleJoinCommand },
1262     { "SUBSTRING"_s, HandleSubstringCommand },
1263     { "STRIP"_s, HandleStripCommand },
1264     { "REPEAT"_s, HandleRepeatCommand },
1265     { "RANDOM"_s, HandleRandomCommand },
1266     { "FIND"_s, HandleFindCommand },
1267     { "TIMESTAMP"_s, HandleTimestampCommand },
1268     { "MAKE_C_IDENTIFIER"_s, HandleMakeCIdentifierCommand },
1269     { "GENEX_STRIP"_s, HandleGenexStripCommand },
1270     { "UUID"_s, HandleUuidCommand },
1271     { "JSON"_s, HandleJSONCommand },
1272   };
1273
1274   return subcommand(args[0], args, status);
1275 }