import {LooseParser} from “./state” import {isDummy} from “./parseutil” import {getLineInfo, tokTypes as tt} from “../index”

const lp = LooseParser.prototype

lp.parseTopLevel = function() {

let node = this.startNodeAt(this.options.locations ? [0, getLineInfo(this.input, 0)] : 0)
node.body = []
while (this.tok.type !== tt.eof) node.body.push(this.parseStatement())
this.last = this.tok
if (this.options.ecmaVersion >= 6) {
  node.sourceType = this.options.sourceType
}
return this.finishNode(node, "Program")

}

lp.parseStatement = function() {

let starttype = this.tok.type, node = this.startNode(), kind

if (this.toks.isLet()) {
  starttype = tt._var
  kind = "let"
}

switch (starttype) {
case tt._break: case tt._continue:
  this.next()
  let isBreak = starttype === tt._break
  if (this.semicolon() || this.canInsertSemicolon()) {
    node.label = null
  } else {
    node.label = this.tok.type === tt.name ? this.parseIdent() : null
    this.semicolon()
  }
  return this.finishNode(node, isBreak ? "BreakStatement" : "ContinueStatement")

case tt._debugger:
  this.next()
  this.semicolon()
  return this.finishNode(node, "DebuggerStatement")

case tt._do:
  this.next()
  node.body = this.parseStatement()
  node.test = this.eat(tt._while) ? this.parseParenExpression() : this.dummyIdent()
  this.semicolon()
  return this.finishNode(node, "DoWhileStatement")

case tt._for:
  this.next()
  this.pushCx()
  this.expect(tt.parenL)
  if (this.tok.type === tt.semi) return this.parseFor(node, null)
  let isLet = this.toks.isLet()
  if (isLet || this.tok.type === tt._var || this.tok.type === tt._const) {
    let init = this.parseVar(true, isLet ? "let" : this.tok.value)
    if (init.declarations.length === 1 && (this.tok.type === tt._in || this.isContextual("of"))) {
      return this.parseForIn(node, init)
    }
    return this.parseFor(node, init)
  }
  let init = this.parseExpression(true)
  if (this.tok.type === tt._in || this.isContextual("of"))
    return this.parseForIn(node, this.toAssignable(init))
  return this.parseFor(node, init)

case tt._function:
  this.next()
  return this.parseFunction(node, true)

case tt._if:
  this.next()
  node.test = this.parseParenExpression()
  node.consequent = this.parseStatement()
  node.alternate = this.eat(tt._else) ? this.parseStatement() : null
  return this.finishNode(node, "IfStatement")

case tt._return:
  this.next()
  if (this.eat(tt.semi) || this.canInsertSemicolon()) node.argument = null
  else { node.argument = this.parseExpression(); this.semicolon() }
  return this.finishNode(node, "ReturnStatement")

case tt._switch:
  let blockIndent = this.curIndent, line = this.curLineStart
  this.next()
  node.discriminant = this.parseParenExpression()
  node.cases = []
  this.pushCx()
  this.expect(tt.braceL)

  let cur
  while (!this.closes(tt.braceR, blockIndent, line, true)) {
    if (this.tok.type === tt._case || this.tok.type === tt._default) {
      let isCase = this.tok.type === tt._case
      if (cur) this.finishNode(cur, "SwitchCase")
      node.cases.push(cur = this.startNode())
      cur.consequent = []
      this.next()
      if (isCase) cur.test = this.parseExpression()
      else cur.test = null
      this.expect(tt.colon)
    } else {
      if (!cur) {
        node.cases.push(cur = this.startNode())
        cur.consequent = []
        cur.test = null
      }
      cur.consequent.push(this.parseStatement())
    }
  }
  if (cur) this.finishNode(cur, "SwitchCase")
  this.popCx()
  this.eat(tt.braceR)
  return this.finishNode(node, "SwitchStatement")

case tt._throw:
  this.next()
  node.argument = this.parseExpression()
  this.semicolon()
  return this.finishNode(node, "ThrowStatement")

case tt._try:
  this.next()
  node.block = this.parseBlock()
  node.handler = null
  if (this.tok.type === tt._catch) {
    let clause = this.startNode()
    this.next()
    this.expect(tt.parenL)
    clause.param = this.toAssignable(this.parseExprAtom(), true)
    this.expect(tt.parenR)
    clause.body = this.parseBlock()
    node.handler = this.finishNode(clause, "CatchClause")
  }
  node.finalizer = this.eat(tt._finally) ? this.parseBlock() : null
  if (!node.handler && !node.finalizer) return node.block
  return this.finishNode(node, "TryStatement")

case tt._var:
case tt._const:
  return this.parseVar(false, kind || this.tok.value)

case tt._while:
  this.next()
  node.test = this.parseParenExpression()
  node.body = this.parseStatement()
  return this.finishNode(node, "WhileStatement")

case tt._with:
  this.next()
  node.object = this.parseParenExpression()
  node.body = this.parseStatement()
  return this.finishNode(node, "WithStatement")

case tt.braceL:
  return this.parseBlock()

case tt.semi:
  this.next()
  return this.finishNode(node, "EmptyStatement")

case tt._class:
  return this.parseClass(true)

case tt._import:
  return this.parseImport()

case tt._export:
  return this.parseExport()

default:
  if (this.toks.isAsyncFunction()) {
    this.next()
    this.next()
    return this.parseFunction(node, true, true)
  }
  let expr = this.parseExpression()
  if (isDummy(expr)) {
    this.next()
    if (this.tok.type === tt.eof) return this.finishNode(node, "EmptyStatement")
    return this.parseStatement()
  } else if (starttype === tt.name && expr.type === "Identifier" && this.eat(tt.colon)) {
    node.body = this.parseStatement()
    node.label = expr
    return this.finishNode(node, "LabeledStatement")
  } else {
    node.expression = expr
    this.semicolon()
    return this.finishNode(node, "ExpressionStatement")
  }
}

}

