import macros, future, intsets
from sequtils import apply, map, zip, toSeq, applyIt, allIt, mapIt

## Classy
## ======
##
## Provides ability to define and instantiate haskell-like typeclasses in Nim.
##
## Overview
## --------
##
## This module consists of two macros. ``typeclass`` saves a parameterized AST to
## serve as a template (in an object of type ``Typeclass``). ``instance`` performs
## necessary substitutions in saved AST for provided arguments, and executes
## the resulting code.
##
## As an example, let's say we want to define a ``Functor`` typeclass:
##
## .. code-block:: nim
##   typeclass Functor, F[_]:
##     # proc fmap[A, B](fa: F[A], g: A -> B): F[B]
##     proc `$>`[A, B](fa: F[A], b: B): F[B]=
##       fmap(fa, (a: A) => b)
##
## This code does not declare any procs - only a compile-time variable ``Functor``.
## Notice that our code abstracts over type constructor - ``F`` is just a
## placeholder.
##
## Notice that ``fmap`` is not part of the typeclass. At the moment forward
## declarations don't work for generic procs in Nim. As we'll see, even if
## proc AST in our typeclass has no generic parameters, the generated proc
## can have some. So it is recommended to not define unimplemented procs
## inside typeclasses. This will probably change after this issue is closed:
## https://github.com/nim-lang/Nim/issues/4104.
##
## Now let's instantiate our typeclass. For example, let's define a ``Functor``
## instance for all tuples of two elements, where the left value is ``SomeInteger``:
##
## .. code-block:: nim
##
##   instance Functor, (C: SomeInteger) => (C, _)
##
## This generates the following proc definition:
##
## .. code-block:: nim
##
##   proc `$>`[A, B, C: SomeInteger](fa: (C, A), b: B): (C, B)=
##       fmap(fa, (a: A) => b)
##
## Here are the few things to notice:
##
## 1. All ocurrences of form ``F[X]`` were replaced with ``(C, X)``
## 2. ``C``, the parameter of our instance, became the parameter of the generated
##    definition, with its constraint preserved.
## 3. We're referring to a proc ``fmap``. So any procs, that are assumed to be
##    present in typeclass body, should be defined with corresponding types
##    before the typeclass is instantiated

type
  Unit = tuple[]

  AbstractPattern = object
    ## Type/pattern placeholder for typeclass declaration.
    ##
    ## Has either form ``A`` (placeholder for a concrete type) or ``A[_,...]`` (for
    ## a type constructor).
    ## Doesn't have to evaluate to concrete type after parameter substitution -
    ## must be eliminated after typeclass instantiation.
    ident: NimIdent
    arity: Natural
      ## types with arity of zero are considered concrete, with corresponding
      ## matching rules

  ConcretePattern = object
    ## A tree with zero or more wildcards (_).
    ##
    ## Should evaluate to concrete type once all the wildcards are replaced with
    ## types.
    tree: NimNode
    arity: Natural

  Typeclass* = object
    # Only here for better error messaging.
    # Do not use for membership checks and such!
    name: string
    patterns: seq[AbstractPattern]
    body: NimNode

  TypeclassMember = object
    patterns: seq[ConcretePattern]
    params: seq[NimNode]

  ExportOptionsKind = enum eoNone, eoSome, eoAll
  ExportOptions = object
    case kind: ExportOptionsKind
    of {eoNone, eoAll}: discard
    of eoSome:
      patterns: seq[NimNode]

  MemberOptions = object
    # Idents of the top-level procs to be skipped
    skipping: seq[NimNode]
    exporting: ExportOptions

  TypeclassOptions = object
    exported: bool

  # https://github.com/nim-lang/Nim/issues/4952
  GenTransformTuple[S] = object
    newNode: NimNode
    recurse: bool
    state: S

  TransformTuple = GenTransformTuple[Unit]

const unit: Unit = ()

proc mkGenTransformTuple[S](newNode: NimNode, recurse: bool, state: S): auto =
  GenTransformTuple[S](newNode: newNode, recurse: recurse, state: state)

proc mkTransformTuple(newNode: NimNode, recurse: bool): TransformTuple =
  mkGenTransformTuple(newNode, recurse, unit)

template fail(msg: string, n: NimNode = nil) =
  let errMsg = if n != nil: msg & ": " & $toStrLit(n) else: msg
  when compiles(error("", nil.NimNode)):
    error(errMsg, n)
  else:
    # Nim 0.15.2 and older
    error(errMsg)

