Index: frontend/node_modules/es-module-lexer/lexer.js
===================================================================
--- frontend/node_modules/es-module-lexer/lexer.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/es-module-lexer/lexer.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,969 @@
+let source, pos, end,
+  openTokenDepth,
+  lastTokenPos,
+  openTokenPosStack,
+  openClassPosStack,
+  curDynamicImport,
+  templateStackDepth,
+  facade,
+  lastSlashWasDivision,
+  nextBraceIsClass,
+  templateDepth,
+  templateStack,
+  imports,
+  exports,
+  name;
+
+function addImport (ss, s, e, d) {
+  const impt = { ss, se: d === -2 ? e : d === -1 ? e + 1 : 0, s, e, d, a: -1, n: undefined, at: null };
+  imports.push(impt);
+  return impt;
+}
+
+function addExport (s, e, ls, le) {
+  exports.push({
+    s,
+    e,
+    ls,
+    le,
+    n: s[0] === '"' ? readString(s, '"') : s[0] === "'" ? readString(s, "'") : source.slice(s, e),
+    ln: ls[0] === '"' ? readString(ls, '"') : ls[0] === "'" ? readString(ls, "'") : source.slice(ls, le)
+  });
+}
+
+function readName (impt) {
+  let { d, s } = impt;
+  if (d !== -1)
+    s++;
+  impt.n = readString(s, source.charCodeAt(s - 1));
+}
+
+// Note: parsing is based on the _assumption_ that the source is already valid
+export function parse (_source, _name) {
+  openTokenDepth = 0;
+  curDynamicImport = null;
+  templateDepth = -1;
+  lastTokenPos = -1;
+  lastSlashWasDivision = false;
+  templateStack = Array(1024);
+  templateStackDepth = 0;
+  openTokenPosStack = Array(1024);
+  openClassPosStack = Array(1024);
+  nextBraceIsClass = false;
+  facade = true;
+  name = _name || '@';
+
+  imports = [];
+  exports = [];
+
+  source = _source;
+  pos = -1;
+  end = source.length - 1;
+  let ch = 0;
+
+  // start with a pure "module-only" parser
+  m: while (pos++ < end) {
+    ch = source.charCodeAt(pos);
+
+    if (ch === 32 || ch < 14 && ch > 8)
+      continue;
+
+    switch (ch) {
+      case 101/*e*/:
+        if (openTokenDepth === 0 && keywordStart(pos) && source.startsWith('xport', pos + 1)) {
+          tryParseExportStatement();
+          // export might have been a non-pure declaration
+          if (!facade) {
+            lastTokenPos = pos;
+            break m;
+          }
+        }
+        break;
+      case 105/*i*/:
+        if (keywordStart(pos) && source.startsWith('mport', pos + 1))
+          tryParseImportStatement();
+        break;
+      case 59/*;*/:
+        break;
+      case 47/*/*/: {
+        const next_ch = source.charCodeAt(pos + 1);
+        if (next_ch === 47/*/*/) {
+          lineComment();
+          // dont update lastToken
+          continue;
+        }
+        else if (next_ch === 42/***/) {
+          blockComment(true);
+          // dont update lastToken
+          continue;
+        }
+        // fallthrough
+      }
+      default:
+        // as soon as we hit a non-module token, we go to main parser
+        facade = false;
+        pos--;
+        break m;
+    }
+    lastTokenPos = pos;
+  }
+
+  while (pos++ < end) {
+    ch = source.charCodeAt(pos);
+
+    if (ch === 32 || ch < 14 && ch > 8)
+      continue;
+
+    switch (ch) {
+      case 101/*e*/:
+        if (openTokenDepth === 0 && keywordStart(pos) && source.startsWith('xport', pos + 1))
+          tryParseExportStatement();
+        break;
+      case 105/*i*/:
+        if (keywordStart(pos) && source.startsWith('mport', pos + 1))
+          tryParseImportStatement();
+        break;
+      case 99/*c*/:
+        if (keywordStart(pos) && source.startsWith('lass', pos + 1) && isBrOrWs(source.charCodeAt(pos + 5)))
+          nextBraceIsClass = true;
+        break;
+      case 40/*(*/:
+        openTokenPosStack[openTokenDepth++] = lastTokenPos;
+        break;
+      case 41/*)*/:
+        if (openTokenDepth === 0)
+          syntaxError();
+        openTokenDepth--;
+        if (curDynamicImport && curDynamicImport.d === openTokenPosStack[openTokenDepth]) {
+          if (curDynamicImport.e === 0)
+            curDynamicImport.e = pos;
+          curDynamicImport.se = pos;
+          curDynamicImport = null;
+        }
+        break;
+      case 91/*[*/:
+        openTokenPosStack[openTokenDepth++] = lastTokenPos;
+        break;
+      case 93/*]*/:
+        if (openTokenDepth === 0)
+          syntaxError();
+        openTokenDepth--;
+        break;
+      case 44/*,*/:
+        if (curDynamicImport && curDynamicImport.e === 0 && curDynamicImport.d === openTokenPosStack[openTokenDepth - 1]) {
+          curDynamicImport.e = lastTokenPos + 1;
+          pos++;
+          commentWhitespace(true);
+          curDynamicImport.a = pos;
+          pos--;
+        }
+        break;
+      case 123/*{*/:
+        // dynamic import followed by { is not a dynamic import (so remove)
+        // this is a sneaky way to get around { import () {} } v { import () }
+        // block / object ambiguity without a parser (assuming source is valid)
+        if (source.charCodeAt(lastTokenPos) === 41/*)*/ && imports.length && imports[imports.length - 1].e === lastTokenPos) {
+          imports.pop();
+        }
+        openClassPosStack[openTokenDepth] = nextBraceIsClass;
+        nextBraceIsClass = false;
+        openTokenPosStack[openTokenDepth++] = lastTokenPos;
+        break;
+      case 125/*}*/:
+        if (openTokenDepth === 0)
+          syntaxError();
+        if (openTokenDepth-- === templateDepth) {
+          templateDepth = templateStack[--templateStackDepth];
+          templateString();
+        }
+        else {
+          if (templateDepth !== -1 && openTokenDepth < templateDepth)
+            syntaxError();
+        }
+        break;
+      case 39/*'*/:
+      case 34/*"*/:
+        stringLiteral(ch);
+        break;
+      case 47/*/*/: {
+        const next_ch = source.charCodeAt(pos + 1);
+        if (next_ch === 47/*/*/) {
+          lineComment();
+          // dont update lastToken
+          continue;
+        }
+        else if (next_ch === 42/***/) {
+          blockComment(true);
+          // dont update lastToken
+          continue;
+        }
+        else {
+          // Division / regex ambiguity handling based on checking backtrack analysis of:
+          // - what token came previously (lastToken)
+          // - if a closing brace or paren, what token came before the corresponding
+          //   opening brace or paren (lastOpenTokenIndex)
+          const lastToken = source.charCodeAt(lastTokenPos);
+          const lastExport = exports[exports.length - 1];
+          if (isExpressionPunctuator(lastToken) &&
+              !(lastToken === 46/*.*/ && (source.charCodeAt(lastTokenPos - 1) >= 48/*0*/ && source.charCodeAt(lastTokenPos - 1) <= 57/*9*/)) &&
+              !(lastToken === 43/*+*/ && source.charCodeAt(lastTokenPos - 1) === 43/*+*/) && !(lastToken === 45/*-*/ && source.charCodeAt(lastTokenPos - 1) === 45/*-*/) ||
+              lastToken === 41/*)*/ && isParenKeyword(openTokenPosStack[openTokenDepth]) ||
+              openTokenDepth > 0 && lastToken === 102/*f*/ && source.charCodeAt(lastTokenPos - 1) === 111/*o*/ && isForOfBinding(lastTokenPos - 2) && isForParen(openTokenPosStack[openTokenDepth - 1]) ||
+              lastToken === 125/*}*/ && (isExpressionTerminator(openTokenPosStack[openTokenDepth]) || openClassPosStack[openTokenDepth]) ||
+              lastToken === 47/*/*/ && lastSlashWasDivision ||
+              isExpressionKeyword(lastTokenPos) ||
+              !lastToken) {
+            regularExpression();
+            lastSlashWasDivision = false;
+          }
+          else if (lastExport && lastTokenPos >= lastExport.s && lastTokenPos <= lastExport.e) {
+            // export default /some-regexp/
+            regularExpression();
+            lastSlashWasDivision = false;
+          }
+          else {
+            lastSlashWasDivision = true;
+          }
+        }
+        break;
+      }
+      case 96/*`*/:
+        templateString();
+        break;
+    }
+    lastTokenPos = pos;
+  }
+
+  if (templateDepth !== -1 || openTokenDepth)
+    syntaxError();
+
+  return [imports, exports, facade];
+}
+
+function tryParseImportStatement () {
+  const startPos = pos;
+
+  pos += 6;
+
+  let ch = commentWhitespace(true);
+  
+  switch (ch) {
+    // dynamic import
+    case 40/*(*/:
+      openTokenPosStack[openTokenDepth++] = startPos;
+      if (source.charCodeAt(lastTokenPos) === 46/*.*/)
+        return;
+      // dynamic import indicated by positive d
+      const impt = addImport(startPos, pos + 1, 0, startPos);
+      curDynamicImport = impt;
+      // try parse a string, to record a safe dynamic import string
+      pos++;
+      ch = commentWhitespace(true);
+      if (ch === 39/*'*/ || ch === 34/*"*/) {
+        stringLiteral(ch);
+      }
+      else {
+        pos--;
+        return;
+      }
+      pos++;
+      ch = commentWhitespace(true);
+      if (ch === 44/*,*/) {
+        impt.e = pos;
+        pos++;
+        ch = commentWhitespace(true);
+        impt.a = pos;
+        readName(impt);
+        pos--;
+      }
+      else if (ch === 41/*)*/) {
+        openTokenDepth--;
+        impt.e = pos;
+        impt.se = pos;
+        readName(impt);
+      }
+      else {
+        pos--;
+      }
+      return;
+    // import.meta
+    case 46/*.*/:
+      pos++;
+      ch = commentWhitespace(true);
+      // import.meta indicated by d === -2
+      if (ch === 109/*m*/ && source.startsWith('eta', pos + 1) && source.charCodeAt(lastTokenPos) !== 46/*.*/)
+        addImport(startPos, startPos, pos + 4, -2);
+      return;
+    
+    default:
+      // no space after "import" -> not an import keyword
+      if (pos === startPos + 6)
+        break;
+    case 34/*"*/:
+    case 39/*'*/:
+    case 123/*{*/:
+    case 42/***/:
+      // import statement only permitted at base-level
+      if (openTokenDepth !== 0) {
+        pos--;
+        return;
+      }
+      while (pos < end) {
+        ch = source.charCodeAt(pos);
+        if (ch === 39/*'*/ || ch === 34/*"*/) {
+          readImportString(startPos, ch);
+          return;
+        }
+        pos++;
+      }
+      syntaxError();
+  }
+}
+
+function tryParseExportStatement () {
+  const sStartPos = pos;
+  const prevExport = exports.length;
+
+  pos += 6;
+
+  const curPos = pos;
+
+  let ch = commentWhitespace(true);
+
+  if (pos === curPos && !isPunctuator(ch))
+    return;
+
+  switch (ch) {
+    // export default ...
+    case 100/*d*/:
+      addExport(pos, pos + 7, -1, -1);
+      return;
+
+    // export async? function*? name () {
+    case 97/*a*/:
+      pos += 5;
+      commentWhitespace(true);
+    // fallthrough
+    case 102/*f*/:
+      pos += 8;
+      ch = commentWhitespace(true);
+      if (ch === 42/***/) {
+        pos++;
+        ch = commentWhitespace(true);
+      }
+      const startPos = pos;
+      ch = readToWsOrPunctuator(ch);
+      addExport(startPos, pos, startPos, pos);
+      pos--;
+      return;
+
+    // export class name ...
+    case 99/*c*/:
+      if (source.startsWith('lass', pos + 1) && isBrOrWsOrPunctuatorNotDot(source.charCodeAt(pos + 5))) {
+        pos += 5;
+        ch = commentWhitespace(true);
+        const startPos = pos;
+        ch = readToWsOrPunctuator(ch);
+        addExport(startPos, pos, startPos, pos);
+        pos--;
+        return;
+      }
+      pos += 2;
+    // fallthrough
+
+    // export var/let/const name = ...(, name = ...)+
+    case 118/*v*/:
+    case 109/*l*/:
+      // destructured initializations not currently supported (skipped for { or [)
+      // also, lexing names after variable equals is skipped (export var p = function () { ... }, q = 5 skips "q")
+      pos += 2;
+      facade = false;
+      do {
+        pos++;
+        ch = commentWhitespace(true);
+        const startPos = pos;
+        ch = readToWsOrPunctuator(ch);
+        // dont yet handle [ { destructurings
+        if (ch === 123/*{*/ || ch === 91/*[*/) {
+          pos--;
+          return;
+        }
+        if (pos === startPos)
+          return;
+        addExport(startPos, pos, startPos, pos);
+        ch = commentWhitespace(true);
+        if (ch === 61/*=*/) {
+          pos--;
+          return;
+        }
+      } while (ch === 44/*,*/);
+      pos--;
+      return;
+
+
+    // export {...}
+    case 123/*{*/:
+      pos++;
+      ch = commentWhitespace(true);
+      while (true) {
+        const startPos = pos;
+        readToWsOrPunctuator(ch);
+        const endPos = pos;
+        commentWhitespace(true);
+        ch = readExportAs(startPos, endPos);
+        // ,
+        if (ch === 44/*,*/) {
+          pos++;
+          ch = commentWhitespace(true);
+        }
+        if (ch === 125/*}*/)
+          break;
+        if (pos === startPos)
+          return syntaxError(); 
+        if (pos > end)
+          return syntaxError();
+      }
+      pos++;
+      ch = commentWhitespace(true);
+    break;
+    
+    // export *
+    // export * as X
+    case 42/***/:
+      pos++;
+      commentWhitespace(true);
+      ch = readExportAs(pos, pos);
+      ch = commentWhitespace(true);
+    break;
+  }
+
+  // from ...
+  if (ch === 102/*f*/ && source.startsWith('rom', pos + 1)) {
+    pos += 4;
+    readImportString(sStartPos, commentWhitespace(true));
+
+    // There were no local names.
+    for (let i = prevExport; i < exports.length; ++i) {
+      exports[i].ls = exports[i].le = -1;
+      exports[i].ln = undefined;
+    }
+  }
+  else {
+    pos--;
+  }
+}
+
+/*
+ * Ported from Acorn
+ *   
+ * MIT License
+
+ * Copyright (C) 2012-2020 by various contributors (see AUTHORS)
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+let acornPos;
+function readString (start, quote) {
+  acornPos = start;
+  let out = '', chunkStart = acornPos;
+  for (;;) {
+    if (acornPos >= source.length) syntaxError();
+    const ch = source.charCodeAt(acornPos);
+    if (ch === quote) break;
+    if (ch === 92) { // '\'
+      out += source.slice(chunkStart, acornPos);
+      out += readEscapedChar();
+      chunkStart = acornPos;
+    }
+    else if (ch === 0x2028 || ch === 0x2029) {
+      ++acornPos;
+    }
+    else {
+      if (isBr(ch)) syntaxError();
+      ++acornPos;
+    }
+  }
+  out += source.slice(chunkStart, acornPos++);
+  return out;
+}
+
+// Used to read escaped characters
+
+function readEscapedChar () {
+  let ch = source.charCodeAt(++acornPos);
+  ++acornPos;
+  switch (ch) {
+    case 110: return '\n'; // 'n' -> '\n'
+    case 114: return '\r'; // 'r' -> '\r'
+    case 120: return String.fromCharCode(readHexChar(2)); // 'x'
+    case 117: return readCodePointToString(); // 'u'
+    case 116: return '\t'; // 't' -> '\t'
+    case 98: return '\b'; // 'b' -> '\b'
+    case 118: return '\u000b'; // 'v' -> '\u000b'
+    case 102: return '\f'; // 'f' -> '\f'
+    case 13: if (source.charCodeAt(acornPos) === 10) ++acornPos; // '\r\n'
+    case 10: // ' \n'
+      return '';
+    case 56:
+    case 57:
+      syntaxError();
+    default:
+      if (ch >= 48 && ch <= 55) {
+        let octalStr = source.substr(acornPos - 1, 3).match(/^[0-7]+/)[0];
+        let octal = parseInt(octalStr, 8);
+        if (octal > 255) {
+          octalStr = octalStr.slice(0, -1);
+          octal = parseInt(octalStr, 8);
+        }
+        acornPos += octalStr.length - 1;
+        ch = source.charCodeAt(acornPos);
+        if (octalStr !== '0' || ch === 56 || ch === 57)
+          syntaxError();
+        return String.fromCharCode(octal);
+      }
+      if (isBr(ch)) {
+        // Unicode new line characters after \ get removed from output in both
+        // template literals and strings
+        return '';
+      }
+      return String.fromCharCode(ch);
+  }
+}
+
+// Used to read character escape sequences ('\x', '\u', '\U').
+
+function readHexChar (len) {
+  const start = acornPos;
+  let total = 0, lastCode = 0;
+  for (let i = 0; i < len; ++i, ++acornPos) {
+    let code = source.charCodeAt(acornPos), val;
+
+    if (code === 95) {
+      if (lastCode === 95 || i === 0) syntaxError();
+      lastCode = code;
+      continue;
+    }
+
+    if (code >= 97) val = code - 97 + 10; // a
+    else if (code >= 65) val = code - 65 + 10; // A
+    else if (code >= 48 && code <= 57) val = code - 48; // 0-9
+    else break;
+    if (val >= 16) break;
+    lastCode = code;
+    total = total * 16 + val;
+  }
+
+  if (lastCode === 95 || acornPos - start !== len) syntaxError();
+
+  return total;
+}
+
+// Read a string value, interpreting backslash-escapes.
+
+function readCodePointToString () {
+  const ch = source.charCodeAt(acornPos);
+  let code;
+  if (ch === 123) { // '{'
+    ++acornPos;
+    code = readHexChar(source.indexOf('}', acornPos) - acornPos);
+    ++acornPos;
+    if (code > 0x10FFFF) syntaxError();
+  } else {
+    code = readHexChar(4);
+  }
+  // UTF-16 Decoding
+  if (code <= 0xFFFF) return String.fromCharCode(code);
+  code -= 0x10000;
+  return String.fromCharCode((code >> 10) + 0xD800, (code & 1023) + 0xDC00);
+}
+
+/*
+ * </ Acorn Port>
+ */
+
+function readExportAs (startPos, endPos) {
+  let ch = source.charCodeAt(pos);
+  let ls = startPos, le = endPos;
+  if (ch === 97 /*a*/) {
+    pos += 2;
+    ch = commentWhitespace(true);
+    startPos = pos;
+    readToWsOrPunctuator(ch);
+    endPos = pos;
+    ch = commentWhitespace(true);
+  }
+  if (pos !== startPos)
+    addExport(startPos, endPos, ls, le);
+  return ch;
+}
+
+function readImportString (ss, ch) {
+  const startPos = pos + 1;
+  if (ch === 39/*'*/ || ch === 34/*"*/) {
+    stringLiteral(ch);
+  }
+  else {
+    syntaxError();
+    return;
+  }
+  const impt = addImport(ss, startPos, pos, -1);
+  readName(impt);
+  pos++;
+  ch = commentWhitespace(false);
+  if (ch !== 119/*w*/ || !source.startsWith('ith', pos + 1)) {
+    pos--;
+    return;
+  }
+  const attrIndex = pos;
+
+  pos += 4;
+  ch = commentWhitespace(true);
+  if (ch !== 123/*{*/) {
+    pos = attrIndex;
+    return;
+  }
+  const attrStart = pos;
+  const attrs = [];
+  do {
+    pos++;
+    ch = commentWhitespace(true);
+    let key, keyStart, keyEnd;
+    if (ch === 39/*'*/ || ch === 34/*"*/) {
+      keyStart = pos;
+      stringLiteral(ch);
+      keyEnd = pos + 1;
+      key = readString(keyStart, ch);
+      pos++;
+      ch = commentWhitespace(true);
+    }
+    else {
+      keyStart = pos;
+      ch = readToWsOrPunctuator(ch);
+      keyEnd = pos;
+      key = source.slice(keyStart, keyEnd);
+    }
+    if (ch !== 58/*:*/) {
+      pos = attrIndex;
+      return;
+    }
+    pos++;
+    ch = commentWhitespace(true);
+    let value, valueStart;
+    if (ch === 39/*'*/ || ch === 34/*"*/) {
+      valueStart = pos;
+      stringLiteral(ch);
+      value = readString(valueStart, ch);
+    }
+    else {
+      pos = attrIndex;
+      return;
+    }
+    attrs.push([key, value]);
+    pos++;
+    ch = commentWhitespace(true);
+    if (ch === 44/*,*/) {
+      pos++;
+      continue;
+    }
+    if (ch === 125/*}*/)
+      break;
+    pos = attrIndex;
+    return;
+  } while (true);
+  impt.a = attrStart;
+  impt.at = attrs;
+  impt.se = pos + 1;
+}
+
+function commentWhitespace (br) {
+  let ch;
+  do {
+    ch = source.charCodeAt(pos);
+    if (ch === 47/*/*/) {
+      const next_ch = source.charCodeAt(pos + 1);
+      if (next_ch === 47/*/*/)
+        lineComment();
+      else if (next_ch === 42/***/)
+        blockComment(br);
+      else
+        return ch;
+    }
+    else if (br ? !isBrOrWs(ch): !isWsNotBr(ch)) {
+      return ch;
+    }
+  } while (pos++ < end);
+  return ch;
+}
+
+function templateString () {
+  while (pos++ < end) {
+    const ch = source.charCodeAt(pos);
+    if (ch === 36/*$*/ && source.charCodeAt(pos + 1) === 123/*{*/) {
+      pos++;
+      templateStack[templateStackDepth++] = templateDepth;
+      templateDepth = ++openTokenDepth;
+      return;
+    }
+    if (ch === 96/*`*/)
+      return;
+    if (ch === 92/*\*/)
+      pos++;
+  }
+  syntaxError();
+}
+
+function blockComment (br) {
+  pos++;
+  while (pos++ < end) {
+    const ch = source.charCodeAt(pos);
+    if (!br && isBr(ch))
+      return;
+    if (ch === 42/***/ && source.charCodeAt(pos + 1) === 47/*/*/) {
+      pos++;
+      return;
+    }
+  }
+}
+
+function lineComment () {
+  while (pos++ < end) {
+    const ch = source.charCodeAt(pos);
+    if (ch === 10/*\n*/ || ch === 13/*\r*/)
+      return;
+  }
+}
+
+function stringLiteral (quote) {
+  while (pos++ < end) {
+    let ch = source.charCodeAt(pos);
+    if (ch === quote)
+      return;
+    if (ch === 92/*\*/) {
+      ch = source.charCodeAt(++pos);
+      if (ch === 13/*\r*/ && source.charCodeAt(pos + 1) === 10/*\n*/)
+        pos++;
+    }
+    else if (isBr(ch))
+      break;
+  }
+  syntaxError();
+}
+
+function regexCharacterClass () {
+  while (pos++ < end) {
+    let ch = source.charCodeAt(pos);
+    if (ch === 93/*]*/)
+      return ch;
+    if (ch === 92/*\*/)
+      pos++;
+    else if (ch === 10/*\n*/ || ch === 13/*\r*/)
+      break;
+  }
+  syntaxError();
+}
+
+function regularExpression () {
+  while (pos++ < end) {
+    let ch = source.charCodeAt(pos);
+    if (ch === 47/*/*/)
+      return;
+    if (ch === 91/*[*/)
+      ch = regexCharacterClass();
+    else if (ch === 92/*\*/)
+      pos++;
+    else if (ch === 10/*\n*/ || ch === 13/*\r*/)
+      break;
+  }
+  syntaxError();
+}
+
+function readToWsOrPunctuator (ch) {
+  do {
+    if (isBrOrWs(ch) || isPunctuator(ch))
+      return ch;
+  } while (ch = source.charCodeAt(++pos));
+  return ch;
+}
+
+// Note: non-asii BR and whitespace checks omitted for perf / footprint
+// if there is a significant user need this can be reconsidered
+function isBr (c) {
+  return c === 13/*\r*/ || c === 10/*\n*/;
+}
+
+function isWsNotBr (c) {
+  return c === 9 || c === 11 || c === 12 || c === 32 || c === 160;
+}
+
+function isBrOrWs (c) {
+  return c > 8 && c < 14 || c === 32 || c === 160;
+}
+
+function isBrOrWsOrPunctuatorNotDot (c) {
+  return c > 8 && c < 14 || c === 32 || c === 160 || isPunctuator(c) && c !== 46/*.*/;
+}
+
+function keywordStart (pos) {
+  return pos === 0 || isBrOrWsOrPunctuatorNotDot(source.charCodeAt(pos - 1));
+}
+
+function readPrecedingKeyword (pos, match) {
+  if (pos < match.length - 1)
+    return false;
+  return source.startsWith(match, pos - match.length + 1) && (pos === 0 || isBrOrWsOrPunctuatorNotDot(source.charCodeAt(pos - match.length)));
+}
+
+function readPrecedingKeyword1 (pos, ch) {
+  return source.charCodeAt(pos) === ch && (pos === 0 || isBrOrWsOrPunctuatorNotDot(source.charCodeAt(pos - 1)));
+}
+
+// Detects one of case, debugger, delete, do, else, in, instanceof, new,
+//   return, throw, typeof, void, yield, await
+function isExpressionKeyword (pos) {
+  switch (source.charCodeAt(pos)) {
+    case 100/*d*/:
+      switch (source.charCodeAt(pos - 1)) {
+        case 105/*i*/:
+          // void
+          return readPrecedingKeyword(pos - 2, 'vo');
+        case 108/*l*/:
+          // yield
+          return readPrecedingKeyword(pos - 2, 'yie');
+        default:
+          return false;
+      }
+    case 101/*e*/:
+      switch (source.charCodeAt(pos - 1)) {
+        case 115/*s*/:
+          switch (source.charCodeAt(pos - 2)) {
+            case 108/*l*/:
+              // else
+              return readPrecedingKeyword1(pos - 3, 101/*e*/);
+            case 97/*a*/:
+              // case
+              return readPrecedingKeyword1(pos - 3, 99/*c*/);
+            default:
+              return false;
+          }
+        case 116/*t*/:
+          // delete
+          return readPrecedingKeyword(pos - 2, 'dele');
+        default:
+          return false;
+      }
+    case 102/*f*/:
+      if (source.charCodeAt(pos - 1) !== 111/*o*/ || source.charCodeAt(pos - 2) !== 101/*e*/)
+        return false;
+      switch (source.charCodeAt(pos - 3)) {
+        case 99/*c*/:
+          // instanceof
+          return readPrecedingKeyword(pos - 4, 'instan');
+        case 112/*p*/:
+          // typeof
+          return readPrecedingKeyword(pos - 4, 'ty');
+        default:
+          return false;
+      }
+    case 110/*n*/:
+      // in, return
+      return readPrecedingKeyword1(pos - 1, 105/*i*/) || readPrecedingKeyword(pos - 1, 'retur');
+    case 111/*o*/:
+      // do
+      return readPrecedingKeyword1(pos - 1, 100/*d*/);
+    case 114/*r*/:
+      // debugger
+      return readPrecedingKeyword(pos - 1, 'debugge');
+    case 116/*t*/:
+      // await
+      return readPrecedingKeyword(pos - 1, 'awai');
+    case 119/*w*/:
+      switch (source.charCodeAt(pos - 1)) {
+        case 101/*e*/:
+          // new
+          return readPrecedingKeyword1(pos - 2, 110/*n*/);
+        case 111/*o*/:
+          // throw
+          return readPrecedingKeyword(pos - 2, 'thr');
+        default:
+          return false;
+      }
+  }
+  return false;
+}
+
+function isParenKeyword (curPos) {
+  return source.charCodeAt(curPos) === 101/*e*/ && source.startsWith('whil', curPos - 4) ||
+      source.charCodeAt(curPos) === 114/*r*/ && source.startsWith('fo', curPos - 2) ||
+      source.charCodeAt(curPos - 1) === 105/*i*/ && source.charCodeAt(curPos) === 102/*f*/;
+}
+
+function isForParen (curPos) {
+  return source.charCodeAt(curPos) === 114/*r*/ && source.startsWith('fo', curPos - 2);
+}
+
+// In valid JS, the for-of `of` keyword always follows a binding,
+// which ends with an identifier-tail char, ']', '}', or ')'.
+function isForOfBinding (pos) {
+  const ch = source.charCodeAt(pos);
+  if (!isBrOrWs(ch) && ch !== 93/*]*/ && ch !== 125/*}*/ && ch !== 41/*)*/)
+    return false;
+  while (pos > 0 && isBrOrWs(source.charCodeAt(pos)))
+    pos--;
+  const c = source.charCodeAt(pos);
+  return c === 93/*]*/ || c === 125/*}*/ || c === 41/*)*/ || !isPunctuator(c);
+}
+
+function isPunctuator (ch) {
+  // 23 possible punctuator endings: !%&()*+,-./:;<=>?[]^{}|~
+  return ch === 33/*!*/ || ch === 37/*%*/ || ch === 38/*&*/ ||
+    ch > 39 && ch < 48 || ch > 57 && ch < 64 ||
+    ch === 91/*[*/ || ch === 93/*]*/ || ch === 94/*^*/ ||
+    ch > 122 && ch < 127;
+}
+
+function isExpressionPunctuator (ch) {
+  // 20 possible expression endings: !%&(*+,-.:;<=>?[^{|~
+  return ch === 33/*!*/ || ch === 37/*%*/ || ch === 38/*&*/ ||
+    ch > 39 && ch < 47 && ch !== 41 || ch > 57 && ch < 64 ||
+    ch === 91/*[*/ || ch === 94/*^*/ || ch > 122 && ch < 127 && ch !== 125/*}*/;
+}
+
+function isExpressionTerminator (curPos) {
+  // detects:
+  // => ; ) finally catch else
+  // as all of these followed by a { will indicate a statement brace
+  switch (source.charCodeAt(curPos)) {
+    case 62/*>*/:
+      return source.charCodeAt(curPos - 1) === 61/*=*/;
+    case 59/*;*/:
+    case 41/*)*/:
+      return true;
+    case 104/*h*/:
+      return source.startsWith('catc', curPos - 4);
+    case 121/*y*/:
+      return source.startsWith('finall', curPos - 6);
+    case 101/*e*/:
+      return source.startsWith('els', curPos - 3);
+  }
+  return false;
+}
+
+function syntaxError () {
+  throw Object.assign(new Error(`Parse error ${name}:${source.slice(0, pos).split('\n').length}:${pos - source.lastIndexOf('\n', pos - 1)}`), { idx: pos });
+}
