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 ES2PANDA_UTIL_OPTIONS_H
17#define ES2PANDA_UTIL_OPTIONS_H
18
19#include "libpandabase/os/file.h"
20#include "es2panda.h"
21#include "util/helpers.h"
22#include "utils/pandargs.h"
23#include "arktsconfig.h"
24
25#include <exception>
26#include <fstream>
27#include <iostream>
28#include <variant>
29
30namespace ark {
31class PandArgParser;
32class PandaArg;
33}  // namespace ark
34
35namespace ark::es2panda::util {
36enum class OptionFlags : uint32_t {
37    DEFAULT = 0U,
38    PARSE_ONLY = 1U << 0U,
39    PARSE_MODULE = 1U << 1U,
40    SIZE_STAT = 1U << 2U,
41};
42
43constexpr int MAX_OPT_LEVEL = 2;
44
45inline std::underlying_type_t<OptionFlags> operator&(OptionFlags a, OptionFlags b)
46{
47    using Utype = std::underlying_type_t<OptionFlags>;
48    /* NOLINTNEXTLINE(hicpp-signed-bitwise) */
49    return static_cast<Utype>(static_cast<Utype>(a) & static_cast<Utype>(b));
50}
51
52inline OptionFlags &operator|=(OptionFlags &a, OptionFlags b)
53{
54    using Utype = std::underlying_type_t<OptionFlags>;
55    /* NOLINTNEXTLINE(hicpp-signed-bitwise) */
56    return a = static_cast<OptionFlags>(static_cast<Utype>(a) | static_cast<Utype>(b));
57}
58
59template <class T>
60T BaseName(T const &path)
61{
62    return path.substr(path.find_last_of(ark::os::file::File::GetPathDelim()) + 1);
63}
64
65class Options {
66public:
67    Options();
68    NO_COPY_SEMANTIC(Options);
69    NO_MOVE_SEMANTIC(Options);
70    ~Options();
71
72    bool Parse(int argc, const char **argv);
73
74    es2panda::ScriptExtension Extension() const
75    {
76        return extension_;
77    }
78
79    const es2panda::CompilerOptions &CompilerOptions() const
80    {
81        return compilerOptions_;
82    }
83
84    void SetCompilerOptions(const es2panda::CompilerOptions &options)
85    {
86        compilerOptions_ = options;
87    }
88
89    const std::string &ParserInput() const
90    {
91        return parserInput_;
92    }
93
94    const std::string &CompilerOutput() const
95    {
96        return compilerOutput_;
97    }
98
99    void SetCompilerOutput(const std::string &compilerOutput)
100    {
101        compilerOutput_ = compilerOutput;
102    }
103
104    std::string_view LogLevel() const
105    {
106        switch (logLevel_) {
107            case util::LogLevel::DEBUG: {
108                return "debug";
109            }
110            case util::LogLevel::INFO: {
111                return "info";
112            }
113            case util::LogLevel::WARNING: {
114                return "warning";
115            }
116            case util::LogLevel::ERROR: {
117                return "error";
118            }
119            case util::LogLevel::FATAL: {
120                return "fatal";
121            }
122            default: {
123                UNREACHABLE();
124            }
125        }
126    }
127
128    void DetermineLogLevel(const ark::PandArg<std::string> &logLevel)
129    {
130        if (const auto logLevelStr = logLevel.GetValue(); !logLevelStr.empty()) {
131            if (logLevelStr == "debug") {
132                logLevel_ = util::LogLevel::DEBUG;
133            } else if (logLevelStr == "info") {
134                logLevel_ = util::LogLevel::INFO;
135            } else if (logLevelStr == "warning") {
136                logLevel_ = util::LogLevel::WARNING;
137            } else if (logLevelStr == "error") {
138                logLevel_ = util::LogLevel::ERROR;
139            } else if (logLevelStr == "fatal") {
140                logLevel_ = util::LogLevel::FATAL;
141            } else {
142                logLevel_ = util::LogLevel::INVALID;
143                std::cerr << "Invalid log level: '" << logLevelStr
144                          << R"('. Possible values: ["debug", "info", "warning", "error", "fatal"])";
145            }
146        }
147    }
148
149    void DetermineExtension(const ark::PandArg<std::string> &inputExtension,
150                            const ark::PandArg<std::string> &arktsConfig, const es2panda::CompilationMode &compMode)
151    {
152        std::string extension = inputExtension.GetValue();
153        std::string sourceFileExtension = sourceFile_.substr(sourceFile_.find_last_of('.') + 1);
154
155        bool extensionIsEmpty = extension.empty();
156        if (!sourceFile_.empty() && !extensionIsEmpty && extension != sourceFileExtension) {
157            std::cerr << "Warning: Not matching extensions! Sourcefile: " << sourceFileExtension
158                      << ", Manual(used): " << extension << std::endl;
159        }
160        auto tempExtension = !extensionIsEmpty ? extension : sourceFileExtension;
161        if (tempExtension == "js") {
162            extension_ = es2panda::ScriptExtension::JS;
163#ifndef PANDA_WITH_ECMASCRIPT
164            errorMsg_ = "js extension is not supported within current build";
165            extension_ = es2panda::ScriptExtension::INVALID;
166            return;
167#endif
168        } else if (tempExtension == "ts") {
169            extension_ = es2panda::ScriptExtension::TS;
170        } else if (tempExtension == "as") {
171            extension_ = es2panda::ScriptExtension::AS;
172        } else if (tempExtension == "sts") {
173            extension_ = es2panda::ScriptExtension::ETS;
174
175            std::ifstream inputStream(arktsConfig.GetValue());
176            if (inputStream.fail()) {
177                errorMsg_ = "Failed to open arktsconfig: ";
178                errorMsg_.append(arktsConfig.GetValue());
179                extension_ = es2panda::ScriptExtension::INVALID;
180                return;
181            }
182        } else if (extensionIsEmpty && (compMode == CompilationMode::PROJECT)) {
183            extension_ = es2panda::ScriptExtension::ETS;
184        } else {
185            if (!extensionIsEmpty) {
186                errorMsg_ = "Invalid extension (available options: js, ts, as, sts)";
187            } else {
188                errorMsg_ =
189                    "Unknown extension of sourcefile, set the extension manually or change the file format (available "
190                    "options: js, ts, as, sts)";
191            }
192            extension_ = es2panda::ScriptExtension::INVALID;
193            return;
194        }
195    }
196
197    CompilationMode DetermineCompilationMode(const ark::PandArg<bool> &genStdLib,
198                                             const ark::PandArg<std::string> &inputFile) const
199    {
200        return genStdLib.GetValue()           ? CompilationMode::GEN_STD_LIB
201               : inputFile.GetValue().empty() ? CompilationMode::PROJECT
202                                              : CompilationMode::SINGLE_FILE;
203    }
204
205    void AddOptionFlags(const ark::PandArg<bool> &opParseOnly, const ark::PandArg<bool> &opModule,
206                        const ark::PandArg<bool> &opSizeStat)
207    {
208        if (opParseOnly.GetValue()) {
209            options_ |= OptionFlags::PARSE_ONLY;
210        }
211
212        if (opModule.GetValue()) {
213            options_ |= OptionFlags::PARSE_MODULE;
214        }
215
216        if (opSizeStat.GetValue()) {
217            options_ |= OptionFlags::SIZE_STAT;
218        }
219    }
220
221    bool CheckEtsSpecificOptions(const es2panda::CompilationMode &compMode,
222                                 const ark::PandArg<std::string> &arktsConfig)
223    {
224        if (extension_ != es2panda::ScriptExtension::ETS) {
225            if (compMode == CompilationMode::PROJECT) {
226                errorMsg_ = "Error: only --extension=sts is supported for project compilation mode.";
227                return false;
228            }
229        } else {
230            if (!compilerOptions_.arktsConfig->Parse()) {
231                errorMsg_ = "Invalid ArkTsConfig: ";
232                errorMsg_.append(arktsConfig.GetValue());
233                return false;
234            }
235        }
236        return true;
237    }
238
239    const std::string &SourceFile() const
240    {
241        return sourceFile_;
242    }
243
244    const std::string &ErrorMsg() const
245    {
246        return errorMsg_;
247    }
248
249    int OptLevel() const
250    {
251        return optLevel_;
252    }
253
254    int ThreadCount() const
255    {
256        return threadCount_;
257    }
258
259    bool ParseModule() const
260    {
261        return (options_ & OptionFlags::PARSE_MODULE) != 0;
262    }
263
264    bool ParseOnly() const
265    {
266        return (options_ & OptionFlags::PARSE_ONLY) != 0;
267    }
268
269    bool SizeStat() const
270    {
271        return (options_ & OptionFlags::SIZE_STAT) != 0;
272    }
273
274    bool IsDynamic() const
275    {
276        return extension_ != es2panda::ScriptExtension::ETS;
277    }
278
279    bool ListFiles() const
280    {
281        return listFiles_;
282    }
283
284    bool ListPhases() const
285    {
286        return listPhases_;
287    }
288
289private:
290    es2panda::ScriptExtension extension_ {es2panda::ScriptExtension::JS};
291    OptionFlags options_ {OptionFlags::DEFAULT};
292    es2panda::CompilerOptions compilerOptions_ {};
293    ark::PandArgParser *argparser_;
294    std::string parserInput_;
295    std::string compilerOutput_;
296    std::string result_;
297    std::string sourceFile_;
298    std::string errorMsg_;
299    int optLevel_ {0};
300    int threadCount_ {0};
301    bool listFiles_ {false};
302    bool listPhases_ {false};
303    util::LogLevel logLevel_ {util::LogLevel::ERROR};
304};
305}  // namespace ark::es2panda::util
306
307#endif  // UTIL_OPTIONS_H
308