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 <fstream>
17#include <iomanip>
18#include <iostream>
19#include <map>
20#include <sstream>
21#include <string>
22#include <vector>
23
24#include "ark_version.h"
25
26#include "assembly-emitter.h"
27#include "assembly-parser.h"
28#include "file_format_version.h"
29#include "error.h"
30#include "lexer.h"
31#include "utils/expected.h"
32#include "utils/logger.h"
33#include "utils/pandargs.h"
34#include "pandasm.h"
35
36namespace panda::pandasm {
37
38void PrintError(const panda::pandasm::Error &e, const std::string &msg)
39{
40    std::stringstream sos;
41    std::cerr << msg << ": " << e.message << std::endl;
42    sos << "      Line " << e.line_number << ", Column " << e.pos + 1 << ": ";
43    std::cerr << sos.str() << e.whole_line << std::endl;
44    std::cerr << std::setw(static_cast<int>(e.pos + sos.str().size()) + 1) << "^" << std::endl;
45}
46
47void PrintErrors(const panda::pandasm::ErrorList &warnings, const std::string &msg)
48{
49    for (const auto &iter : warnings) {
50        PrintError(iter, msg);
51    }
52}
53
54void PrintHelp(const panda::PandArgParser &pa_parser)
55{
56    std::cerr << "Usage:" << std::endl;
57    std::cerr << "pandasm [OPTIONS] INPUT_FILE OUTPUT_FILE" << std::endl << std::endl;
58    std::cerr << "Supported options:" << std::endl << std::endl;
59    std::cerr << pa_parser.GetHelpString() << std::endl;
60}
61
62bool PrepareArgs(panda::PandArgParser &pa_parser, const panda::PandArg<std::string> &input_file,
63                 const panda::PandArg<std::string> &output_file, const panda::PandArg<std::string> &log_file,
64                 const panda::PandArg<bool> &help, const panda::PandArg<bool> &verbose,
65                 const panda::PandArg<bool> &version, std::ifstream &inputfile, int argc, const char **argv)
66{
67    if (!pa_parser.Parse(argc, argv)) {
68        PrintHelp(pa_parser);
69        return false;
70    }
71
72    if (version.GetValue()) {
73        panda::PrintPandaVersion();
74        panda_file::PrintBytecodeVersion();
75        return false;
76    }
77
78    if (input_file.GetValue().empty() || output_file.GetValue().empty() || help.GetValue()) {
79        PrintHelp(pa_parser);
80        return false;
81    }
82
83    if (verbose.GetValue()) {
84        if (log_file.GetValue().empty()) {
85            panda::Logger::ComponentMask component_mask;
86            component_mask.set(panda::Logger::Component::ASSEMBLER);
87            component_mask.set(panda::Logger::Component::BYTECODE_OPTIMIZER);
88            panda::Logger::InitializeStdLogging(panda::Logger::Level::DEBUG, component_mask);
89        } else {
90            panda::Logger::ComponentMask component_mask;
91            component_mask.set(panda::Logger::Component::ASSEMBLER);
92            component_mask.set(panda::Logger::Component::BYTECODE_OPTIMIZER);
93            panda::Logger::InitializeFileLogging(log_file.GetValue(), panda::Logger::Level::DEBUG, component_mask);
94        }
95    }
96
97    inputfile.open(input_file.GetValue(), std::ifstream::in);
98
99    if (!inputfile) {
100        std::cerr << "The input file does not exist." << std::endl;
101        return false;
102    }
103
104    return true;
105}
106
107bool Tokenize(panda::pandasm::Lexer &lexer, std::vector<std::vector<panda::pandasm::Token>> &tokens,
108              std::ifstream &inputfile)
109{
110    std::string s;
111
112    while (getline(inputfile, s)) {
113        panda::pandasm::Tokens q = lexer.TokenizeString(s);
114
115        auto e = q.second;
116
117        if (e.err != panda::pandasm::Error::ErrorType::ERR_NONE) {
118            e.line_number = tokens.size() + 1;
119            PrintError(e, "ERROR");
120            return false;
121        }
122
123        tokens.push_back(q.first);
124    }
125
126    return true;
127}
128
129bool ParseProgram(panda::pandasm::Parser &parser, std::vector<std::vector<panda::pandasm::Token>> &tokens,
130                  const panda::PandArg<std::string> &input_file,
131                  panda::Expected<panda::pandasm::Program, panda::pandasm::Error> &res)
132{
133    res = parser.Parse(tokens, input_file.GetValue());
134    if (!res) {
135        PrintError(res.Error(), "ERROR");
136        return false;
137    }
138
139    return true;
140}
141
142bool DumpProgramInJson(panda::pandasm::Program &program, const panda::PandArg<std::string> &scopes_file)
143{
144    if (!scopes_file.GetValue().empty()) {
145        std::ofstream dump_file;
146        dump_file.open(scopes_file.GetValue());
147
148        if (!dump_file) {
149            std::cerr << "Cannot write scopes into the given file." << std::endl;
150            return false;
151        }
152        dump_file << program.JsonDump();
153    }
154
155    return true;
156}
157
158bool EmitProgramInBinary(panda::pandasm::Program &program, panda::PandArgParser &pa_parser,
159                         const panda::PandArg<std::string> &output_file, panda::PandArg<bool> &optimize,
160                         panda::PandArg<bool> &size_stat)
161{
162    auto emit_debug_info = !optimize.GetValue();
163    std::map<std::string, size_t> stat;
164    std::map<std::string, size_t> *statp = size_stat.GetValue() ? &stat : nullptr;
165    panda::pandasm::AsmEmitter::PandaFileToPandaAsmMaps maps {};
166    panda::pandasm::AsmEmitter::PandaFileToPandaAsmMaps *mapsp = optimize.GetValue() ? &maps : nullptr;
167
168    if (!panda::pandasm::AsmEmitter::Emit(output_file.GetValue(), program, statp, mapsp, emit_debug_info)) {
169        std::cerr << "Failed to emit binary data: " << panda::pandasm::AsmEmitter::GetLastError() << std::endl;
170        return false;
171    }
172
173    if (size_stat.GetValue()) {
174        size_t total_size = 0;
175        std::cout << "Panda file size statistic:" << std::endl;
176
177        for (auto [name, size] : stat) {
178            std::cout << name << " section: " << size << std::endl;
179            total_size += size;
180        }
181
182        std::cout << "total: " << total_size << std::endl;
183    }
184
185    pa_parser.DisableTail();
186
187    return true;
188}
189
190bool BuildFiles(panda::pandasm::Program &program, panda::PandArgParser &pa_parser,
191                const panda::PandArg<std::string> &output_file, panda::PandArg<bool> &optimize,
192                panda::PandArg<bool> &size_stat, panda::PandArg<std::string> &scopes_file)
193{
194    if (!DumpProgramInJson(program, scopes_file)) {
195        return false;
196    }
197
198    if (!EmitProgramInBinary(program, pa_parser, output_file, optimize, size_stat)) {
199        return false;
200    }
201
202    return true;
203}
204
205}  // namespace panda::pandasm
206
207int main(int argc, const char *argv[])
208{
209    panda::PandArg<bool> verbose("verbose", false, "Enable verbose output (will be printed to standard output)");
210    panda::PandArg<std::string> log_file("log-file", "", "(--log-file FILENAME) Set log file name");
211    panda::PandArg<std::string> scopes_file("dump-scopes", "",
212                                            "(--dump-scopes FILENAME) Enable dump of scopes to file");
213    panda::PandArg<bool> help("help", false, "Print this message and exit");
214    panda::PandArg<bool> size_stat("size-stat", false, "Print panda file size statistic");
215    panda::PandArg<bool> optimize("optimize", false, "Run the bytecode optimization");
216    panda::PandArg<bool> version {"version", false,
217                                  "Ark version, file format version and minimum supported file format version"};
218    // tail arguments
219    panda::PandArg<std::string> input_file("INPUT_FILE", "", "Path to the source assembly code");
220    panda::PandArg<std::string> output_file("OUTPUT_FILE", "", "Path to the generated binary code");
221    panda::PandArgParser pa_parser;
222    pa_parser.Add(&verbose);
223    pa_parser.Add(&help);
224    pa_parser.Add(&log_file);
225    pa_parser.Add(&scopes_file);
226    pa_parser.Add(&size_stat);
227    pa_parser.Add(&optimize);
228    pa_parser.Add(&version);
229    pa_parser.PushBackTail(&input_file);
230    pa_parser.PushBackTail(&output_file);
231    pa_parser.EnableTail();
232
233    std::ifstream inputfile;
234
235    if (!panda::pandasm::PrepareArgs(pa_parser, input_file, output_file, log_file, help, verbose, version, inputfile,
236                                     argc, argv)) {
237        return 1;
238    }
239
240    LOG(DEBUG, ASSEMBLER) << "Lexical analysis:";
241
242    panda::pandasm::Lexer lexer;
243
244    std::vector<std::vector<panda::pandasm::Token>> tokens;
245
246    if (!Tokenize(lexer, tokens, inputfile)) {
247        return 1;
248    }
249
250    LOG(DEBUG, ASSEMBLER) << "parsing:";
251
252    panda::pandasm::Parser parser;
253
254    panda::Expected<panda::pandasm::Program, panda::pandasm::Error> res;
255    if (!panda::pandasm::ParseProgram(parser, tokens, input_file, res)) {
256        return 1;
257    }
258
259    auto &program = res.Value();
260
261    auto w = parser.ShowWarnings();
262    if (!w.empty()) {
263        panda::pandasm::PrintErrors(w, "WARNING");
264    }
265
266    if (!panda::pandasm::BuildFiles(program, pa_parser, output_file, optimize, size_stat, scopes_file)) {
267        return 1;
268    }
269
270    return 0;
271}
272