1 /*
2  * Copyright (c) 2023 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 MAPLE_UTIL_INCLUDE_OPTION_H
17 #define MAPLE_UTIL_INCLUDE_OPTION_H
18 
19 #include "cl_parser.h"
20 #include "mpl_logging.h"
21 
22 #include <any>
23 #include <cstdint>
24 #include <deque>
25 #include <functional>
26 #include <map>
27 #include <memory>
28 #include <string>
29 #include <vector>
30 #include <iostream>
31 
32 namespace maplecl {
33 
34 template <typename T>
35 constexpr inline bool digitalCheck = (std::is_same_v<std::uint8_t, T> || std::is_same_v<std::uint16_t, T> ||
36                                       std::is_same_v<std::uint32_t, T> || std::is_same_v<std::uint64_t, T> ||
37                                       std::is_same_v<std::int64_t, T> || std::is_same_v<std::int32_t, T> ||
38                                       std::is_same_v<std::int16_t, T> || std::is_same_v<std::int8_t, T>);
39 
40 /* is key VALUE needed ? */
41 enum class ValueExpectedType {
42     kValueOptional,   /* "--key VALUE" and "--key" are allowed */
43     kValueRequired,   /* only "--key VALUE" is allowed */
44     kValueDisallowed, /* only "--key" is allowed */
45 };
46 
47 /* is "joined option" (like -DMACRO) allowed ? */
48 enum class ValueJoinedType {
49     kValueSeparated,
50     kValueJoined, /* joined option (like -DMACRO) is allowed */
51 };
52 
53 /* is option visible from Help ? */
54 enum class OptionVisibilityType {
55     kVisibleOption, /* an Option will be visible in Help */
56     kHidedOption    /* an Option will be NOT visible in Help */
57 };
58 
59 /* This enum is used as the option attribute to detect "=" in the original option.
60  * It can be useful to forward original option in an external tool.
61  * As example: user sets maple -std=gnu11 ; In this case, maple will forward -std
62  * option in clang, but clang can detect this option only with "=":
63  * clang -std=gnu11 (NOT clang -std gnu11). So this attribute is used for correct forwarding.
64  */
65 enum class EqualType { kWithEqual, kWithoutEqual };
66 
67 /* These constexpr are needed to use short name in option description, like this:
68  * maplecl::Option<int32_t> option({"--option"}, "Description", optionalValue);
69  * instead of:
70  * maplecl::Option<int32_t> option({"--option"}, "Description", ValueExpectedType::kValueOptional);
71  */
72 constexpr ValueExpectedType optionalValue = ValueExpectedType::kValueOptional;
73 constexpr ValueExpectedType requiredValue = ValueExpectedType::kValueRequired;
74 constexpr ValueExpectedType disallowedValue = ValueExpectedType::kValueDisallowed;
75 constexpr ValueJoinedType joinedValue = ValueJoinedType::kValueJoined;
76 constexpr ValueJoinedType separatedValue = ValueJoinedType::kValueSeparated;
77 constexpr OptionVisibilityType visible = OptionVisibilityType::kVisibleOption;
78 constexpr OptionVisibilityType hide = OptionVisibilityType::kHidedOption;
79 
80 /* Initializer is used to set default value for an option */
81 template <typename T>
82 struct Init {
83     const T &defaultVal;
Initmaplecl::Init84     explicit Init(const T &val) : defaultVal(val) {}
85 };
86 
87 /* DisableWithData is used to set additional option name to disable this option.
88  * Example: Base option: --opt; Additional option: --no-opt.
89  * --no-opt will disable this option in this example.
90  */
91 struct DisableWith {
92     const std::string &disableWith;
DisableWithmaplecl::DisableWith93     explicit DisableWith(const std::string &val) : disableWith(val) {}
94 };
95 struct DisableEvery {
96     const std::vector<std::string> &disableEvery;
DisableEverymaplecl::DisableEvery97     explicit DisableEvery(const std::vector<std::string> &val) : disableEvery(val) {}
98 };
99 
100 using OptionCategoryRefWrp = std::reference_wrapper<OptionCategory>;
101 
102 class OptionWrp {
103 public:
104     template <typename T>
OptionWrp(T v)105     /* implicit */ OptionWrp(T v) : val(v)
106     {
107     }
108 
109     template <typename T>
operator T()110     operator T()
111     {
112         T *pval = std::any_cast<T>(&val);
113         DEBUG_ASSERT(pval != nullptr, "pval should not be nullptr");
114         return *pval;
115     }
116 
117     std::any val;
118 };
119 
120 /* Interface for templated Option class */
121 class OptionInterface {
122 public:
123     virtual ~OptionInterface() = default;
124 
125     virtual RetCode Parse(size_t &argsIndex, const std::deque<std::string_view> &args, KeyArg &keyArg) = 0;
126     virtual void Clear() = 0;
127     virtual std::vector<std::string> GetRawValues() = 0;
128     virtual OptionWrp GetCommonValue() const = 0;
129 
130     void FinalizeInitialization(const std::vector<std::string> &optnames, const std::string &descr,
131                                 const std::vector<OptionCategoryRefWrp> &optionCategories);
132 
IsEnabledByUser() const133     bool IsEnabledByUser() const
134     {
135         return isEnabledByUser;
136     }
137 
ExpectedVal() const138     ValueExpectedType ExpectedVal() const
139     {
140         return valueExpected;
141     }
142 
IsJoinedValPermitted() const143     bool IsJoinedValPermitted() const
144     {
145         return (valueJoined == ValueJoinedType::kValueJoined);
146     }
147 
IsVisibleOption() const148     bool IsVisibleOption() const
149     {
150         return (visibleOption == OptionVisibilityType::kVisibleOption);
151     }
152 
GetDisabledName() const153     const std::vector<std::string> &GetDisabledName() const
154     {
155         return disableWith;
156     }
157 
GetName() const158     virtual std::string GetName() const
159     {
160         DEBUG_ASSERT(names.size() > 0, "names.size should be non zero");
161         return names[0];
162     }
163 
GetDescription() const164     const std::string &GetDescription() const
165     {
166         return optDescription;
167     }
168 
GetEqualType() const169     EqualType GetEqualType() const
170     {
171         return equalSign;
172     }
173 
SetEqualType(EqualType equal)174     void SetEqualType(EqualType equal)
175     {
176         equalSign = equal;
177     }
178 
179     std::string rawKey;
180     std::vector<OptionCategory *> optCategories;  // The option is registred in these categories
181 
182 protected:
183     std::vector<std::string> names;        // names of the option
184     std::string optDescription;            // overview of option
185     std::vector<std::string> disableWith;  // name to disable the option
186 
187     bool isEnabledByUser = false;  // it's set if the option is in command line
188 
189     ValueExpectedType valueExpected = ValueExpectedType::kValueRequired;        // whether the value is expected
190     ValueJoinedType valueJoined = ValueJoinedType::kValueSeparated;             // Joined option like -DMACRO
191     OptionVisibilityType visibleOption = OptionVisibilityType::kVisibleOption;  // Visible in Help
192 
193     EqualType equalSign = EqualType::kWithoutEqual;
194 };
195 
196 /* Option class describes command line option */
197 template <typename T>
198 class Option : public OptionInterface {
199 public:
200     /* variadic template is used to apply any number of options parameters in any order */
201     template <typename... ArgsT>
Option(const std::vector<std::string> &optnames, const std::string &descr, const ArgsT &... args)202     explicit Option(const std::vector<std::string> &optnames, const std::string &descr, const ArgsT &... args)
203     {
204         /* It's needed to avoid empty Apply() */
205         if constexpr (sizeof...(ArgsT) > 0) {
206             Apply(args...);
207         }
208         FinalizeInitialization(optnames, descr, {});
209     }
210 
211     /* variadic template is used to apply any number of options parameters in any order */
212     template <typename... ArgsT>
Option(const std::vector<std::string> &optnames, const std::string &descr, const std::vector<OptionCategoryRefWrp> &optionCategories, const ArgsT &... args)213     explicit Option(const std::vector<std::string> &optnames, const std::string &descr,
214                     const std::vector<OptionCategoryRefWrp> &optionCategories, const ArgsT &... args)
215     {
216         /* It's needed to avoid empty Apply() */
217         if constexpr (sizeof...(ArgsT) > 0) {
218             Apply(args...);
219         }
220         FinalizeInitialization(optnames, descr, optionCategories);
221     }
222 
223     // options must not be copyable and assignment
224     Option(const Option &) = delete;
225     Option &operator=(const Option &) = delete;
226 
227     /* Conversation operator is needed to use the option like this:
228      * strding test = option1; or int dig = option2 - here will be implicit conversation.
229      */
operator T()230     /*implicit*/ operator T()
231     {
232         return GetValue();
233     }
234 
235     RetCode Parse(size_t &argsIndex, const std::deque<std::string_view> &args, KeyArg &keyArg) override
236     {
237         RetCode err = RetCode::noError;
238 #ifdef ARK_LITECG_DEBUG
239         auto &key = args[argsIndex];
onstexpr(digitalCheck<T>)240         if constexpr (digitalCheck<T>) {
241             err = ParseDigit(argsIndex, args, keyArg);
242         } else if constexpr (std::is_same_v<std::string, T>) {
243             err = ParseString(argsIndex, args, keyArg);
onstexpr(std::is_same_v<bool, T>)244         } else if constexpr (std::is_same_v<bool, T>) {
245             err = ParseBool(argsIndex, args);
246         } else {
247             /* Type dependent static_assert. Simple static_assert(false") does not work */
248             static_assert(false && sizeof(T), "T not supported");
249         }
250 
251         if (err == RetCode::noError) {
252             isEnabledByUser = true;
253             rawKey = key;
254         }
255 #endif
256         return err;
257     }
258 
259     void Clear() override
260     {
261         if (defaultValue.isSet) {
262             value = defaultValue.defaultValue;
263         } else {
onstexpr(digitalCheck<T>)264             if constexpr (digitalCheck<T>) {
265                 value = 0;
266             } else if constexpr (std::is_same_v<std::string, T>) {
267                 value = "";
onstexpr(std::is_same_v<bool, T>)268             } else if constexpr (std::is_same_v<bool, T>) {
269                 value = false;
270             } else {
271                 /* Type dependent static_assert. Simple static_assert(false") does not work */
272                 static_assert(false && sizeof(T), "T not supported");
273             }
274         }
275 
276         for (auto &category : optCategories) {
277             category->Remove(this);
278         }
279 
280         isEnabledByUser = false;
281     }
282 
283     std::vector<std::string> GetRawValues() override
284     {
285         std::vector<std::string> rawVals;
286         FillVal(value, rawVals);
287         return rawVals;
288     }
289 
290     std::string GetName() const override
291     {
onstexpr(std::is_same_v<bool, T>)292         if constexpr (std::is_same_v<bool, T>) {
293             DEBUG_ASSERT(names.size() > 0, "names.size should be non zero");
294             return ((value == true) ? names[0] : this->GetDisabledName()[0]);
295         } else {
296             return OptionInterface::GetName();
297         }
298     }
299 
GetValue() const300     const T &GetValue() const
301     {
302         return value;
303     }
304 
305     OptionWrp GetCommonValue() const override
306     {
307         return value;
308     }
309 
SetValue(const T &val)310     virtual void SetValue(const T &val)
311     {
312         value = val;
313     }
314 
315 protected:
316     RetCode ParseDigit(size_t &argsIndex, const std::deque<std::string_view> &args, KeyArg &keyArg);
317     RetCode ParseString(size_t &argsIndex, const std::deque<std::string_view> &args, KeyArg &keyArg);
318     RetCode ParseBool(size_t &argsIndex, const std::deque<std::string_view> &args);
319 
FillVal(const T &val, std::vector<std::string> &vals)320     void FillVal(const T &val, std::vector<std::string> &vals)
321     {
322         if constexpr (digitalCheck<T>) {
323             vals.emplace_back(std::to_string(val));
324         } else if constexpr (std::is_same_v<std::string, T>) {
325             vals.emplace_back(val);
326         } else if constexpr (std::is_same_v<bool, T>) {
327             vals.emplace_back("");
328         } else {
329             /* Type dependent static_assert. Simple static_assert(false") does not work */
330             static_assert(false && sizeof(T), "T not supported");
331         }
332     }
333 
334     struct DefaultValue {
335         T defaultValue;
336         bool isSet = false;
337     } defaultValue;
338 
339 private:
340     /* To apply input arguments in any order */
341     template <typename ArgT, typename... ArgsT>
Apply(const ArgT &arg, const ArgsT &... args)342     void Apply(const ArgT &arg, const ArgsT &... args)
343     {
344         if constexpr (std::is_same_v<ValueExpectedType, ArgT>) {
345             SetExpectingAttribute(arg);
346         } else if constexpr (std::is_same_v<ValueJoinedType, ArgT>) {
347             SetJoinAttribute(arg);
348         } else if constexpr (std::is_same_v<OptionVisibilityType, ArgT>) {
349             SetVisibilityAttribute(arg);
350         } else if constexpr (std::is_same_v<DisableWith, ArgT>) {
351             SetDisablingAttribute(arg);
352         } else if constexpr (std::is_same_v<DisableEvery, ArgT>) {
353             SetDisablingAttribute(arg);
354         } else {
355             SetDefaultAttribute(arg);
356         }
357 
358         /* It's needed to avoid empty Apply() */
359         if constexpr (sizeof...(ArgsT) > 0) {
360             Apply(args...);
361         }
362     }
363 
364     template <typename InitT>
SetDefaultAttribute(const Init<InitT> &initializer)365     void SetDefaultAttribute(const Init<InitT> &initializer)
366     {
367         SetValue(initializer.defaultVal);
368         defaultValue.isSet = true;
369         defaultValue.defaultValue = value;
370     }
371 
SetExpectingAttribute(ValueExpectedType value)372     void SetExpectingAttribute(ValueExpectedType value)
373     {
374         valueExpected = value;
375     }
376 
SetJoinAttribute(ValueJoinedType value)377     void SetJoinAttribute(ValueJoinedType value)
378     {
379         valueJoined = value;
380     }
381 
SetVisibilityAttribute(OptionVisibilityType value)382     void SetVisibilityAttribute(OptionVisibilityType value)
383     {
384         visibleOption = value;
385     }
386 
SetDisablingAttribute(const DisableWith &value)387     void SetDisablingAttribute(const DisableWith &value)
388     {
389         disableWith.push_back(value.disableWith);
390     }
391 
SetDisablingAttribute(const DisableEvery &value)392     void SetDisablingAttribute(const DisableEvery &value)
393     {
394         for (auto &val : value.disableEvery) {
395             disableWith.push_back(val);
396         }
397     }
398 
399     T value;
400 };
401 
402 template <typename T>
operator ==(Option<T> &opt, const T &arg)403 bool operator==(Option<T> &opt, const T &arg)
404 {
405     return (opt.GetValue() == arg);
406 }
407 
408 template <typename T>
operator ==(const T &arg, Option<T> &opt)409 bool operator==(const T &arg, Option<T> &opt)
410 {
411     return opt == arg;
412 }
413 
414 /* To handle the comparation of "const char *" and "Option<string>".
415  * This comparation can not be handled with comparation template above! */
416 template <typename T>
operator ==(Option<T> &opt, const char *arg)417 bool operator==(Option<T> &opt, const char *arg)
418 {
419     return (opt.GetValue() == arg);
420 }
421 
422 template <typename T>
operator ==(const char *arg, Option<T> &opt)423 bool operator==(const char *arg, Option<T> &opt)
424 {
425     return opt == arg;
426 }
427 
428 template <typename T>
CopyIfEnabled(T &dst, maplecl::Option<T> &src)429 void CopyIfEnabled(T &dst, maplecl::Option<T> &src)
430 {
431     if (src.IsEnabledByUser()) {
432         dst = src;
433     }
434 }
435 
436 template <typename T>
CopyIfEnabled(T &dst, const T &src, OptionInterface &opt)437 void CopyIfEnabled(T &dst, const T &src, OptionInterface &opt)
438 {
439     if (opt.IsEnabledByUser()) {
440         dst = src;
441     }
442 }
443 
444 template <typename T>
445 class List : public Option<T> {
446 public:
447     // options must not be copyable and assignment
448     List(const List &) = delete;
449     List &operator=(const List &) = delete;
450 
451     /* variadic template is used to apply any number of options parameters in any order */
452     template <typename... ArgsT>
List(const std::vector<std::string> &optnames, const std::string &descr, const ArgsT &... args)453     explicit List(const std::vector<std::string> &optnames, const std::string &descr, const ArgsT &... args)
454         : Option<T>(optnames, descr, args...) {};
455 
456     template <typename... ArgsT>
List(const std::vector<std::string> &optnames, const std::string &descr, const std::vector<OptionCategoryRefWrp> &optionCategories, const ArgsT &... args)457     explicit List(const std::vector<std::string> &optnames, const std::string &descr,
458                   const std::vector<OptionCategoryRefWrp> &optionCategories, const ArgsT &... args)
459         : Option<T>(optnames, descr, optionCategories, args...) {};
460 
461     void Clear() override
462     {
463         values.clear();
464         if (this->defaultValue.isSet) {
465             SetValue(this->defaultValue.defaultValue);
466         }
467         this->isEnabledByUser = false;
468 
469         for (auto &category : this->optCategories) {
470             category->Remove(this);
471         }
472     }
473 
474     void SetValue(const T &val) override
475     {
476         values.push_back(val);
477     }
478 
GetValue() const479     const T &GetValue() const
480     {
481         static_assert(false && sizeof(T), "GetValue must be not used for List");
482         return T();
483     }
484 
GetValues() const485     const std::vector<T> &GetValues() const
486     {
487         return values;
488     }
489 
490     std::vector<std::string> GetRawValues() override
491     {
492         std::vector<std::string> rawVals;
493         for (const auto &val : values) {
494             this->FillVal(val, rawVals);
495         }
496 
497         return rawVals;
498     }
499 
500 private:
501     std::vector<T> values;
502 };
503 
504 }  // namespace maplecl
505 
506 #endif /* MAPLE_UTIL_INCLUDE_OPTION_H */
507