lp.parseBlock = function() {

let node = this.startNode()
this.pushCx()
this.expect(tt.braceL)
let blockIndent = this.curIndent, line = this.curLineStart
node.body = []
while (!this.closes(tt.braceR, blockIndent, line, true))
  node.body.push(this.parseStatement())
this.popCx()
this.eat(tt.braceR)
return this.finishNode(node, "BlockStatement")

}

lp.parseFor = function(node, init) {

node.init = init
node.test = node.update = null
if (this.eat(tt.semi) && this.tok.type !== tt.semi) node.test = this.parseExpression()
if (this.eat(tt.semi) && this.tok.type !== tt.parenR) node.update = this.parseExpression()
this.popCx()
this.expect(tt.parenR)
node.body = this.parseStatement()
return this.finishNode(node, "ForStatement")

}

lp.parseForIn = function(node, init) {

let type = this.tok.type === tt._in ? "ForInStatement" : "ForOfStatement"
this.next()
node.left = init
node.right = this.parseExpression()
this.popCx()
this.expect(tt.parenR)
node.body = this.parseStatement()
return this.finishNode(node, type)

}

lp.parseVar = function(noIn, kind) {

let node = this.startNode()
node.kind = kind
this.next()
node.declarations = []
do {
  let decl = this.startNode()
  decl.id = this.options.ecmaVersion >= 6 ? this.toAssignable(this.parseExprAtom(), true) : this.parseIdent()
  decl.init = this.eat(tt.eq) ? this.parseMaybeAssign(noIn) : null
  node.declarations.push(this.finishNode(decl, "VariableDeclarator"))
} while (this.eat(tt.comma))
if (!node.declarations.length) {
  let decl = this.startNode()
  decl.id = this.dummyIdent()
  node.declarations.push(this.finishNode(decl, "VariableDeclarator"))
}
if (!noIn) this.semicolon()
return this.finishNode(node, "VariableDeclaration")

}

