diff --git a/es2panda/BUILD.gn b/es2panda/BUILD.gn index 0762c34ffb598b81a6f9cc38ba758d1f8830abbb..71169e2b0dbe3b17683943d58c53dab6eb9adda4 100644 --- a/es2panda/BUILD.gn +++ b/es2panda/BUILD.gn @@ -33,6 +33,7 @@ es2panda_src = [ "compiler/core/compilerContext.cpp", "compiler/core/compilerImpl.cpp", "compiler/core/dynamicContext.cpp", + "compiler/core/emitter/commonjs.cpp", "compiler/core/emitter/emitter.cpp", "compiler/core/emitter/moduleRecordEmitter.cpp", "compiler/core/envScope.cpp", @@ -184,6 +185,7 @@ es2panda_src = [ "lexer/regexp/regexp.cpp", "lexer/token/sourceLocation.cpp", "lexer/token/token.cpp", + "parser/commonjs.cpp", "parser/context/parserContext.cpp", "parser/expressionParser.cpp", "parser/module/sourceTextModuleRecord.cpp", diff --git a/es2panda/aot/main.cpp b/es2panda/aot/main.cpp index 8a18c0a436316172f1c09999ae47eb70901adf8b..3660a2af0be8f1344a901eddb611c63ed1b238d9 100644 --- a/es2panda/aot/main.cpp +++ b/es2panda/aot/main.cpp @@ -148,7 +148,7 @@ int Run(int argc, const char **argv) } es2panda::Compiler compiler(options->Extension(), options->ThreadCount()); - es2panda::SourceFile input(options->SourceFile(), options->ParserInput(), options->ParseModule()); + es2panda::SourceFile input(options->SourceFile(), options->ParserInput(), options->ScriptKind()); auto *program = compiler.Compile(input, options->CompilerOptions()); diff --git a/es2panda/aot/options.cpp b/es2panda/aot/options.cpp index 19ba2b689cf8060272fbd57ca18525fae67e04be..dcbb292ec680c53ba5924a0c933a614916600874 100644 --- a/es2panda/aot/options.cpp +++ b/es2panda/aot/options.cpp @@ -52,6 +52,7 @@ bool Options::Parse(int argc, const char **argv) panda::PandArg inputExtension("extension", "js", "Parse the input as the given extension (options: js | ts | as)"); panda::PandArg opModule("module", false, "Parse the input as module"); + panda::PandArg opCommonjs("commonjs", false, "Parse the input as commonjs"); panda::PandArg opParseOnly("parse-only", false, "Parse the input only"); panda::PandArg opDumpAst("dump-ast", false, "Dump the parsed AST"); @@ -72,6 +73,7 @@ bool Options::Parse(int argc, const char **argv) argparser_->Add(&opHelp); argparser_->Add(&opModule); + argparser_->Add(&opCommonjs); argparser_->Add(&opDumpAst); argparser_->Add(&opParseOnly); argparser_->Add(&opDumpAssembly); @@ -167,8 +169,17 @@ bool Options::Parse(int argc, const char **argv) options_ |= OptionFlags::PARSE_ONLY; } + if (opModule.GetValue() && opCommonjs.GetValue()) { + errorMsg_ = "[--module] and [--commonjs] can not be used simultaneously"; + return false; + } + if (opModule.GetValue()) { - options_ |= OptionFlags::PARSE_MODULE; + scriptKind_ = es2panda::parser::ScriptKind::MODULE; + } + + if (opCommonjs.GetValue()) { + scriptKind_ = es2panda::parser::ScriptKind::COMMONJS; } if (opSizeStat.GetValue()) { diff --git a/es2panda/aot/options.h b/es2panda/aot/options.h index 1dfecfe39bf0a9c41d860e8ae318af81d732bfd2..b8e8e82c06e36a7d65ffbfda8ac4626266e79b7c 100644 --- a/es2panda/aot/options.h +++ b/es2panda/aot/options.h @@ -18,6 +18,7 @@ #include #include +#include #include #include @@ -34,9 +35,8 @@ namespace panda::es2panda::aot { enum class OptionFlags { DEFAULT = 0, PARSE_ONLY = 1 << 1, - PARSE_MODULE = 1 << 2, - SIZE_STAT = 1 << 3, - DEBUGGER_EVALUATE_EXPRESSION = 1 << 4, + SIZE_STAT = 1 << 2, + DEBUGGER_EVALUATE_EXPRESSION = 1 << 3, }; inline std::underlying_type_t operator&(OptionFlags a, OptionFlags b) @@ -72,6 +72,11 @@ public: return compilerOptions_; } + es2panda::parser::ScriptKind ScriptKind() const + { + return scriptKind_; + } + const std::string &ParserInput() const { return parserInput_; @@ -102,11 +107,6 @@ public: return threadCount_; } - bool ParseModule() const - { - return (options_ & OptionFlags::PARSE_MODULE) != 0; - } - bool ParseOnly() const { return (options_ & OptionFlags::PARSE_ONLY) != 0; @@ -127,6 +127,7 @@ public: private: es2panda::ScriptExtension extension_ {es2panda::ScriptExtension::JS}; es2panda::CompilerOptions compilerOptions_ {}; + es2panda::parser::ScriptKind scriptKind_ {es2panda::parser::ScriptKind::SCRIPT}; OptionFlags options_ {OptionFlags::DEFAULT}; panda::PandArgParser *argparser_; std::string parserInput_; diff --git a/es2panda/binder/binder.cpp b/es2panda/binder/binder.cpp index 905a417b917804d5cbf6ea57e6d4e7f0656b5aab..3398132ee70311d98826e07a196fc02430acdb06 100644 --- a/es2panda/binder/binder.cpp +++ b/es2panda/binder/binder.cpp @@ -500,13 +500,6 @@ void Binder::AddMandatoryParam(const std::string_view &name) scope_->AsFunctionVariableScope()->Bindings().insert({decl->Name(), param}); } -void Binder::AddMandatoryParams(const MandatoryParams ¶ms) -{ - for (auto iter = params.rbegin(); iter != params.rend(); iter++) { - AddMandatoryParam(*iter); - } -} - void Binder::AddMandatoryParams() { ASSERT(scope_ == topScope_); @@ -515,7 +508,12 @@ void Binder::AddMandatoryParams() [[maybe_unused]] auto *funcScope = *iter++; ASSERT(funcScope->IsGlobalScope() || funcScope->IsModuleScope()); - AddMandatoryParams(FUNCTION_MANDATORY_PARAMS); + + if (program_->Kind() == parser::ScriptKind::COMMONJS) { + AddMandatoryParams(CJS_MAINFUNC_MANDATORY_PARAMS); + } else { + AddMandatoryParams(FUNCTION_MANDATORY_PARAMS); + } for (; iter != functionScopes_.end(); iter++) { funcScope = *iter; diff --git a/es2panda/binder/binder.h b/es2panda/binder/binder.h index 7205fe7d1837a670bb231bf7312c523519573bdf..97a7a0c735eb473289f59bcdc219ac02e57a32fd 100644 --- a/es2panda/binder/binder.h +++ b/es2panda/binder/binder.h @@ -107,8 +107,15 @@ public: static constexpr std::string_view MANDATORY_PARAM_NEW_TARGET = "=nt"; static constexpr std::string_view MANDATORY_PARAM_THIS = "=t"; + static constexpr std::string_view CJS_MANDATORY_PARAM_EXPORTS = "exports"; + static constexpr std::string_view CJS_MANDATORY_PARAM_REQUIRE = "require"; + static constexpr std::string_view CJS_MANDATORY_PARAM_MODULE = "module"; + static constexpr std::string_view CJS_MANDATORY_PARAM_FILENAME = "__filename"; + static constexpr std::string_view CJS_MANDATORY_PARAM_DIRNAME = "__dirname"; + static constexpr uint32_t MANDATORY_PARAM_FUNC_REG = 0; static constexpr uint32_t MANDATORY_PARAMS_NUMBER = 3; + static constexpr uint32_t CJS_MANDATORY_PARAMS_NUMBER = 8; static constexpr std::string_view LEXICAL_MANDATORY_PARAM_FUNC = "!f"; static constexpr std::string_view LEXICAL_MANDATORY_PARAM_NEW_TARGET = "!nt"; @@ -119,6 +126,7 @@ public: private: using MandatoryParams = std::array; + using CommonjsMandatoryParams = std::array; static constexpr MandatoryParams FUNCTION_MANDATORY_PARAMS = {MANDATORY_PARAM_FUNC, MANDATORY_PARAM_NEW_TARGET, MANDATORY_PARAM_THIS}; @@ -129,8 +137,21 @@ private: static constexpr MandatoryParams CTOR_ARROW_MANDATORY_PARAMS = { LEXICAL_MANDATORY_PARAM_FUNC, LEXICAL_MANDATORY_PARAM_NEW_TARGET, LEXICAL_MANDATORY_PARAM_THIS}; + static constexpr CommonjsMandatoryParams CJS_MAINFUNC_MANDATORY_PARAMS = { + MANDATORY_PARAM_FUNC, MANDATORY_PARAM_NEW_TARGET, MANDATORY_PARAM_THIS, + CJS_MANDATORY_PARAM_EXPORTS, CJS_MANDATORY_PARAM_REQUIRE, CJS_MANDATORY_PARAM_MODULE, + CJS_MANDATORY_PARAM_FILENAME, CJS_MANDATORY_PARAM_DIRNAME}; + void AddMandatoryParam(const std::string_view &name); - void AddMandatoryParams(const MandatoryParams ¶ms); + + template + void AddMandatoryParams(const T ¶ms) + { + for (auto iter = params.rbegin(); iter != params.rend(); iter++) { + AddMandatoryParam(*iter); + } + } + void AddMandatoryParams(); void BuildFunction(FunctionScope *funcScope, util::StringView name); void BuildScriptFunction(Scope *outerScope, const ir::ScriptFunction *scriptFunc); diff --git a/es2panda/compiler/core/emitter/commonjs.cpp b/es2panda/compiler/core/emitter/commonjs.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d9b7c319df85961b0661c8e39fed404adabfb2cc --- /dev/null +++ b/es2panda/compiler/core/emitter/commonjs.cpp @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "emitter.h" + +#include + +namespace panda::es2panda::compiler { +constexpr const auto LANG_EXT = panda::pandasm::extensions::Language::ECMASCRIPT; + +void Emitter::GenCommonjsRecord(bool isCommonjs) +{ + auto commonjsRecord = panda::pandasm::Record("_CommonJsRecord", LANG_EXT); + commonjsRecord.metadata->SetAccessFlags(panda::ACC_PUBLIC); + auto isCommonJsField = panda::pandasm::Field(LANG_EXT); + isCommonJsField.name = "isCommonJs"; + isCommonJsField.type = panda::pandasm::Type("u8", 0); + isCommonJsField.metadata->SetValue( + panda::pandasm::ScalarValue::Create(static_cast(isCommonjs))); + commonjsRecord.field_list.emplace_back(std::move(isCommonJsField)); + + prog_->record_table.emplace(commonjsRecord.name, std::move(commonjsRecord)); +} +} // namespace panda::es2panda::compiler \ No newline at end of file diff --git a/es2panda/compiler/core/emitter/emitter.cpp b/es2panda/compiler/core/emitter/emitter.cpp index 31e8a224a985fb3a13ccf21d28cd8b3bfbfc8d81..f69dc27fdf1c371ac5c86bab4476b1041a6750e2 100644 --- a/es2panda/compiler/core/emitter/emitter.cpp +++ b/es2panda/compiler/core/emitter/emitter.cpp @@ -356,6 +356,7 @@ Emitter::Emitter(const CompilerContext *context) prog_->function_table.reserve(context->Binder()->Functions().size()); GenESAnnoatationRecord(); + GenCommonjsRecord(context->Binder()->Program()->Kind() == parser::ScriptKind::COMMONJS); } Emitter::~Emitter() diff --git a/es2panda/compiler/core/emitter/emitter.h b/es2panda/compiler/core/emitter/emitter.h index 9e3d9f934b2418d5b6c24dbcb51b73d75e22170a..315868c08695d36b3831e82c2c5337b3f17d8bf4 100644 --- a/es2panda/compiler/core/emitter/emitter.h +++ b/es2panda/compiler/core/emitter/emitter.h @@ -106,6 +106,7 @@ public: private: void GenESAnnoatationRecord(); + void GenCommonjsRecord(bool isCommonjs); std::mutex m_; panda::pandasm::Program *prog_; diff --git a/es2panda/es2panda.cpp b/es2panda/es2panda.cpp index 7bb412786ee1a9c11bb7555e706728235d66dcf1..59fc0b90d050eb6f07de75a0a54807aa1de46f33 100644 --- a/es2panda/es2panda.cpp +++ b/es2panda/es2panda.cpp @@ -47,9 +47,10 @@ panda::pandasm::Program *Compiler::Compile(const SourceFile &input, const Compil /* TODO(dbatyai): pass string view */ std::string fname(input.fileName); std::string src(input.source); + parser::ScriptKind kind(input.scriptKind); try { - auto ast = input.isModule ? parser_->ParseModule(fname, src) : parser_->ParseScript(fname, src); + auto ast = parser_->Parse(fname, src, kind); if (options.dumpAst) { std::cout << ast.Dump() << std::endl; @@ -72,5 +73,4 @@ void Compiler::DumpAsm(const panda::pandasm::Program *prog) { compiler::CompilerImpl::DumpAsm(prog); } - } // namespace panda::es2panda diff --git a/es2panda/es2panda.h b/es2panda/es2panda.h index 675a51d58ff551bfd50cad77f74d4489501d5eca..602f77642cfd22ed9af92d51480c010896565365 100644 --- a/es2panda/es2panda.h +++ b/es2panda/es2panda.h @@ -27,6 +27,7 @@ struct Program; namespace panda::es2panda { namespace parser { class ParserImpl; +enum class ScriptKind; } // namespace parser namespace compiler { @@ -40,12 +41,14 @@ enum class ScriptExtension { }; struct SourceFile { - SourceFile(std::string_view fn, std::string_view s) : fileName(fn), source(s) {}; - SourceFile(std::string_view fn, std::string_view s, bool m) : fileName(fn), source(s), isModule(m) {}; + SourceFile(std::string_view fn, std::string_view s, parser::ScriptKind sk) + : fileName(fn), source(s), scriptKind(sk) + { + } std::string_view fileName {}; std::string_view source {}; - bool isModule {false}; + parser::ScriptKind scriptKind {}; }; struct CompilerOptions { diff --git a/es2panda/parser/commonjs.cpp b/es2panda/parser/commonjs.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0f41609e7a47ef02c4c86fb2a86243200155fcba --- /dev/null +++ b/es2panda/parser/commonjs.cpp @@ -0,0 +1,99 @@ +/** + * Copyright (c) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "parserImpl.h" + +namespace panda::es2panda::parser { +static std::vector cjsMandatoryParams = {binder::Binder::CJS_MANDATORY_PARAM_EXPORTS, + binder::Binder::CJS_MANDATORY_PARAM_REQUIRE, + binder::Binder::CJS_MANDATORY_PARAM_MODULE, + binder::Binder::CJS_MANDATORY_PARAM_FILENAME, + binder::Binder::CJS_MANDATORY_PARAM_DIRNAME}; + +void ParserImpl::AddCommonjsParams(ArenaVector ¶ms) +{ + for (auto paramName : cjsMandatoryParams) { + ir::Expression *param = AllocNode(paramName, Allocator()); + param->AsIdentifier()->SetReference(); + Binder()->AddParamDecl(param); + params.push_back(param); + } +} + +void ParserImpl::AddCommonjsArgs(ArenaVector &args) +{ + for (auto argName : cjsMandatoryParams) { + ir::Expression *arg = AllocNode(argName, Allocator()); + arg->AsIdentifier()->SetReference(); + args.push_back(arg); + } +} + +void ParserImpl::ParseCommonjs(const std::string &fileName, const std::string &source) +{ + // create FunctionExpression as callee + ir::FunctionExpression *funcExpr = nullptr; + { + FunctionContext functionContext(this, ParserStatus::FUNCTION | ParserStatus::ALLOW_NEW_TARGET); + FunctionParameterContext funcParamContext(&context_, Binder()); + auto *funcParamScope = funcParamContext.LexicalScope().GetScope(); + + ArenaVector params(Allocator()->Adapter()); + AddCommonjsParams(params); + + auto functionCtx = binder::LexicalScope(Binder()); + auto *functionScope = functionCtx.GetScope(); + functionScope->BindParamScope(funcParamScope); + funcParamScope->BindFunctionScope(functionScope); + + ParseProgram(ScriptKind::COMMONJS); + + auto *funcNode = + AllocNode(functionScope, std::move(params), nullptr, program_.Ast(), nullptr, + functionContext.Flags(), false); + functionScope->BindNode(funcNode); + funcParamScope->BindNode(funcNode); + + funcExpr = AllocNode(funcNode); + } + + // create CallExpression + ArenaVector arguments(Allocator()->Adapter()); + AddCommonjsArgs(arguments); + auto *callExpr = AllocNode(funcExpr, std::move(arguments), nullptr, false); + // create ExpressionStatement + auto *exprStatementNode = AllocNode(callExpr); + + ArenaVector statements(Allocator()->Adapter()); + statements.push_back(exprStatementNode); + + auto *blockStmt = AllocNode(Binder()->GetScope(), std::move(statements)); + Binder()->GetScope()->BindNode(blockStmt); + + program_.SetAst(blockStmt); +} +} // namespace panda::es2panda::parser \ No newline at end of file diff --git a/es2panda/parser/parserImpl.cpp b/es2panda/parser/parserImpl.cpp index 3344f0ba2df3d5edcd63f5e74781e153db866131..4c06cda889b03a01483220f2b06fdf109b07bc41 100644 --- a/es2panda/parser/parserImpl.cpp +++ b/es2panda/parser/parserImpl.cpp @@ -108,28 +108,50 @@ std::unique_ptr ParserImpl::InitLexer(const std::string &fileName, return lexer; } -Program ParserImpl::ParseScript(const std::string &fileName, const std::string &source) +Program ParserImpl::Parse(const std::string &fileName, const std::string &source, ScriptKind kind) { - auto lexer = InitLexer(fileName, source); + program_.SetKind(kind); - ParseProgram(ScriptKind::SCRIPT); + /* + * In order to make the lexer's memory alive, the return value 'lexer' can not be omitted. + */ + auto lexer = InitLexer(fileName, source); + switch (kind) { + case ScriptKind::SCRIPT: { + ParseScript(fileName, source); + break; + } + case ScriptKind::MODULE: { + ParseModule(fileName, source); + break; + } + case ScriptKind::COMMONJS: { + ParseCommonjs(fileName, source); + break; + } + default: { + UNREACHABLE(); + } + } + Binder()->IdentifierAnalysis(); return std::move(program_); } -Program ParserImpl::ParseModule(const std::string &fileName, const std::string &source) +void ParserImpl::ParseScript(const std::string &fileName, const std::string &source) { - auto lexer = InitLexer(fileName, source); + ParseProgram(ScriptKind::SCRIPT); +} +void ParserImpl::ParseModule(const std::string &fileName, const std::string &source) +{ context_.Status() |= (ParserStatus::MODULE); ParseProgram(ScriptKind::MODULE); - return std::move(program_); } void ParserImpl::ParseProgram(ScriptKind kind) { lexer::SourcePosition startLoc = lexer_->GetToken().Start(); lexer_->NextToken(); - program_.SetKind(kind); auto statements = ParseStatementList(StatementParsingFlags::STMT_GLOBAL_LEXICAL); @@ -138,7 +160,6 @@ void ParserImpl::ParseProgram(ScriptKind kind) blockStmt->SetRange({startLoc, lexer_->GetToken().End()}); program_.SetAst(blockStmt); - Binder()->IdentifierAnalysis(); } /* diff --git a/es2panda/parser/parserImpl.h b/es2panda/parser/parserImpl.h index 7eb03e42708e6e32c9e4224de363479d2027f0b0..258d5c91692a5f8f5c1c01b0ac03eb3975934aa0 100644 --- a/es2panda/parser/parserImpl.h +++ b/es2panda/parser/parserImpl.h @@ -176,8 +176,7 @@ public: NO_MOVE_SEMANTIC(ParserImpl); ~ParserImpl() = default; - Program ParseScript(const std::string &fileName, const std::string &source); - Program ParseModule(const std::string &fileName, const std::string &source); + Program Parse(const std::string &fileName, const std::string &source, ScriptKind kind); ScriptExtension Extension() const; @@ -206,6 +205,17 @@ private: } [[nodiscard]] std::unique_ptr InitLexer(const std::string &fileName, const std::string &source); + void ParseScript(const std::string &fileName, const std::string &source); + void ParseModule(const std::string &fileName, const std::string &source); + /* + * Transform the commonjs's AST by wrapping the sourceCode + * e.g. (function (exports, require, module, __filename, __dirname) { + * [Origin_SourceCode] + * })(exports, require, module, __filename, __dirname); + */ + void ParseCommonjs(const std::string &fileName, const std::string &source); + void AddCommonjsParams(ArenaVector ¶ms); + void AddCommonjsArgs(ArenaVector &args); void ParseProgram(ScriptKind kind); static ExpressionParseFlags CarryExpressionParserFlag(ExpressionParseFlags origin, ExpressionParseFlags carry); static ExpressionParseFlags CarryPatternFlags(ExpressionParseFlags flags); diff --git a/es2panda/parser/program/program.h b/es2panda/parser/program/program.h index 33b750ed2db31dba27d0296e1b8182ccd0c6c9e7..667d38cffe0f84a005622c928038c690a16151aa 100644 --- a/es2panda/parser/program/program.h +++ b/es2panda/parser/program/program.h @@ -34,7 +34,7 @@ class Binder; namespace panda::es2panda::parser { -enum class ScriptKind { SCRIPT, MODULE }; +enum class ScriptKind { SCRIPT, MODULE, COMMONJS }; class Program { public: diff --git a/es2panda/scripts/generate_js_bytecode.py b/es2panda/scripts/generate_js_bytecode.py index 668a50da0a59da3df92264def511cd81a33ec3df..6b191694f2ea3e1bb1602513b056c8a328cec8a8 100755 --- a/es2panda/scripts/generate_js_bytecode.py +++ b/es2panda/scripts/generate_js_bytecode.py @@ -64,7 +64,7 @@ def gen_abc_info(input_arguments): cmd.insert(src_index, '--module') if input_arguments.commonjs: src_index = cmd.index(input_arguments.src_js) - # insert commonjs option to cmd later + cmd.insert(src_index, '--commonjs') # insert d.ts option to cmd later run_command(cmd, path)