From a156ebf48f17eeddf6751b5a7914fe7d75c1a7b2 Mon Sep 17 00:00:00 2001 From: Soma Simon Date: Thu, 26 Oct 2023 15:18:56 +0200 Subject: [PATCH] Move Compile and check logic from ASTNode classes Compile logic is moved to JSCompiler and ETSCompiler classes. Check logic with their helper functions is moved to TSAnalyzer and ETSAnalyzer classes. AssertStatement, BlockStatement, BooleanLiteral, BreakStatement, CharLiteral Linked Internal issue: 13840 Change-Id: I1635d3e59d50c5bea2d687bd25c43607ee4d5c72 Signed-off-by: Soma Simon --- ets2panda/checker/ETSAnalyzer.cpp | 58 +++++++++++----- ets2panda/checker/TSAnalyzer.cpp | 25 +++---- ets2panda/compiler/core/ETSCompiler.cpp | 66 ++++++++++++++++--- ets2panda/compiler/core/JSCompiler.cpp | 29 ++++---- .../expressions/literals/booleanLiteral.cpp | 11 ++-- .../ir/expressions/literals/charLiteral.cpp | 15 +++-- ets2panda/ir/statements/assertStatement.cpp | 51 ++------------ ets2panda/ir/statements/assertStatement.h | 9 ++- ets2panda/ir/statements/blockStatement.cpp | 34 ++-------- ets2panda/ir/statements/blockStatement.h | 7 ++ ets2panda/ir/statements/breakStatement.cpp | 20 ++---- ets2panda/ir/statements/breakStatement.h | 11 ++++ 12 files changed, 179 insertions(+), 157 deletions(-) diff --git a/ets2panda/checker/ETSAnalyzer.cpp b/ets2panda/checker/ETSAnalyzer.cpp index 1b18db86c4..d199ca6805 100644 --- a/ets2panda/checker/ETSAnalyzer.cpp +++ b/ets2panda/checker/ETSAnalyzer.cpp @@ -19,14 +19,6 @@ #include "checker/ETSchecker.h" #include "checker/ets/castingContext.h" #include "checker/ets/typeRelationContext.h" -#include "ir/base/catchClause.h" -#include "ir/base/classProperty.h" -#include "ir/base/classStaticBlock.h" -#include "ir/expressions/identifier.h" -#include "ir/expressions/objectExpression.h" -#include "ir/expressions/arrayExpression.h" -#include "ir/statements/blockStatement.h" -#include "ir/statements/returnStatement.h" #include "util/helpers.h" namespace panda::es2panda::checker { @@ -515,14 +507,20 @@ checker::Type *ETSAnalyzer::Check(ir::BigIntLiteral *expr) const checker::Type *ETSAnalyzer::Check(ir::BooleanLiteral *expr) const { - (void)expr; - UNREACHABLE(); + ETSChecker *checker = GetETSChecker(); + if (expr->TsType() == nullptr) { + expr->SetTsType(checker->CreateETSBooleanType(expr->Value())); + } + return expr->TsType(); } checker::Type *ETSAnalyzer::Check(ir::CharLiteral *expr) const { - (void)expr; - UNREACHABLE(); + ETSChecker *checker = GetETSChecker(); + if (expr->TsType() == nullptr) { + expr->SetTsType(checker->Allocator()->New(expr->Char())); + } + return expr->TsType(); } checker::Type *ETSAnalyzer::Check(ir::NullLiteral *expr) const @@ -599,20 +597,44 @@ checker::Type *ETSAnalyzer::Check(ir::ImportSpecifier *st) const // compile methods for STATEMENTS in alphabetical order checker::Type *ETSAnalyzer::Check(ir::AssertStatement *st) const { - (void)st; - UNREACHABLE(); + ETSChecker *checker = GetETSChecker(); + checker->CheckTruthinessOfType(st->test_); + + if (st->Second() != nullptr) { + auto *msg_type = st->second_->Check(checker); + + if (!msg_type->IsETSStringType()) { + checker->ThrowTypeError("Assert message must be string", st->Second()->Start()); + } + } + + return nullptr; } checker::Type *ETSAnalyzer::Check(ir::BlockStatement *st) const { - (void)st; - UNREACHABLE(); + ETSChecker *checker = GetETSChecker(); + checker::ScopeContext scope_ctx(checker, st->Scope()); + + for (auto *it : st->Statements()) { + it->Check(checker); + } + + for (auto [stmt, trailing_block] : st->trailing_blocks_) { + auto iterator = std::find(st->Statements().begin(), st->Statements().end(), stmt); + ASSERT(iterator != st->Statements().end()); + st->Statements().insert(iterator + 1, trailing_block); + trailing_block->Check(checker); + } + + return nullptr; } checker::Type *ETSAnalyzer::Check(ir::BreakStatement *st) const { - (void)st; - UNREACHABLE(); + ETSChecker *checker = GetETSChecker(); + st->target_ = checker->FindJumpTarget(st->Type(), st, st->Ident()); + return nullptr; } checker::Type *ETSAnalyzer::Check(ir::ClassDeclaration *st) const diff --git a/ets2panda/checker/TSAnalyzer.cpp b/ets2panda/checker/TSAnalyzer.cpp index 6b42c2deb6..0d09ae19aa 100644 --- a/ets2panda/checker/TSAnalyzer.cpp +++ b/ets2panda/checker/TSAnalyzer.cpp @@ -413,13 +413,12 @@ checker::Type *TSAnalyzer::Check(ir::BigIntLiteral *expr) const checker::Type *TSAnalyzer::Check(ir::BooleanLiteral *expr) const { - (void)expr; - UNREACHABLE(); + TSChecker *checker = GetTSChecker(); + return expr->Value() ? checker->GlobalTrueType() : checker->GlobalFalseType(); } -checker::Type *TSAnalyzer::Check(ir::CharLiteral *expr) const +checker::Type *TSAnalyzer::Check([[maybe_unused]] ir::CharLiteral *expr) const { - (void)expr; UNREACHABLE(); } @@ -495,22 +494,26 @@ checker::Type *TSAnalyzer::Check(ir::ImportSpecifier *st) const UNREACHABLE(); } // compile methods for STATEMENTS in alphabetical order -checker::Type *TSAnalyzer::Check(ir::AssertStatement *st) const +checker::Type *TSAnalyzer::Check([[maybe_unused]] ir::AssertStatement *st) const { - (void)st; UNREACHABLE(); } checker::Type *TSAnalyzer::Check(ir::BlockStatement *st) const { - (void)st; - UNREACHABLE(); + TSChecker *checker = GetTSChecker(); + checker::ScopeContext scope_ctx(checker, st->Scope()); + + for (auto *it : st->Statements()) { + it->Check(checker); + } + + return nullptr; } -checker::Type *TSAnalyzer::Check(ir::BreakStatement *st) const +checker::Type *TSAnalyzer::Check([[maybe_unused]] ir::BreakStatement *st) const { - (void)st; - UNREACHABLE(); + return nullptr; } checker::Type *TSAnalyzer::Check(ir::ClassDeclaration *st) const diff --git a/ets2panda/compiler/core/ETSCompiler.cpp b/ets2panda/compiler/core/ETSCompiler.cpp index 4770e12ab3..e5e78ae10c 100644 --- a/ets2panda/compiler/core/ETSCompiler.cpp +++ b/ets2panda/compiler/core/ETSCompiler.cpp @@ -413,14 +413,14 @@ void ETSCompiler::Compile(const ir::BigIntLiteral *expr) const void ETSCompiler::Compile(const ir::BooleanLiteral *expr) const { - (void)expr; - UNREACHABLE(); + ETSGen *etsg = GetETSGen(); + etsg->LoadAccumulatorBoolean(expr, expr->Value()); } void ETSCompiler::Compile(const ir::CharLiteral *expr) const { - (void)expr; - UNREACHABLE(); + ETSGen *etsg = GetETSGen(); + etsg->LoadAccumulatorChar(expr, expr->Char()); } void ETSCompiler::Compile(const ir::NullLiteral *expr) const @@ -494,23 +494,69 @@ void ETSCompiler::Compile(const ir::ImportSpecifier *st) const (void)st; UNREACHABLE(); } + +static void ThrowError(compiler::ETSGen *const etsg, const ir::AssertStatement *st) +{ + const compiler::RegScope rs(etsg); + + if (st->Second() != nullptr) { + st->Second()->Compile(etsg); + } else { + etsg->LoadAccumulatorString(st, "Assertion failed."); + } + + const auto message = etsg->AllocReg(); + etsg->StoreAccumulator(st, message); + + const auto assertion_error = etsg->AllocReg(); + etsg->NewObject(st, assertion_error, compiler::Signatures::BUILTIN_ASSERTION_ERROR); + etsg->CallThisStatic1(st, assertion_error, compiler::Signatures::BUILTIN_ASSERTION_ERROR_CTOR, message); + etsg->EmitThrow(st, assertion_error); +} // compile methods for STATEMENTS in alphabetical order void ETSCompiler::Compile(const ir::AssertStatement *st) const { - (void)st; - UNREACHABLE(); + ETSGen *etsg = GetETSGen(); + auto res = compiler::Condition::CheckConstantExpr(etsg, st->Test()); + if (res == compiler::Condition::Result::CONST_TRUE) { + return; + } + + if (res == compiler::Condition::Result::CONST_FALSE) { + ThrowError(etsg, st); + return; + } + + compiler::Label *end_label = etsg->AllocLabel(); + + st->Test()->Compile(etsg); + etsg->BranchIfTrue(st, end_label); + ThrowError(etsg, st); + etsg->SetLabel(st, end_label); } void ETSCompiler::Compile(const ir::BlockStatement *st) const { - (void)st; - UNREACHABLE(); + ETSGen *etsg = GetETSGen(); + compiler::LocalRegScope lrs(etsg, st->Scope()); + + etsg->CompileStatements(st->Statements()); +} + +template +static void CompileImpl(const ir::BreakStatement *self, [[maybe_unused]] CodeGen *cg) +{ + compiler::Label *target = cg->ControlFlowChangeBreak(self->Ident()); + cg->Branch(self, target); } void ETSCompiler::Compile(const ir::BreakStatement *st) const { - (void)st; - UNREACHABLE(); + ETSGen *etsg = GetETSGen(); + if (etsg->ExtendWithFinalizer(st->parent_, st)) { + return; + } + CompileImpl(st, etsg); } void ETSCompiler::Compile(const ir::ClassDeclaration *st) const diff --git a/ets2panda/compiler/core/JSCompiler.cpp b/ets2panda/compiler/core/JSCompiler.cpp index 23fc28f983..0272ccfb82 100644 --- a/ets2panda/compiler/core/JSCompiler.cpp +++ b/ets2panda/compiler/core/JSCompiler.cpp @@ -20,7 +20,6 @@ #include "compiler/core/pandagen.h" #include "compiler/function/functionBuilder.h" #include "util/helpers.h" - namespace panda::es2panda::compiler { PandaGen *JSCompiler::GetPandaGen() const @@ -716,13 +715,12 @@ void JSCompiler::Compile(const ir::BigIntLiteral *expr) const void JSCompiler::Compile(const ir::BooleanLiteral *expr) const { - (void)expr; - UNREACHABLE(); + PandaGen *pg = GetPandaGen(); + pg->LoadConst(expr, expr->Value() ? compiler::Constant::JS_TRUE : compiler::Constant::JS_FALSE); } -void JSCompiler::Compile(const ir::CharLiteral *expr) const +void JSCompiler::Compile([[maybe_unused]] const ir::CharLiteral *expr) const { - (void)expr; UNREACHABLE(); } @@ -798,22 +796,31 @@ void JSCompiler::Compile(const ir::ImportSpecifier *st) const UNREACHABLE(); } // Compile methods for STATEMENTS in alphabetical order -void JSCompiler::Compile(const ir::AssertStatement *st) const +void JSCompiler::Compile([[maybe_unused]] const ir::AssertStatement *st) const { - (void)st; UNREACHABLE(); } void JSCompiler::Compile(const ir::BlockStatement *st) const { - (void)st; - UNREACHABLE(); + PandaGen *pg = GetPandaGen(); + compiler::LocalRegScope lrs(pg, st->Scope()); + + for (const auto *it : st->Statements()) { + it->Compile(pg); + } } +template +static void CompileImpl(const ir::BreakStatement *self, [[maybe_unused]] CodeGen *cg) +{ + compiler::Label *target = cg->ControlFlowChangeBreak(self->Ident()); + cg->Branch(self, target); +} void JSCompiler::Compile(const ir::BreakStatement *st) const { - (void)st; - UNREACHABLE(); + PandaGen *pg = GetPandaGen(); + CompileImpl(st, pg); } void JSCompiler::Compile(const ir::ClassDeclaration *st) const diff --git a/ets2panda/ir/expressions/literals/booleanLiteral.cpp b/ets2panda/ir/expressions/literals/booleanLiteral.cpp index 81fc1cb147..6e66795cdf 100644 --- a/ets2panda/ir/expressions/literals/booleanLiteral.cpp +++ b/ets2panda/ir/expressions/literals/booleanLiteral.cpp @@ -32,25 +32,22 @@ void BooleanLiteral::Dump(ir::AstDumper *dumper) const void BooleanLiteral::Compile(compiler::PandaGen *pg) const { - pg->LoadConst(this, boolean_ ? compiler::Constant::JS_TRUE : compiler::Constant::JS_FALSE); + pg->GetAstCompiler()->Compile(this); } void BooleanLiteral::Compile(compiler::ETSGen *etsg) const { - etsg->LoadAccumulatorBoolean(this, boolean_); + etsg->GetAstCompiler()->Compile(this); } checker::Type *BooleanLiteral::Check(checker::TSChecker *checker) { - return boolean_ ? checker->GlobalTrueType() : checker->GlobalFalseType(); + return checker->GetAnalyzer()->Check(this); } checker::Type *BooleanLiteral::Check([[maybe_unused]] checker::ETSChecker *checker) { - if (TsType() == nullptr) { - SetTsType(checker->CreateETSBooleanType(boolean_)); - } - return TsType(); + return checker->GetAnalyzer()->Check(this); } // NOLINTNEXTLINE(google-default-arguments) diff --git a/ets2panda/ir/expressions/literals/charLiteral.cpp b/ets2panda/ir/expressions/literals/charLiteral.cpp index 6596de2577..c3ce97217d 100644 --- a/ets2panda/ir/expressions/literals/charLiteral.cpp +++ b/ets2panda/ir/expressions/literals/charLiteral.cpp @@ -15,6 +15,7 @@ #include "charLiteral.h" +#include "checker/TSchecker.h" #include "compiler/core/pandagen.h" #include "compiler/core/ETSGen.h" #include "checker/ETSchecker.h" @@ -31,24 +32,24 @@ void CharLiteral::Dump(ir::AstDumper *dumper) const dumper->Add({{"type", "CharLiteral"}, {"value", char_}}); } -void CharLiteral::Compile([[maybe_unused]] compiler::PandaGen *pg) const {} +void CharLiteral::Compile([[maybe_unused]] compiler::PandaGen *pg) const +{ + pg->GetAstCompiler()->Compile(this); +} void CharLiteral::Compile([[maybe_unused]] compiler::ETSGen *etsg) const { - etsg->LoadAccumulatorChar(this, char_); + etsg->GetAstCompiler()->Compile(this); } checker::Type *CharLiteral::Check([[maybe_unused]] checker::TSChecker *checker) { - return nullptr; + return checker->GetAnalyzer()->Check(this); } checker::Type *CharLiteral::Check([[maybe_unused]] checker::ETSChecker *checker) { - if (TsType() == nullptr) { - SetTsType(checker->Allocator()->New(char_)); - } - return TsType(); + return checker->GetAnalyzer()->Check(this); } // NOLINTNEXTLINE(google-default-arguments) diff --git a/ets2panda/ir/statements/assertStatement.cpp b/ets2panda/ir/statements/assertStatement.cpp index ded0f27717..a79d809f9d 100644 --- a/ets2panda/ir/statements/assertStatement.cpp +++ b/ets2panda/ir/statements/assertStatement.cpp @@ -48,64 +48,23 @@ void AssertStatement::Dump(ir::AstDumper *dumper) const dumper->Add({{"type", "AssertStatement"}, {"test", test_}, {"second", AstDumper::Nullable(second_)}}); } -void AssertStatement::Compile([[maybe_unused]] compiler::PandaGen *pg) const {} - -void AssertStatement::ThrowError(compiler::ETSGen *const etsg) const +void AssertStatement::Compile([[maybe_unused]] compiler::PandaGen *pg) const { - const compiler::RegScope rs(etsg); - - if (second_ != nullptr) { - second_->Compile(etsg); - } else { - etsg->LoadAccumulatorString(this, "Assertion failed."); - } - - const auto message = etsg->AllocReg(); - etsg->StoreAccumulator(this, message); - - const auto assertion_error = etsg->AllocReg(); - etsg->NewObject(this, assertion_error, compiler::Signatures::BUILTIN_ASSERTION_ERROR); - etsg->CallThisStatic1(this, assertion_error, compiler::Signatures::BUILTIN_ASSERTION_ERROR_CTOR, message); - etsg->EmitThrow(this, assertion_error); + pg->GetAstCompiler()->Compile(this); } void AssertStatement::Compile([[maybe_unused]] compiler::ETSGen *etsg) const { - auto res = compiler::Condition::CheckConstantExpr(etsg, test_); - if (res == compiler::Condition::Result::CONST_TRUE) { - return; - } - - if (res == compiler::Condition::Result::CONST_FALSE) { - ThrowError(etsg); - return; - } - - compiler::Label *end_label = etsg->AllocLabel(); - - test_->Compile(etsg); - etsg->BranchIfTrue(this, end_label); - ThrowError(etsg); - etsg->SetLabel(this, end_label); + etsg->GetAstCompiler()->Compile(this); } checker::Type *AssertStatement::Check([[maybe_unused]] checker::TSChecker *checker) { - return nullptr; + return checker->GetAnalyzer()->Check(this); } checker::Type *AssertStatement::Check([[maybe_unused]] checker::ETSChecker *checker) { - checker->CheckTruthinessOfType(test_); - - if (second_ != nullptr) { - auto *msg_type = second_->Check(checker); - - if (!msg_type->IsETSStringType()) { - checker->ThrowTypeError("Assert message must be string", second_->Start()); - } - } - - return nullptr; + return checker->GetAnalyzer()->Check(this); } } // namespace panda::es2panda::ir diff --git a/ets2panda/ir/statements/assertStatement.h b/ets2panda/ir/statements/assertStatement.h index 749c5c739b..1a20e32a62 100644 --- a/ets2panda/ir/statements/assertStatement.h +++ b/ets2panda/ir/statements/assertStatement.h @@ -18,6 +18,10 @@ #include "ir/statement.h" +namespace panda::es2panda::checker { +class ETSAnalyzer; +} // namespace panda::es2panda::checker + namespace panda::es2panda::ir { class Expression; @@ -27,6 +31,8 @@ public: : Statement(AstNodeType::ASSERT_STATEMENT), test_(test), second_(second) { } + // TODO (somas): this friend relationship can be removed once there are getters for private fields + friend class checker::ETSAnalyzer; const Expression *Test() const { @@ -46,9 +52,6 @@ public: checker::Type *Check([[maybe_unused]] checker::TSChecker *checker) override; checker::Type *Check([[maybe_unused]] checker::ETSChecker *checker) override; -protected: - void ThrowError(compiler::ETSGen *etsg) const; - private: Expression *test_; Expression *second_; diff --git a/ets2panda/ir/statements/blockStatement.cpp b/ets2panda/ir/statements/blockStatement.cpp index 948c0fc83d..4bb8908d32 100644 --- a/ets2panda/ir/statements/blockStatement.cpp +++ b/ets2panda/ir/statements/blockStatement.cpp @@ -16,6 +16,7 @@ #include "blockStatement.h" #include "varbinder/scope.h" +#include "compiler/core/pandagen.h" #include "compiler/core/regScope.h" #include "compiler/core/ETSGen.h" #include "checker/TSchecker.h" @@ -44,46 +45,21 @@ void BlockStatement::Dump(ir::AstDumper *dumper) const void BlockStatement::Compile([[maybe_unused]] compiler::PandaGen *pg) const { - compiler::LocalRegScope lrs(pg, scope_); - - for (const auto *it : statements_) { - it->Compile(pg); - } + pg->GetAstCompiler()->Compile(this); } void BlockStatement::Compile([[maybe_unused]] compiler::ETSGen *etsg) const { - compiler::LocalRegScope lrs(etsg, scope_); - - etsg->CompileStatements(statements_); + etsg->GetAstCompiler()->Compile(this); } checker::Type *BlockStatement::Check([[maybe_unused]] checker::TSChecker *checker) { - checker::ScopeContext scope_ctx(checker, scope_); - - for (auto *it : statements_) { - it->Check(checker); - } - - return nullptr; + return checker->GetAnalyzer()->Check(this); } checker::Type *BlockStatement::Check([[maybe_unused]] checker::ETSChecker *checker) { - checker::ScopeContext scope_ctx(checker, scope_); - - for (auto *it : statements_) { - it->Check(checker); - } - - for (auto [stmt, trailing_block] : trailing_blocks_) { - auto iterator = std::find(statements_.begin(), statements_.end(), stmt); - ASSERT(iterator != statements_.end()); - statements_.insert(iterator + 1, trailing_block); - trailing_block->Check(checker); - } - - return nullptr; + return checker->GetAnalyzer()->Check(this); } } // namespace panda::es2panda::ir diff --git a/ets2panda/ir/statements/blockStatement.h b/ets2panda/ir/statements/blockStatement.h index 2716162ef2..86a00ffc80 100644 --- a/ets2panda/ir/statements/blockStatement.h +++ b/ets2panda/ir/statements/blockStatement.h @@ -18,6 +18,10 @@ #include "ir/statement.h" +namespace panda::es2panda::checker { +class ETSAnalyzer; +} // namespace panda::es2panda::checker + namespace panda::es2panda::ir { class BlockStatement : public Statement { public: @@ -30,6 +34,9 @@ public: { } + // TODO (somas): this friend relationship can be removed once there are getters for private fields + friend class checker::ETSAnalyzer; + bool IsScopeBearer() const override { return true; diff --git a/ets2panda/ir/statements/breakStatement.cpp b/ets2panda/ir/statements/breakStatement.cpp index 43ab14c951..143129257f 100644 --- a/ets2panda/ir/statements/breakStatement.cpp +++ b/ets2panda/ir/statements/breakStatement.cpp @@ -15,6 +15,7 @@ #include "breakStatement.h" +#include "checker/TSchecker.h" #include "compiler/core/pandagen.h" #include "compiler/core/ETSGen.h" #include "ir/astDump.h" @@ -41,34 +42,23 @@ void BreakStatement::Dump(ir::AstDumper *dumper) const dumper->Add({{"type", "BreakStatement"}, {"label", AstDumper::Nullable(ident_)}}); } -template -void CompileImpl(const BreakStatement *self, [[maybe_unused]] CodeGen *cg) -{ - compiler::Label *target = cg->ControlFlowChangeBreak(self->Ident()); - cg->Branch(self, target); -} - void BreakStatement::Compile([[maybe_unused]] compiler::PandaGen *pg) const { - CompileImpl(this, pg); + pg->GetAstCompiler()->Compile(this); } void BreakStatement::Compile([[maybe_unused]] compiler::ETSGen *etsg) const { - if (etsg->ExtendWithFinalizer(parent_, this)) { - return; - } - CompileImpl(this, etsg); + etsg->GetAstCompiler()->Compile(this); } checker::Type *BreakStatement::Check([[maybe_unused]] checker::TSChecker *checker) { - return nullptr; + return checker->GetAnalyzer()->Check(this); } checker::Type *BreakStatement::Check(checker::ETSChecker *checker) { - target_ = checker->FindJumpTarget(Type(), this, ident_); - return nullptr; + return checker->GetAnalyzer()->Check(this); } } // namespace panda::es2panda::ir diff --git a/ets2panda/ir/statements/breakStatement.h b/ets2panda/ir/statements/breakStatement.h index e927396096..e51f71040d 100644 --- a/ets2panda/ir/statements/breakStatement.h +++ b/ets2panda/ir/statements/breakStatement.h @@ -18,6 +18,14 @@ #include "ir/statement.h" +namespace panda::es2panda::checker { +class ETSAnalyzer; +} // namespace panda::es2panda::checker + +namespace panda::es2panda::compiler { +class ETSCompiler; +} // namespace panda::es2panda::compiler + namespace panda::es2panda::ir { class Identifier; @@ -26,6 +34,9 @@ public: explicit BreakStatement() : Statement(AstNodeType::BREAK_STATEMENT) {} explicit BreakStatement(Identifier *ident) : Statement(AstNodeType::BREAK_STATEMENT), ident_(ident) {} + friend checker::ETSAnalyzer; + friend compiler::ETSCompiler; + const Identifier *Ident() const { return ident_; -- Gitee