proc arity(x: Typeclass): Natural {.compileTime.} =
  x.patterns.len

proc arity(x: TypeclassMember): Natural {.compileTime.} =
  x.patterns.len

proc replaceInBody(
  tree: NimNode,
  substs: seq[(AbstractPattern, ConcretePattern)],
  substIndices: IntSet
): tuple[tree: NimNode, substIndices: IntSet]

proc transformDown[S](
  tree: NimNode,
  f: (n: NimNode, s: S) -> GenTransformTuple[S],
  s: S
): (NimNode, S) {.compileTime.} =
  let tup = f(tree.copyNimTree, s)
  var resNode = tup.newNode
  var resState = tup.state
  if tup.recurse:
    for i in 0..<resNode.len:
      var resSub: NimNode
      (resSub, resState) = transformDown(resNode[i], f, resState)
      resNode[i] = resSub

  (resNode, resState)

proc transformDown(
  tree: NimNode,
  f: (n: NimNode) -> TransformTuple
): NimNode {.compileTime.} =
  proc fs(n: NimNode, s: Unit): GenTransformTuple[Unit] {.closure.} =
    f(n)
  transformDown[tuple[]](
    tree = tree,
    f = fs,
    s = unit
  )[0]

proc asTree(p: AbstractPattern): NimNode {.compileTime.} =
  ## Restore `p`'s tree form
  ##
  ## Only useful for error messages - use fields for matching.
  if p.arity == 0:
    result = newIdentNode(p.ident)
  else:
    result = newTree(
      nnkBracketExpr,
      newIdentNode(p.ident)
    )
    for i in 1..p.arity:
      result.add(newIdentNode("_"))

proc getArity(tree: NimNode): int {.compileTime.} =
  ## Counts all underscore idents in ``tree``
  if tree.eqIdent("_"):
    result = 1
  else:
    result = 0
    for child in tree:
      result.inc(getArity(child))

proc matchesPattern(
  tree: NimNode, pattern: AbstractPattern
): bool {.compileTime.} =
  ## Checks whether ``tree`` is an occurence of ``pattern``
  ##
  ## Returns ``true`` if the patern matches, ``false`` otherwise.
  ## Raises if the arity does not match!
  if tree.eqIdent($pattern.ident):

    # TODO: This should happen at instantiation!
    # We should not allow invalid class body.
    if pattern.arity > 0:
      fail("Constructor pattern cannot be used without arguments", tree)

    # Concrete type - does not require brackets
    true

  elif tree.kind == nnkBracketExpr and
    tree.len > 0 and
    tree[0].eqIdent($pattern.ident):
    # Constructor - check arity
    let arity = tree.len - 1
    if arity != pattern.arity:
      let msg = "Wrong number of type arguments in expression " &
        "(expected " & $pattern.arity & ")"
      fail(msg, tree)

    true
  else:
    false

proc instantiateConstructor(
  concrete: ConcretePattern, abstract: AbstractPattern, tree: NimNode,
  processParam: NimNode -> NimNode
): NimNode {.compileTime.} =

  proc replaceUnderscores(
    tree: NimNode, args: seq[NimNode]
  ): (NimNode, seq[NimNode]) =
    var argsNew = args
    # Traverse ``tree`` and replace all underscore identifiers
    # with nodes from ``args`` in order.
    let treeNew = transformDown(tree) do (sub: NimNode) -> auto:
      if sub.eqIdent("_"):
        let res = argsNew[0].copyNimTree
        argsNew.delete(0)
        mkTransformTuple(res, false)
      else:
        mkTransformTuple(sub, true)

    (treeNew, argsNew)

  tree.expectKind(nnkBracketExpr)
  # First one is the constructor itself
  var args = toSeq(tree.children)
  args.delete(0)

  # we can have recursion in type arguments!
  args.apply(processParam)

  (result, args) = replaceUnderscores(concrete.tree, args)
  doAssert: args.len == 0

proc instantiate(
  concrete: ConcretePattern, abstract: AbstractPattern, tree: NimNode,
  processParam: NimNode -> NimNode
): NimNode {.compileTime.} =
  if abstract.arity > 0:
    concrete.instantiateConstructor(abstract, tree, processParam)
  else:
    # Members without parameters do not have brackets
    concrete.tree.copyNimTree


