1 /**
2  * Copyright (c) 2021-2024 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #ifndef LIBPANDABASE_UTILS_PANDARGS_H_
17 #define LIBPANDABASE_UTILS_PANDARGS_H_
18 
19 #include <algorithm>
20 #include <array>
21 #include <list>
22 #include <set>
23 #include <map>
24 #include <string>
25 #include <string_view>
26 #include <sstream>
27 #include <type_traits>
28 #include <vector>
29 #include <cerrno>
30 #include <optional>
31 #include <utility>
32 
33 #include "macros.h"
34 
35 namespace ark {
36 class PandArgBase;
37 // NOLINTNEXTLINE(readability-identifier-naming)
38 using sub_args_t = std::vector<PandArgBase *>;
39 // NOLINTNEXTLINE(readability-identifier-naming)
40 using arg_list_t = std::vector<std::string>;
41 using std::enable_if_t;
42 using std::is_same_v;
43 
44 enum class PandArgType : uint8_t { STRING, INTEGER, DOUBLE, BOOL, LIST, UINT32, UINT64, COMPOUND, NOTYPE };
45 
46 // Base class for panda argument
47 class PandArgBase {
48 public:
PandArgBase(std::string name, std::string desc, PandArgType type = PandArgType::NOTYPE)49     explicit PandArgBase(std::string name, std::string desc, PandArgType type = PandArgType::NOTYPE)
50         : name_(std::move(name)), desc_(std::move(desc)), type_(type)
51     {
52     }
53 
GetType() const54     PandArgType GetType() const
55     {
56         return type_;
57     }
58 
GetName() const59     std::string GetName() const
60     {
61         return name_;
62     }
63 
GetDesc() const64     std::string GetDesc() const
65     {
66         return desc_;
67     }
68 
WasSet() const69     bool WasSet() const
70     {
71         return wasSet_;
72     }
73 
SetWasSet(bool value)74     void SetWasSet(bool value)
75     {
76         wasSet_ = value;
77     }
78 
79     virtual void ResetDefaultValue() = 0;
80 
81 private:
82     std::string name_;
83     std::string desc_;
84     PandArgType type_;
85     bool wasSet_ {false};
86 };
87 
88 template <typename T,
89           enable_if_t<is_same_v<std::string, T> || is_same_v<double, T> || is_same_v<bool, T> || is_same_v<int, T> ||
90                       is_same_v<uint32_t, T> || is_same_v<uint64_t, T> || is_same_v<arg_list_t, T>> * = nullptr>
91 class PandArg : public PandArgBase {
92 public:
PandArg(const std::string &name, T defaultVal, const std::string &desc)93     explicit PandArg(const std::string &name, T defaultVal, const std::string &desc)
94         : PandArgBase(name, desc, this->EvalType()), defaultVal_(defaultVal), realVal_(defaultVal)
95     {
96     }
97 
PandArg(const std::string &name, T defaultVal, const std::string &desc, PandArgType type)98     explicit PandArg(const std::string &name, T defaultVal, const std::string &desc, PandArgType type)
99         : PandArgBase(name, desc, type), defaultVal_(defaultVal), realVal_(defaultVal)
100     {
101     }
102 
PandArg(const std::string &name, int defaultVal, const std::string &desc, T minVal, T maxVal)103     explicit PandArg(const std::string &name, int defaultVal, const std::string &desc, T minVal, T maxVal)
104         : PandArgBase(name, desc, this->EvalType()),
105           defaultVal_(defaultVal),
106           realVal_(defaultVal),
107           minMaxVal_(std::pair<T, T>(minVal, maxVal))
108     {
109     }
110 
PandArg(const std::string &name, const arg_list_t &defaultVal, const std::string &desc, std::string delimiter)111     explicit PandArg(const std::string &name, const arg_list_t &defaultVal, const std::string &desc,
112                      std::string delimiter)
113         : PandArgBase(name, desc, PandArgType::LIST),
114           defaultVal_ {defaultVal},
115           realVal_ {defaultVal},
116           delimiter_ {std::move(delimiter)}
117     {
118     }
119 
GetValue() const120     T GetValue() const
121     {
122         return realVal_;
123     }
124 
GetDefaultValue() const125     T GetDefaultValue() const
126     {
127         return defaultVal_;
128     }
129 
130     template <bool UPDATE_FLAG = true>
SetValue(T val)131     void SetValue(T val)
132     {
133         realVal_ = val;
134         // NOLINTNEXTLINE(bugprone-suspicious-semicolon)
135         if constexpr (UPDATE_FLAG) {  // NOLINT(readability-braces-around-statements)
136             SetWasSet(true);
137         }
138     }
139 
140     void ResetDefaultValue() override
141     {
142         realVal_ = defaultVal_;
143     }
144 
GetDelimiter() const145     std::optional<std::string> GetDelimiter() const
146     {
147         return delimiter_;
148     }
GetMinMaxVal()149     std::optional<std::pair<T, T>> GetMinMaxVal()
150     {
151         return minMaxVal_;
152     }
153 
154 private:
EvalType()155     constexpr PandArgType EvalType()
156     {
157         // NOLINTNEXTLINE(bugprone-branch-clone)
158         if constexpr (is_same_v<std::string, T>) {  // NOLINT(readability-braces-around-statements)
159             return PandArgType::STRING;
160             // NOLINTNEXTLINE(readability-braces-around-statements,readability-misleading-indentation)
161         } else if constexpr (is_same_v<double, T>) {
162             return PandArgType::DOUBLE;
163             // NOLINTNEXTLINE(readability-braces-around-statements,readability-misleading-indentation)
164         } else if constexpr (is_same_v<bool, T>) {
165             return PandArgType::BOOL;
166             // NOLINTNEXTLINE(readability-braces-around-statements,readability-misleading-indentation)
167         } else if constexpr (is_same_v<int, T>) {
168             return PandArgType::INTEGER;
169             // NOLINTNEXTLINE(readability-braces-around-statements,readability-misleading-indentation)
170         } else if constexpr (is_same_v<uint32_t, T>) {
171             return PandArgType::UINT32;
172             // NOLINTNEXTLINE(readability-braces-around-statements,readability-misleading-indentation)
173         } else if constexpr (is_same_v<uint64_t, T>) {
174             return PandArgType::UINT64;
175             // NOLINTNEXTLINE(readability-braces-around-statements,readability-misleading-indentation)
176         } else if constexpr (is_same_v<arg_list_t, T>) {
177             return PandArgType::LIST;
178         }
179         UNREACHABLE();
180     }
181 
182     T defaultVal_;
183     T realVal_;
184 
185     // Only for integer arguments with range
186     std::optional<std::pair<T, T>> minMaxVal_;
187 
188     // Only for strings with delimiter
189     std::optional<std::string> delimiter_;
190 };
191 
192 class PandArgCompound : public PandArg<bool> {
193 public:
PandArgCompound(const std::string &name, const std::string &desc, std::initializer_list<PandArgBase *> subArgs)194     PandArgCompound(const std::string &name, const std::string &desc, std::initializer_list<PandArgBase *> subArgs)
195         : PandArg<bool>(name, false, desc, PandArgType::COMPOUND), subArgs_ {subArgs}
196     {
197     }
198 
FindSubArg(std::string_view name)199     PandArgBase *FindSubArg(std::string_view name)
200     {
201         auto res =
202             std::find_if(subArgs_.begin(), subArgs_.end(), [name](const auto &arg) { return arg->GetName() == name; });
203         return res == subArgs_.end() ? nullptr : *res;
204     }
205 
206     void ResetDefaultValue() override
207     {
208         PandArg<bool>::ResetDefaultValue();
209         std::for_each(subArgs_.begin(), subArgs_.end(), [](auto &arg) { arg->ResetDefaultValue(); });
210     }
211 
GetSubArgs() const212     const auto &GetSubArgs() const
213     {
214         return subArgs_;
215     }
216 
217 private:
218     sub_args_t subArgs_;
219 };
220 
221 class PandArgParser {
222 public:
Add(PandArgBase *arg)223     bool Add(PandArgBase *arg)
224     {
225         if (arg == nullptr) {
226             errstr_ += "pandargs: Can't add `nullptr` as an argument\n";
227             return false;
228         }
229         bool success = args_.insert(arg).second;
230         if (!success) {
231             errstr_ += "pandargs: Argument " + arg->GetName() + " has duplicate\n";
232         }
233         return success;
234     }
235 
PushBackTail(PandArgBase *arg)236     bool PushBackTail(PandArgBase *arg)
237     {
238         if (arg == nullptr) {
239             errstr_ += "pandargs: Can't add `nullptr` as a tail argument\n";
240             return false;
241         }
242         if (std::find(tailArgs_.begin(), tailArgs_.end(), arg) != tailArgs_.end()) {
243             errstr_ += "pandargs: Tail argument " + arg->GetName() + " is already in tail arguments list\n";
244             return false;
245         }
246         tailArgs_.emplace_back(arg);
247         return true;
248     }
249 
PopBackTail()250     bool PopBackTail()
251     {
252         if (tailArgs_.empty()) {
253             errstr_ += "pandargs: Nothing to pop back from tail arguments\n";
254             return false;
255         }
256         tailArgs_.pop_back();
257         return true;
258     }
259 
EraseTail()260     void EraseTail()
261     {
262         tailArgs_.erase(tailArgs_.begin(), tailArgs_.end());
263     }
264 
Parse(const std::vector<std::string> &argvVec)265     bool Parse(const std::vector<std::string> &argvVec)
266     {
267         InitDefault();
268         std::copy(argvVec.begin(), argvVec.end(), std::back_inserter(argvVec_));
269         return ParseArgs();
270     }
271 
Parse(int argc, const char *const argv[])272     bool Parse(int argc, const char *const argv[])  // NOLINT(modernize-avoid-c-arrays, hicpp-avoid-c-arrays)
273     {
274         InitDefault();
275         for (int i = 1; i < argc; i++) {
276             argvVec_.emplace_back(argv[i]);  // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
277         }
278         return ParseArgs();
279     }
280 
281     /// Parses a string to the option's value.
ParseSingleArg(PandArgBase *option, const std::string_view &optionValue)282     bool ParseSingleArg(PandArgBase *option, const std::string_view &optionValue)
283     {
284         ASSERT(option != nullptr);
285         argvVec_ = {std::string(optionValue)};
286         argvIndex_ = 0;
287         argvIndex_ += ParseNextParam(option, argvVec_[argvIndex_]);
288         return errstr_.empty();
289     }
290 
291     /// Parses option's name and returns corresponding pointer.
GetPandArg(const std::string_view &argName)292     PandArgBase *GetPandArg(const std::string_view &argName)
293     {
294         auto argIt = args_.find(argName);
295         return (argIt != args_.end()) ? *argIt : nullptr;
296     }
297 
GetErrorString() const298     std::string GetErrorString() const
299     {
300         return errstr_;
301     }
302 
EnableTail()303     void EnableTail()
304     {
305         tailFlag_ = true;
306     }
307 
DisableTail()308     void DisableTail()
309     {
310         tailFlag_ = false;
311     }
312 
IsTailEnabled() const313     bool IsTailEnabled() const
314     {
315         return tailFlag_;
316     }
317 
GetTailSize() const318     std::size_t GetTailSize() const
319     {
320         return tailArgs_.size();
321     }
322 
323     void EnableRemainder() noexcept
324     {
325         remainderFlag_ = true;
326     }
327 
328     void DisableRemainder() noexcept
329     {
330         remainderFlag_ = false;
331     }
332 
IsRemainderEnabled() const333     bool IsRemainderEnabled() const
334     {
335         return remainderFlag_;
336     }
337 
GetRemainder()338     arg_list_t GetRemainder()
339     {
340         return remainder_;
341     }
342 
IsArgSet(PandArgBase *arg) const343     bool IsArgSet(PandArgBase *arg) const
344     {
345         return args_.find(arg) != args_.end();
346     }
347 
IsArgSet(const std::string &argName) const348     bool IsArgSet(const std::string &argName) const
349     {
350         return args_.find(argName) != args_.end();
351     }
352 
GetHelpString() const353     std::string GetHelpString() const
354     {
355         std::ostringstream helpstr;
356         for (auto i : args_) {
357             if (i->GetType() == PandArgType::COMPOUND) {
358                 auto arg = static_cast<PandArgCompound *>(i);
359                 helpstr << DOUBLE_DASH << i->GetName() << ": " << i->GetDesc() << "\n";
360                 helpstr << "  Sub arguments:\n";
361                 for (auto subArg : arg->GetSubArgs()) {
362                     helpstr << "    " << subArg->GetName() << ": " << subArg->GetDesc() << "\n";
363                 }
364             } else {
365                 helpstr << DOUBLE_DASH << i->GetName() << ": " << i->GetDesc() << "\n";
366             }
367         }
368         if (!tailArgs_.empty()) {
369             helpstr << "Tail arguments:\n";
370             for (auto i : tailArgs_) {
371                 helpstr << i->GetName() << ": " << i->GetDesc() << "\n";
372             }
373         }
374         return helpstr.str();
375     }
376 
GetRegularArgs()377     std::string GetRegularArgs()
378     {
379         std::string argsStr;
380         std::string value;
381         for (auto i : args_) {
382             switch (i->GetType()) {
383                 case PandArgType::STRING:
384                     value = static_cast<PandArg<std::string> *>(i)->GetValue();
385                     break;
386                 case PandArgType::INTEGER:
387                     value = std::to_string(static_cast<PandArg<int> *>(i)->GetValue());
388                     break;
389                 case PandArgType::DOUBLE:
390                     value = std::to_string(static_cast<PandArg<double> *>(i)->GetValue());
391                     break;
392                 case PandArgType::BOOL:
393                     // NOLINTNEXTLINE(readability-implicit-bool-conversion)
394                     value = std::to_string(static_cast<PandArg<bool> *>(i)->GetValue());
395                     break;
396                 case PandArgType::UINT32:
397                     value = std::to_string(static_cast<PandArg<uint32_t> *>(i)->GetValue());
398                     break;
399                 case PandArgType::UINT64:
400                     value = std::to_string(static_cast<PandArg<uint64_t> *>(i)->GetValue());
401                     break;
402                 case PandArgType::LIST: {
403                     value = "";
404                     std::vector<std::string> valuesBuf = static_cast<PandArg<arg_list_t> *>(i)->GetValue();
405                     for (const auto &j : valuesBuf) {
406                         value += j + ", ";
407                     }
408                     break;
409                 }
410                 default:
411                     errstr_ += "Invalid argument type \"" + i->GetName() + "\"\n";
412                     break;
413             }
414             argsStr += DOUBLE_DASH + i->GetName() + "=" + value + "\n";
415         }
416         return argsStr;
417     }
418 
419 private:
420     struct PandArgPtrComparator {
421         // NOLINTNEXTLINE(readability-identifier-naming)
422         using is_transparent = void;
operator ()ark::PandArgParser::PandArgPtrComparator423         bool operator()(const PandArgBase *lhs, const PandArgBase *rhs) const
424         {
425             return lhs->GetName() < rhs->GetName();
426         }
operator ()ark::PandArgParser::PandArgPtrComparator427         bool operator()(std::string_view lhs, const PandArgBase *rhs) const
428         {
429             return lhs < rhs->GetName();
430         }
operator ()ark::PandArgParser::PandArgPtrComparator431         bool operator()(const PandArgBase *lhs, std::string_view rhs) const
432         {
433             return lhs->GetName() < rhs;
434         }
435     };
436 
ParseArgs()437     bool ParseArgs()
438     {
439         while (argvIndex_ < argvVec_.size()) {
440             PandArgBase *parsedArg = ParseNextArg();
441             if (!errstr_.empty()) {
442                 return false;
443             }
444             if (argvIndex_ < argvVec_.size()) {
445                 argvIndex_ += ParseNextParam(parsedArg, argvVec_[argvIndex_]);
446                 if (!errstr_.empty()) {
447                     return false;
448                 }
449             }
450         }
451         return true;
452     }
453 
InitDefault()454     void InitDefault()
455     {
456         equalFlag_ = false;
457         tailParsedFlag_ = false;
458         argvVec_.clear();
459         argvIndex_ = 0;
460         errstr_ = "";
461         // reset tail
462         for (auto tailArg : tailArgs_) {
463             switch (tailArg->GetType()) {
464                 case PandArgType::STRING:
465                     static_cast<PandArg<std::string> *>(tailArg)->ResetDefaultValue();
466                     break;
467                 case PandArgType::INTEGER:
468                     static_cast<PandArg<int> *>(tailArg)->ResetDefaultValue();
469                     break;
470                 case PandArgType::DOUBLE:
471                     static_cast<PandArg<double> *>(tailArg)->ResetDefaultValue();
472                     break;
473                 case PandArgType::BOOL:
474                     static_cast<PandArg<bool> *>(tailArg)->ResetDefaultValue();
475                     break;
476                 case PandArgType::UINT32:
477                     static_cast<PandArg<uint32_t> *>(tailArg)->ResetDefaultValue();
478                     break;
479                 case PandArgType::UINT64:
480                     static_cast<PandArg<uint64_t> *>(tailArg)->ResetDefaultValue();
481                     break;
482                 case PandArgType::LIST:
483                     static_cast<PandArg<arg_list_t> *>(tailArg)->ResetDefaultValue();
484                     break;
485                 default:
486                     break;
487             }
488         }
489         // reset remainder
490         remainder_ = arg_list_t();
491     }
492 
FindArg(std::string_view argName)493     PandArgBase *FindArg(std::string_view argName)
494     {
495         auto argIt = args_.find(argName);
496         if (argIt == args_.end()) {
497             errstr_.append("pandargs: Invalid option \"");
498             errstr_.append(argName);
499             errstr_.append("\"\n");
500             return nullptr;
501         }
502         return *argIt;
503     }
504 
ParseSubArgument(PandArgCompound *parentArg, std::string_view str)505     bool ParseSubArgument(PandArgCompound *parentArg, std::string_view str)
506     {
507         size_t assignPos = str.find('=');
508         std::string_view argName = str.substr(0, assignPos);
509         auto arg = parentArg->FindSubArg(argName);
510         if (arg == nullptr) {
511             errstr_.append("pandargs: Invalid sub-argument \"");
512             errstr_.append(argName);
513             errstr_.append("\"\n");
514             return false;
515         }
516 
517         if (assignPos != std::string_view::npos) {
518             std::string_view valueStr = str.substr(assignPos + 1);
519             ParseNextParam(arg, valueStr);
520             if (!errstr_.empty()) {
521                 return false;
522             }
523         } else {
524             if (arg->GetType() != PandArgType::BOOL) {
525                 errstr_.append("pandargs: Only boolean arguments might have no value \"");
526                 errstr_.append(argName);
527                 errstr_.append("\"\n");
528                 return false;
529             }
530             static_cast<PandArg<bool> *>(arg)->SetValue(true);
531         }
532 
533         return true;
534     }
535 
ParseCompoundArg(std::string_view argstr, size_t sepPos)536     PandArgBase *ParseCompoundArg(std::string_view argstr, size_t sepPos)
537     {
538         auto argName = argstr.substr(0, sepPos);
539 
540         auto arg = static_cast<PandArgCompound *>(FindArg(argName));
541         if (arg == nullptr) {
542             return nullptr;
543         }
544         if (arg->GetType() != PandArgType::COMPOUND) {
545             errstr_.append("pandargs: Following argument is not compound one or syntax error: \"");
546             errstr_.append(argName);
547             errstr_.append("\"\n");
548             return nullptr;
549         }
550 
551         arg->SetValue(true);
552 
553         auto subArgsStr = argstr.substr(sepPos + 1);
554         size_t start = 0;
555         for (size_t pos = subArgsStr.find(',', 0); pos != std::string_view::npos;
556              start = pos + 1, pos = subArgsStr.find(',', start)) {
557             auto argStr = subArgsStr.substr(start, pos - start);
558             if (!ParseSubArgument(arg, argStr)) {
559                 return nullptr;
560             }
561         }
562         if (start < subArgsStr.size()) {
563             if (!ParseSubArgument(arg, subArgsStr.substr(start))) {
564                 return nullptr;
565             }
566         }
567         argvIndex_++;
568         return arg;
569     }
570 
ParseNextRegularArg()571     PandArgBase *ParseNextRegularArg()
572     {
573         std::string argstr = argvVec_[argvIndex_];
574 
575         const std::size_t sepFound = NextSeparator(argstr);
576 
577         const std::size_t sepCompound = argstr.find_first_of(':', 0);
578         if (sepCompound != std::string::npos && (sepFound == std::string::npos || sepCompound < sepFound)) {
579             return ParseCompoundArg(std::string_view(argstr).substr(DASH_COUNT), sepCompound - DASH_COUNT);
580         }
581 
582         std::string argName;
583 
584         if (sepFound != std::string::npos) {
585             equalFlag_ = true;
586             argvVec_[argvIndex_] = argstr.substr(sepFound + 1);
587             argName = argstr.substr(DASH_COUNT, sepFound - DASH_COUNT);
588         } else {
589             argName = argstr.substr(DASH_COUNT, sepFound);
590             // check if there is next argv element to iterate into
591             if (argvIndex_ + 1 < argvVec_.size()) {
592                 argvIndex_++;
593             } else {
594                 argvVec_[argvIndex_] = "";
595             }
596         }
597 
598         PandArgBase *arg = FindArg(argName);
599         if (arg == nullptr) {
600             return nullptr;
601         }
602 
603         if (arg->GetType() == PandArgType::COMPOUND) {
604             // It is forbidden to explicitly set compound option, e.g. `--compound=true`, must be `--compound`.
605             if (sepFound != std::string::npos) {
606                 errstr_.append("pandargs: Compound option can not be explicitly set \"");
607                 errstr_.append(argName);
608                 errstr_.append("\"\n");
609                 return nullptr;
610             }
611             static_cast<PandArgCompound *>(arg)->SetValue(true);
612         }
613 
614         return arg;
615     }
616 
ParseNextArg()617     PandArgBase *ParseNextArg()
618     {
619         PandArgBase *arg = nullptr;
620         std::string argstr = argvVec_[argvIndex_];
621         equalFlag_ = false;
622 
623         // NOTE: currently we have only double dash argument prefix
624         std::size_t dashesFound = argstr.find(DOUBLE_DASH);
625         if (dashesFound == 0 && argstr.size() > DASH_COUNT) {
626             // regular argument
627             return ParseNextRegularArg();
628         }
629 
630         if (dashesFound == 0 && argstr.size() == DASH_COUNT) {
631             // remainder argument
632             if (!remainderFlag_) {
633                 errstr_.append("pandargs: Remainder arguments are not enabled\n");
634                 errstr_.append("pandargs: Remainder found at literal \"");
635                 errstr_.append(argstr);
636                 errstr_.append("\"\n");
637                 return nullptr;
638             }
639 
640             argvIndex_++;
641             ParseRemainder();
642         } else if (dashesFound > 0) {
643             // tail argument, N.B. std::string::npos > 0
644             if (!tailFlag_) {
645                 errstr_.append("pandargs: Tail arguments are not enabled\n");
646                 errstr_.append("pandargs: Tail found at literal \"");
647                 errstr_.append(argstr);
648                 errstr_.append("\"\n");
649                 return nullptr;
650             }
651             if (tailParsedFlag_) {
652                 errstr_.append("pandargs: Too many tail arguments\n");
653                 return nullptr;
654             }
655             ParseTail();
656 
657             if (argvIndex_ < argvVec_.size()) {
658                 if (argvVec_[argvIndex_] != DOUBLE_DASH && !remainderFlag_) {
659                     errstr_ += "pandargs: Too many tail arguments given\n";
660                 }
661             }
662         } else {
663             errstr_.append("pandargs: Invalid option \"");
664             errstr_.append(argstr);
665             errstr_.append("\"\n");
666             UNREACHABLE();
667         }
668         return arg;
669     }
670 
ParseTail()671     void ParseTail()
672     {
673         for (auto &tailArg : tailArgs_) {
674             switch (tailArg->GetType()) {
675                 case PandArgType::STRING:
676                     argvIndex_ +=
677                         ParseStringArgParam(static_cast<PandArg<std::string> *>(tailArg), argvVec_[argvIndex_]);
678                     break;
679                 case PandArgType::INTEGER:
680                     argvIndex_ += ParseIntArgParam(static_cast<PandArg<int> *>(tailArg), argvVec_[argvIndex_]);
681                     break;
682                 case PandArgType::DOUBLE:
683                     argvIndex_ += ParseDoubleArgParam(static_cast<PandArg<double> *>(tailArg), argvVec_[argvIndex_]);
684                     break;
685                 case PandArgType::BOOL:
686                     argvIndex_ += ParseBoolArgParam(static_cast<PandArg<bool> *>(tailArg), argvVec_[argvIndex_], true);
687                     break;
688                 case PandArgType::UINT32:
689                     argvIndex_ += ParseUint32ArgParam(static_cast<PandArg<uint32_t> *>(tailArg), argvVec_[argvIndex_]);
690                     break;
691                 case PandArgType::UINT64:
692                     argvIndex_ += ParseUint64ArgParam(static_cast<PandArg<uint64_t> *>(tailArg), argvVec_[argvIndex_]);
693                     break;
694                 case PandArgType::LIST:
695                     argvIndex_ += ParseListArgParam(static_cast<PandArg<arg_list_t> *>(tailArg), argvVec_[argvIndex_]);
696                     break;
697                 default:
698                     errstr_.append("pandargs: Invalid tail option type: \"");
699                     errstr_.append(tailArg->GetName());
700                     errstr_.append("\"\n");
701                     UNREACHABLE();
702                     break;
703             }
704             if (argvIndex_ >= argvVec_.size() || !errstr_.empty()) {
705                 break;
706             }
707         }
708         tailParsedFlag_ = true;
709     }
710 
ParseRemainder()711     void ParseRemainder()
712     {
713         remainder_ = arg_list_t(argvVec_.begin() + static_cast<arg_list_t::iterator::difference_type>(argvIndex_),
714                                 argvVec_.end());
715         argvIndex_ = argvVec_.size();
716     }
717 
ParseNextParam(PandArgBase *arg, std::string_view argstr)718     size_t ParseNextParam(PandArgBase *arg, std::string_view argstr)
719     {
720         if (argvIndex_ >= argvVec_.size() || arg == nullptr) {
721             return 0;
722         }
723         switch (arg->GetType()) {
724             case PandArgType::STRING:
725                 return ParseStringArgParam(static_cast<PandArg<std::string> *>(arg), argstr);
726             case PandArgType::INTEGER:
727                 return ParseIntArgParam(static_cast<PandArg<int> *>(arg), argstr);
728             case PandArgType::DOUBLE:
729                 return ParseDoubleArgParam(static_cast<PandArg<double> *>(arg), argstr);
730             case PandArgType::BOOL:
731                 return ParseBoolArgParam(static_cast<PandArg<bool> *>(arg), argstr);
732             case PandArgType::UINT32:
733                 return ParseUint32ArgParam(static_cast<PandArg<uint32_t> *>(arg), argstr);
734             case PandArgType::UINT64:
735                 return ParseUint64ArgParam(static_cast<PandArg<uint64_t> *>(arg), argstr);
736             case PandArgType::LIST:
737                 return ParseListArgParam(static_cast<PandArg<arg_list_t> *>(arg), argstr);
738             case PandArgType::COMPOUND:
739                 return argstr.empty() ? 1 : 0;
740             case PandArgType::NOTYPE:
741                 errstr_.append("pandargs: Invalid option type: \"");
742                 errstr_.append(arg->GetName());
743                 errstr_.append("\"\n");
744                 UNREACHABLE();
745                 break;
746             default:
747                 UNREACHABLE();
748                 break;
749         }
750         return 0;
751     }
752 
ParseStringArgParam(PandArg<std::string> *arg, std::string_view argstr)753     std::size_t ParseStringArgParam(PandArg<std::string> *arg, std::string_view argstr)
754     {
755         arg->SetValue(std::string(argstr));
756         return 1;
757     }
758 
ParseIntArgParam(PandArg<int> *arg, std::string_view argstr)759     std::size_t ParseIntArgParam(PandArg<int> *arg, std::string_view argstr)
760     {
761         std::string paramStr(argstr);
762         if (IsIntegerNumber(paramStr)) {
763             int64_t result = 0;
764             errno = 0;
765             if (StartsWith(paramStr, "0x")) {
766                 const int hex = 16;
767                 result = std::strtol(paramStr.c_str(), nullptr, hex);
768             } else {
769                 const int dec = 10;
770                 result = std::strtol(paramStr.c_str(), nullptr, dec);
771             }
772 
773             int num = static_cast<int>(result);
774             if (num != result || errno == ERANGE) {
775                 errstr_ +=
776                     "pandargs: \"" + arg->GetName() + "\" argument has invalid parameter value \"" + paramStr + "\"\n";
777 
778                 return 1;
779             }
780 
781             if (IsIntegerArgInRange(arg, num)) {
782                 arg->SetValue(num);
783             } else {
784                 errstr_ += "pandargs: \"" + arg->GetName() + "\" argument has out of range parameter value \"" +
785                            paramStr + "\"\n";
786             }
787         } else {
788             errstr_ +=
789                 "pandargs: \"" + arg->GetName() + "\" argument has out of range parameter value \"" + paramStr + "\"\n";
790         }
791         return 1;
792     }
793 
ParseDoubleArgParam(PandArg<double> *arg, std::string_view argstr)794     std::size_t ParseDoubleArgParam(PandArg<double> *arg, std::string_view argstr)
795     {
796         std::string paramStr(argstr);
797         if (IsRationalNumber(paramStr)) {
798             arg->SetValue(std::stod(paramStr));
799         } else {
800             errstr_ +=
801                 "pandargs: \"" + arg->GetName() + "\" argument has invalid parameter value \"" + paramStr + "\"\n";
802         }
803         return 1;
804     }
805 
ParseBoolArgParam(PandArg<bool> *arg, std::string_view argstr, bool isTailParam = false)806     std::size_t ParseBoolArgParam(PandArg<bool> *arg, std::string_view argstr, bool isTailParam = false)
807     {
808         std::string paramStr(argstr);
809 
810         // if not a tail argument, assume two following cases
811         if (!isTailParam) {
812             arg->SetValue(true);
813             // bool with no param, next argument comes right after
814             if (StartsWith(paramStr, DOUBLE_DASH)) {
815                 // check that bool param comes without "="
816                 if (equalFlag_) {
817                     SetBoolUnexpectedValueError(arg, paramStr);
818                 }
819                 return 0;
820             }
821             // OR bool arg at the end of arguments line
822             if (paramStr.empty()) {
823                 // check that bool param comes without "="
824                 if (equalFlag_) {
825                     SetBoolUnexpectedValueError(arg, paramStr);
826                 }
827                 return 1;
828             }
829         }
830 
831         constexpr std::array<std::string_view, 3> TRUE_VALUES = {"on", "true", "1"};
832         constexpr std::array<std::string_view, 3> FALSE_VALUES = {"off", "false", "0"};
833 
834         for (const auto &i : TRUE_VALUES) {
835             if (paramStr == i) {
836                 arg->SetValue(true);
837                 return 1;
838             }
839         }
840         for (const auto &i : FALSE_VALUES) {
841             if (paramStr == i) {
842                 arg->SetValue(false);
843                 return 1;
844             }
845         }
846 
847         // if it's not a part of tail argument,
848         // assume that it's bool with no param,
849         // preceiding tail argument
850         if (!isTailParam) {
851             // check that bool param came without "="
852             if (equalFlag_) {
853                 SetBoolUnexpectedValueError(arg, paramStr);
854             } else {
855                 arg->SetValue(true);
856             }
857         } else {
858             errstr_ +=
859                 "pandargs: Tail argument " + arg->GetName() + " has unexpected parameter value " + paramStr + "\n";
860             arg->ResetDefaultValue();
861         }
862 
863         return 0;
864     }
865 
ParseUint64ArgParam(PandArg<uint64_t> *arg, std::string_view argstr)866     std::size_t ParseUint64ArgParam(PandArg<uint64_t> *arg, std::string_view argstr)
867     {
868         std::string paramStr(argstr);
869         if (IsUintNumber(paramStr)) {
870             errno = 0;
871             uint64_t num;
872             if (StartsWith(paramStr, "0x")) {
873                 const int hex = 16;
874                 num = std::strtoull(paramStr.c_str(), nullptr, hex);
875             } else {
876                 const int dec = 10;
877                 num = std::strtoull(paramStr.c_str(), nullptr, dec);
878             }
879             if (errno == ERANGE) {
880                 errstr_ +=
881                     "pandargs: \"" + arg->GetName() + "\" argument has invalid parameter value \"" + paramStr + "\"\n";
882 
883                 return 1;
884             }
885 
886             if (IsIntegerArgInRange<uint64_t>(arg, num)) {
887                 arg->SetValue(num);
888             } else {
889                 errstr_ += "pandargs: \"" + arg->GetName() + "\" argument has out of range parameter value \"" +
890                            paramStr + "\"\n";
891             }
892         } else {
893             errstr_ +=
894                 "pandargs: \"" + arg->GetName() + "\" argument has invalid parameter value \"" + paramStr + "\"\n";
895         }
896         return 1;
897     }
898 
ParseUint32ArgParam(PandArg<uint32_t> *arg, std::string_view argstr)899     std::size_t ParseUint32ArgParam(PandArg<uint32_t> *arg, std::string_view argstr)
900     {
901         std::string paramStr(argstr);
902         if (IsUintNumber(paramStr)) {
903             uint64_t result = 0;
904             errno = 0;
905             if (StartsWith(paramStr, "0x")) {
906                 const int hex = 16;
907                 result = std::strtoull(paramStr.c_str(), nullptr, hex);
908             } else {
909                 const int dec = 10;
910                 result = std::strtoull(paramStr.c_str(), nullptr, dec);
911             }
912 
913             auto num = static_cast<uint32_t>(result);
914             if (num != result || errno == ERANGE) {
915                 errstr_ +=
916                     "pandargs: \"" + arg->GetName() + "\" argument has invalid parameter value \"" + paramStr + "\"\n";
917 
918                 return 1;
919             }
920 
921             if (IsIntegerArgInRange<uint32_t>(arg, num)) {
922                 arg->SetValue(num);
923             } else {
924                 errstr_ += "pandargs: \"" + arg->GetName() + "\" argument has out of range parameter value \"" +
925                            paramStr + "\"\n";
926             }
927         } else {
928             errstr_ +=
929                 "pandargs: \"" + arg->GetName() + "\" argument has invalid parameter value \"" + paramStr + "\"\n";
930         }
931         return 1;
932     }
933 
ParseListArgParam(PandArg<arg_list_t> *arg, std::string_view argstr)934     std::size_t ParseListArgParam(PandArg<arg_list_t> *arg, std::string_view argstr)
935     {
936         std::string paramStr(argstr);
937         arg_list_t value;
938         if (arg->WasSet()) {
939             value = arg->GetValue();
940         } else {
941             value = arg_list_t();
942         }
943         if (!arg->GetDelimiter().has_value()) {
944             value.push_back(paramStr);
945             arg->SetValue(value);
946             return 1;
947         }
948         std::string delimiter = arg->GetDelimiter().value();
949         std::size_t paramStrIndex = 0;
950         std::size_t pos = paramStr.find_first_of(delimiter, paramStrIndex);
951         while (pos < paramStr.size()) {
952             value.push_back(paramStr.substr(paramStrIndex, pos - paramStrIndex));
953             paramStrIndex = pos;
954             paramStrIndex = paramStr.find_first_not_of(delimiter, paramStrIndex);
955             pos = paramStr.find_first_of(delimiter, paramStrIndex);
956         }
957 
958         if (paramStrIndex <= paramStr.size()) {
959             value.push_back(paramStr.substr(paramStrIndex, pos - paramStrIndex));
960         }
961         arg->SetValue(value);
962         return 1;
963     }
964 
NextSeparator(std::string_view argstr, std::size_t pos = 0, const std::string &separarors = EQ_SEPARATOR)965     static std::size_t NextSeparator(std::string_view argstr, std::size_t pos = 0,
966                                      const std::string &separarors = EQ_SEPARATOR)
967     {
968         return argstr.find_first_of(separarors, pos);
969     }
970 
IsIntegerNumber(const std::string_view &str)971     static bool IsIntegerNumber(const std::string_view &str)
972     {
973         if (str.empty()) {
974             return false;
975         }
976         std::size_t pos = 0;
977         // look for dash if it's negative one
978         if (str[0] == '-') {
979             pos++;
980         }
981         // look for hex-style integer
982         if ((str.size() > pos + 2U) && (str.compare(pos, 2U, "0x") == 0)) {
983             pos += HEX_PREFIX_WIDTH;
984             return str.find_first_not_of("0123456789abcdefABCDEF", pos) == std::string::npos;
985         }
986         return str.find_first_not_of("0123456789", pos) == std::string::npos;
987     }
988 
IsRationalNumber(const std::string_view &str)989     static bool IsRationalNumber(const std::string_view &str)
990     {
991         if (str.empty()) {
992             return false;
993         }
994         std::size_t pos = 0;
995         // look for dash if it's negative one
996         if (str[0] == '-') {
997             pos++;
998         }
999         return str.find_first_not_of(".0123456789", pos) == std::string::npos;
1000     }
1001 
IsUintNumber(const std::string_view &str)1002     static bool IsUintNumber(const std::string_view &str)
1003     {
1004         if (str.empty()) {
1005             return false;
1006         }
1007 
1008         constexpr std::string_view CHAR_SEARCH = "0123456789";
1009         constexpr std::string_view CHAR_SEARCH_HEX = "0123456789abcdef";
1010         std::size_t pos = 0;
1011         // look for hex-style uint_t integer
1012         if ((str.size() > 2U) && (str.compare(0, 2U, "0x") == 0)) {
1013             pos += HEX_PREFIX_WIDTH;
1014         }
1015         return str.find_first_not_of((pos != 0) ? CHAR_SEARCH_HEX.data() : CHAR_SEARCH.data(), pos) ==
1016                std::string::npos;
1017     }
1018 
1019     template <typename T,
1020               enable_if_t<is_same_v<T, int> || is_same_v<T, uint32_t> || is_same_v<T, uint64_t>> * = nullptr>
IsIntegerArgInRange(PandArg<T> *arg, T num)1021     bool IsIntegerArgInRange(PandArg<T> *arg, T num)
1022     {
1023         if (!(arg->GetMinMaxVal().has_value())) {
1024             return true;
1025         }
1026         std::pair<T, T> minMax = arg->GetMinMaxVal().value();
1027         return ((num >= std::get<0>(minMax)) && (num <= std::get<1>(minMax)));
1028     }
1029 
StartsWith(const std::string &haystack, const std::string &needle)1030     static bool StartsWith(const std::string &haystack, const std::string &needle)
1031     {
1032         return std::equal(needle.begin(), needle.end(), haystack.begin());
1033     }
1034 
SetBoolUnexpectedValueError(PandArg<bool> *arg, const std::string &wrongvalue)1035     void SetBoolUnexpectedValueError(PandArg<bool> *arg, const std::string &wrongvalue)
1036     {
1037         errstr_ += "pandargs: Bool argument " + arg->GetName() + " has unexpected parameter value " + wrongvalue + "\n";
1038         arg->ResetDefaultValue();
1039     }
1040 
1041     constexpr static size_t HEX_PREFIX_WIDTH = 2;
1042     constexpr static unsigned int DASH_COUNT = 2;
1043 
1044     std::vector<std::string> argvVec_ {};
1045     std::size_t argvIndex_ = 0;
1046     std::string errstr_ {};
1047     bool tailFlag_ = false;
1048     bool remainderFlag_ = false;
1049     bool equalFlag_ = false;
1050     bool tailParsedFlag_ = false;
1051     static constexpr const char *DOUBLE_DASH = "--";
1052     static constexpr const char *EQ_SEPARATOR = "=";
1053     std::set<PandArgBase *, PandArgPtrComparator> args_ {};
1054     std::vector<PandArgBase *> tailArgs_ {};
1055     arg_list_t remainder_ = arg_list_t();
1056 };
1057 
1058 }  // namespace ark
1059 
1060 #endif  // LIBPANDABASE_UTILS_PANDARGS_H_
1061