Skip to content

Commit d678807

Browse files
committed
Initial demangler implementation, i.e. for use with TypeScript definitions
1 parent 25cf518 commit d678807

4 files changed

Lines changed: 147 additions & 11 deletions

File tree

lib/demangle/README.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
![AS](https://avatars1.githubusercontent.com/u/28916798?s=48) demangle
2+
======================
3+
4+
Demangles AssemblyScript module exports to a friendly object structure compatible with WebIDL and TypeScript definitions.
5+
6+
Usage
7+
-----
8+
9+
```js
10+
var module = require("@assemblyscript/demangle")(myWasmModule.instance.exports);
11+
// use module as defined in the .d.ts
12+
```
13+
14+
Converting a memory offset (`this` value) to a class instance, i.e. where a class instance is returned by a WebAssembly function:
15+
16+
```js
17+
18+
var thisValue = wasmFunctionReturningAClassInstance();
19+
var myClass = MyClass.wrap(thisValue);
20+
```
21+
22+
Converting a class instance to a memory offset (`this` value), i.e. where calling a WebAssembly function that expects a class instance:
23+
24+
```js
25+
var thisValue = myClass.this;
26+
wasmFunctionExpectingAClassInstance(thisValue);
27+
```
28+
29+
How does it work?
30+
-----------------
31+
32+
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.
33+
34+
* A `.` seperates a parent from a static child
35+
* A `#` separates a parent from an instance child
36+
* The `get:` prefix indicates a getter
37+
* The `set:` prefix indicates a setter
38+
39+
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.

lib/demangle/index.js

Lines changed: 50 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,57 @@
1-
/**
2-
* @file AssemblyScript demangler.
3-
*/
4-
51
module.exports = demangle;
62

7-
/**
8-
* Demangles module exports to a friendly object structure compatible with WebIDL and TypeScript
9-
* definitions.
10-
*/
3+
/** Demangles AssemblyScript module exports to a friendly object structure. */
114
function demangle(exports) {
125
var root = {};
13-
for (let i in exports) {
14-
if (exports.hasOwnProperty(i)) {
15-
// TODO
6+
for (let internalName in exports) {
7+
if (!exports.hasOwnProperty(internalName)) continue;
8+
let elem = exports[internalName];
9+
let parts = internalName.split(".");
10+
let curr = root;
11+
while (parts.length > 1) {
12+
let part = parts.shift();
13+
if (!curr.hasOwnProperty(part)) curr[part] = {};
14+
curr = curr[part];
15+
}
16+
let name = parts[0];
17+
let hash = name.indexOf("#");
18+
if (hash >= 0) {
19+
let className = name.substring(0, hash);
20+
let classElem = curr[className];
21+
if (typeof classElem === "undefined" || !classElem.prototype) {
22+
let ctor = function(...args) {
23+
return ctor.wrap(ctor.prototype.constructor(...args));
24+
};
25+
ctor.prototype = {};
26+
ctor.wrap = function(thisValue) {
27+
return Object.create(ctor.prototype, { "this": { value: thisValue, writable: false } });
28+
};
29+
if (classElem) Object.getOwnPropertyNames(classElem).forEach(name =>
30+
Object.defineProperty(ctor, name, Object.getOwnPropertyDescriptor(classElem, name))
31+
);
32+
curr[className] = ctor;
33+
}
34+
name = name.substring(hash + 1);
35+
curr = curr[className].prototype;
36+
if (/^(get|set):/.test(name)) {
37+
if (!curr.hasOwnProperty(name = name.substring(4))) {
38+
let getter = exports[internalName.replace("set:", "get:")];
39+
let setter = exports[internalName.replace("get:", "set:")];
40+
Object.defineProperty(curr, name, {
41+
get: function() { return getter(this.this); },
42+
set: function(value) { setter(this.this, value); }
43+
});
44+
}
45+
} else curr[name] = function(...args) { return elem(this.this, ...args); };
46+
} else {
47+
if (/^(get|set):/.test(name)) {
48+
if (!curr.hasOwnProperty(name = name.substring(4))) {
49+
Object.defineProperty(curr, name, {
50+
get: exports[internalName.replace("set:", "get:")],
51+
set: exports[internalName.replace("get:", "set:")]
52+
});
53+
}
54+
} else curr[name] = elem;
1655
}
1756
}
1857
return root;

lib/demangle/package.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"name": "@assemblyscript/demangle",
3+
"version": "1.0.0",
4+
"main": "index.js",
5+
"scripts": {
6+
"test": "node tests"
7+
},
8+
"files": [
9+
"index.js",
10+
"package.json",
11+
"README.md"
12+
]
13+
}

lib/demangle/tests/index.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
var assert = require("assert");
2+
var inspect = require("util").inspect;
3+
var demangle = require("..");
4+
5+
var __this = 8;
6+
var __usualDoors = 3;
7+
var __doors = -1;
8+
9+
var exports = demangle({
10+
"vroom": function() { console.log("vroom", arguments); },
11+
"Car.MAX_DOORS": 5,
12+
"Car.get:usualDoors": function() { console.log("Car#get:usualDoors", arguments); return __usualDoors; },
13+
"Car.set:usualDoors": function(value) { console.log("Car#set:usualDoors", arguments); __usualDoors = value; },
14+
"Car#constructor": function(this_, doors) { console.log("Car#constructor", arguments); __doors = doors; return __this; },
15+
"Car#openDoors": function(this_) { console.log("Car#openDoors", arguments); return true; },
16+
"Car#get:doors": function(this_) { console.log("Car#get:doors", arguments); return __doors; },
17+
"Car#set:doors": function(this_, value) { console.log("Car#set:doors", arguments); __doors = value; }
18+
});
19+
20+
console.log(inspect(exports, true));
21+
22+
exports.vroom(1, 2, 3);
23+
24+
var Car = exports.Car;
25+
26+
assert(Car.usualDoors == 3);
27+
exports.Car.usualDoors = exports.Car.usualDoors + 2;
28+
assert(Car.usualDoors == 5);
29+
30+
var car = new exports.Car(2);
31+
32+
assert(car.this == 8);
33+
34+
assert(car.openDoors() == true);
35+
assert(car.doors == 2);
36+
car.doors = car.doors + 2;
37+
assert(car.doors == 4);
38+
39+
console.log(inspect(car, true));
40+
41+
var wrappedCar = exports.Car.wrap(16);
42+
43+
assert(wrappedCar.this == 16);
44+
45+
console.log(inspect(wrappedCar, true));

0 commit comments

Comments
 (0)