1/*
2 * Copyright (c) 2021-2022 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#include "options.h"
17
18#include <fstream>
19#include <set>
20#include <sstream>
21#include <utility>
22
23#if defined(PANDA_TARGET_WINDOWS)
24#include <io.h>
25#else
26#include <dirent.h>
27#endif
28
29#include "bytecode_optimizer/bytecodeopt_options.h"
30#include "compiler_options.h"
31#include "os/file.h"
32#include "utils/pandargs.h"
33#include "utils/timers.h"
34
35#include "mergeProgram.h"
36#include "util/helpers.h"
37
38namespace panda::es2panda::aot {
39constexpr char PROCESS_AS_LIST_MARK = '@';
40// item list: [filePath; recordName; moduleKind; sourceFile; pkgName; isSharedModule]
41constexpr size_t ITEM_COUNT_MERGE = 6;
42// item list: [filePath; recordName; moduleKind; sourceFile; outputfile; isSharedModule]
43constexpr size_t ITEM_COUNT_NOT_MERGE = 6;
44const std::string LIST_ITEM_SEPERATOR = ";";
45const std::set<std::string> VALID_EXTENSIONS = { "js", "ts", "as", "abc" };
46
47template <class T>
48T RemoveExtension(T const &filename)
49{
50    typename T::size_type const P(filename.find_last_of('.'));
51    return P > 0 && P != T::npos ? filename.substr(0, P) : filename;
52}
53
54static es2panda::ScriptExtension GetScriptExtensionFromStr(const std::string &extension)
55{
56    if (extension == "js") {
57        return es2panda::ScriptExtension::JS;
58    } else if (extension == "ts") {
59        return es2panda::ScriptExtension::TS;
60    } else if (extension == "as") {
61        return es2panda::ScriptExtension::AS;
62    } else if (extension == "abc") {
63        return es2panda::ScriptExtension::ABC;
64    } else {
65        return es2panda::ScriptExtension::JS;
66    }
67}
68
69static es2panda::ScriptExtension GetScriptExtension(const std::string &filename, const std::string &inputExtension)
70{
71    std::string fileExtension = "";
72    std::string::size_type pos(filename.find_last_of('.'));
73    if (pos > 0 && pos != std::string::npos) {
74        fileExtension = filename.substr(pos + 1);
75    }
76
77    if (VALID_EXTENSIONS.find(fileExtension) != VALID_EXTENSIONS.end()) {
78        return GetScriptExtensionFromStr(fileExtension);
79    }
80
81    return GetScriptExtensionFromStr(inputExtension);
82}
83
84static std::vector<std::string> GetStringItems(std::string &input, const std::string &delimiter)
85{
86    std::vector<std::string> items;
87    size_t pos = 0;
88    std::string token;
89    while ((pos = input.find(delimiter)) != std::string::npos) {
90        token = input.substr(0, pos);
91        if (!token.empty()) {
92            items.push_back(token);
93        }
94        input.erase(0, pos + delimiter.length());
95    }
96    if (!input.empty()) {
97        items.push_back(input);
98    }
99    return items;
100}
101
102void Options::CollectInputAbcFile(const std::vector<std::string> &itemList, const std::string &inputExtension)
103{
104    std::string fileName = itemList[0];
105    // Split the fileInfo string to itemList by the delimiter ';'. If the element is empty, it will not be added to
106    // the itemList. The fileInfo string of the abc file only contains the file path and pkgName, so the index
107    // of pkgName is 1.
108    constexpr uint32_t PKG_NAME_IDX = 1;
109    es2panda::SourceFile src(fileName, "", parser::ScriptKind::SCRIPT, GetScriptExtension(fileName,
110                             inputExtension));
111    src.isSourceMode = false;
112    if (itemList.size() > PKG_NAME_IDX && compilerOptions_.mergeAbc) {
113        src.pkgName = itemList[PKG_NAME_IDX];
114    }
115    sourceFiles_.push_back(src);
116}
117
118void Options::CollectInputSourceFile(const std::vector<std::string> &itemList, const std::string &inputExtension)
119{
120    std::string fileName = itemList[0];
121    std::string recordName = compilerOptions_.mergeAbc ? itemList[1] : "";
122    constexpr uint32_t SCRIPT_KIND_IDX = 2;
123    constexpr uint32_t SOURCE_FIEL_IDX = 3;
124    constexpr uint32_t PKG_NAME_IDX = 4;
125    constexpr uint32_t Is_SHARED_MODULE_IDX = 5;
126    parser::ScriptKind scriptKind;
127    if (itemList[SCRIPT_KIND_IDX] == "script") {
128        scriptKind = parser::ScriptKind::SCRIPT;
129    } else if (itemList[SCRIPT_KIND_IDX] == "commonjs") {
130        scriptKind = parser::ScriptKind::COMMONJS;
131    } else {
132        scriptKind = parser::ScriptKind::MODULE;
133    }
134
135    es2panda::SourceFile src(fileName, recordName, scriptKind, GetScriptExtension(fileName, inputExtension));
136    src.sourcefile = itemList[SOURCE_FIEL_IDX];
137    if (compilerOptions_.mergeAbc) {
138        src.pkgName = itemList[PKG_NAME_IDX];
139    }
140
141    if (itemList.size() == ITEM_COUNT_MERGE) {
142        src.isSharedModule = itemList[Is_SHARED_MODULE_IDX] == "true";
143    }
144
145    sourceFiles_.push_back(src);
146    if (!compilerOptions_.mergeAbc) {
147        outputFiles_.insert({fileName, itemList[PKG_NAME_IDX]});
148    }
149}
150
151bool Options::CheckFilesValidity(const std::string &input, const std::vector<std::string> &itemList,
152                                 const std::string &line)
153{
154    // For compatibility, only throw error when item list's size is bigger than given size.
155    if ((compilerOptions_.mergeAbc && itemList.size() > ITEM_COUNT_MERGE) ||
156        (!compilerOptions_.mergeAbc && itemList.size() > ITEM_COUNT_NOT_MERGE) || itemList.empty()) {
157        std::cerr << "Failed to parse line " << line << " of the input file: '"
158            << input << "'." << std::endl
159            << "Expected " << (compilerOptions_.mergeAbc ? ITEM_COUNT_MERGE : ITEM_COUNT_NOT_MERGE)
160            << " items per line, but found " << itemList.size() << " items." << std::endl
161            << "Please check the file format and content for correctness." << std::endl;
162        return false;
163    }
164    return true;
165}
166
167bool Options::IsAbcFile(const std::string &fileName, const std::string &inputExtension)
168{
169    return (GetScriptExtension(fileName, inputExtension) == es2panda::ScriptExtension::ABC);
170}
171
172// Options
173bool Options::CollectInputFilesFromFileList(const std::string &input, const std::string &inputExtension)
174{
175    std::ifstream ifs;
176    std::string line;
177    ifs.open(panda::os::file::File::GetExtendedFilePath(input));
178    if (!ifs.is_open()) {
179        std::cerr << "Failed to open source list file '" << input << "' during input file collection." << std::endl
180                  << "Please check if the file exists or the path is correct, "
181                  << "and you have the necessary permissions to access it." << std::endl;
182        return false;
183    }
184
185    while (std::getline(ifs, line)) {
186        std::vector<std::string> itemList = GetStringItems(line, LIST_ITEM_SEPERATOR);
187        if (!CheckFilesValidity(input, itemList, line)) {
188            return false;
189        }
190        if (IsAbcFile(itemList[0], inputExtension)) {
191            CollectInputAbcFile(itemList, inputExtension);
192        } else {
193            CollectInputSourceFile(itemList, inputExtension);
194        }
195    }
196    return true;
197}
198
199bool Options::CollectInputFilesFromFileDirectory(const std::string &input, const std::string &extension)
200{
201    std::vector<std::string> files;
202    if (!proto::MergeProgram::GetProtoFiles(input, extension, files)) {
203        return false;
204    }
205
206    for (const auto &f : files) {
207        es2panda::SourceFile src(f, RemoveExtension(f.substr(input.length() + 1)),
208                                 scriptKind_, GetScriptExtensionFromStr(extension));
209        sourceFiles_.push_back(src);
210    }
211
212    return true;
213}
214
215void Options::ParseCacheFileOption(const std::string &cacheInput)
216{
217    if (cacheInput[0] != PROCESS_AS_LIST_MARK) {
218        compilerOptions_.cacheFiles.insert({sourceFile_, cacheInput});
219        return;
220    }
221
222    std::ifstream ifs;
223    std::string line;
224    ifs.open(panda::os::file::File::GetExtendedFilePath(cacheInput.substr(1)));
225    if (!ifs.is_open()) {
226        std::cerr << "Failed to open cache file list from the provided path: '" << cacheInput << "'." << std::endl
227                  << "Please check if the file exists or the path is correct, "
228                  << "and you have the necessary permissions to read the file." << std::endl;
229        return;
230    }
231
232    constexpr int cacheListItemCount = 2;
233    while (std::getline(ifs, line)) {
234        std::vector<std::string> itemList = GetStringItems(line, LIST_ITEM_SEPERATOR);
235        if (itemList.size() != cacheListItemCount) {
236            continue;
237        }
238        compilerOptions_.cacheFiles.insert({itemList[0], itemList[1]});
239    }
240}
241
242void Options::ParseUpdateVersionInfo(nlohmann::json &compileContextInfoJson)
243{
244    if (compileContextInfoJson.contains("updateVersionInfo") &&
245        compileContextInfoJson["updateVersionInfo"].is_object()) {
246        std::unordered_map<std::string, std::unordered_map<std::string, PkgInfo>> updateVersionInfo {};
247        for (const auto& [abcName, versionInfo] : compileContextInfoJson["updateVersionInfo"].items()) {
248            if (!versionInfo.is_object()) {
249                std::cerr << "The input file '" << compilerOptions_.compileContextInfoPath
250                          << "' is incomplete format of json" << std::endl;
251            }
252            std::unordered_map<std::string, PkgInfo> pkgContextMap {};
253            for (const auto& [pkgName, version] : versionInfo.items()) {
254                PkgInfo pkgInfo;
255                pkgInfo.version = version;
256                pkgInfo.packageName = pkgName;
257                pkgContextMap[pkgName] = pkgInfo;
258            }
259            updateVersionInfo[abcName] = pkgContextMap;
260        }
261        compilerOptions_.compileContextInfo.updateVersionInfo = updateVersionInfo;
262    } else if (compileContextInfoJson.contains("pkgContextInfo") &&
263               compileContextInfoJson["pkgContextInfo"].is_object()) {
264        std::unordered_map<std::string, PkgInfo> pkgContextMap {};
265        for (const auto& [pkgName, pkgContextInfo] : compileContextInfoJson["pkgContextInfo"].items()) {
266            PkgInfo pkgInfo;
267            if (pkgContextInfo.contains("version") && pkgContextInfo["version"].is_string()) {
268                pkgInfo.version = pkgContextInfo["version"];
269            } else {
270                std::cerr << "Failed to get version from pkgContextInfo."  << std::endl;
271            }
272            if (pkgContextInfo.contains("packageName") && pkgContextInfo["packageName"].is_string()) {
273                pkgInfo.packageName = pkgContextInfo["packageName"];
274            } else {
275                std::cerr << "Failed to get package name from pkgContextInfo."  << std::endl;
276            }
277            pkgContextMap[pkgName] = pkgInfo;
278        }
279        compilerOptions_.compileContextInfo.pkgContextInfo = pkgContextMap;
280    } else {
281        UNREACHABLE();
282    }
283}
284
285void Options::ParseCompileContextInfo(const std::string compileContextInfoPath)
286{
287    std::stringstream ss;
288    std::string buffer;
289    if (!util::Helpers::ReadFileToBuffer(compileContextInfoPath, ss)) {
290        return;
291    }
292
293    buffer = ss.str();
294    if (buffer.empty() || !nlohmann::json::accept(buffer)) {
295        std::cerr << "The input file '" << compileContextInfoPath <<"' is incomplete format of json" << std::endl;
296        return;
297    }
298    // Parser compile context info base on the input json file.
299    nlohmann::json compileContextInfoJson = nlohmann::json::parse(buffer);
300    if (!compileContextInfoJson.contains("compileEntries") || !compileContextInfoJson.contains("hspPkgNames")) {
301        std::cerr << "The input json file '" << compileContextInfoPath << "' content format is incorrect" << std::endl;
302        return;
303    }
304    if (!compileContextInfoJson["compileEntries"].is_array() || !compileContextInfoJson["hspPkgNames"].is_array()) {
305        std::cerr << "The input json file '" << compileContextInfoPath << "' content type is incorrect" << std::endl;
306        return;
307    }
308    std::set<std::string> externalPkgNames;
309    for (const auto& elem : compileContextInfoJson["hspPkgNames"]) {
310        if (elem.is_string()) {
311            externalPkgNames.insert(elem.get<std::string>());
312        }
313    }
314    compilerOptions_.compileContextInfo.externalPkgNames = externalPkgNames;
315    compilerOptions_.compileContextInfo.compileEntries = compileContextInfoJson["compileEntries"];
316    ParseUpdateVersionInfo(compileContextInfoJson);
317}
318
319// Collect dependencies based on the compile entries.
320bool Options::NeedCollectDepsRelation()
321{
322    return compilerOptions_.enableAbcInput && !compilerOptions_.compileContextInfo.compileEntries.empty();
323}
324
325// Remove redundant content from the abc file and remove programs generated from redundant source files.
326bool Options::NeedRemoveRedundantRecord()
327{
328    return compilerOptions_.removeRedundantFile && NeedCollectDepsRelation();
329}
330
331Options::Options() : argparser_(new panda::PandArgParser()) {}
332
333Options::~Options()
334{
335    delete argparser_;
336}
337
338bool Options::Parse(int argc, const char **argv)
339{
340    panda::PandArg<bool> opHelp("help", false, "Print this message and exit");
341
342    // parser
343    panda::PandArg<std::string> inputExtension("extension", "js",
344                                               "Parse the input as the given extension (options: js | ts | as | abc)");
345    panda::PandArg<bool> opModule("module", false, "Parse the input as module");
346    panda::PandArg<bool> opCommonjs("commonjs", false, "Parse the input as commonjs");
347    panda::PandArg<bool> opParseOnly("parse-only", false, "Parse the input only");
348    panda::PandArg<bool> opEnableTypeCheck("enable-type-check", false, "Check the type in ts after parse");
349    panda::PandArg<bool> opDumpAst("dump-ast", false, "Dump the parsed AST");
350    panda::PandArg<bool> opDumpTransformedAst("dump-transformed-ast", false, "Dump the parsed AST after transform");
351    panda::PandArg<bool> opCheckTransformedAstStructure("check-transformed-ast-structure", false,
352                                                        "Check the AST structure after transform");
353    panda::PandArg<bool> opRecordDebugSource("record-debug-source", false, "Record source code to support "\
354        "multi-platform debugger & detailed backtrace in debug mode");
355
356    // compiler
357    panda::PandArg<bool> opEnableAbcInput("enable-abc-input", false, "Allow abc file as input");
358    panda::PandArg<bool> opDumpAsmProgram("dump-asm-program", false, "Dump program");
359    std::string descOfDumpNormalizedProg =
360        "Dump program in normalized form to ensure the output of source code compilation is consistent with that of "
361        "abc file compilation.\n"
362        "  The normalized form differs mainly as follows:\n"
363        "  1. all instructions will be labled consecutively and all the labels will be dumped\n"
364        "  2. the content of a literal array, rather than its id, will be dumped when the literal array appears in "
365        "an opcode or is nested in another literal array\n"
366        "  3. labels stored in catch blocks will be unified\n"
367        "  4. strings won't be dumped\n"
368        "  5. invalid opcodes won't be dumped, local variables' start and end offset will skip invalid opcodes";
369    panda::PandArg<bool> opDumpNormalizedAsmProgram("dump-normalized-asm-program", false, descOfDumpNormalizedProg);
370    panda::PandArg<bool> opDumpAssembly("dump-assembly", false, "Dump pandasm");
371    panda::PandArg<bool> opDebugInfo("debug-info", false, "Compile with debug info");
372    panda::PandArg<bool> opDumpDebugInfo("dump-debug-info", false, "Dump debug info");
373    panda::PandArg<int> opOptLevel("opt-level", 2,
374        "Compiler optimization level (options: 0 | 1 | 2). In debug and base64Input mode, optimizer is disabled");
375    panda::PandArg<int> opFunctionThreadCount("function-threads", 0, "Number of worker threads to compile function");
376    panda::PandArg<int> opFileThreadCount("file-threads", 0, "Number of worker threads to compile file");
377    panda::PandArg<int> opAbcClassThreadCount("abc-class-threads", 4,
378        "Number of worker threads to compile classes of abc file");
379    panda::PandArg<bool> opSizeStat("dump-size-stat", false, "Dump size statistics");
380    panda::PandArg<bool> opSizePctStat("dump-file-item-size", false, "Dump the size of each kind of file item "\
381        "of the abc file");
382    panda::PandArg<bool> opDumpLiteralBuffer("dump-literal-buffer", false, "Dump literal buffer");
383    panda::PandArg<std::string> outputFile("output", "", "Compiler binary output (.abc)");
384    panda::PandArg<std::string> recordName("record-name", "", "Specify the record name");
385    panda::PandArg<bool> debuggerEvaluateExpression("debugger-evaluate-expression", false,
386                                                    "evaluate expression in debugger mode");
387    panda::PandArg<std::string> base64Input("base64Input", "", "base64 input of js content");
388    panda::PandArg<bool> base64Output("base64Output", false, "output panda file content as base64 to std out");
389    panda::PandArg<std::string> sourceFile("source-file", "",
390                                           "specify the file path info recorded in generated abc");
391    panda::PandArg<std::string> outputProto("outputProto", "",
392                                            "specify the output name for serializd protobuf file (.protoBin)");
393    panda::PandArg<std::string> opCacheFile("cache-file", "", "cache file for incremental compile");
394    panda::PandArg<std::string> opNpmModuleEntryList("npm-module-entry-list", "", "entry list file for module compile");
395    panda::PandArg<bool> opMergeAbc("merge-abc", false, "Compile as merge abc");
396    panda::PandArg<std::string> opPerfFile("perf-file", "perf.txt", "Specify the file path to dump time consuming data"\
397        " during compilation process, default to 'perf.txt' in the current directory");
398    panda::PandArg<int> opPerfLevel("perf-level", 0, "Specify the performance data output level:"\
399        "  0: Output compilation time data(default)");
400    panda::PandArg<bool> opuseDefineSemantic("use-define-semantic", false, "Compile ts class fields "\
401        "in accordance with ECMAScript2022");
402    panda::PandArg<std::string> moduleRecordFieldName("module-record-field-name", "", "Specify the field name "\
403        "of module record in unmerged abc");
404
405    // optimizer
406    panda::PandArg<bool> opBranchElimination("branch-elimination", false, "Enable branch elimination optimization");
407    panda::PandArg<bool> opOptTryCatchFunc("opt-try-catch-func", true, "Enable optimizations for functions with "\
408        "try-catch blocks");
409
410    // patchfix && hotreload
411    panda::PandArg<std::string> opDumpSymbolTable("dump-symbol-table", "", "dump symbol table to file");
412    panda::PandArg<std::string> opInputSymbolTable("input-symbol-table", "", "input symbol table file");
413    panda::PandArg<bool> opGeneratePatch("generate-patch", false, "generate patch abc, default as hotfix mode unless "\
414        "the cold-fix argument is set");
415    panda::PandArg<bool> opHotReload("hot-reload", false, "compile as hot-reload mode");
416    panda::PandArg<bool> opColdReload("cold-reload", false, "compile as cold-reload mode");
417    panda::PandArg<bool> opColdFix("cold-fix", false, "generate patch abc as cold-fix mode");
418
419    // version
420    panda::PandArg<bool> bcVersion("bc-version", false, "Print ark bytecode version. If both bc-version and"\
421        "bc-min-version are enabled, only bc-version will take effects");
422    panda::PandArg<bool> bcMinVersion("bc-min-version", false, "Print ark bytecode minimum supported version");
423    // todo(huyunhui): change default api verion to 0 after refactoring
424    // The latest api version in isa.yaml is 15
425    panda::PandArg<int> targetApiVersion("target-api-version", 15,
426        "Specify the targeting api version for es2abc to generated the corresponding version of bytecode");
427    panda::PandArg<bool> targetBcVersion("target-bc-version", false, "Print the corresponding ark bytecode version"\
428        "for target api version. If both target-bc-version and bc-version are enabled, only target-bc-version"\
429        "will take effects");
430    panda::PandArg<std::string> targetApiSubVersion("target-api-sub-version",
431        std::string {util::Helpers::DEFAULT_SUB_API_VERSION},
432        "Specify the targeting api sub version for es2abc to generated the corresponding version of bytecode");
433    panda::PandArg<bool> enableAnnotations("enable-annotations", false, "Permits es2abc to compile annotations");
434
435    // compile entries and pkg context info
436    panda::PandArg<std::string> compileContextInfoPath("compile-context-info", "", "The path to compile context"\
437        "info file");
438    panda::PandArg<bool> opDumpDepsInfo("dump-deps-info", false, "Dump all dependency files and records "\
439        "including source files and bytecode files");
440    panda::PandArg<bool> opRemoveRedundantFile("remove-redundant-file", false, "Remove redundant info"\
441        " from abc file and remove redundant source file, which is effective when the compile-context-info switch"\
442        "  is turned on and there is abc input");
443    panda::PandArg<bool> opDumpString("dump-string", false, "Dump program strings");
444
445    // aop transform
446    panda::PandArg<std::string> transformLib("transform-lib", "", "aop transform lib file path");
447
448    // tail arguments
449    panda::PandArg<std::string> inputFile("input", "", "input file");
450
451    argparser_->Add(&opHelp);
452    argparser_->Add(&opModule);
453    argparser_->Add(&opCommonjs);
454    argparser_->Add(&opDumpAst);
455    argparser_->Add(&opDumpTransformedAst);
456    argparser_->Add(&opCheckTransformedAstStructure);
457    argparser_->Add(&opRecordDebugSource);
458    argparser_->Add(&opParseOnly);
459    argparser_->Add(&opEnableTypeCheck);
460    argparser_->Add(&opEnableAbcInput);
461    argparser_->Add(&opDumpAsmProgram);
462    argparser_->Add(&opDumpNormalizedAsmProgram);
463    argparser_->Add(&opDumpAssembly);
464    argparser_->Add(&opDebugInfo);
465    argparser_->Add(&opDumpDebugInfo);
466    argparser_->Add(&debuggerEvaluateExpression);
467    argparser_->Add(&base64Input);
468    argparser_->Add(&base64Output);
469
470    argparser_->Add(&opOptLevel);
471    argparser_->Add(&opFunctionThreadCount);
472    argparser_->Add(&opFileThreadCount);
473    argparser_->Add(&opAbcClassThreadCount);
474    argparser_->Add(&opSizeStat);
475    argparser_->Add(&opSizePctStat);
476    argparser_->Add(&opDumpLiteralBuffer);
477
478    argparser_->Add(&inputExtension);
479    argparser_->Add(&outputFile);
480    argparser_->Add(&sourceFile);
481    argparser_->Add(&recordName);
482    argparser_->Add(&outputProto);
483    argparser_->Add(&opCacheFile);
484    argparser_->Add(&opNpmModuleEntryList);
485    argparser_->Add(&opMergeAbc);
486    argparser_->Add(&opPerfLevel);
487    argparser_->Add(&opPerfFile);
488    argparser_->Add(&opuseDefineSemantic);
489    argparser_->Add(&moduleRecordFieldName);
490    argparser_->Add(&opBranchElimination);
491    argparser_->Add(&opOptTryCatchFunc);
492
493    argparser_->Add(&opDumpSymbolTable);
494    argparser_->Add(&opInputSymbolTable);
495    argparser_->Add(&opGeneratePatch);
496    argparser_->Add(&opHotReload);
497    argparser_->Add(&opColdReload);
498    argparser_->Add(&opColdFix);
499
500    argparser_->Add(&bcVersion);
501    argparser_->Add(&bcMinVersion);
502    argparser_->Add(&targetApiVersion);
503    argparser_->Add(&targetBcVersion);
504    argparser_->Add(&targetApiSubVersion);
505    argparser_->Add(&enableAnnotations);
506
507    argparser_->Add(&compileContextInfoPath);
508    argparser_->Add(&opDumpDepsInfo);
509    argparser_->Add(&opRemoveRedundantFile);
510    argparser_->Add(&opDumpString);
511
512    argparser_->Add(&transformLib);
513
514    argparser_->PushBackTail(&inputFile);
515    argparser_->EnableTail();
516    argparser_->EnableRemainder();
517
518    bool parseStatus = argparser_->Parse(argc, argv);
519
520    compilerOptions_.targetApiVersion = targetApiVersion.GetValue();
521    compilerOptions_.targetApiSubVersion = targetApiSubVersion.GetValue();
522    if (parseStatus && targetBcVersion.GetValue()) {
523        compilerOptions_.targetBcVersion = targetBcVersion.GetValue();
524        return true;
525    }
526
527    if (parseStatus && (bcVersion.GetValue() || bcMinVersion.GetValue())) {
528        compilerOptions_.bcVersion = bcVersion.GetValue();
529        compilerOptions_.bcMinVersion = bcMinVersion.GetValue();
530        return true;
531    }
532
533    if (!parseStatus || opHelp.GetValue() || (inputFile.GetValue().empty() && base64Input.GetValue().empty())) {
534        std::stringstream ss;
535
536        ss << argparser_->GetErrorString() << std::endl;
537        ss << "Usage: "
538           << "es2panda"
539           << " [OPTIONS] [input file] -- [arguments]" << std::endl;
540        ss << std::endl;
541        ss << "optional arguments:" << std::endl;
542        ss << argparser_->GetHelpString() << std::endl;
543
544        errorMsg_ = ss.str();
545        return false;
546    }
547
548    bool inputIsEmpty = inputFile.GetValue().empty();
549    bool base64InputIsEmpty = base64Input.GetValue().empty();
550    bool outputIsEmpty = outputFile.GetValue().empty();
551
552    if (!inputIsEmpty && !base64InputIsEmpty) {
553        errorMsg_ = "--input and --base64Input can not be used simultaneously";
554        return false;
555    }
556
557    if (!outputIsEmpty && base64Output.GetValue()) {
558        errorMsg_ = "--output and --base64Output can not be used simultaneously";
559        return false;
560    }
561
562    if (opModule.GetValue() && opCommonjs.GetValue()) {
563        errorMsg_ = "[--module] and [--commonjs] can not be used simultaneously";
564        return false;
565    }
566
567    if (opModule.GetValue()) {
568        scriptKind_ = es2panda::parser::ScriptKind::MODULE;
569    } else if (opCommonjs.GetValue()) {
570        scriptKind_ = es2panda::parser::ScriptKind::COMMONJS;
571    } else {
572        scriptKind_ = es2panda::parser::ScriptKind::SCRIPT;
573    }
574
575    std::string extension = inputExtension.GetValue();
576    if (!extension.empty()) {
577        if (VALID_EXTENSIONS.find(extension) == VALID_EXTENSIONS.end()) {
578            errorMsg_ = "Invalid extension (available options: js, ts, as, abc)";
579            return false;
580        }
581    }
582
583    bool isInputFileList = false;
584    if (!inputIsEmpty) {
585        std::string rawInput = inputFile.GetValue();
586        isInputFileList = rawInput[0] == PROCESS_AS_LIST_MARK;
587        std::string input = isInputFileList ? rawInput.substr(1) : rawInput;
588        sourceFile_ = input;
589    }
590
591    if (base64Output.GetValue()) {
592        compilerOutput_ = "";
593    } else if (!outputIsEmpty) {
594        compilerOutput_ = outputFile.GetValue();
595    } else if (outputIsEmpty && !inputIsEmpty) {
596        compilerOutput_ = RemoveExtension(util::Helpers::BaseName(sourceFile_)).append(".abc");
597    }
598
599    if (opMergeAbc.GetValue()) {
600        recordName_ = recordName.GetValue();
601        if (recordName_.empty()) {
602            recordName_ = compilerOutput_.empty() ? "Base64Output" :
603                RemoveExtension(util::Helpers::BaseName(compilerOutput_));
604        }
605        compilerOptions_.mergeAbc = opMergeAbc.GetValue();
606    }
607
608    if (opuseDefineSemantic.GetValue()) {
609        compilerOptions_.useDefineSemantic = opuseDefineSemantic.GetValue();
610    }
611
612    if (!inputIsEmpty) {
613        // common mode
614        auto inputAbs = panda::os::file::File::GetAbsolutePath(sourceFile_);
615        if (!inputAbs) {
616            std::cerr << "Failed to find file '" << sourceFile_ << "' during input file resolution" << std::endl
617                      << "Please check if the file name is correct, the file exists at the specified path, "
618                      << "and your project has the necessary permissions to access it." << std::endl;
619            return false;
620        }
621
622        auto fpath = inputAbs.Value();
623        if (isInputFileList) {
624            CollectInputFilesFromFileList(fpath, extension);
625        } else if (panda::os::file::File::IsDirectory(fpath)) {
626            CollectInputFilesFromFileDirectory(fpath, extension);
627        } else {
628            es2panda::SourceFile src(sourceFile_, recordName_, scriptKind_, GetScriptExtension(sourceFile_, extension));
629            src.isSourceMode = !IsAbcFile(sourceFile_, extension);
630            sourceFiles_.push_back(src);
631        }
632    } else if (!base64InputIsEmpty) {
633        // input content is base64 string
634        base64Input_ = ExtractContentFromBase64Input(base64Input.GetValue());
635        if (base64Input_.empty()) {
636            errorMsg_ = "The input string is not a valid base64 data";
637            return false;
638        }
639
640        es2panda::SourceFile src("", recordName_, es2panda::parser::ScriptKind::SCRIPT,
641                                 GetScriptExtensionFromStr(extension));
642        src.source = base64Input_;
643        sourceFiles_.push_back(src);
644    }
645
646    if (!outputProto.GetValue().empty()) {
647        compilerProtoOutput_ = outputProto.GetValue();
648    }
649
650    optLevel_ = opOptLevel.GetValue();
651    functionThreadCount_ = opFunctionThreadCount.GetValue();
652    fileThreadCount_ = opFileThreadCount.GetValue();
653    abcClassThreadCount_ = opAbcClassThreadCount.GetValue();
654    npmModuleEntryList_ = opNpmModuleEntryList.GetValue();
655
656    if (!opCacheFile.GetValue().empty()) {
657        ParseCacheFileOption(opCacheFile.GetValue());
658    }
659
660    if (opParseOnly.GetValue()) {
661        options_ |= OptionFlags::PARSE_ONLY;
662    }
663
664    if (opSizeStat.GetValue()) {
665        options_ |= OptionFlags::SIZE_STAT;
666    }
667
668    if (opSizePctStat.GetValue()) {
669        options_ |= OptionFlags::SIZE_PCT_STAT;
670    }
671
672    perfFile_ = "";
673    perfLevel_ = opPerfLevel.GetValue();
674    if (opPerfFile.WasSet() && (perfLevel_ == 0)) {
675        perfFile_ = opPerfFile.GetValue().empty() ? opPerfFile.GetDefaultValue() : opPerfFile.GetValue();
676    }
677    panda::Timer::InitializeTimer(perfFile_);
678
679    compilerOptions_.recordDebugSource = opRecordDebugSource.GetValue();
680    compilerOptions_.enableAbcInput = opEnableAbcInput.GetValue();
681    compilerOptions_.dumpAsmProgram = opDumpAsmProgram.GetValue();
682    compilerOptions_.dumpNormalizedAsmProgram = opDumpNormalizedAsmProgram.GetValue();
683    compilerOptions_.dumpAsm = opDumpAssembly.GetValue();
684    compilerOptions_.dumpAst = opDumpAst.GetValue();
685    compilerOptions_.dumpTransformedAst = opDumpTransformedAst.GetValue();
686    compilerOptions_.checkTransformedAstStructure = opCheckTransformedAstStructure.GetValue();
687    compilerOptions_.dumpDebugInfo = opDumpDebugInfo.GetValue();
688    compilerOptions_.isDebug = opDebugInfo.GetValue();
689    compilerOptions_.parseOnly = opParseOnly.GetValue();
690    compilerOptions_.enableTypeCheck = opEnableTypeCheck.GetValue();
691    compilerOptions_.dumpLiteralBuffer = opDumpLiteralBuffer.GetValue();
692    compilerOptions_.isDebuggerEvaluateExpressionMode = debuggerEvaluateExpression.GetValue();
693
694    compilerOptions_.functionThreadCount = functionThreadCount_;
695    compilerOptions_.fileThreadCount = fileThreadCount_;
696    compilerOptions_.abcClassThreadCount = abcClassThreadCount_;
697    compilerOptions_.output = compilerOutput_;
698    compilerOptions_.debugInfoSourceFile = sourceFile.GetValue();
699    compilerOptions_.optLevel = (compilerOptions_.isDebug || !base64Input.GetValue().empty() ||
700        base64Output.GetValue()) ? 0 : opOptLevel.GetValue();
701    compilerOptions_.sourceFiles = sourceFiles_;
702    compilerOptions_.mergeAbc = opMergeAbc.GetValue();
703    compilerOptions_.compileContextInfoPath = compileContextInfoPath.GetValue();
704    if (!compileContextInfoPath.GetValue().empty()) {
705        ParseCompileContextInfo(compileContextInfoPath.GetValue());
706    }
707    compilerOptions_.dumpDepsInfo = opDumpDepsInfo.GetValue();
708    compilerOptions_.updatePkgVersionForAbcInput = compilerOptions_.enableAbcInput &&
709        (!compilerOptions_.compileContextInfo.pkgContextInfo.empty() ||
710        !compilerOptions_.compileContextInfo.updateVersionInfo.empty());
711    compilerOptions_.removeRedundantFile = opRemoveRedundantFile.GetValue();
712    compilerOptions_.dumpString = opDumpString.GetValue();
713    compilerOptions_.moduleRecordFieldName = moduleRecordFieldName.GetValue();
714
715    compilerOptions_.patchFixOptions.dumpSymbolTable = opDumpSymbolTable.GetValue();
716    compilerOptions_.patchFixOptions.symbolTable = opInputSymbolTable.GetValue();
717
718    bool generatePatch = opGeneratePatch.GetValue();
719    bool hotReload = opHotReload.GetValue();
720    bool coldReload = opColdReload.GetValue();
721    bool coldFix = opColdFix.GetValue();
722    if (generatePatch && hotReload) {
723        errorMsg_ = "--generate-patch and --hot-reload can not be used simultaneously";
724        return false;
725    }
726    if (coldFix && !generatePatch) {
727        errorMsg_ = "--cold-fix can not be used without --generate-patch";
728        return false;
729    }
730    compilerOptions_.patchFixOptions.generatePatch = generatePatch;
731    compilerOptions_.patchFixOptions.hotReload = hotReload;
732    compilerOptions_.patchFixOptions.coldReload = coldReload;
733    compilerOptions_.patchFixOptions.coldFix = coldFix;
734
735    compilerOptions_.enableAnnotations = enableAnnotations.GetValue();
736
737    bool transformLibIsEmpty = transformLib.GetValue().empty();
738    if (!transformLibIsEmpty) {
739        auto libName = transformLib.GetValue();
740        // check file exist or not
741        auto transformLibAbs = panda::os::file::File::GetAbsolutePath(libName);
742        if (!transformLibAbs) {
743            std::cerr << "Failed to find file '" << libName << "' during transformLib file resolution" << std::endl
744                      << "Please check if the file name is correct, the file exists at the specified path, "
745                      << "and your project has the necessary permissions to access it." << std::endl;
746            return false;
747        }
748        compilerOptions_.transformLib = transformLibAbs.Value();
749    }
750
751    compilerOptions_.branchElimination = opBranchElimination.GetValue();
752    compilerOptions_.requireGlobalOptimization = compilerOptions_.optLevel > 0 &&
753                                                 compilerOptions_.branchElimination &&
754                                                 compilerOptions_.mergeAbc;
755    panda::compiler::options.SetCompilerBranchElimination(compilerOptions_.branchElimination);
756    panda::bytecodeopt::options.SetSkipMethodsWithEh(!opOptTryCatchFunc.GetValue());
757
758    return true;
759}
760
761std::string Options::ExtractContentFromBase64Input(const std::string &inputBase64String)
762{
763    std::string inputContent = util::Base64Decode(inputBase64String);
764    if (inputContent == "") {
765        return "";
766    }
767    bool validBase64Input = util::Base64Encode(inputContent) == inputBase64String;
768    if (!validBase64Input) {
769        return "";
770    }
771    return inputContent;
772}
773}  // namespace panda::es2panda::aot
774