#       Nimony
# (c) Copyright 2024 Andreas Rumpf
#
# See the file "license.txt", included in this
# distribution, for details about the copyright.

## Helpers for declarative constructs like `let` statements or `proc` declarations.

import std / [assertions, syncio]
import ".." / lib / [nifstreams, nifcursors, lineinfos]
import ".." / nimony / [nimony_model, reporters]

include ".." / lib / compat2

template reportImpl(msg: string; c: Cursor; level: string) =
  when defined(debug) and not defined(nimony):
    writeStackTrace()
  write stdout, level
  if isValid(c.info):
    write stdout, infoToStr(c.info)
    write stdout, " "
  write stdout, msg
  writeLine stdout, toString(c, false)
  quit 1

template reportImpl(msg: string; level: string) =
  when defined(debug) and not defined(nimony):
    writeStackTrace()
  write stdout, level
  writeLine stdout, msg
  quit 1

proc error*(msg: string; c: Cursor) {.noreturn.} =
  reportImpl(msg, c, "[Error] ")

proc error*(msg: string) {.noreturn.} =
  reportImpl(msg, "[Error] ")

proc bug*(msg: string; c: Cursor) {.noreturn.} =
  when not defined(nimony):
    writeStackTrace()
  reportImpl(msg, c, "[Bug] ")

proc bug*(msg: string) {.noreturn.} =
  when not defined(nimony):
    writeStackTrace()
  reportImpl(msg, "[Bug] ")

proc isRoutine*(t: SymKind): bool {.inline.} =
  t in {ProcY, FuncY, IteratorY, MacroY, TemplateY, ConverterY, MethodY}

proc isLocal*(t: SymKind): bool {.inline.} =
  t in {LetY, VarY, ResultY, ConstY, ParamY, TypevarY, CursorY, PatternvarY, FldY, GfldY, EfldY, GletY, TletY, GvarY, TvarY}

proc isNominal*(t: TypeKind): bool {.inline.} =
  ## type kinds that should stay as symbols, see sigmatch.matchSymbol
  t in {ObjectT, EnumT, OnumT, AnumT, DistinctT, ConceptT}

proc skipToParams*(c: var Cursor) =
  ## In-place: advance `c` past the prefix slots of a proctype or
  ## proc-decl-shaped cursor so it points at the params slot.
  ## Proctype layout: `(proctype <NilTag> (params...) RetType <Pragmas>)`.
  ## Proc-decl layout: `(proc Name ExportMarker Pattern Typevars (params...) ...)`.
  let kind = c.typeKind
  inc c # skip ParLe
  if kind == ProctypeT:
    skip c # nilability tag
  elif kind in RoutineTypes:
    skip c # name
    skip c # export marker
    skip c # pattern
    skip c # generics

proc skipProcTypeToParams*(t: Cursor): Cursor =
  ## Pure version: returns a cursor advanced past the prefix slots.
  result = t
  if result.typeKind in RoutineTypes:
    skipToParams result

const
  LocalPragmasPos* = 2
  LocalTypePos* = 3
  LocalValuePos* = 4

type
  SkipMode* = enum
    SkipExclBody,
    SkipInclBody,
    SkipFinalParRi

  Local* = object
    kind*: SymKind
    name*: Cursor
    exported*: Cursor
    pragmas*: Cursor
    typ*: Cursor
    val*: Cursor

proc takeLocal*(c: var Cursor; mode: SkipMode): Local =
  let kind = symKind c
  result = Local(kind: kind)
  if isLocal(kind):
    inc c
    result.name = c
    skip c
    result.exported = c
    skip c
    result.pragmas = c
    skip c
    result.typ = c
    if mode >= SkipInclBody:
      skip c
      result.val = c
      skip c
      if mode == SkipFinalParRi:
        if c.kind == ParRi:
          inc c
        else:
          bug "expected ')' inside (" & $result.kind

proc asLocal*(c: Cursor; mode = SkipInclBody): Local =
  var c = c
  result = takeLocal(c, mode)

proc asTypevar*(c: Cursor): Local {.inline.} =
  result = asLocal(c)

type
  Routine* = object
    kind*: SymKind
    name*: Cursor
    exported*: Cursor
    pattern*: Cursor # for TR templates/macros
    typevars*: Cursor # generic parameters
    params*: Cursor
    retType*: Cursor
    pragmas*: Cursor
    effects*: Cursor
    body*: Cursor

proc isGeneric*(r: Routine): bool {.inline.} =
  r.typevars.substructureKind == TypevarsU

