Skip to content

Commit 576c295

Browse files
authored
Implement transform classes (AssemblyScript#892)
1 parent 274c571 commit 576c295

File tree

9 files changed

+192
-43
lines changed

9 files changed

+192
-43
lines changed

cli/asc.js

Lines changed: 47 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -212,20 +212,45 @@ exports.main = function main(argv, options, callback) {
212212
// Set up transforms
213213
const transforms = [];
214214
if (args.transform) {
215-
args.transform.forEach(transform =>
216-
transforms.push(
217-
require(
218-
path.isAbsolute(transform = transform.trim())
219-
? transform
220-
: path.join(process.cwd(), transform)
221-
)
222-
)
223-
);
215+
let transformArgs = args.transform;
216+
for (let i = 0, k = transformArgs.length; i < k; ++i) {
217+
let filename = transformArgs[i];
218+
filename = path.isAbsolute(filename = filename.trim())
219+
? filename
220+
: path.join(process.cwd(), filename);
221+
if (/\.ts$/.test(filename)) require("ts-node").register({ transpileOnly: true, skipProject: true });
222+
try {
223+
const classOrModule = require(filename);
224+
if (typeof classOrModule === "function") {
225+
Object.assign(classOrModule.prototype, {
226+
baseDir,
227+
stdout,
228+
stderr,
229+
log: console.error,
230+
readFile,
231+
writeFile,
232+
listFiles
233+
});
234+
transforms.push(new classOrModule());
235+
} else {
236+
transforms.push(classOrModule); // legacy module
237+
}
238+
} catch (e) {
239+
return callback(e);
240+
}
241+
}
224242
}
225243
function applyTransform(name, ...args) {
226-
transforms.forEach(transform => {
227-
if (typeof transform[name] === "function") transform[name](...args);
228-
});
244+
for (let i = 0, k = transforms.length; i < k; ++i) {
245+
let transform = transforms[i];
246+
if (typeof transform[name] === "function") {
247+
try {
248+
transform[name](...args);
249+
} catch (e) {
250+
return e;
251+
}
252+
}
253+
}
229254
}
230255

231256
// Begin parsing
@@ -426,7 +451,10 @@ exports.main = function main(argv, options, callback) {
426451
}
427452

428453
// Call afterParse transform hook
429-
applyTransform("afterParse", parser);
454+
{
455+
let error = applyTransform("afterParse", parser);
456+
if (error) return callback(error);
457+
}
430458

431459
// Parse additional files, if any
432460
{
@@ -530,6 +558,12 @@ exports.main = function main(argv, options, callback) {
530558
return callback(Error("Compile error"));
531559
}
532560

561+
// Call afterCompile transform hook
562+
{
563+
let error = applyTransform("afterCompile", module);
564+
if (error) return callback(error);
565+
}
566+
533567
// Validate the module if requested
534568
if (args.validate) {
535569
stats.validateCount++;

cli/transform.d.ts

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,35 @@
33
* @module cli/transform
44
*//***/
55

6-
import { Parser } from "../src/parser";
6+
import { Parser, Module } from "..";
7+
import { OutputStream } from "./asc";
8+
9+
export abstract class Transform {
10+
11+
/** Base directory. */
12+
readonly baseDir: string;
13+
14+
/** Output stream used by the compiler. */
15+
readonly stdout: OutputStream;
16+
17+
/** Error stream used by the compiler. */
18+
readonly stderr: OutputStream;
19+
20+
/** Logs a message to console. */
21+
readonly log: typeof console.log;
22+
23+
/** Writes a file to disk. */
24+
writeFile(filename: string, contents: string | Uint8Array, baseDir: string): boolean;
25+
26+
/** Reads a file from disk. */
27+
readFile(filename: string, baseDir: string): string | null;
28+
29+
/** Lists all files in a directory. */
30+
listFiles(dirname: string, baseDir: string): string[] | null;
731

8-
export interface Transform {
932
/** Called when parsing is complete, before a program is instantiated from the AST. */
10-
afterParse(parser: Parser): void;
33+
afterParse?(parser: Parser): void;
34+
35+
/** Called when compilation is complete, before the module is being validated. */
36+
afterCompile?(module: Module): void;
1137
}

cli/transform.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// becomes replaced with the actual base by asc
2+
exports.Transform = function Transform() {};

examples/transform/README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
Compiler transform examples
2+
===========================
3+
4+
Both transforms written in JS and transforms written in TS can be used, with the
5+
latter requiring that the ts-node package is present.
6+
7+
* [Example JavaScript transform](./mytransform.js)
8+
* [Example TypeScript transform](./mytransform.ts)

examples/transform/assembly/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
// empty

examples/transform/mytransform.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
const { Transform } = require("../../cli/transform"); // "assemblyscript/cli/transform"
2+
const { SourceKind } = require("../.."); // "assemblyscript"
3+
const binaryen = require("binaryen");
4+
5+
class MyTransform extends Transform {
6+
afterParse(parser) {
7+
this.log("[mytransform.js] afterParse called, baseDir = " + this.baseDir);
8+
var sources = parser.program.sources;
9+
sources.forEach(source => this.log(" " + source.internalPath + " [" + SourceKind[source.sourceKind] + "]"));
10+
}
11+
afterCompile(asModule) {
12+
this.log("[mytransform.js] afterCompile called");
13+
var module = binaryen.wrapModule(asModule.ref);
14+
this.log(module.emitBinary());
15+
}
16+
}
17+
18+
module.exports = MyTransform;

examples/transform/mytransform.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { Transform } from "../../cli/transform"; // "assemblyscript/cli/transform"
2+
import { Parser, Module, SourceKind } from "../.."; // "assemblyscript"
3+
import * as binaryen from "binaryen";
4+
5+
class MyTransform extends Transform {
6+
afterParse(parser: Parser): void {
7+
this.log("[mytransform.ts] afterParse called, baseDir = " + this.baseDir);
8+
var sources = parser.program.sources;
9+
sources.forEach(source => this.log(" " + source.internalPath + " [" + SourceKind[source.sourceKind] + "]"));
10+
}
11+
afterCompile(asModule: Module): void {
12+
this.log("[mytransform.ts] afterCompile called");
13+
var module = binaryen.wrapModule(asModule.ref);
14+
this.log(module.emitBinary());
15+
}
16+
}
17+
18+
export = MyTransform;

examples/transform/package.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"scripts": {
3+
"test:js": "asc assembly/index.ts --runtime none --transform mytransform.js",
4+
"test:ts": "asc assembly/index.ts --runtime none --transform mytransform.ts",
5+
"test:multi": "asc assembly/index.ts --runtime none --transform mytransform.js --transform mytransform.ts",
6+
"test": "npm run test:js && npm run test:ts && npm run test:multi"
7+
}
8+
}

scripts/build-dts.js

Lines changed: 61 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -408,41 +408,40 @@
408408
exports.default = generate;
409409
});
410410

411-
const prelude = `declare type bool = boolean;
412-
declare type i8 = number;
413-
declare type i16 = number;
414-
declare type i32 = number;
415-
declare type isize = number;
416-
declare type u8 = number;
417-
declare type u16 = number;
418-
declare type u32 = number;
419-
declare type usize = number;
420-
declare type f32 = number;
421-
declare type f64 = number;
422-
declare module 'assemblyscript' {
423-
export * from 'assemblyscript/src/index';
411+
const path = require("path");
412+
const fs = require("fs");
413+
const stream = require("stream");
414+
const util = require("util");
415+
416+
function OutputStream(options) {
417+
stream.Writable.call(this, options);
418+
this.chunks = [];
424419
}
425-
`;
420+
util.inherits(OutputStream, stream.Writable);
421+
OutputStream.prototype._write = function(chunk, enc, cb) {
422+
this.chunks.push(chunk);
423+
cb();
424+
};
425+
OutputStream.prototype.toBuffer = function() {
426+
return Buffer.concat(this.chunks);
427+
};
428+
OutputStream.prototype.toString = function() {
429+
return this.toBuffer().toString("utf8");
430+
};
426431

427-
var path = require("path");
428-
var fs = require("fs");
429-
var stdout = fs.createWriteStream(path.resolve(__dirname, "..", "dist", "assemblyscript.d.ts"));
430-
stdout.write(prelude);
431-
stdout.write = (function(_write) {
432-
return function(...args) {
433-
if (typeof args[0] === "string") {
434-
args[0] = args[0].replace(/\/\/\/ <reference[^>]*>\r?\n/g, "");
435-
}
436-
return _write.apply(stdout, args);
437-
};
438-
})(stdout.write);
432+
const stdout = new OutputStream();
433+
stdout.write(`declare module 'assemblyscript' {
434+
export * from 'assemblyscript/src/index';
435+
}
436+
`);
439437

440438
module.exports.default({
441439
project: path.resolve(__dirname, "..", "src"),
442440
prefix: "assemblyscript",
443441
exclude: [
444442
"glue/js/index.ts",
445-
"glue/js/node.d.ts"
443+
"glue/js/node.d.ts",
444+
"glue/binaryen.d.ts"
446445
],
447446
verbose: true,
448447
sendMessage: console.log,
@@ -459,3 +458,38 @@ module.exports.default({
459458
sendMessage: console.log,
460459
stdout: stdout
461460
});
461+
462+
var source = stdout.toString().replace(/\/\/\/ <reference[^>]*>\r?\n/g, "");
463+
464+
const ts = require("typescript");
465+
const sourceFile = ts.createSourceFile("assemblyscript.d.ts", source, ts.ScriptTarget.ESNext, false, ts.ScriptKind.TS);
466+
467+
console.log("transforming:");
468+
var numReplaced = 0;
469+
const result = ts.transform(sourceFile, [
470+
function(context) {
471+
const visit = node => {
472+
node = ts.visitEachChild(node, visit, context);
473+
if (ts.isTypeNode(node)) {
474+
const name = node.getText(sourceFile);
475+
switch (name) {
476+
// this is wrong, but works
477+
case "bool": ++numReplaced; return ts.createIdentifier("boolean");
478+
default: if (!/^(?:Binaryen|Relooper)/.test(name)) break;
479+
case "i8": case "i16": case "i32": case "isize":
480+
case "u8": case "u16": case "u32": case "usize":
481+
case "f32": case "f64": ++numReplaced; return ts.createIdentifier("number");
482+
}
483+
}
484+
return node;
485+
};
486+
return node => ts.visitNode(node, visit);
487+
}
488+
]);
489+
console.log(" replaced " + numReplaced + " AS types with JS types");
490+
491+
fs.writeFileSync(
492+
path.resolve(__dirname, "..", "dist", "assemblyscript.d.ts"),
493+
ts.createPrinter().printFile(result.transformed[0]),
494+
"utf8"
495+
);

0 commit comments

Comments
 (0)