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