#
#
#           The Nim Compiler
#        (c) Copyright 2013 Andreas Rumpf
#
#    See the file "copying.txt", included in this
#    distribution, for details about the copyright.
#

## Generates traversal procs for the C backend.

# included from cgen.nim

type
  TTraversalClosure = object
    p: BProc
    visitorFrmt: string

const
  visitorFrmt  = "#nimGCvisit((void*)$1, $2);$n"

proc genTraverseProc(c: TTraversalClosure, accessor: Rope, typ: PType)
proc genCaseRange(p: BProc, branch: PNode)
proc getTemp(p: BProc, t: PType, result: var TLoc; needsInit=false)

proc genTraverseProc(c: TTraversalClosure, accessor: Rope, n: PNode;
                     typ: PType) =
  if n == nil: return
  case n.kind
  of nkRecList:
    for i in 0 ..< len(n):
      genTraverseProc(c, accessor, n.sons[i], typ)
  of nkRecCase:
    if (n.sons[0].kind != nkSym): internalError(c.p.config, n.info, "genTraverseProc")
    var p = c.p
    let disc = n.sons[0].sym
    if disc.loc.r == nil: fillObjectFields(c.p.module, typ)
    if disc.loc.t == nil:
      internalError(c.p.config, n.info, "genTraverseProc()")
    lineF(p, cpsStmts, "switch ($1.$2) {$n", [accessor, disc.loc.r])
    for i in 1 ..< len(n):
      let branch = n.sons[i]
      assert branch.kind in {nkOfBranch, nkElse}
      if branch.kind == nkOfBranch:
        genCaseRange(c.p, branch)
      else:
        lineF(p, cpsStmts, "default:$n", [])
      genTraverseProc(c, accessor, lastSon(branch), typ)
      lineF(p, cpsStmts, "break;$n", [])
    lineF(p, cpsStmts, "} $n", [])
  of nkSym:
    let field = n.sym
    if field.typ.kind == tyVoid: return
    if field.loc.r == nil: fillObjectFields(c.p.module, typ)
    if field.loc.t == nil:
      internalError(c.p.config, n.info, "genTraverseProc()")
    genTraverseProc(c, "$1.$2" % [accessor, field.loc.r], field.loc.t)
  else: internalError(c.p.config, n.info, "genTraverseProc()")

proc parentObj(accessor: Rope; m: BModule): Rope {.inline.} =
  if not m.compileToCpp:
    result = "$1.Sup" % [accessor]
  else:
    result = accessor

proc genTraverseProcSeq(c: TTraversalClosure, accessor: Rope, typ: PType)
proc genTraverseProc(c: TTraversalClosure, accessor: Rope, typ: PType) =
  if typ == nil: return

  var p = c.p
  case typ.kind
  of tyGenericInst, tyGenericBody, tyTypeDesc, tyAlias, tyDistinct, tyInferred,
     tySink, tyOwned:
    genTraverseProc(c, accessor, lastSon(typ))
  of tyArray:
    let arraySize = lengthOrd(c.p.config, typ.sons[0])
    var i: TLoc
    getTemp(p, getSysType(c.p.module.g.graph, unknownLineInfo(), tyInt), i)
    let oldCode = p.s(cpsStmts)
    linefmt(p, cpsStmts, "for ($1 = 0; $1 < $2; $1++) {$n",
            [i.r, arraySize])
    let oldLen = p.s(cpsStmts).len
    genTraverseProc(c, ropecg(c.p.module, "$1[$2]", [accessor, i.r]), typ.sons[1])
    if p.s(cpsStmts).len == oldLen:
      # do not emit dummy long loops for faster debug builds:
      p.s(cpsStmts) = oldCode
    else:
      lineF(p, cpsStmts, "}$n", [])
  of tyObject:
    for i in 0 ..< len(typ):
      var x = typ.sons[i]
      if x != nil: x = x.skipTypes(skipPtrs)
      genTraverseProc(c, accessor.parentObj(c.p.module), x)
    if typ.n != nil: genTraverseProc(c, accessor, typ.n, typ)
  of tyTuple:
    let typ = getUniqueType(typ)
    for i in 0 ..< len(typ):
      genTraverseProc(c, ropecg(c.p.module, "$1.Field$2", [accessor, i]), typ.sons[i])
  of tyRef:
    lineCg(p, cpsStmts, visitorFrmt, [accessor, c.visitorFrmt])
  of tySequence:
    if tfHasAsgn notin typ.flags:
      lineCg(p, cpsStmts, visitorFrmt, [accessor, c.visitorFrmt])
    elif containsGarbageCollectedRef(typ.lastSon):
      # destructor based seqs are themselves not traced but their data is, if
      # they contain a GC'ed type:
      genTraverseProcSeq(c, accessor, typ)
  of tyString:
    if tfHasAsgn notin typ.flags:
      lineCg(p, cpsStmts, visitorFrmt, [accessor, c.visitorFrmt])
  of tyProc:
    if typ.callConv == ccClosure:
      lineCg(p, cpsStmts, visitorFrmt, [ropecg(c.p.module, "$1.ClE_0", [accessor]), c.visitorFrmt])
  else:
    discard

