"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = void 0;

var _types = require("../tokenizer/types");

var _tokenizer = _interopRequireDefault(require("../tokenizer"));

var _state = _interopRequireDefault(require("../tokenizer/state"));

var _whitespace = require("../util/whitespace");

var _identifier = require("../util/identifier");

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

const literal = /^('|")((?:\\?.)*?)\1/;

class UtilParser extends _tokenizer.default {
  addExtra(node, key, val) {
    if (!node) return;
    const extra = node.extra = node.extra || {};
    extra[key] = val;
  }

  isRelational(op) {
    return this.match(_types.types.relational) && this.state.value === op;
  }

  isLookaheadRelational(op) {
    const next = this.nextTokenStart();

    if (this.input.charAt(next) === op) {
      if (next + 1 === this.input.length) {
        return true;
      }

      const afterNext = this.input.charCodeAt(next + 1);
      return afterNext !== op.charCodeAt(0) && afterNext !== 61;
    }

    return false;
  }

  expectRelational(op) {
    if (this.isRelational(op)) {
      this.next();
    } else {
      this.unexpected(null, _types.types.relational);
    }
  }

  eatRelational(op) {
    if (this.isRelational(op)) {
      this.next();
      return true;
    }

    return false;
  }

  isContextual(name) {
    return this.match(_types.types.name) && this.state.value === name && !this.state.containsEsc;
  }

  isUnparsedContextual(nameStart, name) {
    const nameEnd = nameStart + name.length;
    return this.input.slice(nameStart, nameEnd) === name && (nameEnd === this.input.length || !(0, _identifier.isIdentifierChar)(this.input.charCodeAt(nameEnd)));
  }

  isLookaheadContextual(name) {
    const next = this.nextTokenStart();
    return this.isUnparsedContextual(next, name);
  }

  eatContextual(name) {
    return this.isContextual(name) && this.eat(_types.types.name);
  }

  expectContextual(name, message) {
    if (!this.eatContextual(name)) this.unexpected(null, message);
  }

  canInsertSemicolon() {
    return this.match(_types.types.eof) || this.match(_types.types.braceR) || this.hasPrecedingLineBreak();
  }

  hasPrecedingLineBreak() {
    return _whitespace.lineBreak.test(this.input.slice(this.state.lastTokEnd, this.state.start));
  }

  isLineTerminator() {
    return this.eat(_types.types.semi) || this.canInsertSemicolon();
  }

  semicolon() {
    if (!this.isLineTerminator()) this.unexpected(null, _types.types.semi);
  }

  expect(type, pos) {
    this.eat(type) || this.unexpected(pos, type);
  }

  assertNoSpace(message = "Unexpected space.") {
    if (this.state.start > this.state.lastTokEnd) {
      this.raise(this.state.lastTokEnd, message);
    }
  }

  unexpected(pos, messageOrType = "Unexpected token") {
    if (typeof messageOrType !== "string") {
      messageOrType = `Unexpected token, expected "${messageOrType.label}"`;
    }

    throw this.raise(pos != null ? pos : this.state.start, messageOrType);
  }

  expectPlugin(name, pos) {
    if (!this.hasPlugin(name)) {
      throw this.raise(pos != null ? pos : this.state.start, `This experimental syntax requires enabling the parser plugin: '${name}'`, {
        missingPluginNames: [name]
      });
    }

    return true;
  }

  expectOnePlugin(names, pos) {
    if (!names.some(n => this.hasPlugin(n))) {
      throw this.raise(pos != null ? pos : this.state.start, `This experimental syntax requires enabling one of the following parser plugin(s): '${names.join(", ")}'`, {
        missingPluginNames: names
      });
    }
  }

  checkYieldAwaitInDefaultParams() {
    if (this.state.yieldPos !== -1 && (this.state.awaitPos === -1 || this.state.yieldPos < this.state.awaitPos)) {
      this.raise(this.state.yieldPos, "Yield cannot be used as name inside a generator function");
    }

    if (this.state.awaitPos !== -1) {
      this.raise(this.state.awaitPos, "Await cannot be used as name inside an async function");
    }
  }

  strictDirective(start) {
    for (;;) {
      _whitespace.skipWhiteSpace.lastIndex = start;
      start += _whitespace.skipWhiteSpace.exec(this.input)[0].length;
      const match = literal.exec(this.input.slice(start));
      if (!match) break;
      if (match[2] === "use strict") return true;
      start += match[0].length;
      _whitespace.skipWhiteSpace.lastIndex = start;
      start += _whitespace.skipWhiteSpace.exec(this.input)[0].length;

      if (this.input[start] === ";") {
        start++;
      }
    }

    return false;
  }

  tryParse(fn, oldState = this.state.clone()) {
    const abortSignal = {
      node: null
    };

    try {
      const node = fn((node = null) => {
        abortSignal.node = node;
        throw abortSignal;
      });

      if (this.state.errors.length > oldState.errors.length) {
        const failState = this.state;
        this.state = oldState;
        return {
          node,
          error: failState.errors[oldState.errors.length],
          thrown: false,
          aborted: false,
          failState
        };
      }

      return {
        node,
        error: null,
        thrown: false,
        aborted: false,
        failState: null
      };
    } catch (error) {
      const failState = this.state;
      this.state = oldState;

      if (error instanceof SyntaxError) {
        return {
          node: null,
          error,
          thrown: true,
          aborted: false,
          failState
        };
      }

      if (error === abortSignal) {
        return {
          node: abortSignal.node,
          error: null,
          thrown: false,
          aborted: true,
          failState
        };
      }

      throw error;
    }
  }

}

exports.default = UtilParser;