Skip to content

Commit 3b0c523

Browse files
TimothyGudomenic
authored andcommitted
Implement initial support for iterable declarations
That is, adds support for iterable<K, V> syntax, by delegating to the [Symbol.iterator]() method on the impl class, which is assumed to return something of the correct form. This will generate the [Symbol.iterator](), keys(), values(), entries(), and forEach() methods on the wrapper class. This initial implementation is somewhat inefficient (e.g. each call to next() and each iteration of forEach() does a O(n) iteration-and-copy over the original impl's list of keys and values) but we can change that later. Also updates webidl-conversions to ^3.0.0. Fixes #30.
1 parent effba26 commit 3b0c523

File tree

7 files changed

+219
-34
lines changed

7 files changed

+219
-34
lines changed

lib/constructs/attribute.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ Attribute.prototype.generate = function () {
8282
if (!this || !module.exports.is(this)) {
8383
throw new TypeError("Illegal invocation");
8484
}
85-
return this.${this.idl.name};
85+
${getterBody};
8686
};\n\n`;
8787
}
8888

lib/constructs/interface.js

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const path = require("path");
55
const utils = require("../utils");
66
const Attribute = require("./attribute");
77
const Constant = require("./constant");
8+
const Iterable = require("./iterable");
89
const Operation = require("./operation");
910
const Overloads = require("../overloads");
1011
const Parameters = require("../parameters");
@@ -18,6 +19,7 @@ function Interface(idl, opts) {
1819
this.requires = {};
1920
this.str = null;
2021
this.opts = opts;
22+
this.iterable = false;
2123
}
2224

2325
Interface.prototype.type = "interface";
@@ -130,6 +132,52 @@ Interface.prototype.generateExport = function () {
130132
exposers.push(keys[i] + ": { " + exposedOnObj.join(", ") + " }");
131133
}
132134

135+
if (this.iterable) {
136+
this.str += `\nconst IteratorPrototype = Object.create(utils.IteratorPrototype, {
137+
next: {
138+
value: function next() {
139+
const internal = this[utils.iterInternalSymbol];
140+
const target = internal.target;
141+
const kind = internal.kind;
142+
const index = internal.index;
143+
const values = Array.from(target[impl]);
144+
const len = values.length;
145+
if (index >= len) {
146+
return { value: undefined, done: true };
147+
}
148+
149+
const pair = values[index];
150+
internal.index = index + 1;
151+
152+
let result;
153+
switch (kind) {
154+
case "key":
155+
result = pair[0];
156+
break;
157+
case "value":
158+
result = pair[1];
159+
break;
160+
case "key+value":
161+
result = [pair[0], pair[1]];
162+
break;
163+
}
164+
return { value: result, done: false };
165+
},
166+
writable: true,
167+
enumerable: true,
168+
configurable: true
169+
},
170+
toString: {
171+
value: function toString() {
172+
return "[object ${this.name}Iterator]";
173+
},
174+
writable: true,
175+
enumerable: false,
176+
configurable: true
177+
}
178+
});`;
179+
}
180+
133181
// since we don't have spread arg calls, we can't do new Interface(...arguments) yet
134182
// add initialized symbol as to not destroy the object shape and cause deopts
135183
this.str += `\nmodule.exports = {
@@ -172,6 +220,15 @@ Interface.prototype.generateExport = function () {
172220
this.setup(obj, constructorArgs, privateData);
173221
return utils.implForWrapper(obj);
174222
},
223+
createDefaultIterator(target, kind) {
224+
const iterator = Object.create(IteratorPrototype);
225+
iterator[utils.iterInternalSymbol] = {
226+
target,
227+
kind,
228+
index: 0
229+
};
230+
return iterator;
231+
},
175232
_internalSetup(obj) {`;
176233

177234
if (this.idl.inheritance) {
@@ -234,6 +291,10 @@ Interface.prototype.generateOperations = function () {
234291
}
235292
done[member.name] = true;
236293
break;
294+
case "iterable":
295+
this.iterable = true;
296+
member = new Iterable(this, this.idl, memberIdl, { customTypes: this.opts.customTypes });
297+
break;
237298
default:
238299
//throw new Error("Can't handle member of type '" + memberIdl.type + "'");
239300
break;

lib/constructs/iterable.js

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
"use strict";
2+
3+
const conversions = require("webidl-conversions");
4+
const keywords = require("../keywords");
5+
6+
function Iterable(obj, I, idl, opts) {
7+
this.obj = obj;
8+
this.interface = I;
9+
this.idl = idl;
10+
this.name = idl.type;
11+
this.opts = opts;
12+
}
13+
14+
Iterable.prototype.generateFunction = function (key, kind, keyExpr, fnName) {
15+
if (fnName === undefined) {
16+
fnName = typeof key === "symbol" ? "" : (keywords.has(key) ? "_" : key);
17+
}
18+
19+
const propExpr = typeof key === "symbol" ? `[${keyExpr}]` : `.${key}`;
20+
21+
return `\n${this.obj.name}.prototype${propExpr} = function ${fnName}() {
22+
if (!this || !module.exports.is(this)) {
23+
throw new TypeError("Illegal invocation");
24+
}
25+
return module.exports.createDefaultIterator(this, "${kind}");
26+
}`;
27+
};
28+
29+
Iterable.prototype.generate = function () {
30+
const isPairIterator = Array.isArray(this.idl.idlType) && this.idl.idlType.length === 2;
31+
let str = "";
32+
33+
if (isPairIterator) {
34+
str += this.generateFunction(Symbol.iterator, "key+value", "Symbol.iterator", "entries");
35+
str += `\n${this.obj.name}.prototype.entries = ${this.obj.name}.prototype[Symbol.iterator];`;
36+
str += this.generateFunction("keys", "key");
37+
str += this.generateFunction("values", "value");
38+
str += `\n${this.obj.name}.prototype.forEach = function forEach(callback) {
39+
if (!this || !module.exports.is(this)) {
40+
throw new TypeError("Illegal invocation");
41+
}
42+
if (arguments.length < 1) {
43+
throw new TypeError("Failed to execute 'forEach' on '${this.obj.name}': 1 argument required, but only 0 present.");
44+
}
45+
if (typeof callback !== "function") {
46+
throw new TypeError("Failed to execute 'forEach' on '${this.obj.name}': The callback provided as parameter 1 is not a function.");
47+
}
48+
const thisArg = arguments[1];
49+
let pairs = Array.from(this[impl]);
50+
let i = 0;
51+
while (i < pairs.length) {
52+
const [key, value] = pairs[i];
53+
callback.call(thisArg, value, key, this);
54+
pairs = Array.from(this[impl]);
55+
i++;
56+
}
57+
};`;
58+
}
59+
60+
61+
return {
62+
requires: {},
63+
body: str
64+
}
65+
};
66+
67+
module.exports = Iterable;

lib/output/utils.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,14 @@ function tryImplForWrapper(wrapper) {
3232
return impl ? impl : wrapper;
3333
};
3434

35+
const iterInternalSymbol = Symbol("internal");
36+
const IteratorPrototype = Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]()));
37+
3538
module.exports.wrapperSymbol = wrapperSymbol;
3639
module.exports.implSymbol = implSymbol;
3740
module.exports.wrapperForImpl = wrapperForImpl;
3841
module.exports.implForWrapper = implForWrapper;
3942
module.exports.tryWrapperForImpl = tryWrapperForImpl;
4043
module.exports.tryImplForWrapper = tryImplForWrapper;
44+
module.exports.iterInternalSymbol = iterInternalSymbol;
45+
module.exports.IteratorPrototype = IteratorPrototype;

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"dependencies": {
77
"co": "^4.6.0",
88
"pn": "^1.0.0",
9-
"webidl-conversions": "^2.0.0",
9+
"webidl-conversions": "^3.0.0",
1010
"webidl2": "^2.0.11"
1111
},
1212
"devDependencies": {},

test/cases/URL.idl

Lines changed: 5 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,9 @@
1+
/*
12
[Constructor(USVString url, optional USVString base),
23
Exposed=(Window,Worker)]
34
interface URL {
4-
static USVString domainToASCII(USVString domain);
5-
static USVString domainToUnicode(USVString domain);
6-
};
7-
URL implements URLUtils;
8-
URL implements URLUtilsSearchParams;
9-
10-
[NoInterfaceObject,
11-
Exposed=(Window,Worker)]
12-
interface URLUtils {
135
stringifier attribute USVString href;
146
readonly attribute USVString origin;
15-
167
attribute USVString protocol;
178
attribute USVString username;
189
attribute USVString password;
@@ -21,29 +12,10 @@ interface URLUtils {
2112
attribute USVString port;
2213
attribute USVString pathname;
2314
attribute USVString search;
15+
[SameObject] readonly attribute URLSearchParams searchParams;
2416
attribute USVString hash;
2517
};
26-
27-
[NoInterfaceObject,
28-
Exposed=(Window, Worker)]
29-
interface URLUtilsSearchParams {
30-
attribute URLSearchParams searchParams;
31-
};
32-
33-
[NoInterfaceObject,
34-
Exposed=(Window,Worker)]
35-
interface URLUtilsReadOnly {
36-
stringifier readonly attribute USVString href;
37-
readonly attribute USVString origin;
38-
39-
readonly attribute USVString protocol;
40-
readonly attribute USVString host;
41-
readonly attribute USVString hostname;
42-
readonly attribute USVString port;
43-
readonly attribute USVString pathname;
44-
readonly attribute USVString search;
45-
readonly attribute USVString hash;
46-
};
18+
*/
4719

4820
[Constructor(optional (USVString or URLSearchParams) init = ""),
4921
Exposed=(Window,Worker)]
@@ -55,5 +27,6 @@ interface URLSearchParams {
5527
boolean has(USVString name);
5628
void set(USVString name, USVString value);
5729
iterable<USVString, USVString>;
58-
stringifier;
30+
// The following operation on its own currently doesn't work.
31+
// stringifier;
5932
};
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
"use strict";
2+
3+
const conversions = require("webidl-conversions");
4+
const querystring = require("querystring");
5+
6+
class URLSearchParamsImpl {
7+
constructor(init) {
8+
if (init instanceof URLSearchParamsImpl) {
9+
this._list = init._list.slice();
10+
} else {
11+
init = conversions.USVString(init);
12+
if (init[0] === "?") { init = init.slice(1); }
13+
14+
this._list = [];
15+
const parsed = querystring.parse(init);
16+
for (let name in parsed) {
17+
if (Array.isArray(parsed[name])) {
18+
for (let value of parsed[name]) {
19+
this._list.push([name, value]);
20+
}
21+
} else {
22+
this._list.push([name, parsed[name]]);
23+
}
24+
}
25+
}
26+
}
27+
28+
append(name, value) {
29+
this._list.push([name, value]);
30+
}
31+
32+
delete(name) {
33+
let i = 0;
34+
while (i < this._list.length) {
35+
if (this._list[i][0] === name) {
36+
this._list.splice(i, 1);
37+
} else {
38+
i++;
39+
}
40+
}
41+
}
42+
43+
get(name) {
44+
for (let pair of this._list) {
45+
if (pair[0] === name) {
46+
return pair[1];
47+
}
48+
}
49+
50+
return null;
51+
}
52+
53+
getAll(name) {
54+
const output = [];
55+
for (let pair of this._list) {
56+
if (pair[0] === name) {
57+
output.push(pair[1]);
58+
}
59+
}
60+
return output;
61+
}
62+
63+
has(name) {
64+
return this._list.some(pair => pair[0] === name);
65+
}
66+
67+
set(name, value) {
68+
this.delete(name);
69+
this.append(name, value);
70+
}
71+
72+
[Symbol.iterator]() {
73+
return this._list[Symbol.iterator]();
74+
}
75+
}
76+
77+
module.exports = {
78+
implementation: URLSearchParamsImpl
79+
};

0 commit comments

Comments
 (0)