when declared(nnkTupleConstr):
  const TupleConstrKinds = {nnkPar, nnkTupleConstr}
else:
  const TupleConstrKinds = {nnkPar}

proc parseMemberParams(
  tree: NimNode
): seq[NimNode] =
  ## parse instance parameters in following forms:
  ## ``A``, ``(A, B)``, ``(A: T1, B)`` etc.

  if tree.kind in TupleConstrKinds:
    result = toSeq(tree.children)
  else:
    result = @[tree]

  for i in 0..<len(result):
    let n = result[i]
    if n.kind == nnkExprColonExpr:
      result[i] = newIdentDefs(n[0], n[1])
    else:
      result[i] = newIdentDefs(n, newEmptyNode())

proc parseAbstractPattern(
  tree: NimNode
): AbstractPattern {.compileTime.}  =
  ## Parses abstract pattern in forms ``A`` and ``A[_,...]``
  let wildcard = newIdentNode("_")
  let isValid = tree.kind == nnkIdent or (
    tree.kind == nnkBracketExpr and
    tree.len > 1 and
    tree[0].kind == nnkIdent and
    (toSeq(tree.children))[1..tree.len-1].allIt(it == wildcard)
  )

  if not isValid:
    fail("Illegal typeclass parameter expression", tree)

  if tree.kind == nnkBracketExpr:
    AbstractPattern(
      ident: tree[0].ident,
      arity: tree.len - 1
    )
  else:
    AbstractPattern(ident: tree.ident, arity: 0)

proc parseAbstractPatterns(
  tree: NimNode
): seq[AbstractPattern] {.compileTime.} =
  let patternNodes = (block:
    if tree.kind == nnkBracket:
      toSeq(tree.children)
    else:
      @[tree]
  )
  patternNodes.map(parseAbstractPattern)

proc replace(n: NimNode, subst: seq[(NimNode, NimNode)]): NimNode {.compileTime.} =
  transformDown(n) do (sub: NimNode) -> auto:
    for pair in subst:
      if sub == pair[0]:
        return mkTransformTuple(pair[1], false)
    return mkTransformTuple(sub, true)

proc containsSubtree(n: NimNode, sub: NimNode): bool =
  var q = @[n]
  while q.len > 0:
    let cur = q.pop
    if cur == sub:
      return true
    else:
      for child in cur:
        q.add(child)

  return false

# Ugly workaround for Nim bug:
# https://github.com/nim-lang/Nim/issues/4939
# TODO: remove this the second the bug is fixed
var cnt {.compileTime.} = 0
proc genIdent(
  s: string
): NimNode {.compileTime.} =
  inc cnt
  newIdentNode(s & "_classy_" & $cnt)

proc genSymParams(
  inParams: seq[NimNode],
  inPattern: NimNode
): tuple[params: seq[NimNode], pattern: NimNode] {.compileTime.} =
  ## Replace instance parameters with unique symbols
  var substitutions = newSeq[(NimNode, NimNode)]()

  result.params = inParams
  for i in 0..<len(result.params):
    let def = result.params[i]
    def.expectKind(nnkIdentDefs)
    let id = def[0]
    id.expectKind({nnkIdent, nnkSym})
    let newId = genIdent($id.ident & "_")
    result.params[i][0] = newId

    substitutions.add((id, newId))

  result.pattern = inPattern.replace(substitutions)

proc parseMember(
  tree: NimNode
): TypeclassMember {.compileTime.} =
  ## Parse typeclass member patterns in one of following forms:
  ## - ``(A: T1, B..) => [Constr[A, _, ...], Concrete, ...]``
  ## - ``(A: T1, B..) => Constr[A, _, ...]``
  ## - ``A => Constr[A, _, ...]``
  ## - ``Constr[_, ...]``
  ## - ``Concrete``
  let hasParams = tree.kind == nnkInfix and tree[0].eqIdent("=>")
  let (params0, pattern0) = (block:
    if hasParams:
      (parseMemberParams(tree[1]), tree[2])
    else:
      (@[], tree)
  )

  # Make sure parameters don't clash with anything in body
  let (params, patternsTree) = genSymParams(params0, pattern0)

  # Strip possible brackets around patterns
  let patternNodes = (block:
    if patternsTree.kind == nnkBracket:
      toSeq(patternsTree.children)
    else:
      @[patternsTree]
  )
  let patterns = patternNodes.map(n =>
    ConcretePattern(tree: n, arity: getArity(n))
  )

  TypeclassMember(
    params: params,
    patterns: patterns
  )

