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);