load(libdir + "class.js");

function test_syntax(replacements, check_error, ignore_opts) {
  function test_reflect(code) {
    for (var repl of replacements) {
      var cur_code = code.replace(/@/, repl);

      var caught = false;
      try {
        Reflect.parse(cur_code);
      } catch (e) {
        caught = true;
        check_error(e, cur_code, "reflect");
      }
      assertEq(caught, true);
    }
  }

  function test_eval(code) {
    for (var repl of replacements) {
      var cur_code = code.replace(/@/, repl);

      var caught = false;
      try {
        eval(cur_code);
      } catch (e) {
        caught = true;
        check_error(e, cur_code, "eval");
      }
      assertEq(caught, true);
    }
  }

  function test(code, opts={}) {
    if (ignore_opts) {
      opts = {};
    }

    let no_strict = "no_strict" in opts && opts.no_strict;
    let no_fun = "no_fun" in opts && opts.no_fun;
    let no_eval = "no_eval" in opts && opts.no_eval;

    test_reflect(code);
    if (!no_strict) {
      test_reflect("'use strict'; " + code);
    }
    if (!no_fun) {
      test_reflect("(function() { " + code);
      if (!no_strict) {
        test_reflect("(function() { 'use strict'; " + code);
      }
    }

    if (!no_eval) {
      test_eval(code);
      if (!no_strict) {
        test_eval("'use strict'; " + code);
      }
      if (!no_fun) {
        test_eval("(function() { " + code);
        if (!no_strict) {
          test_eval("(function() { 'use strict'; " + code);
        }
      }
    }
  }

  function test_no_strict(code) {
    test(code, { no_strict: true });
  }

  function test_no_fun_no_eval(code) {
    test(code, { no_fun: true, no_eval: true });
  }

  function test_fun_arg(arg) {
    for (var repl of replacements) {
      var cur_arg = arg.replace(/@/, repl);

      var caught = false;
      try {
        new Function(cur_arg, "");
      } catch (e) {
        caught = true;
        check_error(e, cur_arg, "fun_arg");
      }
      assertEq(caught, true);
    }
  }

  // ==== Statements and declarations ====

  // ---- Control flow ----

  // Block

  test("{ @");
  test("{ } @");

  test("{ 1 @");
  test("{ 1; @");
  test("{ 1; } @");

  // break

  test("a: for (;;) { break @");
  test("a: for (;;) { break; @");
  test("a: for (;;) { break a @");
  test("a: for (;;) { break a; @");

  test("a: for (;;) { break\n@");

  // continue

  test("a: for (;;) { continue @");
  test("a: for (;;) { continue; @");
  test("a: for (;;) { continue a @");
  test("a: for (;;) { continue a; @");

  test("a: for (;;) { continue\n@");

  // Empty

  test("@");
  test("; @");

  // if...else

  test("if @");
  test("if (@");
  test("if (x @");
  test("if (x) @");
  test("if (x) { @");
  test("if (x) {} @");
  test("if (x) {} else @");
  test("if (x) {} else { @");
  test("if (x) {} else {} @");
  test("if (x) x @");
  test("if (x) x; @");
  test("if (x) x; else @");
  test("if (x) x; else y @");
  test("if (x) x; else y; @");

  // switch

  test("switch @");
  test("switch (@");
  test("switch (x @");
  test("switch (x) @");
  test("switch (x) { @");
  test("switch (x) { case @");
  test("switch (x) { case 1 @");
  test("switch (x) { case 1: @");
  test("switch (x) { case 1: case @");
  test("switch (x) { case 1: case 2 @");
  test("switch (x) { case 1: case 2: @");
  test("switch (x) { case 1: case 2: x @");
  test("switch (x) { case 1: case 2: x; @");
  test("switch (x) { case 1: case 2: x; break @");
  test("switch (x) { case 1: case 2: x; break; @");
  test("switch (x) { case 1: case 2: x; break; case @");
  test("switch (x) { case 1: case 2: x; break; case 3 @");
  test("switch (x) { case 1: case 2: x; break; case 3: y @");
  test("switch (x) { case 1: case 2: x; break; case 3: y; @");
  test("switch (x) { case 1: case 2: x; break; case 3: y; default @");
  test("switch (x) { case 1: case 2: x; break; case 3: y; default: @");
  test("switch (x) { case 1: case 2: x; break; case 3: y; default: z @");
  test("switch (x) { case 1: case 2: x; break; case 3: y; default: z; @");
  test("switch (x) { case 1: case 2: x; break; case 3: y; default: z; } @");

  // throw

  test("throw @");
  test("throw x @");
  test("throw x; @");

  // try...catch

  test("try @");
  test("try { @");
  test("try {} @");
  test("try {} catch @");
  test("try {} catch ( @");
  test("try {} catch (e @");
  test("try {} catch (e) @");
  test("try {} catch (e) { @");
  test("try {} catch (e) {} @");
  test("try {} catch (e) {} finally @");
  test("try {} catch (e) {} finally { @");
  test("try {} catch (e) {} finally {} @");

  test("try {} catch (e if @");
  test("try {} catch (e if e  @");
  test("try {} catch (e if e instanceof @");
  test("try {} catch (e if e instanceof x @");
  test("try {} catch (e if e instanceof x) @");
  test("try {} catch (e if e instanceof x) { @");
  test("try {} catch (e if e instanceof x) {} @");

  // ---- Declarations ----

  // var

  test("var @");
  test("var x @");
  test("var x = @");
  test("var x = 1 @");
  test("var x = 1 + @");
  test("var x = 1 + 2 @");
  test("var x = 1 + 2, @");
  test("var x = 1 + 2, y @");
  test("var x = 1 + 2, y, @");
  test("var x = 1 + 2, y, z @");
  test("var x = 1 + 2, y, z; @");

  test("var [ @");
  test("var [ x @");
  test("var [ x, @");
  test("var [ x, ... @");
  test("var { @");
  test("var { x @");
  test("var { x: @");
  test("var { x: y @");
  test("var { x: y, @");
  test("var { x: y } @");
  test("var { x: y } = @");

  // let

  test("let @");
  test("let x @");
  test("let x = @");
  test("let x = 1 @");
  test("let x = 1 + @");
  test("let x = 1 + 2 @");
  test("let x = 1 + 2, @");
  test("let x = 1 + 2, y @");
  test("let x = 1 + 2, y, @");
  test("let x = 1 + 2, y, z @");
  test("let x = 1 + 2, y, z; @");

  test("let [ @");
  test("let [ x @");
  test("let [ x, @");
  test("let [ x, ... @");
  test("let { @");
  test("let { x @");
  test("let { x: @");
  test("let { x: y @");
  test("let { x: y, @");
  test("let { x: y } @");
  test("let { x: y } = @");

  // const

  test("const @");
  test("const x @");
  test("const x = @");
  test("const x = 1 @");
  test("const x = 1 + @");
  test("const x = 1 + 2 @");
  test("const x = 1 + 2, @");
  test("const x = 1 + 2, y = 0@");
  test("const x = 1 + 2, y = 0, @");
  test("const x = 1 + 2, y = 0, z = 0 @");
  test("const x = 1 + 2, y = 0, z = 0; @");

  test("const [ @");
  test("const [ x @");
  test("const [ x, @");
  test("const [ x, ... @");
  test("const { @");
  test("const { x @");
  test("const { x: @");
  test("const { x: y @");
  test("const { x: y, @");
  test("const { x: y } @");
  test("const { x: y } = @");

  // ---- Functions ----

  // function

  test("function @");
  test("function f @");
  test("function f( @");
  test("function f(x @");
  test("function f(x, @");
  test("function f(x, [ @");
  test("function f(x, [y @");
  test("function f(x, [y, @");
  test("function f(x, [y, { @");
  test("function f(x, [y, {z @");
  test("function f(x, [y, {z: @");
  test("function f(x, [y, {z: zz @");
  test("function f(x, [y, {z: zz,  @");
  test("function f(x, [y, {z: zz, w @");
  test("function f(x, [y, {z: zz, w} @");
  test("function f(x, [y, {z: zz, w}] @");
  test("function f(x, [y, {z: zz, w}], @");
  test("function f(x, [y, {z: zz, w}], v @");
  test("function f(x, [y, {z: zz, w}], v= @");
  test("function f(x, [y, {z: zz, w}], v=1 @");
  test("function f(x, [y, {z: zz, w}], v=1, @");
  test("function f(x, [y, {z: zz, w}], v=1, ... @");
  test("function f(x, [y, {z: zz, w}], v=1, ...t @");
  test("function f(x, [y, {z: zz, w}], v=1, ...t) @");
  test("function f(x, [y, {z: zz, w}], v=1, ...t) {@");
  test("function f(x, [y, {z: zz, w}], v=1, ...t) { x @");
  test("function f(x, [y, {z: zz, w}], v=1, ...t) { x; @");
  test("function f(x, [y, {z: zz, w}], v=1, ...t) { x; } @");

  // star function

  test("function* @");
  test("function* f @");
  test("function* f( @");
  test("function* f(x @");
  test("function* f(x, @");
  test("function* f(x, ... @");
  test("function* f(x, ...t @");
  test("function* f(x, ...t) @");
  test("function* f(x, ...t) {@");
  test("function* f(x, ...t) { x @");
  test("function* f(x, ...t) { x; @");
  test("function* f(x, ...t) { x; } @");

  // return

  test("function f() { return @");
  test("function f() { return 1 @");
  test("function f() { return 1; @");
  test("function f() { return 1; } @");
  test("function f() { return; @");
  test("function f() { return\n@");

  // yield

  test("function* f() { yield @");
  test("function* f() { yield 1 @");
  test("function* f() { yield* @");
  test("function* f() { yield* 1 @");

  test("function* f() { yield\n@");
  test("function* f() { yield*\n@");

  // ---- Iterations ----

  // do...while

  test("do @");
  test("do {@");
  test("do {} @");
  test("do {} while @");
  test("do {} while ( @");
  test("do {} while (x @");
  test("do {} while (x) @");
  test("do {} while (x); @");

  test("do x @");
  test("do x; @");
  test("do x; while @");

  // for

  test("for @");
  test("for (@");
  test("for (x @");
  test("for (x; @");
  test("for (x; y @");
  test("for (x; y; @");
  test("for (x; y; z @");
  test("for (x; y; z) @");
  test("for (x; y; z) { @");
  test("for (x; y; z) {} @");

  test("for (x; y; z) x @");
  test("for (x; y; z) x; @");

  test("for (var @");
  test("for (var x @");
  test("for (var x = @");
  test("for (var x = y @");
  test("for (var x = y; @");

  test("for (let @");
  test("for (let x @");
  test("for (let x = @");
  test("for (let x = y @");
  test("for (let x = y; @");

  // for...in

  test("for (x in @");
  test("for (x in y @");
  test("for (x in y) @");

  test("for (var x in @");
  test("for (var x in y @");
  test("for (var x in y) @");

  test("for (let x in @");
  test("for (let x in y @");
  test("for (let x in y) @");

  // for...of

  test("for (x of @");
  test("for (x of y @");
  test("for (x of y) @");

  test("for (var x of @");
  test("for (var x of y @");
  test("for (var x of y) @");

  test("for (let x of @");
  test("for (let x of y @");
  test("for (let x of y) @");

  // while

  test("while @");
  test("while (@");
  test("while (x @");
  test("while (x) @");
  test("while (x) { @");
  test("while (x) {} @");

  test("while (x) x @");
  test("while (x) x; @");

  // ---- Others ----

  // debugger

  test("debugger @");
  test("debugger; @");

  // export

  test_no_fun_no_eval("export @");
  test_no_fun_no_eval("export { @");
  test_no_fun_no_eval("export { x @");
  test_no_fun_no_eval("export { x, @");
  test_no_fun_no_eval("export { x, y @");
  test_no_fun_no_eval("export { x, y as @");
  test_no_fun_no_eval("export { x, y as z @");
  test_no_fun_no_eval("export { x, y as z } @");
  test_no_fun_no_eval("export { x, y as z } from @");
  test_no_fun_no_eval("export { x, y as z } from 'a' @");
  test_no_fun_no_eval("export { x, y as z } from 'a'; @");

  test_no_fun_no_eval("export * @");
  test_no_fun_no_eval("export * from @");
  test_no_fun_no_eval("export * from 'a' @");
  test_no_fun_no_eval("export * from 'a'; @");

  test_no_fun_no_eval("export function @");
  test_no_fun_no_eval("export function f @");
  test_no_fun_no_eval("export function f( @");
  test_no_fun_no_eval("export function f() @");
  test_no_fun_no_eval("export function f() { @");
  test_no_fun_no_eval("export function f() {} @");
  test_no_fun_no_eval("export function f() {}; @");

  test_no_fun_no_eval("export var @");
  test_no_fun_no_eval("export var a @");
  test_no_fun_no_eval("export var a = @");
  test_no_fun_no_eval("export var a = 1 @");
  test_no_fun_no_eval("export var a = 1, @");
  test_no_fun_no_eval("export var a = 1, b @");
  test_no_fun_no_eval("export var a = 1, b = @");
  test_no_fun_no_eval("export var a = 1, b = 2 @");
  test_no_fun_no_eval("export var a = 1, b = 2; @");

  test_no_fun_no_eval("export let @");
  test_no_fun_no_eval("export let a @");
  test_no_fun_no_eval("export let a = @");
  test_no_fun_no_eval("export let a = 1 @");
  test_no_fun_no_eval("export let a = 1, @");
  test_no_fun_no_eval("export let a = 1, b @");
  test_no_fun_no_eval("export let a = 1, b = @");
  test_no_fun_no_eval("export let a = 1, b = 2 @");
  test_no_fun_no_eval("export let a = 1, b = 2; @");

  test_no_fun_no_eval("export const @");
  test_no_fun_no_eval("export const a @");
  test_no_fun_no_eval("export const a = @");
  test_no_fun_no_eval("export const a = 1 @");
  test_no_fun_no_eval("export const a = 1, @");
  test_no_fun_no_eval("export const a = 1, b @");
  test_no_fun_no_eval("export const a = 1, b = @");
  test_no_fun_no_eval("export const a = 1, b = 2 @");
  test_no_fun_no_eval("export const a = 1, b = 2; @");

  if (classesEnabled()) {
    test_no_fun_no_eval("export class @");
    test_no_fun_no_eval("export class Foo @");
    test_no_fun_no_eval("export class Foo {  @");
    test_no_fun_no_eval("export class Foo { constructor @");
    test_no_fun_no_eval("export class Foo { constructor( @");
    test_no_fun_no_eval("export class Foo { constructor() @");
    test_no_fun_no_eval("export class Foo { constructor() { @");
    test_no_fun_no_eval("export class Foo { constructor() {} @");
    test_no_fun_no_eval("export class Foo { constructor() {} } @");
    test_no_fun_no_eval("export class Foo { constructor() {} }; @");
  }

  test_no_fun_no_eval("export default @");
  test_no_fun_no_eval("export default 1 @");
  test_no_fun_no_eval("export default 1; @");

  test_no_fun_no_eval("export default function @");
  test_no_fun_no_eval("export default function() @");
  test_no_fun_no_eval("export default function() { @");
  test_no_fun_no_eval("export default function() {} @");
  test_no_fun_no_eval("export default function() {}; @");

  test_no_fun_no_eval("export default function foo @");
  test_no_fun_no_eval("export default function foo( @");
  test_no_fun_no_eval("export default function foo() @");
  test_no_fun_no_eval("export default function foo() { @");
  test_no_fun_no_eval("export default function foo() {} @");
  test_no_fun_no_eval("export default function foo() {}; @");

  if (classesEnabled()) {
    test_no_fun_no_eval("export default class @");
    test_no_fun_no_eval("export default class { @");
    test_no_fun_no_eval("export default class { constructor @");
    test_no_fun_no_eval("export default class { constructor( @");
    test_no_fun_no_eval("export default class { constructor() @");
    test_no_fun_no_eval("export default class { constructor() { @");
    test_no_fun_no_eval("export default class { constructor() {} @");
    test_no_fun_no_eval("export default class { constructor() {} } @");
    test_no_fun_no_eval("export default class { constructor() {} }; @");

    test_no_fun_no_eval("export default class Foo @");
    test_no_fun_no_eval("export default class Foo { @");
    test_no_fun_no_eval("export default class Foo { constructor @");
    test_no_fun_no_eval("export default class Foo { constructor( @");
    test_no_fun_no_eval("export default class Foo { constructor() @");
    test_no_fun_no_eval("export default class Foo { constructor() { @");
    test_no_fun_no_eval("export default class Foo { constructor() {} @");
    test_no_fun_no_eval("export default class Foo { constructor() {} } @");
    test_no_fun_no_eval("export default class Foo { constructor() {} }; @");
  }

  // import

  test_no_fun_no_eval("import @");
  test_no_fun_no_eval("import x @");
  test_no_fun_no_eval("import x from @");
  test_no_fun_no_eval("import x from 'a' @");
  test_no_fun_no_eval("import x from 'a'; @");

  test_no_fun_no_eval("import { @");
  test_no_fun_no_eval("import { x @");
  test_no_fun_no_eval("import { x, @");
  test_no_fun_no_eval("import { x, y @");
  test_no_fun_no_eval("import { x, y } @");
  test_no_fun_no_eval("import { x, y } from @");
  test_no_fun_no_eval("import { x, y } from 'a' @");
  test_no_fun_no_eval("import { x, y } from 'a'; @");

  test_no_fun_no_eval("import { x as @");
  test_no_fun_no_eval("import { x as y @");
  test_no_fun_no_eval("import { x as y } @");
  test_no_fun_no_eval("import { x as y } from @");
  test_no_fun_no_eval("import { x as y } from 'a' @");
  test_no_fun_no_eval("import { x as y } from 'a'; @");

  test_no_fun_no_eval("import 'a' @");
  test_no_fun_no_eval("import 'a'; @");

  test_no_fun_no_eval("import * @");
  test_no_fun_no_eval("import * as @");
  test_no_fun_no_eval("import * as a @");
  test_no_fun_no_eval("import * as a from @");
  test_no_fun_no_eval("import * as a from 'a' @");
  test_no_fun_no_eval("import * as a from 'a'; @");

  test_no_fun_no_eval("import a @");
  test_no_fun_no_eval("import a, @");
  test_no_fun_no_eval("import a, * @");
  test_no_fun_no_eval("import a, * as @");
  test_no_fun_no_eval("import a, * as b @");
  test_no_fun_no_eval("import a, * as b from @");
  test_no_fun_no_eval("import a, * as b from 'c' @");
  test_no_fun_no_eval("import a, * as b from 'c'; @");

  test_no_fun_no_eval("import a, { @");
  test_no_fun_no_eval("import a, { b @");
  test_no_fun_no_eval("import a, { b } @");
  test_no_fun_no_eval("import a, { b } from @");
  test_no_fun_no_eval("import a, { b } from 'c' @");
  test_no_fun_no_eval("import a, { b } from 'c'; @");

  // label

  test("a @");
  test("a: @");

  // with

  test_no_strict("with @");
  test_no_strict("with (@");
  test_no_strict("with (x @");
  test_no_strict("with (x) @");
  test_no_strict("with (x) { @");
  test_no_strict("with (x) {} @");

  test_no_strict("with (x) x @");
  test_no_strict("with (x) x; @");

  // ==== Expressions and operators ====

  // ---- Primary expressions ----

  // this

  test("this @");

  // function

  test("(function @");
  test("(function ( @");
  test("(function (x @");
  test("(function (x, @");
  test("(function (x, ... @");
  test("(function (x, ...t @");
  test("(function (x, ...t) @");
  test("(function (x, ...t) {@");
  test("(function (x, ...t) { x @");
  test("(function (x, ...t) { x; @");
  test("(function (x, ...t) { x; } @");
  test("(function (x, ...t) { x; }) @");

  // star function

  test("(function* @");
  test("(function* ( @");
  test("(function* (x @");
  test("(function* (x, @");
  test("(function* (x, ... @");
  test("(function* (x, ...t @");
  test("(function* (x, ...t) @");
  test("(function* (x, ...t) {@");
  test("(function* (x, ...t) { x @");
  test("(function* (x, ...t) { x; @");
  test("(function* (x, ...t) { x; } @");
  test("(function* (x, ...t) { x; }) @");

  // Array literal

  test("[ @");
  test("[] @");
  test("[1 @");
  test("[1, @");
  test("[1, ... @");
  test("[1, ...x @");
  test("[1, ...x] @");

  // object

  test("({ @");
  test("({ x @");
  test("({ x: @");
  test("({ x: 1 @");
  test("({ x: 1, @");
  test("({ x: 1, y @");
  test("({ x: 1, y: @");
  test("({ x: 1, y: 2 @");
  test("({ x: 1, y: 2, @");
  test("({ x: 1, y: 2, z @");
  test("({ x: 1, y: 2, z, @");
  test("({ x: 1, y: 2, z, w @");
  test("({ x: 1, y: 2, z, w } @");
  test("({ x: 1, y: 2, z, w }) @");

  // object: computed property

  test("({ [@");
  test("({ [k @");
  test("({ [k] @");
  test("({ [k]: @");
  test("({ [k]: 1 @");
  test("({ [k]: 1, @");

  // object: getter

  test("({ get @");
  test("({ get p @");
  test("({ get p( @");
  test("({ get p() @");
  test("({ get p() { @");
  test("({ get p() {} @");
  test("({ get p() {}, @");
  test("({ get p() {}, } @");

  test("({ get [ @");
  test("({ get [p @");
  test("({ get [p] @");
  test("({ get [p]( @");
  test("({ get [p]() @");

  // object: setter

  test("({ set @");
  test("({ set p @");
  test("({ set p( @");
  test("({ set p(v @");
  test("({ set p(v) @");
  test("({ set p(v) { @");
  test("({ set p(v) {} @");

  test("({ set [ @");
  test("({ set [p @");
  test("({ set [p] @");
  test("({ set [p]( @");
  test("({ set [p](v @");
  test("({ set [p](v) @");

  // object: method

  test("({ m @");
  test("({ m( @");
  test("({ m() @");
  test("({ m() { @");
  test("({ m() {} @");
  test("({ m() {}, @");

  test("({ [ @");
  test("({ [m @");
  test("({ [m] @");
  test("({ [m]( @");
  test("({ [m]() @");
  test("({ [m]() { @");
  test("({ [m]() {} @");
  test("({ [m]() {}, @");

  test("({ * @");
  test("({ *m @");
  test("({ *m( @");
  test("({ *m() @");
  test("({ *m() { @");
  test("({ *m() {} @");
  test("({ *m() {}, @");

  test("({ *[ @");
  test("({ *[m @");
  test("({ *[m] @");
  test("({ *[m]( @");
  test("({ *[m]() @");
  test("({ *[m]() { @");
  test("({ *[m]() {} @");
  test("({ *[m]() {}, @");

  test("({ * get @");
  test("({ * get ( @");
  test("({ * get () @");
  test("({ * get () { @");
  test("({ * get () {} @");
  test("({ * get () {}, @");

  test("({ * set @");
  test("({ * set ( @");
  test("({ * set () @");
  test("({ * set () { @");
  test("({ * set () {} @");
  test("({ * set () {}, @");

  // Regular expression literal

  test("/a/ @");
  test("/a/g @");

  // Array comprehensions

  test("[for @");
  test("[for ( @");
  test("[for (x @");
  test("[for (x of @");
  test("[for (x of y @");
  test("[for (x of y) @");
  test("[for (x of y) x @");
  test("[for (x of y) if @");
  test("[for (x of y) if ( @");
  test("[for (x of y) if (x @");
  test("[for (x of y) if (x == @");
  test("[for (x of y) if (x == 1 @");
  test("[for (x of y) if (x == 1) @");
  test("[for (x of y) if (x == 1) x @");
  test("[for (x of y) if (x == 1) x] @");

  // Generator comprehensions

  test("(for @");
  test("(for ( @");
  test("(for (x @");
  test("(for (x of @");
  test("(for (x of y @");
  test("(for (x of y) @");
  test("(for (x of y) x @");
  test("(for (x of y) if @");
  test("(for (x of y) if ( @");
  test("(for (x of y) if (x @");
  test("(for (x of y) if (x == @");
  test("(for (x of y) if (x == 1 @");
  test("(for (x of y) if (x == 1) @");
  test("(for (x of y) if (x == 1) x @");
  test("(for (x of y) if (x == 1) x) @");

  // ---- Left-hand-side expressions ----

  // property access

  test("a[ @");
  test("a[1 @");
  test("a[1] @");

  test("a. @");
  test("a.b @");
  test("a.b; @");

  // new

  test("new @");
  test("new f @");
  test("new f( @");
  test("new f() @");
  test("new f(); @");

  // ---- Increment and decrement ----

  test("a ++ @");
  test("a ++; @");

  test("-- @");
  test("-- a @");
  test("-- a; @");

  // ---- Unary operators ----

  // delete

  test("delete @");
  test("delete a @");
  test("delete a[ @");
  test("delete a[b @");
  test("delete a[b] @");
  test("delete a[b]; @");

  test("delete ( @");
  test("delete (a @");
  test("delete (a[ @");
  test("delete (a[b @");
  test("delete (a[b] @");
  test("delete (a[b]) @");
  test("delete (a[b]); @");

  // void

  test("void @");
  test("void a @");
  test("void a; @");

  test("void (@");
  test("void (a @");
  test("void (a) @");
  test("void (a); @");

  // typeof

  test("typeof @");
  test("typeof a @");
  test("typeof a; @");

  test("typeof (@");
  test("typeof (a @");
  test("typeof (a) @");
  test("typeof (a); @");

  // -

  test("- @");
  test("- 1 @");
  test("- 1; @");

  // +

  test("+ @");
  test("+ 1 @");
  test("+ 1; @");

  // ---- Arithmetic operators ----

  // +

  test("1 + @");
  test("1 + 1 @");
  test("1 + 1; @");

  // ---- Relational operators ----

  // in

  test("a in @");
  test("a in b @");
  test("a in b; @");

  // instanceof

  test("a instanceof @");
  test("a instanceof b @");
  test("a instanceof b; @");

  // ---- Equality operators ----

  // ==

  test("1 == @");
  test("1 == 1 @");
  test("1 == 1; @");

  // ---- Bitwise shift operators ----

  // <<

  test("1 << @");
  test("1 << 1 @");
  test("1 << 1; @");

  // ---- Binary bitwise operators ----

  // &

  test("1 & @");
  test("1 & 1 @");
  test("1 & 1; @");

  // ---- Binary logical operators ----

  // ||

  test("1 || @");
  test("1 || 1 @");
  test("1 || 1; @");

  // ---- Conditional (ternary) operator ----

  test("1 ? @");
  test("1 ? 2 @");
  test("1 ? 2 : @");
  test("1 ? 2 : 3 @");
  test("1 ? 2 : 3; @");

  // ---- Assignment operators ----

  test("x = @");
  test("x = 1 @");
  test("x = 1 + @");
  test("x = 1 + 2 @");
  test("x = 1 + 2; @");

  // ---- Comma operator ----

  test("1, @");
  test("1, 2 @");
  test("1, 2; @");

  // ---- Functions ----

  // Arrow functions

  test("a => @");
  test("a => 1 @");
  test("a => 1; @");
  test("a => { @");
  test("a => {} @");
  test("a => {}; @");

  test("( @");
  test("() @");
  test("() => @");

  test("(...@");
  test("(...a @");
  test("(...a) @");
  test("(...a) => @");

  test("([ @");
  test("([a @");
  test("([a] @");
  test("([a]) @");
  test("([a]) => @");

  test("({ @");
  test("({a @");
  test("({a} @");
  test("({a}) @");
  test("({a}) => @");
  test("({a: @");
  test("({a: b @");
  test("({a: b, @");
  test("({a: b} @");
  test("({a: b}) @");
  test("({a: b}) => @");

  if (classesEnabled()) {
    // ---- Class declaration ----

    test("class @");
    test("class a @");
    test("class a { @");
    test("class a { constructor @");
    test("class a { constructor( @");
    test("class a { constructor() @");
    test("class a { constructor() { @");
    test("class a { constructor() { } @");
    test("class a { constructor() { } } @");

    test("class a { constructor() { } static @");
    test("class a { constructor() { } static m @");
    test("class a { constructor() { } static m( @");
    test("class a { constructor() { } static m() @");
    test("class a { constructor() { } static m() { @");
    test("class a { constructor() { } static m() {} @");
    test("class a { constructor() { } static m() {} } @");

    test("class a { constructor() { } static ( @");
    test("class a { constructor() { } static () @");
    test("class a { constructor() { } static () { @");
    test("class a { constructor() { } static () {} @");
    test("class a { constructor() { } static () {} } @");

    test("class a { constructor() { } static get @");
    test("class a { constructor() { } static get p @");
    test("class a { constructor() { } static get p( @");
    test("class a { constructor() { } static get p() @");
    test("class a { constructor() { } static get p() { @");
    test("class a { constructor() { } static get p() {} @");
    test("class a { constructor() { } static get p() {} } @");

    test("class a { constructor() { } static set @");
    test("class a { constructor() { } static set p @");
    test("class a { constructor() { } static set p( @");
    test("class a { constructor() { } static set p(v @");
    test("class a { constructor() { } static set p(v) @");
    test("class a { constructor() { } static set p(v) { @");
    test("class a { constructor() { } static set p(v) {} @");
    test("class a { constructor() { } static set p(v) {} } @");

    test("class a { constructor() { } * @");
    test("class a { constructor() { } *m @");
    test("class a { constructor() { } *m( @");
    test("class a { constructor() { } *m() @");
    test("class a { constructor() { } *m() { @");
    test("class a { constructor() { } *m() {} @");
    test("class a { constructor() { } *m() {} } @");

    test("class a { constructor() { } static * @");
    test("class a { constructor() { } static *m @");
    test("class a { constructor() { } static *m( @");
    test("class a { constructor() { } static *m() @");
    test("class a { constructor() { } static *m() { @");
    test("class a { constructor() { } static *m() {} @");
    test("class a { constructor() { } static *m() {} } @");

    test("class a extends @");
    test("class a extends b @");
    test("class a extends b { @");

    test("class a extends ( @");
    test("class a extends ( b @");
    test("class a extends ( b ) @");
    test("class a extends ( b ) { @");

    // ---- Class expression ----

    test("( class @");
    test("( class a @");
    test("( class a { @");
    test("( class a { constructor @");
    test("( class a { constructor( @");
    test("( class a { constructor() @");
    test("( class a { constructor() { @");
    test("( class a { constructor() { } @");
    test("( class a { constructor() { } } @");
    test("( class a { constructor() { } } ) @");

    test("(class a extends @");
    test("(class a extends b @");
    test("(class a extends b { @");

    test("(class a extends ( @");
    test("(class a extends ( b @");
    test("(class a extends ( b ) @");
    test("(class a extends ( b ) { @");

    test("( class { @");
    test("( class { constructor @");
    test("( class { constructor( @");
    test("( class { constructor() @");
    test("( class { constructor() { @");
    test("( class { constructor() { } @");
    test("( class { constructor() { } } @");
    test("( class { constructor() { } } ) @");

    test("(class extends @");
    test("(class extends b @");
    test("(class extends b { @");

    test("(class extends ( @");
    test("(class extends ( b @");
    test("(class extends ( b ) @");
    test("(class extends ( b ) { @");
  }

  // ---- Other ----

  // Literals

  test("a @");
  test("1 @");
  test("1. @");
  test("1.2 @");
  test("true @");
  test("false @");
  test("\"a\" @");
  test("'a' @");
  test("null @");

  // Template strings

  test("`${ @");
  test("`${a @");
  test("`${a}` @");

  // Function calls

  test("f( @");
  test("f() @");
  test("f(); @");

  test("f(... @");
  test("f(...x @");
  test("f(...x) @");

  // Function constructors

  test_fun_arg("@");
  test_fun_arg("a @");
  test_fun_arg("... @");
  test_fun_arg("...a @");

  // ==== Legacy ====

  // let statement

  test("let ( @");
  test("let ( x @");
  test("let ( x = @");
  test("let ( x = 1 @");
  test("let ( x = 1, @");
  test("let ( x = 1, y @");
  test("let ( x = 1, y = @");
  test("let ( x = 1, y = 2 @");
  test("let ( x = 1, y = 2 ) @");
  test("let ( x = 1, y = 2 ) { @");
  test("let ( x = 1, y = 2 ) { x @");
  test("let ( x = 1, y = 2 ) { x; @");
  test("let ( x = 1, y = 2 ) { x; } @");

  // Expression closures

  test("function f() 1 @");
  test("function f() 1; @");
  test("(function () 1 @");
  test("(function () 1); @");

  // Legacy generator

  test("function f() { (yield @");
  test("function f() { (yield 1 @");
  test("function f() { f(yield @");
  test("function f() { f(yield 1 @");

  // for each...in

  test("for each @");
  test("for each (@");
  test("for each (x @");
  test("for each (x in @");
  test("for each (x in y @");
  test("for each (x in y) @");

  test("for each (var @");
  test("for each (var x @");
  test("for each (var x in @");
  test("for each (var x in y @");
  test("for each (var x in y) @");

  test("for each (let @");
  test("for each (let x @");
  test("for each (let x in @");
  test("for each (let x in y @");
  test("for each (let x in y) @");

  // Legacy array comprehensions

  test("[x @");
  test("[x for @");
  test("[x for ( @");
  test("[x for (x @");
  test("[x for (x of @");
  test("[x for (x of y @");
  test("[x for (x of y) @");
  test("[x for (x of y) if @");
  test("[x for (x of y) if ( @");
  test("[x for (x of y) if (x @");
  test("[x for (x of y) if (x == @");
  test("[x for (x of y) if (x == 1 @");
  test("[x for (x of y) if (x == 1) @");
  test("[x for (x of y) if (x == 1)] @");

  test("[x for (x in @");
  test("[x for (x in y @");
  test("[x for (x in y) @");

  test("[x for each @");
  test("[x for each ( @");
  test("[x for each (x @");
  test("[x for each (x in @");
  test("[x for each (x in y @");
  test("[x for each (x in y) @");

  // Generator expressions

  test("(x @");
  test("(x for @");
  test("(x for ( @");
  test("(x for (x @");
  test("(x for (x of @");
  test("(x for (x of y @");
  test("(x for (x of y) @");
  test("(x for (x of y) if @");
  test("(x for (x of y) if ( @");
  test("(x for (x of y) if (x @");
  test("(x for (x of y) if (x == @");
  test("(x for (x of y) if (x == 1 @");
  test("(x for (x of y) if (x == 1) @");
  test("(x for (x of y) if (x == 1)) @");

  test("(x for (x in @");
  test("(x for (x in y @");
  test("(x for (x in y) @");

  test("(x for each @");
  test("(x for each ( @");
  test("(x for each (x @");
  test("(x for each (x in @");
  test("(x for each (x in y @");
  test("(x for each (x in y) @");

  // asm.js

  test("(function() { 'use asm'; @");
  test("(function() { 'use asm'; var @");
  test("(function() { 'use asm'; var a @");
  test("(function() { 'use asm'; var a = @");
  test("(function() { 'use asm'; var a = 1 @");
  test("(function() { 'use asm'; var a = 1; @");
  test("(function() { 'use asm'; var a = 1; function @");
  test("(function() { 'use asm'; var a = 1; function f @");
  test("(function() { 'use asm'; var a = 1; function f( @");
  test("(function() { 'use asm'; var a = 1; function f() @");
  test("(function() { 'use asm'; var a = 1; function f() { @");
  test("(function() { 'use asm'; var a = 1; function f() { } @");
  test("(function() { 'use asm'; var a = 1; function f() { } var @");
  test("(function() { 'use asm'; var a = 1; function f() { } var tbl @");
  test("(function() { 'use asm'; var a = 1; function f() { } var tbl = @");
  test("(function() { 'use asm'; var a = 1; function f() { } var tbl = [ @");
  test("(function() { 'use asm'; var a = 1; function f() { } var tbl = [f @");
  test("(function() { 'use asm'; var a = 1; function f() { } var tbl = [f] @");
  test("(function() { 'use asm'; var a = 1; function f() { } var tbl = [f]; @");
  test("(function() { 'use asm'; var a = 1; function f() { } var tbl = [f]; return @");
  test("(function() { 'use asm'; var a = 1; function f() { } var tbl = [f]; return f @");
  test("(function() { 'use asm'; var a = 1; function f() { } var tbl = [f]; return f; @");
  test("(function() { 'use asm'; var a = 1; function f() { } var tbl = [f]; return f; } @");
  test("(function() { 'use asm'; var a = 1; function f() { } var tbl = [f]; return f; }) @");
  test("(function() { 'use asm'; var a = 1; function f() { } var tbl = [f]; return f; }); @");
}
