summaryrefslogtreecommitdiffstats
path: root/js/src/tests/ecma_6/Comprehensions
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/tests/ecma_6/Comprehensions')
-rw-r--r--js/src/tests/ecma_6/Comprehensions/arguments.js14
-rw-r--r--js/src/tests/ecma_6/Comprehensions/array-yield.js23
-rw-r--r--js/src/tests/ecma_6/Comprehensions/error-messages.js172
-rw-r--r--js/src/tests/ecma_6/Comprehensions/generator-semantics.js52
-rw-r--r--js/src/tests/ecma_6/Comprehensions/nested-for-if.js20
-rw-r--r--js/src/tests/ecma_6/Comprehensions/scope.js18
-rw-r--r--js/src/tests/ecma_6/Comprehensions/shell.js0
-rw-r--r--js/src/tests/ecma_6/Comprehensions/sudoku.js168
-rw-r--r--js/src/tests/ecma_6/Comprehensions/syntax.js6
-rw-r--r--js/src/tests/ecma_6/Comprehensions/toSource.js14
10 files changed, 487 insertions, 0 deletions
diff --git a/js/src/tests/ecma_6/Comprehensions/arguments.js b/js/src/tests/ecma_6/Comprehensions/arguments.js
new file mode 100644
index 000000000..082374778
--- /dev/null
+++ b/js/src/tests/ecma_6/Comprehensions/arguments.js
@@ -0,0 +1,14 @@
+
+
+function values(g) {
+ return [for (x of g) x];
+}
+
+function argumentsTest() {
+ return values((for (i of [0,1,2]) arguments[i]));
+}
+
+assertDeepEq(argumentsTest('a', 'b', 'c'), ['a', 'b', 'c']);
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/ecma_6/Comprehensions/array-yield.js b/js/src/tests/ecma_6/Comprehensions/array-yield.js
new file mode 100644
index 000000000..69e699522
--- /dev/null
+++ b/js/src/tests/ecma_6/Comprehensions/array-yield.js
@@ -0,0 +1,23 @@
+// Interactions between yield and array comprehensions.
+
+
+function assertIteratorResult(result, value, done) {
+ assertDeepEq(result.value, value);
+ assertEq(result.done, done);
+}
+
+function* t1() { return [for (x of yield 0) x*2] }
+
+var o = t1();
+assertIteratorResult(o.next(), 0, false);
+assertIteratorResult(o.next([0, 1, 2]), [0, 2, 4], true);
+
+function* t2() { return [for (x of [1,2,3]) yield x] }
+
+o = t2();
+assertIteratorResult(o.next(), 1, false);
+assertIteratorResult(o.next(5), 2, false);
+assertIteratorResult(o.next(6), 3, false);
+assertIteratorResult(o.next(7), [5, 6, 7], true);
+
+reportCompare(null, null, "test");
diff --git a/js/src/tests/ecma_6/Comprehensions/error-messages.js b/js/src/tests/ecma_6/Comprehensions/error-messages.js
new file mode 100644
index 000000000..aeacfde27
--- /dev/null
+++ b/js/src/tests/ecma_6/Comprehensions/error-messages.js
@@ -0,0 +1,172 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+// This file tests contextual restrictions for yield and arguments, and is
+// derived from js1_8/genexps/regress-634472.js.
+
+function error(str) {
+ var base;
+ try {
+ // the following line must not be broken up into multiple lines
+ base = (function(){try{eval('throw new Error()')}catch(e){return e.lineNumber}})(); eval(str);
+ return null;
+ } catch (e) {
+ e.lineNumber = e.lineNumber - base + 1;
+ return e;
+ }
+}
+
+const YIELD_PAREN = error("(function*(){(for (y of (yield 1, 2)) y)})").message;
+const GENEXP_YIELD = error("(function*(){(for (x of yield 1) x)})").message;
+const TOP_YIELD = error("yield").message;
+const GENERIC = error("(for)").message;
+const BAD_GENERATOR_SYNTAX = error("(for (x of []) x, 1)").message;
+const MISSING_SEMI = error("yield 1").message;
+const PAREN_PAREN = error("(foo").message;
+const FOR_OF_PAREN = error("(for (x of y, z) w)").message;
+
+const cases = [
+// Expressions involving yield without a value, not currently implemented. Many
+// of these errors would need to be updated. (Note: line numbers below might be
+// mere placeholders, not actually the expected correct behavior -- check before
+// blindly uncommenting.)
+//{ expr: "yield", top: [TOP_YIELD, 777], fun: null, gen: [GENEXP_YIELD, 2], desc: "simple yield" },
+//{ expr: "1, yield", top: [TOP_YIELD, 777], fun: null, gen: [GENEXP_YIELD, 2], desc: "simple yield at end of list" },
+//{ expr: "yield, 1", top: [TOP_YIELD, 777], fun: [YIELD_PAREN, 2], gen: [YIELD_PAREN, 2], desc: "simple yield in list" },
+//{ expr: "(yield)", top: [TOP_YIELD, 777], fun: null, gen: [GENEXP_YIELD, 2], desc: "simple yield, parenthesized" },
+//{ expr: "(1, yield)", top: [TOP_YIELD, 777], fun: null, gen: [GENEXP_YIELD, 2], desc: "simple yield at end of list, parenthesized" },
+//{ expr: "(yield, 1)", top: [TOP_YIELD, 777], fun: [YIELD_PAREN, 2], gen: [YIELD_PAREN, 2], desc: "simple yield in list, parenthesized" },
+//{ expr: "((((yield))))", top: [TOP_YIELD, 777], fun: null, gen: [GENEXP_YIELD, 2], desc: "deeply nested yield" },
+//{ expr: "(for (x of []) yield)", top: [TOP_YIELD, 777], fun: [GENERIC, 777], gen: [GENERIC, 777], desc: "simple yield in genexp" },
+//{ expr: "(for (x of []) yield, 1)", top: [TOP_YIELD, 777], fun: [YIELD_PAREN, 2], gen: [YIELD_PAREN, 2], desc: "simple yield in list in genexp" },
+//{ expr: "(for (x of []) 1, yield)", top: [TOP_YIELD, 777], fun: [GENERIC, 777], gen: [GENERIC, 777], desc: "simple yield at end of list in genexp" },
+//{ expr: "(for (x of []) (yield))", top: [TOP_YIELD, 777], fun: [GENEXP_YIELD, 2], gen: [GENEXP_YIELD, 2], desc: "simple yield, parenthesized in genexp" },
+//{ expr: "(for (x of []) 1, (yield))", top: [TOP_YIELD, 777], fun: [GENEXP_YIELD, 2], gen: [GENEXP_YIELD, 2], desc: "simple yield, parenthesized in list in genexp" },
+//{ expr: "(for (x of []) (1, yield))", top: [TOP_YIELD, 777], fun: [GENEXP_YIELD, 2], gen: [GENEXP_YIELD, 2], desc: "simple yield at end of list, parenthesized in genexp" },
+//{ expr: "(for (x of []) 1, (2, yield))", top: [TOP_YIELD, 777], fun: [GENEXP_YIELD, 2], gen: [GENEXP_YIELD, 2], desc: "simple yield at end of list, parenthesized in list in genexp" },
+//{ expr: "(for (x of []) (yield, 1))", top: [TOP_YIELD, 777], fun: [YIELD_PAREN, 2], gen: [YIELD_PAREN, 2], desc: "simple yield in list, parenthesized in genexp" },
+//{ expr: "(for (x of []) 1, (yield, 2))", top: [TOP_YIELD, 777], fun: [YIELD_PAREN, 2], gen: [YIELD_PAREN, 2], desc: "simple yield in list, parenthesized in list in genexp" },
+//{ expr: "(for (x of []) (function*() { yield }))", top: null, fun: null, gen: null, desc: "legal yield in nested function" },
+
+ // yield expressions
+ { expr: "yield 1", top: [MISSING_SEMI, 2], fun: [MISSING_SEMI, 2], gen: null, genexp: [GENEXP_YIELD, 2], desc: "yield w/ arg" },
+ { expr: "1, yield 2", top: [MISSING_SEMI, 2], fun: [MISSING_SEMI, 2], gen: null, genexp: [FOR_OF_PAREN, 1], desc: "yield w/ arg at end of list" },
+ { expr: "yield 1, 2", top: [MISSING_SEMI, 2], fun: [MISSING_SEMI, 2], gen: null, genexp: [FOR_OF_PAREN, 3], desc: "yield w/ arg in list" },
+ { expr: "(yield 1)", top: [PAREN_PAREN, 2], fun: [PAREN_PAREN, 2], gen: null, genexp: [GENEXP_YIELD, 2], desc: "yield w/ arg, parenthesized" },
+ { expr: "(1, yield 2)", top: [PAREN_PAREN, 2], fun: [PAREN_PAREN, 2], gen: null, genexp: [GENEXP_YIELD, 2], desc: "yield w/ arg at end of list, parenthesized" },
+ { expr: "(yield 1, 2)", top: [PAREN_PAREN, 2], fun: [PAREN_PAREN, 2], gen: null, genexp: [YIELD_PAREN, 2], desc: "yield w/ arg in list, parenthesized" },
+
+ // deeply nested yield expressions
+ { expr: "((((yield 1))))", top: [PAREN_PAREN, 2], fun: [PAREN_PAREN, 2], gen: null, genexp: [GENEXP_YIELD, 2], desc: "deeply nested yield w/ arg" },
+
+ // arguments
+ { expr: "arguments", top: null, fun: null, gen: null, genexp: null, desc: "arguments in list" },
+ { expr: "1, arguments", top: null, fun: null, gen: null, genexp: [FOR_OF_PAREN, 1], desc: "arguments in list" },
+
+ // yield in generator expressions
+ { expr: "(for (x of []) yield 1)", top: [GENEXP_YIELD, 2], fun: [GENEXP_YIELD, 2], gen: [GENEXP_YIELD, 2], genexp: [GENEXP_YIELD, 2], desc: "yield w/ arg in genexp" },
+ { expr: "(for (x of []) yield 1, 2)", top: [GENEXP_YIELD, 2], fun: [GENEXP_YIELD, 2], gen: [GENEXP_YIELD, 2], genexp: [GENEXP_YIELD, 2], desc: "yield w/ arg in list in genexp" },
+ { expr: "(for (x of []) 1, yield 2)", top: [PAREN_PAREN, 1], fun: [PAREN_PAREN, 1], gen: [PAREN_PAREN, 1], genexp: [PAREN_PAREN, 1], desc: "yield w/ arg at end of list in genexp" },
+ { expr: "(for (x of []) (yield 1))", top: [GENEXP_YIELD, 2], fun: [GENEXP_YIELD, 2], gen: [GENEXP_YIELD, 2], genexp: [GENEXP_YIELD, 2], desc: "yield w/ arg, parenthesized in genexp" },
+ { expr: "(for (x of []) 1, (yield 2))", top: [PAREN_PAREN, 1], fun: [PAREN_PAREN, 1], gen: [PAREN_PAREN, 1], genexp: [PAREN_PAREN, 1], desc: "yield w/ arg, parenthesized in list in genexp" },
+ { expr: "(for (x of []) (1, yield 2))", top: [GENEXP_YIELD, 2], fun: [GENEXP_YIELD, 2], gen: [GENEXP_YIELD, 2], genexp: [GENEXP_YIELD, 2], desc: "yield w/ arg at end of list, parenthesized in genexp" },
+ { expr: "(for (x of []) 1, (2, yield 3))", top: [PAREN_PAREN, 1], fun: [PAREN_PAREN, 1], gen: [PAREN_PAREN, 1], genexp: [PAREN_PAREN, 1], desc: "yield w/ arg at end of list, parenthesized in list in genexp" },
+ { expr: "(for (x of []) (yield 1, 2))", top: [YIELD_PAREN, 2], fun: [YIELD_PAREN, 2], gen: [YIELD_PAREN, 2], genexp: [YIELD_PAREN, 2], desc: "yield w/ arg in list, parenthesized in genexp" },
+ { expr: "(for (x of []) 1, (yield 2, 3))", top: [PAREN_PAREN, 1], fun: [PAREN_PAREN, 1], gen: [PAREN_PAREN, 1], genexp: [PAREN_PAREN, 1], desc: "yield w/ arg in list, parenthesized in list in genexp" },
+
+ // deeply nested yield in generator expressions
+ { expr: "(for (x of []) (((1, yield 2))))", top: [GENEXP_YIELD, 2], fun: [GENEXP_YIELD, 2], gen: [GENEXP_YIELD, 2], genexp: [GENEXP_YIELD, 2], desc: "deeply nested yield in genexp" },
+ { expr: "(for (y of []) (for (x of []) ((1, yield 2))))", top: [GENEXP_YIELD, 2], fun: [GENEXP_YIELD, 2], gen: [GENEXP_YIELD, 2], genexp: [GENEXP_YIELD, 2], desc: "deeply nested yield in multiple genexps" },
+
+ // arguments in generator expressions
+ { expr: "(for (x of []) arguments)", top: null, fun: null, gen: null, genexp: null, desc: "simple arguments in genexp" },
+ { expr: "(for (x of []) 1, arguments)", top: [BAD_GENERATOR_SYNTAX, 1], fun: [BAD_GENERATOR_SYNTAX, 1], gen: [BAD_GENERATOR_SYNTAX, 1], genexp: [BAD_GENERATOR_SYNTAX, 1], desc: "arguments in list in genexp" },
+ { expr: "(for (x of []) (arguments))", top: null, fun: null, gen: null, genexp: null, desc: "arguments, parenthesized in genexp" },
+ { expr: "(for (x of []) 1, (arguments))", top: [BAD_GENERATOR_SYNTAX, 1], fun: [BAD_GENERATOR_SYNTAX, 1], gen: [BAD_GENERATOR_SYNTAX, 1], genexp: [BAD_GENERATOR_SYNTAX, 1], desc: "arguments, parenthesized in list in genexp" },
+ { expr: "(for (x of []) (1, arguments))", top: null, fun: null, gen: null, genexp: null, desc: "arguments in list, parenthesized in genexp" },
+ { expr: "(for (x of []) 1, (2, arguments))", top: [BAD_GENERATOR_SYNTAX, 1], fun: [BAD_GENERATOR_SYNTAX, 1], gen: [BAD_GENERATOR_SYNTAX, 1], genexp: [BAD_GENERATOR_SYNTAX, 1], desc: "arguments in list, parenthesized in list in genexp" },
+
+ // deeply nested arguments in generator expressions
+ { expr: "(for (x of []) (((1, arguments))))", top: null, fun: null, gen: null, genexp: null, desc: "deeply nested arguments in genexp" },
+ { expr: "(for (y of []) (for (x of []) ((1, arguments))))", top: null, fun: null, gen: null, genexp: null, desc: "deeply nested arguments in multiple genexps" },
+
+ // legal yield/arguments in nested function
+ { expr: "(for (x of []) (function*() { yield 1 }))", top: null, fun: null, gen: null, genexp: null, desc: "legal yield in nested function" },
+ { expr: "(for (x of []) (function() { arguments }))", top: null, fun: null, gen: null, genexp: null, desc: "legal arguments in nested function" },
+ { expr: "(for (x of []) (function() arguments))", top: null, fun: null, gen: null, genexp: null, desc: "legal arguments in nested expression-closure" }
+];
+
+//-----------------------------------------------------------------------------
+test();
+//-----------------------------------------------------------------------------
+
+function splitKeyword(str) {
+ return str.
+// replace(/[)] yield/, ')\nyield\n').
+ replace(/yield ([0-9])/, '\nyield $1\n').
+ replace(/yield([^ ]|$)/, '\nyield\n$1').
+ replace(/arguments/, '\narguments\n');
+}
+
+function expectError1(err, ctx, msg, lineNumber) {
+ reportCompare('object', typeof err, 'exn for: ' + msg);
+ reportCompare(ctx, err.message, 'exn message for: ' + msg);
+ reportCompare(lineNumber, err.lineNumber, 'exn token for: ' + msg);
+}
+
+function expectError(expr, wrapCtx, [expect, lineNumber], msg) {
+ expectError1(error(wrapCtx(expr)), expect, msg, lineNumber);
+}
+
+function expectSuccess(err, msg) {
+ reportCompare(null, err, 'parse: ' + msg);
+}
+
+function atTop(str) { return str }
+function inFun(str) { return '(function(){' + str + '})' }
+function inGen(str) { return '(function*(){' + str + '})' }
+function inGenExp(str) { return '(for (y of ' + str + ') y)' }
+
+function test()
+{
+ enterFunc ('test');
+ printBugNumber(BUGNUMBER);
+ printStatus (summary);
+
+ for (var i = 0, len = cases.length; i < len; i++) {
+ var expr, top, fun, gen, genexp, desc;
+ expr = cases[i].expr;
+ top = cases[i].top;
+ fun = cases[i].fun;
+ gen = cases[i].gen;
+ genexp = cases[i].genexp;
+ desc = cases[i].desc;
+
+ expr = splitKeyword(expr);
+
+ if (top)
+ expectError(expr, atTop, top, 'top-level context, ' + desc);
+ else
+ expectSuccess(error(expr), 'top-level context, ' + desc);
+
+ if (fun)
+ expectError(expr, inFun, fun, 'function context, ' + desc);
+ else
+ expectSuccess(error(inFun(expr)), 'function context, ' + desc);
+
+ if (gen)
+ expectError(expr, inGen, gen, 'generator context, ' + desc);
+ else
+ expectSuccess(error(inGen(expr)), 'generator context, ' + desc);
+
+ if (genexp)
+ expectError(expr, inGenExp, genexp, 'genexp context, ' + desc);
+ else
+ expectSuccess(error(inGenExp(expr)), 'genexp context, ' + desc);
+ }
+
+ exitFunc ('test');
+}
diff --git a/js/src/tests/ecma_6/Comprehensions/generator-semantics.js b/js/src/tests/ecma_6/Comprehensions/generator-semantics.js
new file mode 100644
index 000000000..52426115f
--- /dev/null
+++ b/js/src/tests/ecma_6/Comprehensions/generator-semantics.js
@@ -0,0 +1,52 @@
+// Interaction of eval with generator expressions.
+function a1() {
+ var a = 10;
+ var g = (for (y of [0]) eval('var a=42;'));
+ g.next();
+ return a;
+}
+assertEq(a1(), 10);
+
+function a2() {
+ var a = 10;
+ (for (y of [0]) eval('a=42')).next();
+ return a;
+}
+assertEq(a2(), 42)
+
+// Arguments and this.
+function b1() {
+ return [for (arg of (for (i of [0, 1, 2]) arguments[i])) arg];
+}
+assertDeepEq(b1('a', 'b', 'c'), ['a', 'b', 'c']);
+
+function b2() {
+ return [for (x of (for (i of [0]) this)) x];
+}
+var b2o = { b2: b2 }
+assertDeepEq(b2o.b2(), [b2o])
+
+// Assignment to eval or arguments.
+function c1() {
+ return [for (arg of (for (i of [0, 1, 2]) arguments = i)) arg];
+}
+assertDeepEq(c1(), [0, 1, 2]);
+
+function c2() {
+ "use strict";
+ return eval('[for (arg of (for (i of [0, 1, 2]) arguments = i)) arg]');
+}
+assertThrows(c2, SyntaxError);
+
+function c3() {
+ return [for (arg of (for (i of [0, 1, 2]) eval = i)) arg];
+}
+assertDeepEq(c3(), [0, 1, 2]);
+
+function c4() {
+ "use strict";
+ return eval('[for (arg of (for (i of [0, 1, 2]) eval = i)) arg]');
+}
+assertThrows(c4, SyntaxError);
+
+reportCompare(null, null, "test");
diff --git a/js/src/tests/ecma_6/Comprehensions/nested-for-if.js b/js/src/tests/ecma_6/Comprehensions/nested-for-if.js
new file mode 100644
index 000000000..4e3291182
--- /dev/null
+++ b/js/src/tests/ecma_6/Comprehensions/nested-for-if.js
@@ -0,0 +1,20 @@
+// For and if clauses can nest without limit in comprehensions. This is
+// unlike JS 1.8 comprehensions, which can only have one trailing "if"
+// clause.
+
+function* range(start, end) {
+ for (var n = start; n < end; n++)
+ yield n;
+}
+
+function primesBetween6And25() {
+ return [for (n of range(6, 25)) if (n % 2) if (n % 3) if (n % 5) n];
+}
+assertDeepEq(primesBetween6And25(), [7,11,13,17,19,23]);
+
+function countUpToEvens(limit) {
+ return [for (n of range(0, limit)) if (!(n % 2)) for (m of range(0, n)) m]
+}
+assertDeepEq(countUpToEvens(7), [0,1,0,1,2,3,0,1,2,3,4,5]);
+
+reportCompare(null, null, "test");
diff --git a/js/src/tests/ecma_6/Comprehensions/scope.js b/js/src/tests/ecma_6/Comprehensions/scope.js
new file mode 100644
index 000000000..e9083d552
--- /dev/null
+++ b/js/src/tests/ecma_6/Comprehensions/scope.js
@@ -0,0 +1,18 @@
+// The identifier of a ComprehensionFor is only bound within its tail.
+
+function t() {
+ var x = [0, 1, 2];
+ return [for (x of x) x*2]
+}
+assertDeepEq(t(), [0, 2, 4]);
+
+// Each iteration should create a fresh binding. Unfortunately this is
+// not currently the case, but bug 449811 will fix this.
+function t2() {
+ var x = [0, 1, 2];
+ return [for (x of x) ()=>x]
+}
+// FIXME: Should be [0, 1, 2].
+assertDeepEq([for (x of t2()) x()], [2, 2, 2]);
+
+reportCompare(null, null, "test");
diff --git a/js/src/tests/ecma_6/Comprehensions/shell.js b/js/src/tests/ecma_6/Comprehensions/shell.js
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/js/src/tests/ecma_6/Comprehensions/shell.js
diff --git a/js/src/tests/ecma_6/Comprehensions/sudoku.js b/js/src/tests/ecma_6/Comprehensions/sudoku.js
new file mode 100644
index 000000000..680f55f64
--- /dev/null
+++ b/js/src/tests/ecma_6/Comprehensions/sudoku.js
@@ -0,0 +1,168 @@
+/* -*- tab-width: 2; indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+function copy(obj) {
+ var o = {};
+ for (var i in obj)
+ o[i] = obj[i];
+ return o;
+}
+
+Array.prototype.repeat = function (n) {
+ var s = this.constructor();
+ for (var i = 0; i < n; i++)
+ s = s.concat(this);
+ return s;
+}
+
+String.prototype.center = function (w) {
+ var n = this.length;
+ if (w <= n)
+ return this;
+ var m = Math.floor((w - n) / 2);
+ return ' '.repeat(m) + this + ' '.repeat(w - n - m);
+}
+
+Array.prototype.toString = Array.prototype.toSource
+Object.prototype.toString = Object.prototype.toSource
+
+function all(seq) {
+ for (var e of seq)
+ if (!e)
+ return false;
+ return true;
+}
+
+function some(seq) {
+ for (var e of seq)
+ if (e)
+ return e;
+ return false;
+}
+
+function cross(A, B) {
+ return [for (a of A) for (b of B) a+b];
+}
+
+function dict(A) {
+ var d = {};
+ for (var e of A)
+ d[e[0]] = e[1];
+ return d;
+}
+
+function set(A) {
+ var s = [];
+ for (var e of A)
+ if (!s.includes(e))
+ s.push(e);
+ return s;
+}
+
+function zip(A, B) {
+ var z = [];
+ var n = Math.min(A.length, B.length);
+ for (var i = 0; i < n; i++)
+ z.push([A[i], B[i]]);
+ return z;
+}
+
+rows = 'ABCDEFGHI';
+cols = '123456789';
+digits = '123456789';
+squares = cross(rows, cols);
+unitlist = [for (c of cols) cross(rows, c)]
+ .concat([for (r of rows) cross(r, cols)])
+ .concat([for (rs of ['ABC','DEF','GHI']) for (cs of ['123','456','789']) cross(rs, cs)]);
+units = dict((for (s of squares)
+ [s, [for (u of unitlist) if (u.includes(s)) u]]));
+
+peers = dict((for (s of squares)
+ [s, set([for (u of units[s]) for (s2 of u) if (s2 != s) s2])]));
+
+// Given a string of 81 digits (or . or 0 or -), return a dict of {cell:values}.
+function parse_grid(grid) {
+ grid = [for (c of grid) if ('0.-123456789'.includes(c)) c];
+ var values = dict((for (s of squares) [s, digits]));
+
+ for (var pair of zip(squares, grid)) {
+ var s = pair[0], d = pair[1];
+ if (digits.includes(d) && !assign(values, s, d))
+ return false;
+ }
+ return values;
+}
+
+// Eliminate all the other values (except d) from values[s] and propagate.
+function assign(values, s, d) {
+ if (all((for (d2 of values[s]) if (d2 != d) eliminate(values, s, d2))))
+ return values;
+ return false;
+}
+
+// Eliminate d from values[s]; propagate when values or places <= 2.
+function eliminate(values, s, d) {
+ if (!values[s].includes(d))
+ return values; // Already eliminated
+ values[s] = values[s].replace(d, '');
+ if (values[s].length == 0)
+ return false; // Contradiction: removed last value
+ if (values[s].length == 1) {
+ // If there is only one value (d2) left in square, remove it from peers
+ var d2 = values[s][0];
+ if (!all((for (s2 of peers[s]) eliminate(values, s2, d2))))
+ return false;
+ }
+ // Now check the places where d appears in the units of s
+ for (var u of units[s]) {
+ var dplaces = [for (s of u) if (values[s].includes(d)) s];
+ if (dplaces.length == 0)
+ return false;
+ if (dplaces.length == 1)
+ // d can only be in one place in unit; assign it there
+ if (!assign(values, dplaces[0], d))
+ return false;
+ }
+ return values;
+}
+
+// Used for debugging.
+function print_board(values) {
+ var width = 1 + Math.max.apply(Math, [for (s of squares) values[s].length]);
+ var line = '\n' + ['-'.repeat(width*3)].repeat(3).join('+');
+ for (var r of rows)
+ print([for (c of cols)
+ values[r+c].center(width) + ('36'.includes(c) && '|' || '')]
+ .join('') + ('CF'.includes(r) && line || ''));
+ print('\n');
+}
+
+easy = "..3.2.6..9..3.5..1..18.64....81.29..7.......8..67.82....26.95..8..2.3..9..5.1.3..";
+
+print_board(parse_grid(easy));
+
+// Using depth-first search and constraint propagation, try all possible values.
+function search(values) {
+ if (!values)
+ return false; // Failed earlier
+ if (all((for (s of squares) values[s].length == 1)))
+ return values; // Solved!
+
+ // Choose the unfilled square s with the fewest possibilities
+ // XXX Math.min etc. should work with generator expressions and other iterators
+ // XXX Math.min etc. should work on arrays (lists or tuples in Python) as well as numbers
+ var a = [for (s of squares) if (values[s].length > 1) values[s].length + s].sort();
+ var s = a[0].slice(-2);
+
+ return some((for (d of values[s]) search(assign(copy(values), s, d))));
+}
+
+hard = '4.....8.5.3..........7......2.....6.....8.4......1.......6.3.7.5..2.....1.4......';
+
+print_board(search(parse_grid(hard)))
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/ecma_6/Comprehensions/syntax.js b/js/src/tests/ecma_6/Comprehensions/syntax.js
new file mode 100644
index 000000000..a57b1e6dd
--- /dev/null
+++ b/js/src/tests/ecma_6/Comprehensions/syntax.js
@@ -0,0 +1,6 @@
+// "let" is not allowed as an identifier.
+
+assertThrowsInstanceOf(function () { eval('[for (let of y) foo]') }, SyntaxError);
+assertThrowsInstanceOf(function () { eval('(for (let of y) foo)') }, SyntaxError);
+
+reportCompare(null, null, "test");
diff --git a/js/src/tests/ecma_6/Comprehensions/toSource.js b/js/src/tests/ecma_6/Comprehensions/toSource.js
new file mode 100644
index 000000000..5ce1db172
--- /dev/null
+++ b/js/src/tests/ecma_6/Comprehensions/toSource.js
@@ -0,0 +1,14 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+assertEq( function () { g = (for (d of [0]) d); g.next(); }.toSource(),
+ '(function () { g = (for (d of [0]) d); g.next(); })');
+
+
+assertEq( function () { return [for (d of [0]) d]; }.toSource(),
+ '(function () { return [for (d of [0]) d]; })');
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);