1/*
2 * Copyright (c) 2023 - 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#include "compiler/lowering/ets/topLevelStmts/globalClassHandler.h"
17#include "compiler/lowering/ets/topLevelStmts/globalDeclTransformer.h"
18#include "compiler/lowering/util.h"
19
20#include "ir/statements/classDeclaration.h"
21#include "ir/base/classDefinition.h"
22#include "ir/base/classProperty.h"
23#include "ir/base/classStaticBlock.h"
24#include "ir/base/scriptFunction.h"
25#include "ir/base/methodDefinition.h"
26#include "ir/expressions/identifier.h"
27#include "ir/expressions/classExpression.h"
28#include "ir/expressions/functionExpression.h"
29#include "ir/expressions/callExpression.h"
30#include "ir/statements/expressionStatement.h"
31#include "ir/statements/blockStatement.h"
32#include "util/helpers.h"
33#include "util/ustring.h"
34
35namespace ark::es2panda::compiler {
36
37using util::NodeAllocator;
38
39static bool FunctionExists(const ArenaVector<ir::Statement *> &statements, const std::string_view name)
40{
41    for (auto stmt : statements) {
42        if (stmt->IsFunctionDeclaration() && stmt->AsFunctionDeclaration()->Function()->Id()->Name().Is(name)) {
43            return true;
44        }
45    }
46    return false;
47}
48
49void GlobalClassHandler::SetupGlobalClass(const ArenaVector<parser::Program *> &programs,
50                                          const ModuleDependencies *moduleDependencies)
51{
52    if (programs.empty()) {
53        return;
54    }
55    ir::ClassDeclaration *const globalDecl = CreateGlobalClass();
56    ir::ClassDefinition *const globalClass = globalDecl->Definition();
57
58    auto addStaticBlock = [this](ir::AstNode *node) {
59        if (node->IsClassDefinition()) {
60            auto classDef = node->AsClassDefinition();
61            if (auto staticBlock = CreateStaticBlock(classDef); staticBlock != nullptr) {
62                classDef->Body().emplace_back(staticBlock);  // NOTE(vpukhov): inserted to end for some reason
63                staticBlock->SetParent(classDef);
64            }
65        }
66    };
67
68    ArenaVector<GlobalStmts> statements(allocator_->Adapter());
69    bool mainExists = false;
70    bool topLevelStatementsExist = false;
71    parser::Program *const globalProgram = programs.front();
72
73    bool isEntrypoint = programs.size() == 1 ? globalProgram->IsEntryPoint() : false;
74    for (auto program : programs) {
75        program->Ast()->IterateRecursively(addStaticBlock);
76        if (program->IsEntryPoint() && !mainExists &&
77            FunctionExists(program->Ast()->Statements(), compiler::Signatures::MAIN)) {
78            mainExists = true;
79        }
80        auto stmts = CollectProgramGlobalStatements(program, globalClass, isEntrypoint);
81        if (!topLevelStatementsExist && !stmts.empty()) {
82            topLevelStatementsExist = true;
83        }
84        statements.emplace_back(GlobalStmts {program, std::move(stmts)});
85        program->SetGlobalClass(globalClass);
86    }
87
88    globalProgram->Ast()->Statements().emplace_back(globalDecl);
89    globalDecl->SetParent(globalProgram->Ast());
90    globalClass->SetGlobalInitialized();
91
92    // NOTE(vpukhov): stdlib checks are to be removed - do not extend the existing logic
93    if (globalProgram->Kind() != parser::ScriptKind::STDLIB) {
94        addStaticBlock(globalClass);
95        if (!util::Helpers::IsStdLib(globalProgram)) {
96            auto initStatements = FormInitMethodStatements(globalProgram, moduleDependencies, std::move(statements));
97            SetupGlobalMethods(globalProgram, std::move(initStatements), mainExists, topLevelStatementsExist);
98        }
99    }
100}
101
102ir::MethodDefinition *GlobalClassHandler::CreateGlobalMethod(const std::string_view name,
103                                                             ArenaVector<ir::Statement *> &&statements)
104{
105    const auto functionFlags = ir::ScriptFunctionFlags::NONE;
106    auto functionModifiers = ir::ModifierFlags::STATIC | ir::ModifierFlags::PUBLIC;
107    auto ident = NodeAllocator::Alloc<ir::Identifier>(allocator_, name, allocator_);
108    auto body = NodeAllocator::ForceSetParent<ir::BlockStatement>(allocator_, allocator_, std::move(statements));
109    auto funcSignature = ir::FunctionSignature(nullptr, ArenaVector<ir::Expression *>(allocator_->Adapter()), nullptr);
110
111    auto *func = NodeAllocator::Alloc<ir::ScriptFunction>(
112        allocator_, allocator_,
113        ir::ScriptFunction::ScriptFunctionData {
114            body, std::move(funcSignature), functionFlags, {}, false, Language(Language::Id::ETS)});
115
116    func->SetIdent(ident);
117    func->AddModifier(functionModifiers);
118
119    auto *funcExpr = NodeAllocator::Alloc<ir::FunctionExpression>(allocator_, func);
120    return NodeAllocator::Alloc<ir::MethodDefinition>(allocator_, ir::MethodDefinitionKind::METHOD,
121                                                      ident->Clone(allocator_, nullptr)->AsExpression(), funcExpr,
122                                                      functionModifiers, allocator_, false);
123}
124
125void GlobalClassHandler::AddInitCallFromStaticBlock(ir::ClassDefinition *globalClass, ir::MethodDefinition *initMethod)
126{
127    ASSERT(initMethod != nullptr);
128
129    auto &globalBody = globalClass->Body();
130    auto maybeStaticBlock = std::find_if(globalBody.begin(), globalBody.end(),
131                                         [](ir::AstNode *node) { return node->IsClassStaticBlock(); });
132    ASSERT(maybeStaticBlock != globalBody.end());
133
134    auto *staticBlock = (*maybeStaticBlock)->AsClassStaticBlock();
135    auto *callee = RefIdent(initMethod->Id()->Name());
136
137    auto *const callExpr = NodeAllocator::Alloc<ir::CallExpression>(
138        allocator_, callee, ArenaVector<ir::Expression *>(allocator_->Adapter()), nullptr, false, false);
139
140    auto *blockBody = staticBlock->Function()->Body()->AsBlockStatement();
141    auto exprStmt = NodeAllocator::Alloc<ir::ExpressionStatement>(allocator_, callExpr);
142    exprStmt->SetParent(blockBody);
143    blockBody->Statements().emplace_back(exprStmt);
144}
145
146ir::Identifier *GlobalClassHandler::RefIdent(const util::StringView &name)
147{
148    auto *const callee = NodeAllocator::Alloc<ir::Identifier>(allocator_, name, allocator_);
149    callee->SetReference();
150    return callee;
151}
152
153util::UString GlobalClassHandler::ReplaceSpecialCharacters(util::UString *word) const
154/*
155 * This function replaces special characters that might occur in a a filename but should not be in a method name.
156 *
157 * `$` is an exception: it is replaced so that it would not crash with the naming in `FormTriggeringCCtorMethodName`.
158 */
159{
160    std::unordered_map<char, std::string> replacements = {
161        {'.', "$DOT$"},
162        {':', "$COLON$"},
163        {';', "$SEMICOLON$"},
164        {',', "$COMMA$"},
165        {'/', "$SLASH$"},
166        {'\\', "$BACKSLASH$"},
167        {'|', "$PIPE$"},
168        {'!', "$EXCL_MARK$"},
169        {'?', "$QUESTION_MARK$"},
170        {'~', "$TILDE$"},
171        {'@', "$AT_SIGN$"},
172        {'&', "$AND_SIGN$"},
173        {'#', "$HASHMARK$"},
174        {'$', "$DOLLAR_SIGN$"},
175        {'^', "$CARET$"},
176        {'*', "$ASTERISK$"},
177        {'=', "$EQUAL_SIGN$"},
178        {'(', "$OPEN_PARENTHESIS$"},
179        {')', "$CLOSE_PARENTHESIS$"},
180        {'{', "$OPEN_CURLY_BRACE$"},
181        {'}', "$CLOSE_CURLY_BRACE$"},
182        {'[', "$OPEN_BRACKET$"},
183        {']', "$CLOSE_BRACKET$"},
184        {'<', "$OPEN_ANGULAR_BRACKET$"},
185        {'>', "$CLOSE_ANGULAR_BRACKET$"},
186        {'\'', "$APOSTROPHE$"},
187        {'"', "$DOUBLE_QUOTATION_MARK$"},
188        {' ', "$SPACE$"},
189    };
190
191    size_t pos = 0;
192
193    auto text = word->View().Mutf8();
194    while (pos < text.size()) {
195        char currentChar = text[pos];
196
197        if (replacements.find(currentChar) != replacements.end()) {
198            const auto replacement = replacements.at(currentChar);
199            text.replace(pos, 1, replacement);
200
201            pos += replacement.size();
202        } else {
203            ++pos;
204        }
205    }
206
207    return util::UString(text, allocator_);
208}
209
210ArenaVector<ir::Statement *> GlobalClassHandler::FormInitMethodStatements(parser::Program *program,
211                                                                          const ModuleDependencies *moduleDependencies,
212                                                                          ArenaVector<GlobalStmts> &&initStatements)
213{
214    ArenaVector<ir::Statement *> statements(allocator_->Adapter());
215    if (!util::Helpers::IsStdLib(program) && moduleDependencies != nullptr) {
216        FormDependentInitTriggers(statements, moduleDependencies);
217    }
218    for (const auto &[p, ps] : initStatements) {
219        statements.insert(statements.end(), ps.begin(), ps.end());
220    }
221    for (auto st : statements) {
222        st->SetParent(nullptr);
223    }
224    return statements;
225}
226
227void GlobalClassHandler::FormDependentInitTriggers(ArenaVector<ir::Statement *> &statements,
228                                                   const ModuleDependencies *moduleDependencies)
229{
230    auto const sequence = [&statements](ir::Statement *stmt) { statements.push_back(stmt); };
231
232    auto triggerInitOf = [this, sequence, initialized = false](parser::Program *prog) mutable {
233        if (!initialized) {
234            initialized = true;
235            sequence(parser_->CreateFormattedStatement("const __linker = Class.ofCaller().getLinker();"));
236        }
237        std::string name = (prog->OmitModuleName() ? "" : std::string(prog->ModuleName()) + ".") + "ETSGLOBAL";
238        sequence(parser_->CreateFormattedStatement("__linker.loadClass(\"" + name + "\", true);"));
239    };
240
241    for (auto depProg : *moduleDependencies) {
242        if (util::Helpers::IsStdLib(depProg)) {
243            continue;
244        }
245        triggerInitOf(depProg);
246    }
247}
248
249ir::ClassStaticBlock *GlobalClassHandler::CreateStaticBlock(ir::ClassDefinition *classDef)
250{
251    bool hasStaticField = false;
252    for (const auto *prop : classDef->Body()) {
253        if (prop->IsClassStaticBlock()) {
254            return nullptr;
255        }
256        if (prop->IsClassProperty() && prop->AsClassProperty()->IsStatic()) {
257            hasStaticField = true;
258        }
259    }
260
261    if (!hasStaticField && !classDef->IsGlobal()) {
262        return nullptr;
263    }
264
265    ArenaVector<ir::Expression *> params(allocator_->Adapter());
266
267    auto *id = NodeAllocator::Alloc<ir::Identifier>(allocator_, compiler::Signatures::CCTOR, allocator_);
268
269    ArenaVector<ir::Statement *> statements(allocator_->Adapter());
270
271    auto *body = NodeAllocator::Alloc<ir::BlockStatement>(allocator_, allocator_, std::move(statements));
272    auto *func = NodeAllocator::Alloc<ir::ScriptFunction>(
273        allocator_, allocator_,
274        ir::ScriptFunction::ScriptFunctionData {body, ir::FunctionSignature(nullptr, std::move(params), nullptr),
275                                                ir::ScriptFunctionFlags::STATIC_BLOCK | ir::ScriptFunctionFlags::HIDDEN,
276                                                ir::ModifierFlags::STATIC, false, Language(Language::Id::ETS)});
277
278    func->SetIdent(id);
279
280    auto *funcExpr = NodeAllocator::Alloc<ir::FunctionExpression>(allocator_, func);
281    auto *staticBlock = NodeAllocator::Alloc<ir::ClassStaticBlock>(allocator_, funcExpr, allocator_);
282    staticBlock->AddModifier(ir::ModifierFlags::STATIC);
283    staticBlock->SetRange({classDef->Start(), classDef->Start()});
284    return staticBlock;
285}
286
287ArenaVector<ir::Statement *> GlobalClassHandler::CollectProgramGlobalStatements(parser::Program *program,
288                                                                                ir::ClassDefinition *classDef,
289                                                                                bool addInitializer)
290{
291    auto ast = program->Ast();
292    auto globalDecl = GlobalDeclTransformer(allocator_);
293    auto statements = globalDecl.TransformStatements(ast->Statements(), addInitializer);
294    classDef->AddProperties(util::Helpers::ConvertVector<ir::AstNode>(statements.classProperties));
295    globalDecl.FilterDeclarations(ast->Statements());
296    return std::move(statements.initStatements);
297}
298
299ir::ClassDeclaration *GlobalClassHandler::CreateGlobalClass()
300{
301    auto *ident = NodeAllocator::Alloc<ir::Identifier>(allocator_, compiler::Signatures::ETS_GLOBAL, allocator_);
302
303    auto *classDef =
304        NodeAllocator::Alloc<ir::ClassDefinition>(allocator_, allocator_, ident, ir::ClassDefinitionModifiers::GLOBAL,
305                                                  ir::ModifierFlags::ABSTRACT, Language(Language::Id::ETS));
306    auto *classDecl = NodeAllocator::Alloc<ir::ClassDeclaration>(allocator_, classDef, allocator_);
307    return classDecl;
308}
309
310void GlobalClassHandler::SetupGlobalMethods(parser::Program *program, ArenaVector<ir::Statement *> &&initStatements,
311                                            bool mainExists, bool topLevelStatementsExist)
312{
313    ir::ClassDefinition *const globalClass = program->GlobalClass();
314
315    auto const insertInGlobal = [globalClass](ir::AstNode *node) {
316        // NOTE(vpukhov): inserted to begin for some reason
317        globalClass->Body().insert(globalClass->Body().begin(), node);
318        node->SetParent(globalClass);
319    };
320
321    if (!program->IsDeclarationModule()) {
322        ir::MethodDefinition *initMethod =
323            CreateGlobalMethod(compiler::Signatures::INIT_METHOD, std::move(initStatements));
324        insertInGlobal(initMethod);
325        if (!initMethod->Function()->Body()->AsBlockStatement()->Statements().empty()) {
326            AddInitCallFromStaticBlock(globalClass, initMethod);
327        }
328    }
329
330    // NOTE(rsipka): unclear call, OmitModuleName() used to determine the entry points without --ets-module option
331    if (program->OmitModuleName()) {
332        if (!mainExists && topLevelStatementsExist) {
333            ir::MethodDefinition *mainMethod =
334                CreateGlobalMethod(compiler::Signatures::MAIN, ArenaVector<ir::Statement *>(allocator_->Adapter()));
335            insertInGlobal(mainMethod);
336        }
337    }
338}
339
340}  // namespace ark::es2panda::compiler
341