enumutils.nim 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. #
  2. #
  3. # Nim's Runtime Library
  4. # (c) Copyright 2020 Nim contributors
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. import macros
  10. from typetraits import OrdinalEnum, HoleyEnum
  11. when defined(nimPreviewSlimSystem):
  12. import std/assertions
  13. # xxx `genEnumCaseStmt` needs tests and runnableExamples
  14. macro genEnumCaseStmt*(typ: typedesc, argSym: typed, default: typed,
  15. userMin, userMax: static[int], normalizer: static[proc(s :string): string]): untyped =
  16. # Generates a case stmt, which assigns the correct enum field given
  17. # a normalized string comparison to the `argSym` input.
  18. # string normalization is done using passed normalizer.
  19. # NOTE: for an enum with fields Foo, Bar, ... we cannot generate
  20. # `of "Foo".nimIdentNormalize: Foo`.
  21. # This will fail, if the enum is not defined at top level (e.g. in a block).
  22. # Thus we check for the field value of the (possible holed enum) and convert
  23. # the integer value to the generic argument `typ`.
  24. let typ = typ.getTypeInst[1]
  25. let impl = typ.getImpl[2]
  26. expectKind impl, nnkEnumTy
  27. let normalizerNode = quote: `normalizer`
  28. expectKind normalizerNode, nnkSym
  29. result = nnkCaseStmt.newTree(newCall(normalizerNode, argSym))
  30. # stores all processed field strings to give error msg for ambiguous enums
  31. var foundFields: seq[string] = @[]
  32. var fStr = "" # string of current field
  33. var fNum = BiggestInt(0) # int value of current field
  34. for f in impl:
  35. case f.kind
  36. of nnkEmpty: continue # skip first node of `enumTy`
  37. of nnkSym, nnkIdent: fStr = f.strVal
  38. of nnkAccQuoted:
  39. fStr = ""
  40. for ch in f:
  41. fStr.add ch.strVal
  42. of nnkEnumFieldDef:
  43. case f[1].kind
  44. of nnkStrLit: fStr = f[1].strVal
  45. of nnkTupleConstr:
  46. fStr = f[1][1].strVal
  47. fNum = f[1][0].intVal
  48. of nnkIntLit:
  49. fStr = f[0].strVal
  50. fNum = f[1].intVal
  51. else:
  52. let fAst = f[0].getImpl
  53. if fAst.kind == nnkStrLit:
  54. fStr = fAst.strVal
  55. else:
  56. error("Invalid tuple syntax!", f[1])
  57. else: error("Invalid node for enum type `" & $f.kind & "`!", f)
  58. # add field if string not already added
  59. if fNum >= userMin and fNum <= userMax:
  60. fStr = normalizer(fStr)
  61. if fStr notin foundFields:
  62. result.add nnkOfBranch.newTree(newLit fStr, nnkCall.newTree(typ, newLit fNum))
  63. foundFields.add fStr
  64. else:
  65. error("Ambiguous enums cannot be parsed, field " & $fStr &
  66. " appears multiple times!", f)
  67. inc fNum
  68. # finally add else branch to raise or use default
  69. if default == nil:
  70. let raiseStmt = quote do:
  71. raise newException(ValueError, "Invalid enum value: " & $`argSym`)
  72. result.add nnkElse.newTree(raiseStmt)
  73. else:
  74. expectKind(default, nnkSym)
  75. result.add nnkElse.newTree(default)
  76. macro enumFullRange(a: typed): untyped =
  77. newNimNode(nnkCurly).add(a.getType[1][1..^1])
  78. macro enumNames(a: typed): untyped =
  79. # this could be exported too; in particular this could be useful for enum with holes.
  80. result = newNimNode(nnkBracket)
  81. for ai in a.getType[1][1..^1]:
  82. assert ai.kind == nnkSym
  83. result.add newLit ai.strVal
  84. iterator items*[T: HoleyEnum](E: typedesc[T]): T =
  85. ## Iterates over an enum with holes.
  86. runnableExamples:
  87. type
  88. A = enum
  89. a0 = 2
  90. a1 = 4
  91. a2
  92. B[T] = enum
  93. b0 = 2
  94. b1 = 4
  95. from std/sequtils import toSeq
  96. assert A.toSeq == [a0, a1, a2]
  97. assert B[float].toSeq == [B[float].b0, B[float].b1]
  98. for a in enumFullRange(E): yield a
  99. func span(T: typedesc[HoleyEnum]): int =
  100. (T.high.ord - T.low.ord) + 1
  101. const invalidSlot = uint8.high
  102. proc genLookup[T: typedesc[HoleyEnum]](_: T): auto =
  103. const n = span(T)
  104. var ret: array[n, uint8]
  105. var i = 0
  106. assert n <= invalidSlot.int
  107. for ai in mitems(ret): ai = invalidSlot
  108. for ai in items(T):
  109. ret[ai.ord - T.low.ord] = uint8(i)
  110. inc(i)
  111. return ret
  112. func symbolRankImpl[T](a: T): int {.inline.} =
  113. const n = T.span
  114. const thres = 255 # must be <= `invalidSlot`, but this should be tuned.
  115. when n <= thres:
  116. const lookup = genLookup(T)
  117. let lookup2 {.global.} = lookup # xxx improve pending https://github.com/timotheecour/Nim/issues/553
  118. #[
  119. This could be optimized using a hash adapted to `T` (possible since it's known at CT)
  120. to get better key distribution before indexing into the lookup table table.
  121. ]#
  122. {.noSideEffect.}: # because it's immutable
  123. let ret = lookup2[ord(a) - T.low.ord]
  124. if ret != invalidSlot: return ret.int
  125. else:
  126. var i = 0
  127. # we could also generate a case statement as optimization
  128. for ai in items(T):
  129. if ai == a: return i
  130. inc(i)
  131. raise newException(IndexDefect, $ord(a) & " invalid for " & $T)
  132. template symbolRank*[T: enum](a: T): int =
  133. ## Returns the index in which `a` is listed in `T`.
  134. ##
  135. ## The cost for a `HoleyEnum` is implementation defined, currently optimized
  136. ## for small enums, otherwise is `O(T.enumLen)`.
  137. runnableExamples:
  138. type
  139. A = enum # HoleyEnum
  140. a0 = -3
  141. a1 = 10
  142. a2
  143. a3 = (20, "f3Alt")
  144. B = enum # OrdinalEnum
  145. b0
  146. b1
  147. b2
  148. C = enum # OrdinalEnum
  149. c0 = 10
  150. c1
  151. c2
  152. assert a2.symbolRank == 2
  153. assert b2.symbolRank == 2
  154. assert c2.symbolRank == 2
  155. assert c2.ord == 12
  156. assert a2.ord == 11
  157. var invalid = 7.A
  158. doAssertRaises(IndexDefect): discard invalid.symbolRank
  159. when T is Ordinal: ord(a) - T.low.ord.static
  160. else: symbolRankImpl(a)
  161. func symbolName*[T: enum](a: T): string =
  162. ## Returns the symbol name of an enum.
  163. ##
  164. ## This uses `symbolRank`.
  165. runnableExamples:
  166. type B = enum
  167. b0 = (10, "kb0")
  168. b1 = "kb1"
  169. b2
  170. let b = B.low
  171. assert b.symbolName == "b0"
  172. assert $b == "kb0"
  173. static: assert B.high.symbolName == "b2"
  174. type C = enum # HoleyEnum
  175. c0 = -3
  176. c1 = 4
  177. c2 = 20
  178. assert c1.symbolName == "c1"
  179. const names = enumNames(T)
  180. names[a.symbolRank]