enumutils.nim 6.0 KB

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