lp.parseClass = function(isStatement) {

let node = this.startNode()
this.next()
if (isStatement == null) isStatement = this.tok.type === tt.name
if (this.tok.type === tt.name) node.id = this.parseIdent()
else if (isStatement) node.id = this.dummyIdent()
else node.id = null
node.superClass = this.eat(tt._extends) ? this.parseExpression() : null
node.body = this.startNode()
node.body.body = []
this.pushCx()
let indent = this.curIndent + 1, line = this.curLineStart
this.eat(tt.braceL)
if (this.curIndent + 1 < indent) { indent = this.curIndent; line = this.curLineStart }
while (!this.closes(tt.braceR, indent, line)) {
  if (this.semicolon()) continue
  let method = this.startNode(), isGenerator, isAsync
  if (this.options.ecmaVersion >= 6) {
    method.static = false
    isGenerator = this.eat(tt.star)
  }
  this.parsePropertyName(method)
  if (isDummy(method.key)) { if (isDummy(this.parseMaybeAssign())) this.next(); this.eat(tt.comma); continue }
  if (method.key.type === "Identifier" && !method.computed && method.key.name === "static" &&
      (this.tok.type != tt.parenL && this.tok.type != tt.braceL)) {
    method.static = true
    isGenerator = this.eat(tt.star)
    this.parsePropertyName(method)
  } else {
    method.static = false
  }
  if (!method.computed &&
      method.key.type === "Identifier" && method.key.name === "async" && this.tok.type !== tt.parenL &&
      !this.canInsertSemicolon()) {
    this.parsePropertyName(method)
    isAsync = true
  } else {
    isAsync = false
  }
  if (this.options.ecmaVersion >= 5 && method.key.type === "Identifier" &&
      !method.computed && (method.key.name === "get" || method.key.name === "set") &&
      this.tok.type !== tt.parenL && this.tok.type !== tt.braceL) {
    method.kind = method.key.name
    this.parsePropertyName(method)
    method.value = this.parseMethod(false)
  } else {
    if (!method.computed && !method.static && !isGenerator && !isAsync && (
      method.key.type === "Identifier" && method.key.name === "constructor" ||
        method.key.type === "Literal" && method.key.value === "constructor")) {
      method.kind = "constructor"
    } else {
      method.kind =  "method"
    }
    method.value = this.parseMethod(isGenerator, isAsync)
  }
  node.body.body.push(this.finishNode(method, "MethodDefinition"))
}
this.popCx()
if (!this.eat(tt.braceR)) {
  // If there is no closing brace, make the node span to the start
  // of the next token (this is useful for Tern)
  this.last.end = this.tok.start
  if (this.options.locations) this.last.loc.end = this.tok.loc.start
}
this.semicolon()
this.finishNode(node.body, "ClassBody")
return this.finishNode(node, isStatement ? "ClassDeclaration" : "ClassExpression")

}

lp.parseFunction = function(node, isStatement, isAsync) {

let oldInAsync = this.inAsync
this.initFunction(node)
if (this.options.ecmaVersion >= 6) {
  node.generator = this.eat(tt.star)
}
if (this.options.ecmaVersion >= 8) {
  node.async = !!isAsync
}
if (isStatement == null) isStatement = this.tok.type === tt.name
if (this.tok.type === tt.name) node.id = this.parseIdent()
else if (isStatement) node.id = this.dummyIdent()
this.inAsync = node.async
node.params = this.parseFunctionParams()
node.body = this.parseBlock()
this.inAsync = oldInAsync
return this.finishNode(node, isStatement ? "FunctionDeclaration" : "FunctionExpression")

}

