text: import {tokenizer, SourceLocation, tokTypes as tt, Node, lineBreak, isNewLine} from ".."
  
  // Registered plugins
  export const pluginsLoose = {};
  export class LooseParser {
    constructor(input, options) {
      this.toks = tokenizer(input, options);
      this.options = this.toks.options;
      this.input = this.toks.input;
      this.tok = this.last = {type: tt.eof, start: 0, end: 0};
      if (this.options.locations) {
        let here = this.toks.curPosition();
        this.tok.loc = new SourceLocation(this.toks, here, here)
      }
      this.ahead = [];
       // Tokens ahead
      this.context = [];
       // Indentation contexted
      this.curIndent = 0;
      this.curLineStart = 0;
      this.nextLineStart = this.lineEnd(this.curLineStart) + 1;
      // Load plugins
      this.options.pluginsLoose = options.pluginsLoose || {};
      this.loadPlugins(this.options.pluginsLoose)
    }
  
    startNode() {
      return new Node(this.toks, this.tok.start, this.options.locations ? this.tok.loc.start : null)
    }
  
    storeCurrentPos() {
      return this.options.locations ? [this.tok.start, this.tok.loc.start] : this.tok.start
    }
  
    startNodeAt(pos) {
      if (this.options.locations) {
        return new Node(this.toks, pos[0], pos[1])
      } else {
        return new Node(this.toks, pos)
      }
    }
  
    finishNode(node, type) {
      node.type = type;
      node.end = this.last.end;
      if (this.options.locations)
        node.loc.end = this.last.loc.end;
      if (this.options.ranges)
        node.range[1] = this.last.end;
      return node
    }
  
    dummyNode(type) {
      let dummy = this.startNode();
      dummy.type = type;
      dummy.end = dummy.start;
      if (this.options.locations)
        dummy.loc.end = dummy.loc.start;
      if (this.options.ranges)
        dummy.range[1] = dummy.start;
      this.last = {type: tt.name, start: dummy.start, end: dummy.start, loc: dummy.loc};
      return dummy
    }
  
    dummyIdent() {
      let dummy = this.dummyNode("Identifier");
      dummy.name = "✖";
      return dummy
    }
  
    dummyString() {
      let dummy = this.dummyNode("Literal");
      dummy.value = dummy.raw = "✖";
      return dummy
    }
  
    eat(type) {
      if (this.tok.type === type) {
        this.next();
        return true
      } else {
        return false
      }
    }
  
    isContextual(name) {
      return this.tok.type === tt.name && this.tok.value === name
    }
  
    eatContextual(name) {
      return this.tok.value === name && this.eat(tt.name)
    }
  
    canInsertSemicolon() {
      return this.tok.type === tt.eof || this.tok.type === tt.braceR ||
        lineBreak.test(this.input.slice(this.last.end, this.tok.start))
    }
  
    semicolon() {
      return this.eat(tt.semi)
    }
  
    expect(type) {
      if (this.eat(type)) return true;
      for (let i = 1; i <= 2; i++) {
        if (this.lookAhead(i).type == type) {
          for (let j = 0; j < i; j++) this.next()
          return true
        }
      }
    }
  
    pushCx() {
      this.context.push(this.curIndent)
    }
  
    popCx() {
      this.curIndent = this.context.pop()
    }
  
    lineEnd(pos) {
      while (pos < this.input.length && !isNewLine(this.input.charCodeAt(pos))) ++pos;
      return pos
    }
  
    indentationAfter(pos) {
      for (let count = 0;; ++pos) {
        let ch = this.input.charCodeAt(pos);
        if (ch === 32) ++count;
      else
        if (ch === 9) count += this.options.tabSize;
      else return count
      }
    }
  
    closes(closeTok, indent, line, blockHeuristic) {
      if (this.tok.type === closeTok || this.tok.type === tt.eof) return true;
      return line != this.curLineStart && this.curIndent < indent && this.tokenStartsLine() &&
        (!blockHeuristic || this.nextLineStart >= this.input.length ||
         this.indentationAfter(this.nextLineStart) < indent)
    }
  
    tokenStartsLine() {
      for (let p = this.tok.start - 1; p >= this.curLineStart; --p) {
        let ch = this.input.charCodeAt(p);
        if (ch !== 9 && ch !== 32) return false
      }
      return true
    }
  
    extend(name, f) {
      this[name] = f(this[name])
    }
  
    loadPlugins(pluginConfigs) {
      for (let name in pluginConfigs) {
        let plugin = pluginsLoose[name];
        if (!plugin) throw new Error("Plugin '" + name + "' not found");
        plugin(this, pluginConfigs[name])
      }
    }
  }
  