Initial demangler implementation, i.e. for use with TypeScript definitions

This commit is contained in:
dcodeIO 2018-05-17 11:59:35 +02:00
parent 25cf51833d
commit d678807286
4 changed files with 147 additions and 11 deletions

39
lib/demangle/README.md Normal file
View File

@ -0,0 +1,39 @@
![AS](https://avatars1.githubusercontent.com/u/28916798?s=48) demangle
======================
Demangles AssemblyScript module exports to a friendly object structure compatible with WebIDL and TypeScript definitions.
Usage
-----
```js
var module = require("@assemblyscript/demangle")(myWasmModule.instance.exports);
// use module as defined in the .d.ts
```
Converting a memory offset (`this` value) to a class instance, i.e. where a class instance is returned by a WebAssembly function:
```js
var thisValue = wasmFunctionReturningAClassInstance();
var myClass = MyClass.wrap(thisValue);
```
Converting a class instance to a memory offset (`this` value), i.e. where calling a WebAssembly function that expects a class instance:
```js
var thisValue = myClass.this;
wasmFunctionExpectingAClassInstance(thisValue);
```
How does it work?
-----------------
AssemblyScript modules expose their exported elements by their internal name, which is a JSDoc-style fully qualified name indicating layers of nesting, and this module is able to recreate the original object structure from those names.
* A `.` seperates a parent from a static child
* A `#` separates a parent from an instance child
* The `get:` prefix indicates a getter
* The `set:` prefix indicates a setter
Note that the compiler generates implicit getters and setters for instance fields for convenience. Support for instance members is achieved by generating wrappers that prepend the `this` value (offset of the instance in memory returned by the constructor) as the first argument when calling an instance method, getter or setter.

View File

@ -1,18 +1,57 @@
/**
* @file AssemblyScript demangler.
*/
module.exports = demangle;
/**
* Demangles module exports to a friendly object structure compatible with WebIDL and TypeScript
* definitions.
*/
/** Demangles AssemblyScript module exports to a friendly object structure. */
function demangle(exports) {
var root = {};
for (let i in exports) {
if (exports.hasOwnProperty(i)) {
// TODO
for (let internalName in exports) {
if (!exports.hasOwnProperty(internalName)) continue;
let elem = exports[internalName];
let parts = internalName.split(".");
let curr = root;
while (parts.length > 1) {
let part = parts.shift();
if (!curr.hasOwnProperty(part)) curr[part] = {};
curr = curr[part];
}
let name = parts[0];
let hash = name.indexOf("#");
if (hash >= 0) {
let className = name.substring(0, hash);
let classElem = curr[className];
if (typeof classElem === "undefined" || !classElem.prototype) {
let ctor = function(...args) {
return ctor.wrap(ctor.prototype.constructor(...args));
};
ctor.prototype = {};
ctor.wrap = function(thisValue) {
return Object.create(ctor.prototype, { "this": { value: thisValue, writable: false } });
};
if (classElem) Object.getOwnPropertyNames(classElem).forEach(name =>
Object.defineProperty(ctor, name, Object.getOwnPropertyDescriptor(classElem, name))
);
curr[className] = ctor;
}
name = name.substring(hash + 1);
curr = curr[className].prototype;
if (/^(get|set):/.test(name)) {
if (!curr.hasOwnProperty(name = name.substring(4))) {
let getter = exports[internalName.replace("set:", "get:")];
let setter = exports[internalName.replace("get:", "set:")];
Object.defineProperty(curr, name, {
get: function() { return getter(this.this); },
set: function(value) { setter(this.this, value); }
});
}
} else curr[name] = function(...args) { return elem(this.this, ...args); };
} else {
if (/^(get|set):/.test(name)) {
if (!curr.hasOwnProperty(name = name.substring(4))) {
Object.defineProperty(curr, name, {
get: exports[internalName.replace("set:", "get:")],
set: exports[internalName.replace("get:", "set:")]
});
}
} else curr[name] = elem;
}
}
return root;

13
lib/demangle/package.json Normal file
View File

@ -0,0 +1,13 @@
{
"name": "@assemblyscript/demangle",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"test": "node tests"
},
"files": [
"index.js",
"package.json",
"README.md"
]
}

View File

@ -0,0 +1,45 @@
var assert = require("assert");
var inspect = require("util").inspect;
var demangle = require("..");
var __this = 8;
var __usualDoors = 3;
var __doors = -1;
var exports = demangle({
"vroom": function() { console.log("vroom", arguments); },
"Car.MAX_DOORS": 5,
"Car.get:usualDoors": function() { console.log("Car#get:usualDoors", arguments); return __usualDoors; },
"Car.set:usualDoors": function(value) { console.log("Car#set:usualDoors", arguments); __usualDoors = value; },
"Car#constructor": function(this_, doors) { console.log("Car#constructor", arguments); __doors = doors; return __this; },
"Car#openDoors": function(this_) { console.log("Car#openDoors", arguments); return true; },
"Car#get:doors": function(this_) { console.log("Car#get:doors", arguments); return __doors; },
"Car#set:doors": function(this_, value) { console.log("Car#set:doors", arguments); __doors = value; }
});
console.log(inspect(exports, true));
exports.vroom(1, 2, 3);
var Car = exports.Car;
assert(Car.usualDoors == 3);
exports.Car.usualDoors = exports.Car.usualDoors + 2;
assert(Car.usualDoors == 5);
var car = new exports.Car(2);
assert(car.this == 8);
assert(car.openDoors() == true);
assert(car.doors == 2);
car.doors = car.doors + 2;
assert(car.doors == 4);
console.log(inspect(car, true));
var wrappedCar = exports.Car.wrap(16);
assert(wrappedCar.this == 16);
console.log(inspect(wrappedCar, true));