proc stripAccQuoted(n: NimNode): NimNode =
  case n.kind:
    of nnkAccQuoted: n[0]
    else: n

proc parseMemberOptions(
  args: seq[NimNode]
): MemberOptions =
  ## Parse following instance options:
  ## - ``skipping(foo)`` - skips ``foo`` definition
  ## - ``skipping(foo, bar)`` - skips ``foo`` and ``bar``
  ## - ``exporting(_)`` - export all symbols
  ## - ``exporting(foo, bar)`` - export ``foo`` and ``bar``

  result = MemberOptions(
    skipping: newSeq[NimNode](),
    exporting: ExportOptions(kind: eoNone)
  )
  for a in args:
    if a.kind == nnkCall and a[0].eqIdent("skipping"):
      # Don't check for duplicate symbols
      for i in 1..<a.len:
        a[i].expectKind({nnkIdent, nnkAccQuoted})
        result.skipping.add(stripAccQuoted(a[i]))

    elif a.kind == nnkCall and a[0].eqIdent("exporting"):
      if result.exporting.kind != eoNone:
        fail("Duplicate exporting clause", a)
      var acc = newSeq[NimNode]()
      for i in 1..<a.len:
        a[i].expectKind({nnkIdent, nnkAccQuoted})
        if a[i].eqIdent("_") and a.len > 2:
          # Can't mix wildcard with other exporting
          fail("Invalid exporting clause", a)
        acc.add(stripAccQuoted(a[i]))

      if acc.len == 1 and acc[0].eqIdent("_"):
        result.exporting = ExportOptions(kind: eoAll)
      else:
        result.exporting = ExportOptions(kind: eoSome, patterns: acc)

    else:
      fail("Invalid instance option", a)

proc parseTypeclassOptions(
  args: seq[NimNode]
): TypeclassOptions =
  result = TypeclassOptions(exported: false)
  for a in args:
    if a.eqIdent("exported"):
      if result.exported:
        fail("Duplicate exported clause", a)
      else:
        result.exported = true
    else:
      fail("Illegal typeclass option: ", a)

# Global for reuse in ``replaceInProcs``
proc mkBodyWorker(
  substs: seq[(AbstractPattern, ConcretePattern)]
): (n: NimNode, s: IntSet) -> GenTransformTuple[IntSet] =
  proc worker(sub: NimNode, substIndices0: IntSet): GenTransformTuple[IntSet] =
    var substIndices = substIndices0
    for ix, subst in substs:
      let (abstract, concrete) = subst
      if sub.matchesPattern(abstract):
        substIndices.incl(ix)

        proc processParam(n: NimNode): NimNode =
          let replaced = n.replaceInBody(substs, substIndices)
          substIndices = replaced.substIndices
          replaced.tree

        let newSub = concrete.instantiate(
          abstract,
          sub,
          processParam
        )
        return mkGenTransformTuple(newSub, false, substIndices)

    return mkGenTransformTuple(sub.copyNimTree, true, substIndices)

  worker

proc replaceInBody(
  tree: NimNode,
  substs: seq[(AbstractPattern, ConcretePattern)],
  substIndices: IntSet
): tuple[tree: NimNode, substIndices: IntSet] =
  ## Replace ``substs`` in a tree.
  ## Add indices of any matching substs to `substIndices`
  transformDown[IntSet](
    tree,
    mkBodyWorker(substs),
    substIndices
  )

proc processProcParams(
  paramsTree: NimNode,
  substs: seq[(AbstractPattern, ConcretePattern)]
): tuple[tree: NimNode, substIndices: IntSet] =
  paramsTree.expectKind(nnkFormalParams)
  var substIndices = initIntSet()
  proc processType(tree: NimNode, substIndices: var IntSet): NimNode =
    let res = tree.replaceInBody(substs, substIndices)
    substIndices = res.substIndices
    res.tree

  var res = newNimNode(nnkFormalParams)

  res.add(paramsTree[0].processType(substIndices))

  for i in 1..<paramsTree.len:
    let oldParam = paramsTree[i]
    var newParam = oldParam.copyNimTree
    newParam[1] = oldParam[1].processType(substIndices)
    res.add(newParam)

  (res, substIndices)

