summary refs log tree commit diff stats
path: root/tests/overload/tvaruintconv.nim
blob: 87ebd285d8970f1df8eafeba92bf1a7e8c201c56 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
discard """
  action: compile
"""

# https://github.com/status-im/nimbus-eth2/pull/6554#issuecomment-2354977102
# failed with "for a 'var' type a variable needs to be passed; but 'uint64(result)' is immutable"

import
  std/[typetraits, macros]

type
  DefaultFlavor = object

template serializationFormatImpl(Name: untyped) {.dirty.} =
  type Name = object

template serializationFormat(Name: untyped) =
  serializationFormatImpl(Name)

template setReader(Format, FormatReader: distinct type) =
  when arity(FormatReader) > 1:
    template Reader(T: type Format, F: distinct type = DefaultFlavor): type = FormatReader[F]
  else:
    template ReaderType(T: type Format): type = FormatReader
    template Reader(T: type Format): type = FormatReader

template useDefaultReaderIn(T: untyped, Flavor: type) =
  mixin Reader

  template readValue(r: var Reader(Flavor), value: var T) =
    mixin readRecordValue
    readRecordValue(r, value)

import mvaruintconv

type
  FieldTag[RecordType: object; fieldName: static string] = distinct void

func declval*(T: type): T {.compileTime.} =
  default(ptr T)[]

macro enumAllSerializedFieldsImpl(T: type, body: untyped): untyped =
  var typeAst = getType(T)[1]
  var typeImpl: NimNode
  let isSymbol = not typeAst.isTuple

  if not isSymbol:
    typeImpl = typeAst
  else:
    typeImpl = getImpl(typeAst)
  result = newStmtList()

  var i = 0
  for field in recordFields(typeImpl):
    let
      fieldIdent = field.name
      realFieldName = newLit($fieldIdent.skipPragma)
      fieldName = realFieldName
      fieldIndex = newLit(i)

    let fieldNameDefs =
      if isSymbol:
        quote:
          const fieldName {.inject, used.} = `fieldName`
          const realFieldName {.inject, used.} = `realFieldName`
      else:
        quote:
          const fieldName {.inject, used.} = $`fieldIndex`
          const realFieldName {.inject, used.} = $`fieldIndex`

    let field =
      if isSymbol:
        quote do: declval(`T`).`fieldIdent`
      else:
        quote do: declval(`T`)[`fieldIndex`]

    result.add quote do:
      block:
        `fieldNameDefs`

        template FieldType: untyped {.inject, used.} = typeof(`field`)

        `body`

  # echo repr(result)

template enumAllSerializedFields(T: type, body): untyped =
  enumAllSerializedFieldsImpl(T, body)

type
  FieldReader[RecordType, Reader] = tuple[
    fieldName: string,
    reader: proc (rec: var RecordType, reader: var Reader)
                 {.gcsafe, nimcall.}
  ]

proc totalSerializedFieldsImpl(T: type): int =
  mixin enumAllSerializedFields
  enumAllSerializedFields(T): inc result

template totalSerializedFields(T: type): int =
  (static(totalSerializedFieldsImpl(T)))

template GetFieldType(FT: type FieldTag): type =
  typeof field(declval(FT.RecordType), FT.fieldName)

proc makeFieldReadersTable(RecordType, ReaderType: distinct type,
                           numFields: static[int]):
                           array[numFields, FieldReader[RecordType, ReaderType]] =
  mixin enumAllSerializedFields, handleReadException
  var idx = 0

  enumAllSerializedFields(RecordType):
    proc readField(obj: var RecordType, reader: var ReaderType)
                  {.gcsafe, nimcall.} =

      mixin readValue

      type F = FieldTag[RecordType, realFieldName]
      field(obj, realFieldName) = reader.readValue(GetFieldType(F))

    result[idx] = (fieldName, readField)
    inc idx

proc fieldReadersTable(RecordType, ReaderType: distinct type): auto =
  mixin readValue
  type T = RecordType
  const numFields = totalSerializedFields(T)
  var tbl {.threadvar.}: ref array[numFields, FieldReader[RecordType, ReaderType]]
  if tbl == nil:
    tbl = new typeof(tbl)
    tbl[] = makeFieldReadersTable(RecordType, ReaderType, numFields)
  return addr(tbl[])

proc readValue(reader: var auto, T: type): T =
  mixin readValue
  reader.readValue(result)

template decode(Format: distinct type,
                 input: string,
                 RecordType: distinct type): auto =
  mixin Reader
  block:  # https://github.com/nim-lang/Nim/issues/22874
    var reader: Reader(Format)
    reader.readValue(RecordType)

template readValue(Format: type,
                    ValueType: type): untyped =
  mixin Reader, init, readValue
  var reader: Reader(Format)
  readValue reader, ValueType

template parseArrayImpl(numElem: untyped,
                        actionValue: untyped) =
  actionValue

serializationFormat Json
template createJsonFlavor(FlavorName: untyped,
                           skipNullFields = false) {.dirty.} =
  type FlavorName = object

  template Reader(T: type FlavorName): type = Reader(Json, FlavorName)
type
  JsonReader[Flavor = DefaultFlavor] = object

Json.setReader JsonReader

template parseArray(r: var JsonReader; body: untyped) =
  parseArrayImpl(idx): body

template parseArray(r: var JsonReader; idx: untyped; body: untyped) =
  parseArrayImpl(idx): body

proc readRecordValue[T](r: var JsonReader, value: var T) =
  type
    ReaderType {.used.} = type r
    T = type value

  discard T.fieldReadersTable(ReaderType)

proc readValue[T](r: var JsonReader, value: var T) =
  mixin readValue

  when value is seq:
    r.parseArray:
      readValue(r, value[0])

  elif value is object:
    readRecordValue(r, value)

type
  RemoteSignerInfo = object
    id: uint32
  RemoteKeystore = object

proc readValue(reader: var JsonReader, value: var RemoteKeystore) =
  discard reader.readValue(seq[RemoteSignerInfo])

createJsonFlavor RestJson
useDefaultReaderIn(RemoteSignerInfo, RestJson)
proc readValue(reader: var JsonReader[RestJson], value: var uint64) =
  discard reader.readValue(string)

discard Json.decode("", RemoteKeystore)
block:  # https://github.com/nim-lang/Nim/issues/22874
  var reader: Reader(RestJson)
  discard reader.readValue(RemoteSignerInfo)