From c36588a75548c44176c85e8b0d3e43085e7e81e4 Mon Sep 17 00:00:00 2001 From: hufeng Date: Fri, 13 May 2022 16:26:18 +0800 Subject: [PATCH 1/2] fixed b0bfd02 from https://gitee.com/hufeng20/ark_ts2abc/pulls/233 Support compiling commonjs module Signed-off-by: hufeng Change-Id: I8d1499161e66378e84dc4fd3fa594e975466ad73 --- ts2panda/src/addVariable2Scope.ts | 8 +++ ts2panda/src/base/util.ts | 33 +++++++++ ts2panda/src/cmdOptions.ts | 10 ++- ts2panda/src/compiler.ts | 2 +- ts2panda/src/compilerDriver.ts | 6 ++ ts2panda/src/index.ts | 9 ++- ts2panda/src/ts2panda.ts | 1 + ts2panda/tests/commonjs.test.ts | 108 ++++++++++++++++++++++++++++++ ts2panda/tests/utils/base.ts | 11 ++- ts2panda/ts2abc/ts2abc.cpp | 26 +++++++ 10 files changed, 208 insertions(+), 6 deletions(-) create mode 100644 ts2panda/tests/commonjs.test.ts diff --git a/ts2panda/src/addVariable2Scope.ts b/ts2panda/src/addVariable2Scope.ts index 9a10cc0b35..a4981ce76c 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 838ea52f47..3662772e19 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 9844d6bd94..beb9828869 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,6 +107,13 @@ export class CmdOptions { return args.length == 2 && args[0] == "stop"; } + static isCommonJs(): boolean { + if (!this.options) { + return false; + } + return this.options["commonjs"]; + } + static isModules(): boolean { if (!this.options) { return false; diff --git a/ts2panda/src/compiler.ts b/ts2panda/src/compiler.ts index 0d311fcb7a..59cd1d776e 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 296f0aaa6e..a5b3543ef5 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 bc5881e33a..cb869b8e03 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 6c83c1654a..d07d50d18d 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 0000000000..f0148848a4 --- /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 b682c86af7..73966bbf1c 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 976dfd609c..bd84aaa274 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); -- Gitee From ff30e39ae7c28f603968752efe47995ec090b24c Mon Sep 17 00:00:00 2001 From: hufeng Date: Thu, 2 Jun 2022 15:11:00 +0800 Subject: [PATCH 2/2] revise README Signed-off-by: hufeng Change-Id: Ia44a1c7f4cd07843270a54d25a917f8b2296ba24 --- README.md | 15 +++++++++++++-- README_zh.md | 15 +++++++++++++-- ts2panda/src/cmdOptions.ts | 10 ++++++++++ 3 files changed, 36 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 4571032896..de22d95f4c 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 b1cc5b1be2..67bd3f9c8e 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/cmdOptions.ts b/ts2panda/src/cmdOptions.ts index beb9828869..1224946ba0 100644 --- a/ts2panda/src/cmdOptions.ts +++ b/ts2panda/src/cmdOptions.ts @@ -111,6 +111,11 @@ export class CmdOptions { 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"]; } @@ -118,6 +123,11 @@ export class CmdOptions { 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"]; } -- Gitee