proc replaceInProcs(
  tree: NimNode,
  instanceParams: seq[NimNode],
  substs: seq[(AbstractPattern, ConcretePattern)]
): NimNode {.compileTime.} =
  ## Traverse ``tree`` looking for top-level procs; inject ``instanceParams`` and
  ## replace ``substs`` in each one.
  ## Otherwise behaves the same as ``ReplaceInBody`` - replaces abstract
  ## patterns if encountered.

  proc worker(sub: NimNode): TransformTuple =
    case sub.kind
    of RoutineNodes:
      # This will be returned
      var res = sub

      # Replace in formal parameters
      let (newParams, substIndices0) =
        sub.params.processProcParams(substs)
      res.params = newParams

      # Replace in proc body
      let (newBody, substIndices1) =
        sub.body.replaceInBody(substs, substIndices0)
      res.body = newBody

      # Inject instance parameters to proc's generic param list
      var genParams = sub[2]
      expectKind(genParams, {nnkEmpty, nnkGenericParams})
      if genParams.kind == nnkEmpty and instanceParams.len > 0:
        genParams = newNimNode(nnkGenericParams)

      var instanceParamIndices = initIntSet()
      for i, param in instanceParams:
        for j, subst in substs:
          if j in substIndices1 and
             subst[1].tree.containsSubtree(param[0]):
            instanceParamIndices.incl(i)
            break

      for i in instanceParamIndices:
        genParams.add(instanceParams[i].copyNimTree)

      res[2] = genParams

      # Do not recurse - we already replaced everything using ``replaceInBody``
      mkTransformTuple(res, false)
    else:
      # Note that arguments of a replaced constructor are handled by
      # ``bodyWorker``, meaning any proc defs inside them don't get parameter
      # injection. This is probably the right thing to do, though.
      let substIndices = initIntSet()
      let genTup = mkBodyWorker(substs)(sub, substIndices)
      mkTransformTuple(genTup.newNode, true)

  transformDown(tree, worker)

proc removeSkippedProcs(
  tree: NimNode,
  skipping: seq[NimNode]
): NimNode {.compileTime.} =
  ## Traverse ``tree`` looking for top-level procs with names
  ## in ``skipping`` and remove their definitions.

  proc worker(sub: NimNode): TransformTuple =
    case sub.kind
    of RoutineNodes:
      let nameNode = stripAccQuoted(sub.name)
      if nameNode in skipping:
        mkTransformTuple(newEmptyNode(), false)
      else:
        mkTransformTuple(sub, false)
    else:
      mkTransformTuple(sub, true)

  transformDown(tree, worker)

proc addExportMarks(
  tree: NimNode,
  exporting: ExportOptions
): NimNode {.compileTime.} =
  proc contains(opts: ExportOptions, n: NimNode): bool =
    case opts.kind
    of eoNone: false
    of eoAll: true
    of eoSome: opts.patterns.contains(n)

  proc stripExportMark(n: NimNode): NimNode =
    if n.kind == nnkPostfix: n.basename else: n

  proc withExportMark(n: NimNode, mark: bool): NimNode =
    if mark: n.postfix("*") else: n

  proc worker(sub: NimNode): TransformTuple =
    case sub.kind
    of RoutineNodes:
      let exported = exporting.contains(sub.name)
      let res = sub.copyNimTree
      # Add or remove export mark
      # `name` proc strips quoting and postfixes - but we need them!
      res[0] = sub[0].stripExportMark.withExportMark(exported)
      # We're only processing top-level routines
      mkTransformTuple(res, false)

    of nnkIdentDefs:
      let name = sub[0].stripExportMark
      let exported = exporting.contains(name)
      let res = sub.copyNimTree
      res[0] = name.withExportMark(exported)
      # We're only processing top-level definitions
      # TODO: handle exports for object fields
      mkTransformTuple(res, false)

    else:
      mkTransformTuple(sub, true)

  transformDown(tree, worker)

proc instanceImpl(
  class: Typeclass,
  member: TypeclassMember,
  options: MemberOptions
): NimNode {.compileTime.} =

  if class.arity != member.arity:
    let msg = "Incorrect number of arguments for typeclass " & class.name
    fail(msg)

  let substs: seq[(AbstractPattern, ConcretePattern)] =
    class.patterns.zip(member.patterns)

  for s in substs:
    let (abstract, concrete) = s
    if abstract.arity != concrete.arity:
      let msg = "Type or constructor does not match typeclass parameter (" &
        $toStrLit(asTree(abstract)) & ")"
      fail(msg, concrete.tree)

  result = class.body.copyNimTree
  result = result.removeSkippedProcs(options.skipping)
  result = result.replaceInProcs(member.params, substs)
  result = result.addExportMarks(options.exporting)