proc genTraverseProcSeq(c: TTraversalClosure, accessor: Rope, typ: PType) =
  var p = c.p
  assert typ.kind == tySequence
  var i: TLoc
  getTemp(p, getSysType(c.p.module.g.graph, unknownLineInfo(), tyInt), i)
  let oldCode = p.s(cpsStmts)
  var a: TLoc
  a.r = accessor

  lineF(p, cpsStmts, "for ($1 = 0; $1 < $2; $1++) {$n",
      [i.r, lenExpr(c.p, a)])
  let oldLen = p.s(cpsStmts).len
  genTraverseProc(c, "$1$3[$2]" % [accessor, i.r, dataField(c.p)], typ.sons[0])
  if p.s(cpsStmts).len == oldLen:
    # do not emit dummy long loops for faster debug builds:
    p.s(cpsStmts) = oldCode
  else:
    lineF(p, cpsStmts, "}$n", [])

proc genTraverseProc(m: BModule, origTyp: PType; sig: SigHash): Rope =
  var c: TTraversalClosure
  var p = newProc(nil, m)
  result = "Marker_" & getTypeName(m, origTyp, sig)
  let
    hcrOn = m.hcrOn
    typ = origTyp.skipTypes(abstractInstOwned)
    markerName = if hcrOn: result & "_actual" else: result
    header = "static N_NIMCALL(void, $1)(void* p, NI op)" % [markerName]
    t = getTypeDesc(m, typ)

  lineF(p, cpsLocals, "$1 a;$n", [t])
  lineF(p, cpsInit, "a = ($1)p;$n", [t])

  c.p = p
  c.visitorFrmt = "op" # "#nimGCvisit((void*)$1, op);$n"

  assert typ.kind != tyTypeDesc
  if typ.kind == tySequence:
    genTraverseProcSeq(c, "a".rope, typ)
  else:
    if skipTypes(typ.sons[0], typedescInst+{tyOwned}).kind == tyArray:
      # C's arrays are broken beyond repair:
      genTraverseProc(c, "a".rope, typ.sons[0])
    else:
      genTraverseProc(c, "(*a)".rope, typ.sons[0])

  let generatedProc = "$1 {$n$2$3$4}\n" %
        [header, p.s(cpsLocals), p.s(cpsInit), p.s(cpsStmts)]

  m.s[cfsProcHeaders].addf("$1;\n", [header])
  m.s[cfsProcs].add(generatedProc)

  if hcrOn:
    addf(m.s[cfsProcHeaders], "N_NIMCALL_PTR(void, $1)(void*, NI);\n", [result])
    addf(m.s[cfsDynLibInit], "\t$1 = (N_NIMCALL_PTR(void, )(void*, NI)) hcrRegisterProc($3, \"$1\", (void*)$2);\n",
         [result, markerName, getModuleDllPath(m)])

proc genTraverseProcForGlobal(m: BModule, s: PSym; info: TLineInfo): Rope =
  discard genTypeInfo(m, s.loc.t, info)

  var c: TTraversalClosure
  var p = newProc(nil, m)
  var sLoc = rdLoc(s.loc)
  result = getTempName(m)

  if sfThread in s.flags and emulatedThreadVars(m.config):
    accessThreadLocalVar(p, s)
    sLoc = "NimTV_->" & sLoc

  c.visitorFrmt = "0" # "#nimGCvisit((void*)$1, 0);$n"
  c.p = p
  let header = "static N_NIMCALL(void, $1)(void)" % [result]
  genTraverseProc(c, sLoc, s.loc.t)

  let generatedProc = "$1 {$n$2$3$4}$n" %
        [header, p.s(cpsLocals), p.s(cpsInit), p.s(cpsStmts)]

  m.s[cfsProcHeaders].addf("$1;$n", [header])
  m.s[cfsProcs].add(generatedProc)
