text: // The algorithm used to determine whether a regexp can appear at a
  // given point in the program is loosely based on sweet.js' approach.
  // See https://github.com/mozilla/sweet.js/wiki/design
  
  import {Parser} from "./state"
  import {types as tt} from "./tokentype"
  import {lineBreak} from "./whitespace"
  
  export class TokContext {
    constructor(token, isExpr, preserveSpace, override) {
      this.token = token
      this.isExpr = isExpr
      this.preserveSpace = preserveSpace
      this.override = override
    }
  }
  
  export const types = {
    b_stat: new TokContext("{", false),
    b_expr: new TokContext("{", true),
    b_tmpl: new TokContext("${", true),
    p_stat: new TokContext("(", false),
    p_expr: new TokContext("(", true),
    q_tmpl: new TokContext("`", true, true, p => p.readTmplToken()),
    f_expr: new TokContext("function", true)
  }
  
  const pp = Parser.prototype
  
  pp.initialContext = function() {
    return [types.b_stat]
  }
  
  pp.braceIsBlock = function(prevType) {
    let parent
    if (prevType === tt.colon && (parent = this.curContext()).token == "{")
      return !parent.isExpr
    if (prevType === tt._return)
      return lineBreak.test(this.input.slice(this.lastTokEnd, this.start))
    if (prevType === tt._else || prevType === tt.semi || prevType === tt.eof)
      return true
    if (prevType == tt.braceL)
      return this.curContext() === types.b_stat
    return !this.exprAllowed
  }
  
  pp.updateContext = function(prevType) {
    let update, type = this.type
    if (type.keyword && prevType == tt.dot)
      this.exprAllowed = false
    else if (update = type.updateContext)
      update.call(this, prevType)
    else
      this.exprAllowed = type.beforeExpr
  }
  
  // Token-specific context update code
  
  tt.parenR.updateContext = tt.braceR.updateContext = function() {
    if (this.context.length == 1) {
      this.exprAllowed = true
      return
    }
    let out = this.context.pop()
    if (out === types.b_stat && this.curContext() === types.f_expr) {
      this.context.pop()
      this.exprAllowed = false
    } else if (out === types.b_tmpl) {
      this.exprAllowed = true
    } else {
      this.exprAllowed = !out.isExpr
    }
  }
  
  tt.braceL.updateContext = function(prevType) {
    this.context.push(this.braceIsBlock(prevType) ? types.b_stat : types.b_expr)
    this.exprAllowed = true
  }
  
  tt.dollarBraceL.updateContext = function() {
    this.context.push(types.b_tmpl)
    this.exprAllowed = true
  }
  
  tt.parenL.updateContext = function(prevType) {
    let statementParens = prevType === tt._if || prevType === tt._for || prevType === tt._with || prevType === tt._while
    this.context.push(statementParens ? types.p_stat : types.p_expr)
    this.exprAllowed = true
  }
  
  tt.incDec.updateContext = function() {
    // tokExprAllowed stays unchanged
  }
  
  tt._function.updateContext = function() {
    if (this.curContext() !== types.b_stat)
      this.context.push(types.f_expr)
    this.exprAllowed = false
  }
  
  tt.backQuote.updateContext = function() {
    if (this.curContext() === types.q_tmpl)
      this.context.pop()
    else
      this.context.push(types.q_tmpl)
    this.exprAllowed = false
  }
  