diff --git a/README.md b/README.md index 4571032896fb92dc1bbd7944c7838f594243e06e..de22d95f4cd879931fbfd35be700864daa68581a 100644 --- a/README.md +++ b/README.md @@ -64,11 +64,22 @@ If no parameter is specified for **\[options\]**, an ARK binary file is generat -

--modules

+

--commonjs

+ +

-c

+ +

Compiles the code based on the commonjs.

+ +

-

+ +

-

+ + +

--modules

-m

-

Compiles the code based on the module.

+

Compiles the code based on the ecmascript standard module.

-

diff --git a/README_zh.md b/README_zh.md index b1cc5b1be2e0765130c128fe14197280446f13d5..67bd3f9c8e909b92c5d2605cadf3a18ad727f529 100644 --- a/README_zh.md +++ b/README_zh.md @@ -65,11 +65,22 @@ $ node --expose-gc src/index.js [options] file.js -

--modules

+

--commonjs

+ +

-c

+ +

按照commonjs模式编译

+ +

-

+ +

-

+ + +

--modules

-m

-

按照module模式编译

+

按照ESM模式编译

-

diff --git a/ts2panda/src/addVariable2Scope.ts b/ts2panda/src/addVariable2Scope.ts index 9a10cc0b35780df3bacb8490f3d826b664705285..a4981ce76c0df394459c704b545314f7ca2ab898 100644 --- a/ts2panda/src/addVariable2Scope.ts +++ b/ts2panda/src/addVariable2Scope.ts @@ -72,6 +72,14 @@ function addInnerArgs(node: ts.Node, scope: VariableScope, enableTypeRecord: boo scope.addParameter("this", VarDeclarationKind.CONST, 0); } + if (CmdOptions.isCommonJs() && node.kind === ts.SyntaxKind.SourceFile) { + scope.addParameter("exports", VarDeclarationKind.LET, 1); + scope.addParameter("require", VarDeclarationKind.LET, 2); + scope.addParameter("module", VarDeclarationKind.LET, 3); + scope.addParameter("__filename", VarDeclarationKind.LET, 4); + scope.addParameter("__dirname", VarDeclarationKind.LET, 5); + } + if (node.kind != ts.SyntaxKind.SourceFile) { let funcNode = node; addParameters(funcNode, scope, enableTypeRecord); diff --git a/ts2panda/src/base/util.ts b/ts2panda/src/base/util.ts index 838ea52f47c4f84cd733db70ca187b3484aae40b..3662772e19f0748b2c4bfe4e39561ea0957afe0f 100644 --- a/ts2panda/src/base/util.ts +++ b/ts2panda/src/base/util.ts @@ -306,3 +306,36 @@ export function isBase64Str(input: string): boolean { } return Buffer.from(Buffer.from(input, 'base64').toString()).toString('base64') == input; } + +export function transformCommonjsModule(sourceFile: ts.SourceFile) { + /* + Transform the commonjs module's AST by wrap the sourceCode. + (function (exports, require, module, __filename, __dirname) { + [SourceCode] + })(exports, require, module, __filename, __dirname); + */ + let newStatements = [ts.factory.createExpressionStatement(ts.factory.createCallExpression( + ts.factory.createParenthesizedExpression(ts.factory.createFunctionExpression( + undefined, undefined, undefined, undefined, + [ + ts.factory.createParameterDeclaration(undefined, undefined, undefined, ts.factory.createIdentifier("exports"), undefined, undefined, undefined), + ts.factory.createParameterDeclaration(undefined, undefined, undefined, ts.factory.createIdentifier("require"), undefined, undefined, undefined), + ts.factory.createParameterDeclaration(undefined, undefined, undefined, ts.factory.createIdentifier("module"), undefined, undefined, undefined), + ts.factory.createParameterDeclaration(undefined, undefined, undefined, ts.factory.createIdentifier("__filename"), undefined, undefined, undefined), + ts.factory.createParameterDeclaration(undefined, undefined, undefined, ts.factory.createIdentifier("__dirname"), undefined, undefined, undefined) + ], + undefined, + ts.factory.createBlock(sourceFile.statements) + )), + undefined, + [ + ts.factory.createIdentifier("exports"), + ts.factory.createIdentifier("require"), + ts.factory.createIdentifier("module"), + ts.factory.createIdentifier("__filename"), + ts.factory.createIdentifier("__dirname") + ] + ))]; + + return ts.factory.updateSourceFile(sourceFile, newStatements); +} \ No newline at end of file diff --git a/ts2panda/src/cmdOptions.ts b/ts2panda/src/cmdOptions.ts index 9844d6bd9424752c77b22189de518346f0ca3ccb..1224946ba0afa900d56e72b90d6d9463c9a5ef26 100644 --- a/ts2panda/src/cmdOptions.ts +++ b/ts2panda/src/cmdOptions.ts @@ -22,8 +22,9 @@ import * as path from "path"; import { execute } from "./base/util"; const ts2pandaOptions = [ + { name: 'commonjs', alias: 'c', type: Boolean, defaultValue: false, description: "compile as commonJs module." }, { name: 'modules', alias: 'm', type: Boolean, defaultValue: false, description: "compile as module." }, - { name: 'debug-log', alias: 'l', type: Boolean, defaultValue: false, description: "show info debug log and generate the json file."}, + { name: 'debug-log', alias: 'l', type: Boolean, defaultValue: false, description: "show info debug log and generate the json file." }, { name: 'dump-assembly', alias: 'a', type: Boolean, defaultValue: false, description: "dump assembly to file." }, { name: 'debug', alias: 'd', type: Boolean, defaultValue: false, description: "compile with debug info." }, { name: 'debug-add-watch', alias: 'w', type: String, lazyMultiple: true, defaultValue: [], description: "watch expression and abc file path in debug mode." }, @@ -106,10 +107,27 @@ export class CmdOptions { return args.length == 2 && args[0] == "stop"; } + static isCommonJs(): boolean { + if (!this.options) { + return false; + } + + if (this.options["commonjs"] && this.options["modules"]) { + throw new Error("Can not compile with [-c] and [-m] options at the same time"); + } + + return this.options["commonjs"]; + } + static isModules(): boolean { if (!this.options) { return false; } + + if (this.options["modules"] && this.options["commonjs"]) { + throw new Error("Can not compile with [-m] and [-c] options at the same time"); + } + return this.options["modules"]; } diff --git a/ts2panda/src/compiler.ts b/ts2panda/src/compiler.ts index 0d311fcb7a79fa3d4e937786ff3ff957066d5d81..59cd1d776e4b8f12eee2ec123589307599e8cb86 100644 --- a/ts2panda/src/compiler.ts +++ b/ts2panda/src/compiler.ts @@ -1586,7 +1586,7 @@ export class Compiler { loadTarget(node: ts.Node, variable: { scope: Scope | undefined, level: number, v: Variable | undefined }) { if (variable.v instanceof LocalVariable) { - if (variable.v.isLetOrConst() || variable.v.isClass()) { + if (!CmdOptions.isCommonJs() && (variable.v.isLetOrConst() || variable.v.isClass())) { if (variable.scope instanceof GlobalScope) { this.pandaGen.tryLoadGlobalByName(node, variable.v.getName()); return; diff --git a/ts2panda/src/compilerDriver.ts b/ts2panda/src/compilerDriver.ts index 296f0aaa6ec8326f47cc3cb138319fb24c83a5e9..a5b3543ef547b6601821157176bd915acc119bd4 100644 --- a/ts2panda/src/compilerDriver.ts +++ b/ts2panda/src/compilerDriver.ts @@ -400,6 +400,12 @@ export class CompilerDriver { // the runtime passes these to global scope when calls it let parametersCount = 3; if (node.kind == ts.SyntaxKind.SourceFile) { + if (CmdOptions.isCommonJs()) { + // global scope accepts 5 additional parameters: + // "exports", "require", "module", "__filename","__dirname" + // when compiled as commonjs + parametersCount += 5; + } return parametersCount; } let decl = node; diff --git a/ts2panda/src/index.ts b/ts2panda/src/index.ts index bc5881e33ab81b1c8ef67c4157b96470cef27dc6..cb869b8e03d5142945550380309f83c722bd3f37 100644 --- a/ts2panda/src/index.ts +++ b/ts2panda/src/index.ts @@ -23,7 +23,7 @@ import * as jshelpers from "./jshelpers"; import { LOGE } from "./log"; import { setGlobalDeclare, setGlobalStrict } from "./strictMode"; import { TypeChecker } from "./typeChecker"; -import { setPos, isBase64Str } from "./base/util"; +import { setPos, isBase64Str, transformCommonjsModule } from "./base/util"; function checkIsGlobalDeclaration(sourceFile: ts.SourceFile) { for (let statement of sourceFile.statements) { @@ -99,6 +99,9 @@ function main(fileNames: string[], options: ts.CompilerOptions) { newStatements.push(...node.statements); node = ts.factory.updateSourceFile(node, newStatements); } + if (CmdOptions.isCommonJs()) { + node = transformCommonjsModule(node); + } let outputBinName = getOutputBinName(node); let compilerDriver = new CompilerDriver(outputBinName); setGlobalStrict(jshelpers.isEffectiveStrictModeSourceFile(node, options)); @@ -285,7 +288,8 @@ function run(args: string[], options?: ts.CompilerOptions): void { updateWatchedFile(); return; } - main(parsed.fileNames.concat(CmdOptions.getIncludedFiles()), parsed.options); + + main(parsed.fileNames.concat(dtsFiles).concat(CmdOptions.getIncludedFiles()), parsed.options); } catch (err) { if (err instanceof diag.DiagnosticError) { let diagnostic = diag.getDiagnostic(err.code); @@ -302,6 +306,5 @@ function run(args: string[], options?: ts.CompilerOptions): void { } let dtsFiles = getDtsFiles(path["join"](__dirname, "../node_modules/typescript/lib")); -process.argv.push(...dtsFiles); run(process.argv.slice(2), Compiler.Options.Default); global.gc(); diff --git a/ts2panda/src/ts2panda.ts b/ts2panda/src/ts2panda.ts index 6c83c1654a1f53bdb668a1bba3679adeb3d52b02..d07d50d18d93755beca1f34616056e7c3131c518 100644 --- a/ts2panda/src/ts2panda.ts +++ b/ts2panda/src/ts2panda.ts @@ -190,6 +190,7 @@ export class Ts2Panda { let options = { "t": JsonType.options, "module_mode": CmdOptions.isModules(), + "commonjs_module": CmdOptions.isCommonJs(), "debug_mode": CmdOptions.isDebugMode(), "log_enabled": CmdOptions.isEnableDebugLog(), "opt_level": CmdOptions.getOptLevel(), diff --git a/ts2panda/tests/commonjs.test.ts b/ts2panda/tests/commonjs.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..f0148848a4a5ffc5818d88be96e4f84509f2c406 --- /dev/null +++ b/ts2panda/tests/commonjs.test.ts @@ -0,0 +1,108 @@ +/* + * 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. + */ + +import { + expect +} from 'chai'; +import 'mocha'; +import { checkInstructions, SnippetCompiler } from "./utils/base"; +import { + EcmaCallarg1dyn, + EcmaCallirangedyn, + EcmaDefinefuncdyn, + EcmaReturnundefined, + EcmaStobjbyname, + Imm, + LdaDyn, + LdaiDyn, + LdaStr, + MovDyn, + StaDyn, + VReg +} from "../src/irnodes"; +import { CmdOptions } from '../src/cmdOptions'; + + +describe("CommonJsTest", function () { + + it("mainFunc", function() { + CmdOptions.isCommonJs = () => {return true}; + let snippetCompiler = new SnippetCompiler(); + snippetCompiler.compileCommonjs(`let a = 1`, 'cjs.js'); + CmdOptions.isCommonJs = () => {return false}; + let funcMainInsns = snippetCompiler.getGlobalInsns(); + let expected = [ + new EcmaDefinefuncdyn('#1#', new Imm(5), new VReg()), + new StaDyn(new VReg()), + new LdaDyn(new VReg()), + new StaDyn(new VReg()), + new LdaDyn(new VReg()), + new StaDyn(new VReg()), + new LdaDyn(new VReg()), + new StaDyn(new VReg()), + new LdaDyn(new VReg()), + new StaDyn(new VReg()), + new LdaDyn(new VReg()), + new StaDyn(new VReg()), + new EcmaCallirangedyn(new Imm(5), [new VReg(), new VReg(), new VReg(), new VReg(), new VReg(), new VReg()]), + new EcmaReturnundefined(), + ]; + expect(checkInstructions(funcMainInsns, expected)).to.be.true; + }); + + it("requireTest", function() { + CmdOptions.isCommonJs = () => {return true}; + let snippetCompiler = new SnippetCompiler(); + snippetCompiler.compileCommonjs(`let a = require('a.js')`, 'cjs.js'); + CmdOptions.isCommonJs = () => {return false}; + let execInsns = snippetCompiler.getPandaGenByName('#1#')!.getInsns(); + let requirePara = new VReg(); + let requireReg = new VReg(); + let moduleRequest = new VReg(); + let expected = [ + new LdaDyn(requirePara), + new StaDyn(requireReg), + new LdaStr("a.js"), + new StaDyn(moduleRequest), + new EcmaCallarg1dyn(requireReg, moduleRequest), + new StaDyn(new VReg()), + new EcmaReturnundefined() + ]; + expect(checkInstructions(execInsns, expected)).to.be.true; + }); + + it("exportTest", function() { + CmdOptions.isCommonJs = () => {return true}; + let snippetCompiler = new SnippetCompiler(); + snippetCompiler.compileCommonjs(`let a = 1; exports.a = a;`, 'cjs.js'); + CmdOptions.isCommonJs = () => {return false}; + let execInsns = snippetCompiler.getPandaGenByName('#1#')!.getInsns(); + let exportsPara = new VReg(); + let exportsReg = new VReg(); + let tmpReg = new VReg(); + let a = new VReg(); + let expected = [ + new LdaiDyn(new Imm(1)), + new StaDyn(a), + new LdaDyn(exportsPara), + new StaDyn(exportsReg), + new MovDyn(tmpReg, exportsReg), + new LdaDyn(a), + new EcmaStobjbyname("a", tmpReg), + new EcmaReturnundefined() + ]; + expect(checkInstructions(execInsns, expected)).to.be.true; + }); +}); \ No newline at end of file diff --git a/ts2panda/tests/utils/base.ts b/ts2panda/tests/utils/base.ts index b682c86af7b294323eb0e66730618e2d54ac30ef..73966bbf1c1c9bb5849d40db26260b13ba5fb34b 100644 --- a/ts2panda/tests/utils/base.ts +++ b/ts2panda/tests/utils/base.ts @@ -32,6 +32,7 @@ import { setGlobalStrict } from "../../src/strictMode"; import { creatAstFromSnippet } from "./asthelper"; import { LiteralBuffer } from "../../src/base/literal"; import { CmdOptions } from "../../src/cmdOptions"; +import { transformCommonjsModule } from "../../src/base/util"; const compileOptions = { outDir: "../tmp/build", @@ -172,7 +173,7 @@ export function compileMainSnippet(snippet: string, pandaGen?: PandaGen, scope?: return compileUnits[0].getInsns(); } -export function compileAfterSnippet(snippet: string, name:string) { +export function compileAfterSnippet(snippet: string, name:string, isCommonJs: boolean = false) { let compileUnits = null; ts.transpileModule( snippet, @@ -185,6 +186,9 @@ export function compileAfterSnippet(snippet: string, name:string) { after : [ (ctx: ts.TransformationContext) => { return (sourceFile: ts.SourceFile) => { + if (isCommonJs) { + sourceFile = transformCommonjsModule(sourceFile); + } jshelpers.bindSourceFile(sourceFile, {}); setGlobalStrict(jshelpers.isEffectiveStrictModeSourceFile(sourceFile, compileOptions)); let compilerDriver = new CompilerDriver('UnitTest'); @@ -218,6 +222,11 @@ export class SnippetCompiler { return this.pandaGens; } + compileCommonjs(snippet: string, name: string) { + this.pandaGens = compileAfterSnippet(snippet, name, true); + return this.pandaGens; + } + getGlobalInsns(): IRNode[] { let root = this.getPandaGenByName("func_main_0"); if (root) { diff --git a/ts2panda/ts2abc/ts2abc.cpp b/ts2panda/ts2abc/ts2abc.cpp index 976dfd609c9d5971595252cb701feea0dabc0fc0..bd84aaa274c43ea25fc56e0766dcf20ecd818562 100644 --- a/ts2panda/ts2abc/ts2abc.cpp +++ b/ts2panda/ts2abc/ts2abc.cpp @@ -794,6 +794,21 @@ static void GenerateESTypeAnnotationRecord(panda::pandasm::Program &prog) prog.record_table.emplace(tsTypeAnnotationRecord.name, std::move(tsTypeAnnotationRecord)); } +static void GenerateCommonJsRecord(panda::pandasm::Program &prog, bool isCommonJs) +{ + // when multi-abc file get merged, field should be inserted in abc's own record + 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)); +} + static void GenerateESModuleRecord(panda::pandasm::Program &prog) { auto ecmaModuleRecord = panda::pandasm::Record("_ESModuleRecord", LANG_EXT); @@ -846,6 +861,16 @@ static void ParseModuleMode(const Json::Value &rootValue, panda::pandasm::Progra } } +static void ParseCommonJsModuleMode(const Json::Value &rootValue, panda::pandasm::Program &prog) +{ + Logd("------------parse commonjs_module_mode-------------"); + if (rootValue.isMember("commonjs_module") && rootValue["commonjs_module"].isBool()) { + if (rootValue["commonjs_module"].asBool()) { + GenerateCommonJsRecord(prog, true); + } + } +} + void ParseLogEnable(const Json::Value &rootValue) { if (rootValue.isMember("log_enabled") && rootValue["log_enabled"].isBool()) { @@ -896,6 +921,7 @@ static void ParseOptions(const Json::Value &rootValue, panda::pandasm::Program & GenerateESCallTypeAnnotationRecord(prog); GenerateESTypeAnnotationRecord(prog); ParseModuleMode(rootValue, prog); + ParseCommonJsModuleMode(rootValue, prog); ParseLogEnable(rootValue); ParseDebugMode(rootValue); ParseOptLevel(rootValue);