lp.parseExport = function() {

let node = this.startNode()
this.next()
if (this.eat(tt.star)) {
  node.source = this.eatContextual("from") ? this.parseExprAtom() : this.dummyString()
  return this.finishNode(node, "ExportAllDeclaration")
}
if (this.eat(tt._default)) {
  // export default (function foo() {}) // This is FunctionExpression.
  let isAsync
  if (this.tok.type === tt._function || (isAsync = this.toks.isAsyncFunction())) {
    let fNode = this.startNode()
    this.next()
    if (isAsync) this.next()
    node.declaration = this.parseFunction(fNode, null, isAsync)
  } else if (this.tok.type === tt._class) {
    node.declaration = this.parseClass(null)
  } else {
    node.declaration = this.parseMaybeAssign()
    this.semicolon()
  }
  return this.finishNode(node, "ExportDefaultDeclaration")
}
if (this.tok.type.keyword || this.toks.isLet() || this.toks.isAsyncFunction()) {
  node.declaration = this.parseStatement()
  node.specifiers = []
  node.source = null
} else {
  node.declaration = null
  node.specifiers = this.parseExportSpecifierList()
  node.source = this.eatContextual("from") ? this.parseExprAtom() : null
  this.semicolon()
}
return this.finishNode(node, "ExportNamedDeclaration")

}

lp.parseImport = function() {

let node = this.startNode()
this.next()
if (this.tok.type === tt.string) {
  node.specifiers = []
  node.source = this.parseExprAtom()
  node.kind = ''
} else {
  let elt
  if (this.tok.type === tt.name && this.tok.value !== "from") {
    elt = this.startNode()
    elt.local = this.parseIdent()
    this.finishNode(elt, "ImportDefaultSpecifier")
    this.eat(tt.comma)
  }
  node.specifiers = this.parseImportSpecifierList()
  node.source = this.eatContextual("from") && this.tok.type == tt.string ? this.parseExprAtom() : this.dummyString()
  if (elt) node.specifiers.unshift(elt)
}
this.semicolon()
return this.finishNode(node, "ImportDeclaration")

}

lp.parseImportSpecifierList = function() {

let elts = []
if (this.tok.type === tt.star) {
  let elt = this.startNode()
  this.next()
  elt.local = this.eatContextual("as") ? this.parseIdent() : this.dummyIdent()
  elts.push(this.finishNode(elt, "ImportNamespaceSpecifier"))
} else {
  let indent = this.curIndent, line = this.curLineStart, continuedLine = this.nextLineStart
  this.pushCx()
  this.eat(tt.braceL)
  if (this.curLineStart > continuedLine) continuedLine = this.curLineStart
  while (!this.closes(tt.braceR, indent + (this.curLineStart <= continuedLine ? 1 : 0), line)) {
    let elt = this.startNode()
    if (this.eat(tt.star)) {
      elt.local = this.eatContextual("as") ? this.parseIdent() : this.dummyIdent()
      this.finishNode(elt, "ImportNamespaceSpecifier")
    } else {
      if (this.isContextual("from")) break
      elt.imported = this.parseIdent()
      if (isDummy(elt.imported)) break
      elt.local = this.eatContextual("as") ? this.parseIdent() : elt.imported
      this.finishNode(elt, "ImportSpecifier")
    }
    elts.push(elt)
    this.eat(tt.comma)
  }
  this.eat(tt.braceR)
  this.popCx()
}
return elts

}

lp.parseExportSpecifierList = function() {

let elts = []
let indent = this.curIndent, line = this.curLineStart, continuedLine = this.nextLineStart
this.pushCx()
this.eat(tt.braceL)
if (this.curLineStart > continuedLine) continuedLine = this.curLineStart
while (!this.closes(tt.braceR, indent + (this.curLineStart <= continuedLine ? 1 : 0), line)) {
  if (this.isContextual("from")) break
  let elt = this.startNode()
  elt.local = this.parseIdent()
  if (isDummy(elt.local)) break
  elt.exported = this.eatContextual("as") ? this.parseIdent() : elt.local
  this.finishNode(elt, "ExportSpecifier")
  elts.push(elt)
  this.eat(tt.comma)
}
this.eat(tt.braceR)
this.popCx()
return elts

}