summaryrefslogtreecommitdiffstats
path: root/js/src/tests/ecma_6/TemplateStrings
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/tests/ecma_6/TemplateStrings')
-rw-r--r--js/src/tests/ecma_6/TemplateStrings/browser.js2
-rw-r--r--js/src/tests/ecma_6/TemplateStrings/debugLineNumber.js50
-rw-r--r--js/src/tests/ecma_6/TemplateStrings/lineNumber.js72
-rw-r--r--js/src/tests/ecma_6/TemplateStrings/noSubst.js148
-rw-r--r--js/src/tests/ecma_6/TemplateStrings/shell.js1
-rw-r--r--js/src/tests/ecma_6/TemplateStrings/tagTempl.js291
-rw-r--r--js/src/tests/ecma_6/TemplateStrings/templLit.js118
7 files changed, 682 insertions, 0 deletions
diff --git a/js/src/tests/ecma_6/TemplateStrings/browser.js b/js/src/tests/ecma_6/TemplateStrings/browser.js
new file mode 100644
index 000000000..139597f9c
--- /dev/null
+++ b/js/src/tests/ecma_6/TemplateStrings/browser.js
@@ -0,0 +1,2 @@
+
+
diff --git a/js/src/tests/ecma_6/TemplateStrings/debugLineNumber.js b/js/src/tests/ecma_6/TemplateStrings/debugLineNumber.js
new file mode 100644
index 000000000..2cf41cd9c
--- /dev/null
+++ b/js/src/tests/ecma_6/TemplateStrings/debugLineNumber.js
@@ -0,0 +1,50 @@
+// 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/.
+
+// TEST BEGIN
+
+// verify debugger line numbers are accurate
+try {
+ `
+ a
+ b
+ c
+ `;
+ throw Error("error");
+} catch (e) {
+ assertEq(e.lineNumber, 14);
+}
+
+try {
+ function tagThatThrows(...args) { throw new Error(); }
+
+ tagThatThrows`
+ multi-line
+ template
+ string`;
+} catch (e) {
+ var stackLines = e.stack.split('\n');
+ var firstLine = stackLines[0].split(':');
+ var secondLine = stackLines[1].split(':');
+ var firstLineSize = firstLine.length;
+ var secondLineSize = secondLine.length;
+ assertEq(firstLine[firstLineSize - 2], "20");
+ assertEq(firstLine[firstLineSize - 1], "45");
+ assertEq(secondLine[secondLineSize - 2], "22");
+ assertEq(secondLine[secondLineSize - 1], "5");
+}
+
+try {
+ ` multi-line
+ template
+ with
+ ${substitutionThatThrows()}`
+
+} catch (e) {
+ assertEq(e.lineNumber, 42);
+}
+
+
+
+reportCompare(0, 0, "ok");
diff --git a/js/src/tests/ecma_6/TemplateStrings/lineNumber.js b/js/src/tests/ecma_6/TemplateStrings/lineNumber.js
new file mode 100644
index 000000000..6163d5421
--- /dev/null
+++ b/js/src/tests/ecma_6/TemplateStrings/lineNumber.js
@@ -0,0 +1,72 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/ */
+
+var BUGNUMBER = 1253847;
+var summary = 'Line numbers should be updated correctly when a template literal contains CRLF sequences.';
+
+print(BUGNUMBER + ": " + summary);
+
+// lineNumber after template literal.
+var base, x, lineno;
+eval([
+ "base = new Error().lineNumber;",
+ "x = `",
+ "`;",
+ "lineno = new Error().lineNumber;",
+].join("\r\n"));
+assertEq(lineno, base + 3);
+
+eval([
+ "base = new Error().lineNumber;",
+ "x = `",
+ "",
+ "`;",
+ "lineno = new Error().lineNumber;",
+].join("\r\n"));
+assertEq(lineno, base + 4);
+
+eval([
+ "base = new Error().lineNumber;",
+ "x = `a",
+ "b`;",
+ "lineno = new Error().lineNumber;",
+].join("\r\n"));
+assertEq(lineno, base + 3);
+
+// lineNumber inside template literal
+eval([
+ "base = new Error().lineNumber;",
+ "x = `${new Error().lineNumber}",
+ "${new Error().lineNumber}",
+ "${new Error().lineNumber}`;",
+ "lineno = new Error().lineNumber;",
+].join("\r\n"));
+assertEq(lineno, base + 4);
+assertEq(x, (base + 1) + '\n' + (base + 2) + '\n' + (base + 3));
+
+eval([
+ "var base = new Error().lineNumber;",
+ "var x = `",
+ "${new Error().lineNumber}",
+ "${new Error().lineNumber}",
+ "${new Error().lineNumber}",
+ "`;",
+ "var lineno = new Error().lineNumber;",
+].join("\r\n"));
+assertEq(lineno, base + 6);
+assertEq(x, '\n' + (base + 2) + '\n' + (base + 3) + '\n' + (base + 4) + '\n');
+
+// When CR, LF, and CRLF are mixed.
+eval([
+ "var base = new Error().lineNumber;", "\n",
+ "var x = `${new Error().lineNumber}", "\n",
+ "${new Error().lineNumber}", "\r\n",
+ "${new Error().lineNumber}", "\r",
+ "${new Error().lineNumber}`;", "\n",
+ "var lineno = new Error().lineNumber;",
+].join(""));
+assertEq(lineno, base + 5);
+assertEq(x, (base + 1) + '\n' + (base + 2) + '\n' + (base + 3) + '\n' + (base + 4));
+
+if (typeof reportCompare === "function")
+ reportCompare(true, true);
diff --git a/js/src/tests/ecma_6/TemplateStrings/noSubst.js b/js/src/tests/ecma_6/TemplateStrings/noSubst.js
new file mode 100644
index 000000000..730275fd9
--- /dev/null
+++ b/js/src/tests/ecma_6/TemplateStrings/noSubst.js
@@ -0,0 +1,148 @@
+// 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 test case is weird in the sense the actual work happens at the eval
+// at the end. If template strings are not enabled, the test cases would throw
+// a syntax error and we'd have failure reported. To avoid that, the entire
+// test case is commented out and is part of a function. We use toString to
+// get the string version, obtain the actual lines to run, and then use eval to
+// do the actual evaluation.
+
+function syntaxError (script) {
+ try {
+ Function(script);
+ } catch (e) {
+ if (e.name === "SyntaxError") {
+ return;
+ }
+ }
+ throw "Expected syntax error: " + script;
+}
+
+// TEST BEGIN
+
+// unterminated quasi literal
+syntaxError("`");
+syntaxError("`$");
+syntaxError("`${");
+syntaxError("`${}");
+syntaxError("`${1}");
+syntaxError("`${1 + 2}");
+
+// almost template substitutions
+assertEq("$", `$`);
+assertEq("$}", `$}`);
+assertEq("}", `}`);
+assertEq("{", `{`);
+
+
+// character escape sequence (single escape character)
+assertEq("\'", `\'`);
+assertEq("\"", `\"`);
+assertEq("\\", `\\`);
+assertEq("\b", `\b`);
+assertEq("\f", `\f`);
+assertEq("\n", `\n`);
+assertEq("\r", `\r`);
+assertEq("\t", `\t`);
+assertEq("\v", `\v`);
+assertEq("\r\n", `\r\n`);
+
+
+assertEq("\0", eval("`\\" + String.fromCharCode(0) + "`"));
+assertEq("$", `\$`);
+assertEq(".", `\.`);
+assertEq("A", `\A`);
+assertEq("a", `\a`);
+
+
+// digit escape sequence
+assertEq("\0", `\0`);
+syntaxError("`\\1`");
+syntaxError("`\\2`");
+syntaxError("`\\3`");
+syntaxError("`\\4`");
+syntaxError("`\\5`");
+syntaxError("`\\6`");
+syntaxError("`\\7`");
+syntaxError("`\\01`");
+syntaxError("`\\001`");
+syntaxError("`\\00`");
+
+// hex escape sequence
+syntaxError("`\\x`");
+syntaxError("`\\x0`");
+syntaxError("`\\x0Z`");
+syntaxError("`\\xZ`");
+
+assertEq("\0", `\x00`);
+assertEq("$", `\x24`);
+assertEq(".", `\x2E`);
+assertEq("A", `\x41`);
+assertEq("a", `\x61`);
+assertEq("AB", `\x41B`);
+assertEq(String.fromCharCode(0xFF), `\xFF`);
+
+
+// unicode escape sequence
+
+assertEq("\0", `\u0000`);
+assertEq("$", `\u0024`);
+assertEq(".", `\u002E`);
+assertEq("A", `\u0041`);
+assertEq("a", `\u0061`);
+assertEq("AB", `\u0041B`);
+assertEq(String.fromCharCode(0xFFFF), `\uFFFF`);
+
+
+// line continuation
+assertEq("", eval("`\\\n`"));
+assertEq("", eval("`\\\r`"));
+assertEq("", eval("`\\\u2028`"));
+assertEq("", eval("`\\\u2029`"));
+assertEq("\u2028", eval("`\u2028`"));
+assertEq("\u2029", eval("`\u2029`"));
+
+assertEq("a\nb", eval("`a\rb`"))
+assertEq("a\nb", eval("`a\r\nb`"))
+assertEq("a\n\nb", eval("`a\r\rb`"))
+
+
+// source character
+for (var i = 0; i < 0xFF; ++i) {
+ var c = String.fromCharCode(i);
+ if (c == "`" || c == "\\" || c == "\r") continue;
+ assertEq(c, eval("`" + c + "`"));
+}
+
+assertEq("", ``);
+assertEq("`", `\``);
+assertEq("$", `$`);
+assertEq("$$", `$$`);
+assertEq("$$}", `$$}`);
+
+// multi-line
+assertEq(`hey
+there`, "hey\nthere");
+
+// differences between strings and template strings
+syntaxError("var obj = { `illegal`: 1}");
+
+// test for JSON.parse
+assertThrowsInstanceOf(() => JSON.parse('[1, `false`]'), SyntaxError);
+
+syntaxError('({get `name`() { return 10; }});');
+
+// test for "use strict" directive
+assertEq(5, Function("`use strict`; return 05;")());
+var func = function f() {
+ `ignored string`;
+ "use strict";
+ return 06;
+}
+assertEq(6, func());
+syntaxError("\"use strict\"; return 06;");
+
+
+reportCompare(0, 0, "ok");
diff --git a/js/src/tests/ecma_6/TemplateStrings/shell.js b/js/src/tests/ecma_6/TemplateStrings/shell.js
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/js/src/tests/ecma_6/TemplateStrings/shell.js
@@ -0,0 +1 @@
+
diff --git a/js/src/tests/ecma_6/TemplateStrings/tagTempl.js b/js/src/tests/ecma_6/TemplateStrings/tagTempl.js
new file mode 100644
index 000000000..1e3f52bfb
--- /dev/null
+++ b/js/src/tests/ecma_6/TemplateStrings/tagTempl.js
@@ -0,0 +1,291 @@
+// 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 test case is weird in the sense the actual work happens at the eval
+// at the end. If template strings are not enabled, the test cases would throw
+// a syntax error and we'd have failure reported. To avoid that, the entire
+// test case is commented out and is part of a function. We use toString to
+// get the string version, obtain the actual lines to run, and then use eval to
+// do the actual evaluation.
+
+function syntaxError (script) {
+ try {
+ Function(script);
+ } catch (e) {
+ if (e.name === "SyntaxError") {
+ return;
+ }
+ }
+ throw new Error('Expected syntax error: ' + script);
+}
+
+// function definitions
+function check(actual, expected) {
+ assertEq(actual.length, expected.length);
+ for (var i = 0; i < expected.length; i++)
+ assertEq(actual[i], expected[i]);
+}
+
+function cooked(cs) { return cs; }
+function raw(cs) { return cs.raw; }
+function args(cs, ...rest) { return rest; }
+
+
+// TEST BEGIN
+
+// Literals
+check(raw``, [""]);
+check(raw`${4}a`, ["","a"]);
+check(raw`${4}`, ["",""]);
+check(raw`a${4}`, ["a",""]);
+check(raw`a${4}b`, ["a","b"]);
+check(raw`a${4}b${3}`, ["a","b",""]);
+check(raw`a${4}b${3}c`, ["a","b","c"]);
+check(raw`a${4}${3}c`, ["a","","c"]);
+check(raw`${4}${3}`, ["","",""]);
+check(raw`${4}\r\r${3}`, ["","\\r\\r",""]);
+check(raw`${4}${3}c`, ["","","c"]);
+check(raw`zyx${4}wvut${3}c`, ["zyx","wvut","c"]);
+check(raw`zyx${4}wvut${3}\r\n`, ["zyx","wvut","\\r\\n"]);
+
+check(raw`hey`, ["hey"]);
+check(raw`he\r\ny`, ["he\\r\\ny"]);
+check(raw`he\ry`, ["he\\ry"]);
+check(raw`he\r\ry`, ["he\\r\\ry"]);
+check(raw`he\ny`, ["he\\ny"]);
+check(raw`he\n\ny`, ["he\\n\\ny"]);
+
+check(cooked`hey`, ["hey"]);
+check(cooked`he\r\ny`, ["he\r\ny"]);
+check(cooked`he\ry`, ["he\ry"]);
+check(cooked`he\r\ry`, ["he\r\ry"]);
+check(cooked`he\ny`, ["he\ny"]);
+check(cooked`he\n\ny`, ["he\n\ny"]);
+
+check(eval("raw`\r`"), ["\n"]);
+check(eval("raw`\r\n`"), ["\n"]);
+check(eval("raw`\r\r\n`"), ["\n\n"]);
+check(eval("raw`he\r\ny`"), ["he\ny"]);
+check(eval("raw`he\ry`"), ["he\ny"]);
+check(eval("raw`he\r\ry`"), ["he\n\ny"]);
+check(eval("raw`he\r\r\ny`"), ["he\n\ny"]);
+
+
+check(eval("cooked`\r`"), ["\n"]);
+check(eval("cooked`\r\n`"), ["\n"]);
+check(eval("cooked`\r\r\n`"), ["\n\n"]);
+check(eval("cooked`he\r\ny`"), ["he\ny"]);
+check(eval("cooked`he\ry`"), ["he\ny"]);
+check(eval("cooked`he\r\ry`"), ["he\n\ny"]);
+check(eval("cooked`he\r\r\ny`"), ["he\n\ny"]);
+
+// Expressions
+check(args`hey${"there"}now`, ["there"]);
+check(args`hey${4}now`, [4]);
+check(args`hey${4}`, [4]);
+check(args`${4}`, [4]);
+check(args`${4}${5}`, [4,5]);
+check(args`a${4}${5}`, [4,5]);
+check(args`a${4}b${5}`, [4,5]);
+check(args`a${4}b${5}c`, [4,5]);
+check(args`${4}b${5}c`, [4,5]);
+check(args`${4}${5}c`, [4,5]);
+
+var a = 10;
+var b = 15;
+check(args`${4 + a}${5 + b}c`, [14,20]);
+check(args`${4 + a}${a + b}c`, [14,25]);
+check(args`${b + a}${5 + b}c`, [25,20]);
+check(args`${4 + a}${a + b}c${"a"}`, [14,25,"a"]);
+check(args`a${"b"}${"c"}${"d"}`, ["b","c","d"]);
+check(args`a${"b"}${"c"}${a + b}`, ["b","c",25]);
+check(args`a${"b"}`, ["b"]);
+
+// Expressions - complex substitutions
+check(args`${`hey ${b + a} there`}${5 + b}c`, ["hey 25 there",20]);
+check(args`${`hey ${`my ${b + a} good`} there`}${5 + b}c`, ["hey my 25 good there",20]);
+
+syntaxError("args`${}`");
+syntaxError("args`${`");
+syntaxError("args`${\\n}`");
+syntaxError("args`${yield 0}`");
+syntaxError("args`");
+syntaxError("args`$");
+syntaxError("args`${");
+syntaxError("args.``");
+
+// Template substitution tests in the context of tagged templates
+// Extra whitespace inside a template substitution is ignored.
+check(args`a${
+0
+}`, [0]);
+
+// Extra whitespace between tag and template is ignored
+check(args
+`a
+${
+0
+}`, [0]);
+
+
+check(args`${5}${ // Comments work in template substitutions.
+// Even comments that look like code:
+// 0}`, "FAIL"); /* NOTE: This whole line is a comment.
+0}`, [5,0]);
+
+check(args // Comments work in template substitutions.
+// Even comments that look like code:
+// 0}`, "FAIL"); /* NOTE: This whole line is a comment.
+`${5}${0}`, [5,0]);
+
+
+// Template substitutions are expressions, not statements.
+syntaxError("args`${0;}`");
+check(args`${
+ function f() {
+ return "ok";
+ }()
+}`, ["ok"]);
+
+// Template substitutions can have side effects.
+var x = 0;
+check(args`${x += 1}`, [1]);
+assertEq(x, 1);
+
+// The production for a template substitution is Expression, not
+// AssignmentExpression.
+x = 0;
+check(args`${++x, "o"}k`, ["o"]);
+assertEq(x, 1);
+
+
+// --> is not a comment inside a template.
+check(cooked`
+--> this is text
+`, ["\n--> this is text\n"]);
+
+// reentrancy
+function f(n) {
+ if (n === 0)
+ return "";
+ var res = args`${n}${f(n - 1)}`;
+ return res[0] + res[1] + "";
+}
+assertEq(f(9), "987654321");
+
+// Template string substitutions in generator functions can yield.
+function* g() {
+ var res = args`${yield 1} ${yield 2}`;
+ return res[0] + res[1] + "";
+}
+
+var it = g();
+var next = it.next();
+assertEq(next.done, false);
+assertEq(next.value, 1);
+next = it.next("hello");
+assertEq(next.done, false);
+assertEq(next.value, 2);
+next = it.next("world");
+assertEq(next.done, true);
+assertEq(next.value, "helloworld");
+
+// undefined
+assertEq(args`${void 0}`[0] + "", "undefined");
+assertEq(args`${Object.doesNotHaveThisProperty}`[0] + "", "undefined");
+
+var callSiteObj = [];
+callSiteObj[0] = cooked`aa${4}bb`;
+for (var i = 1; i < 3; i++)
+ callSiteObj[i] = cooked`aa${4}bb`;
+
+// Same call site object behavior
+assertEq(callSiteObj[1], callSiteObj[2]);
+assertEq(callSiteObj[0] !== callSiteObj[1], true);
+assertEq("raw" in callSiteObj[0], true);
+
+// Array length
+assertEq(callSiteObj[0].raw.length, 2);
+assertEq(callSiteObj[0].length, 2);
+
+// Frozen objects
+assertEq(Object.isFrozen(callSiteObj[0]), true);
+assertEq(Object.isFrozen(callSiteObj[0].raw), true);
+
+// Raw not enumerable
+assertEq(callSiteObj[0].propertyIsEnumerable(callSiteObj[0].raw), false);
+
+// Allow call syntax
+check(new ((cs, sub) => function(){ return sub }) `${[1, 2, 3]}`, [1,2,3]);
+
+var a = [];
+function test() {
+ var x = callSite => callSite;
+ for (var i = 0; i < 2; i++)
+ a[i] = eval("x``");
+}
+test();
+assertEq(a[0] !== a[1], true);
+
+// Test that |obj.method`template`| works
+var newObj = {
+ methodRetThis : function () {
+ return this;
+ },
+ methodRetCooked : function (a) {
+ return a;
+ },
+ methodRetRaw : function (a) {
+ return a.raw;
+ },
+ methodRetArgs : function (a, ...args) {
+ return args;
+ }
+}
+
+assertEq(newObj.methodRetThis`abc${4}`, newObj);
+check(newObj.methodRetCooked`abc${4}\r`, ["abc","\r"]);
+check(eval("newObj.methodRetCooked`abc${4}\r`"), ["abc","\n"]);
+check(newObj.methodRetRaw`abc${4}\r`, ["abc","\\r"]);
+check(eval("newObj.methodRetRaw`abc${4}\r`"), ["abc","\n"]);
+check(eval("newObj.methodRetArgs`abc${4}${5}\r${6}`"), [4,5,6]);
+
+// Chained calls
+function func(a) {
+ if (a[0] === "hey") {
+ return function(a) {
+ if (a[0] === "there") {
+ return function(a) {
+ if (a[0] === "mine")
+ return "was mine";
+ else
+ return "was not mine";
+ }
+ } else {
+ return function(a) {
+ return "was not there";
+ }
+ }
+ }
+ } else {
+ return function(a) {
+ return function(a) {
+ return "was not hey";
+ }
+ }
+ }
+}
+
+assertEq(func`hey``there``mine`, "was mine");
+assertEq(func`hey``there``amine`, "was not mine");
+assertEq(func`hey``tshere``amine`, "was not there");
+assertEq(func`heys``there``mine`, "was not hey");
+
+// String.raw
+assertEq(String.raw`h\r\ney${4}there\n`, "h\\r\\ney4there\\n");
+assertEq(String.raw`hey`, "hey");
+assertEq(String.raw``, "");
+
+
+reportCompare(0, 0, "ok");
diff --git a/js/src/tests/ecma_6/TemplateStrings/templLit.js b/js/src/tests/ecma_6/TemplateStrings/templLit.js
new file mode 100644
index 000000000..0b85ad7d1
--- /dev/null
+++ b/js/src/tests/ecma_6/TemplateStrings/templLit.js
@@ -0,0 +1,118 @@
+// 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 test case is weird in the sense the actual work happens at the eval
+// at the end. If template strings are not enabled, the test cases would throw
+// a syntax error and we'd have failure reported. To avoid that, the entire
+// test case is commented out and is part of a function. We use toString to
+// get the string version, obtain the actual lines to run, and then use eval to
+// do the actual evaluation.
+
+function syntaxError (script) {
+ try {
+ Function(script);
+ } catch (e) {
+ if (e.name === "SyntaxError") {
+ return;
+ }
+ }
+ throw new Error('Expected syntax error: ' + script);
+}
+
+// TEST BEGIN
+
+
+
+// combinations of substitutions
+assertEq("abcdef", `ab${"cd"}ef`);
+assertEq("ab9ef", `ab${4+5}ef`);
+assertEq("cdef", `${"cd"}ef`);
+assertEq("abcd", `ab${"cd"}`);
+assertEq("cd", `${"cd"}`);
+assertEq("", `${""}`);
+assertEq("4", `${4}`);
+
+// multiple substitutions
+assertEq("abcdef", `ab${"cd"}e${"f"}`);
+assertEq("abcdef", `ab${"cd"}${"e"}f`);
+assertEq("abcdef", `a${"b"}${"cd"}e${"f"}`);
+assertEq("abcdef", `${"ab"}${"cd"}${"ef"}`);
+
+// inception
+assertEq("abcdef", `a${`b${"cd"}e${"f"}`}`);
+
+syntaxError("`${}`");
+syntaxError("`${`");
+syntaxError("`${\\n}`");
+syntaxError("`${yield 0}`");
+
+// Extra whitespace inside a template substitution is ignored.
+assertEq(`${
+0
+}`, "0");
+
+assertEq(`${ // Comments work in template substitutions.
+// Even comments that look like code:
+// 0}`, "FAIL"); /* NOTE: This whole line is a comment.
+0}`, "0");
+
+// Template substitutions are expressions, not statements.
+syntaxError("`${0;}`");
+assertEq(`${{}}`, "[object Object]");
+assertEq(`${
+ function f() {
+ return "ok";
+ }()
+}`, "ok");
+
+// Template substitutions can have side effects.
+var x = 0;
+assertEq(`= ${x += 1}`, "= 1");
+assertEq(x, 1);
+
+// The production for a template substitution is Expression, not
+// AssignmentExpression.
+x = 0;
+assertEq(`${++x, "o"}k`, "ok");
+assertEq(x, 1);
+
+// --> is not a comment inside a template.
+assertEq(`
+--> this is text
+`, "\n--> this is text\n");
+
+// reentrancy
+function f(n) {
+ if (n === 0)
+ return "";
+ return `${n}${f(n - 1)}`;
+}
+assertEq(f(9), "987654321");
+
+// Template string substitutions in generator functions can yield.
+function* g() {
+ return `${yield 1} ${yield 2}`;
+}
+
+var it = g();
+var next = it.next();
+assertEq(next.done, false);
+assertEq(next.value, 1);
+next = it.next("hello");
+assertEq(next.done, false);
+assertEq(next.value, 2);
+next = it.next("world");
+assertEq(next.done, true);
+assertEq(next.value, "hello world");
+
+// undefined
+assertEq(`${void 0}`, "undefined");
+assertEq(`${Object.doesNotHaveThisProperty}`, "undefined");
+
+// toString behavior
+assertEq("<toString>", `${{valueOf: () => "<valueOf>", toString: () => "<toString>"}}`);
+assertEq("Hi 42", Function("try {`${{toString: () => { throw 42;}}}`} catch(e) {return \"Hi \" + e;}")());
+
+
+reportCompare(0, 0, "ok");