proc takeRoutine*(c: var Cursor; mode: SkipMode): Routine =
  let kind = symKind c
  result = Routine(kind: kind)
  if isRoutine(kind):
    inc c
    result.name = c
    skip c
    result.exported = c
    skip c
    result.pattern = c
    skip c
    result.typevars = c
    skip c
    result.params = c
    skip c
    result.retType = c
    skip c
    result.pragmas = c
    skip c
    result.effects = c
    if mode >= SkipInclBody:
      skip c
      result.body = c
      skip c
      if mode == SkipFinalParRi:
        if c.kind == ParRi:
          inc c
        else:
          bug "expected ')' inside (" & $result.kind

const
  TypevarsPos* = 3
  ParamsPos* = 4
  ReturnTypePos* = 5
  ProcPragmasPos* = 6
  BodyPos* = 8

proc asRoutine*(c: Cursor; mode = SkipExclBody): Routine =
  var c = c
  result = takeRoutine(c, mode)

type
  TypeDecl* = object
    kind*: SymKind
    name*: Cursor
    exported*: Cursor
    typevars*: Cursor
    pragmas*: Cursor
    body*: Cursor

proc isGeneric*(r: TypeDecl): bool {.inline.} =
  r.typevars.substructureKind == TypevarsU

proc takeTypeDecl*(c: var Cursor; mode: SkipMode): TypeDecl =
  let kind = symKind c
  result = TypeDecl(kind: kind)
  if kind == TypeY:
    inc c
    result.name = c
    skip c
    result.exported = c
    skip c
    result.typevars = c
    skip c
    result.pragmas = c
    if mode >= SkipInclBody:
      skip c
      result.body = c
      if mode == SkipFinalParRi:
        skip c
        if c.kind == ParRi:
          inc c
        else:
          bug "expected ')' inside (" & $result.kind

proc asTypeDecl*(c: Cursor): TypeDecl =
  var c = c
  result = takeTypeDecl(c, SkipInclBody)

type
  ObjectDecl* = object
    kind*: TypeKind
    parentType*: Cursor
    firstField*: Cursor

proc asObjectDecl*(c: Cursor): ObjectDecl =
  var c = c
  let kind = typeKind c
  result = ObjectDecl(kind: kind)
  if kind == ObjectT:
    inc c
    result.parentType = c
    skip c
    result.firstField = c

type ObjFieldIter* = object
  nested: int

proc initObjFieldIter*(): ObjFieldIter =
  result = ObjFieldIter(nested: 1)

proc nextField*(iter: var ObjFieldIter, n: var Cursor, keepCase = false): bool =
  result = false
  while iter.nested != 0:
    if n.kind == ParRi:
      dec iter.nested
      if iter.nested != 0: inc n
    else:
      case n.substructureKind
      of CaseU:
        if keepCase:
          result = true
          break
        else:
          inc iter.nested
          inc n
      of WhenU, StmtsU, NilU, ElseU:
        inc iter.nested
        inc n
      of ElifU, OfU:
        inc iter.nested
        inc n
        skip n
      of FldU, GfldU:
        result = true
        break
      else:
        error "illformed AST inside object: ", n

type
  EnumDecl* = object
    kind*: TypeKind
    baseType*: Cursor
    firstField*: Cursor

proc asEnumDecl*(c: Cursor): EnumDecl =
  var c = c
  let kind = typeKind c
  result = EnumDecl(kind: kind)
  if kind in {EnumT, OnumT, AnumT}:
    inc c
    result.baseType = c
    skip c
    if kind == AnumT:
      skip c # skip owner object type sym (or dot)
    result.firstField = c

type
  TupleField* = object
    kind*: SubstructureKind
    name*: Cursor
    typ*: Cursor

proc asTupleField*(c: Cursor): TupleField =
  var c = c
  let kind = substructureKind c
  result = TupleField(kind: kind)
  case c.substructureKind
  of KvU:
    inc c # tag
    result.name = c
    skip c
    result.typ = c
  else:
    # unnamed
    result.typ = c

proc getTupleFieldType*(c: Cursor): Cursor =
  case c.substructureKind
  of KvU:
    result = c
    inc result # tag
    skip result, SkipName # name
  else:
    result = c

type
  ForStmt* = object
    kind*: StmtKind
    iter*, vars*, body*: Cursor

proc asForStmt*(c: Cursor): ForStmt =
  var c = c
  let kind = stmtKind c
  result = ForStmt(kind: kind)
  if kind == ForS:
    inc c
    result.iter = c
    skip c
    result.vars = c
    skip c
    result.body = c