# A hack to allow passing ``Typeclass`` values from the macro to
# defined variables
var tc {.compiletime.} : Typeclass

macro typeclass*(id, patternsTree: untyped, args: varargs[untyped]): untyped =
  ## Define typeclass with name ``id``.
  ##
  ## This creates a compile-time variable with name ``id``.
  ##
  ## Call syntax:
  ##
  ## .. code-block:: nim
  ##
  ##   typeclass Class, [A[_, ..], B,...], exported:
  ##     <typeclass body>
  ##
  ## Typeclass can have zero or more parameters, each of which can be a type
  ## constructor with arity 1 and higher (like ``A`` in sample above), or be
  ## a concrete type (like ``B``). If a typeclass has exactly one parameter,
  ## the brackets around parameter list can be omitted.
  ##
  ## The ``exported`` option allows to export the typeclass from module.
  ## This marks corresponding variable with export postfix. Notice that in
  ## this case standard restrictions apply: the ``typeclass`` call should be
  ## in module's top scope.
  id.expectKind(nnkIdent)

  # Typeclass body goes last, before it - various options
  let argsSeq = toSeq(args)
  if argsSeq.len == 0:
    fail("Missing body for typeclass" & $id)
  let options = parseTypeclassOptions(argsSeq[0..^2])
  let body = argsSeq[argsSeq.len - 1]
  let patterns = parseAbstractPatterns(patternsTree)

  let idTree = if options.exported: id.postfix("*") else: id

  # Pass the value through ``tc``.
  # I do not know of a cleaner way to do this.
  tc = Typeclass(
    name: $id,
    patterns: patterns,
    body: body
  )
  let tcSym = bindSym("tc")
  quote do:
    let `idTree` {.compileTime.} = `tcSym`

macro instance*(
  class: static[Typeclass],
  argsTree: untyped,
  options: varargs[untyped]
): untyped =
  ## Instantiate typeclass ``class`` with given arguments
  ##
  ## Call syntax:
  ##
  ## .. code-block:: nim
  ##
  ##   instance Class, (K, L) => [AType[_, K,..], BType,...],
  ##     skipping(foo, bar),
  ##     exporting(_)
  ##
  ## ``instance`` does the following:
  ## 1. Replaces each of parameter forms in `typeclass` definition with
  ##    corresponding argument form (like ``AType`` in code example). Parameter
  ##    form list of ``typeclass`` and argument form lists of corresponding
  ##    `instance` calls must have matching length, and corresponding forms
  ##    should have the same arity.
  ## 2. Injects instance parameters (``K`` in the example) into top-level
  ##    routines in typeclass body.
  ## 3. Transforms the body according to options.
  ## 4. Executes the body.
  ##
  ## Instance `parameters` can have constraints in the same form as in a generic
  ## definition. If no instance parameters are present, the corresponding list
  ## can be omitted. If exactly one instance parameter is present (without
  ## constraints), the parenthesis around parameter list can be omitted.
  ##
  ## Instance `argument` forms are trees with zero or more nodes replaced with
  ## wildcards (``_``), the number of wildcards in a tree corresponding to
  ## the form arity. A form can include existing types, as well as instance
  ## `parameters`.
  ##
  ## Here are few valid parameter-argument combinations:
  ##
  ## .. code-block:: nim
  ##
  ##   []
  ##   int
  ##   Option[_]
  ##   [int, string]
  ##   K => Option[K]
  ##   (K: SomeInteger, L) => [(K, L, Option[_]), (L, int)]
  ##
  ## Supported instance options:
  ## 1. ``skipping(foo, bar...)`` - these definitions will be skipped.
  ## 2. ``exporting(foo, bar)`` - these generated definitions will have export
  ##    marker.
  ## 3. ``exporting(_)`` - all generated definitions will have export marker.

  var opts = newSeq[NimNode]()
  for o in options: opts.add(o)

  result = instanceImpl(class, parseMember(argsTree), parseMemberOptions(opts))

  # For debugging purposes
  when defined(classyDumpCode):
    echo toStrLit(result)
  when defined(classyDumpTree):
    echo treeRepr(result)