enumutils.nim 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  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. # xxx `genEnumCaseStmt` needs tests and runnableExamples
  12. macro genEnumCaseStmt*(typ: typedesc, argSym: typed, default: typed,
  13. userMin, userMax: static[int], normalizer: static[proc(s :string): string]): untyped =
  14. # generates a case stmt, which assigns the correct enum field given
  15. # a normalized string comparison to the `argSym` input.
  16. # string normalization is done using passed normalizer.
  17. # NOTE: for an enum with fields Foo, Bar, ... we cannot generate
  18. # `of "Foo".nimIdentNormalize: Foo`.
  19. # This will fail, if the enum is not defined at top level (e.g. in a block).
  20. # Thus we check for the field value of the (possible holed enum) and convert
  21. # the integer value to the generic argument `typ`.
  22. let typ = typ.getTypeInst[1]
  23. let impl = typ.getImpl[2]
  24. expectKind impl, nnkEnumTy
  25. let normalizerNode = quote: `normalizer`
  26. expectKind normalizerNode, nnkSym
  27. result = nnkCaseStmt.newTree(newCall(normalizerNode, argSym))
  28. # stores all processed field strings to give error msg for ambiguous enums
  29. var foundFields: seq[string] = @[]
  30. var fStr = "" # string of current field
  31. var fNum = BiggestInt(0) # int value of current field
  32. for f in impl:
  33. case f.kind
  34. of nnkEmpty: continue # skip first node of `enumTy`
  35. of nnkSym, nnkIdent: fStr = f.strVal
  36. of nnkEnumFieldDef:
  37. case f[1].kind
  38. of nnkStrLit: fStr = f[1].strVal
  39. of nnkTupleConstr:
  40. fStr = f[1][1].strVal
  41. fNum = f[1][0].intVal
  42. of nnkIntLit:
  43. fStr = f[0].strVal
  44. fNum = f[1].intVal
  45. else: error("Invalid tuple syntax!", f[1])
  46. else: error("Invalid node for enum type!", f)
  47. # add field if string not already added
  48. if fNum >= userMin and fNum <= userMax:
  49. fStr = normalizer(fStr)
  50. if fStr notin foundFields:
  51. result.add nnkOfBranch.newTree(newLit fStr, nnkCall.newTree(typ, newLit fNum))
  52. foundFields.add fStr
  53. else:
  54. error("Ambiguous enums cannot be parsed, field " & $fStr &
  55. " appears multiple times!", f)
  56. inc fNum
  57. # finally add else branch to raise or use default
  58. if default == nil:
  59. let raiseStmt = quote do:
  60. raise newException(ValueError, "Invalid enum value: " & $`argSym`)
  61. result.add nnkElse.newTree(raiseStmt)
  62. else:
  63. expectKind(default, nnkSym)
  64. result.add nnkElse.newTree(default)
  65. macro enumFullRange(a: typed): untyped =
  66. newNimNode(nnkCurly).add(a.getType[1][1..^1])
  67. macro enumNames(a: typed): untyped =
  68. # this could be exported too; in particular this could be useful for enum with holes.
  69. result = newNimNode(nnkBracket)
  70. for ai in a.getType[1][1..^1]:
  71. assert ai.kind == nnkSym
  72. result.add newLit ai.strVal
  73. iterator items*[T: HoleyEnum](E: typedesc[T]): T =
  74. ## Iterates over an enum with holes.
  75. runnableExamples:
  76. type A = enum a0 = 2, a1 = 4, a2
  77. type B[T] = enum b0 = 2, b1 = 4
  78. from std/sequtils import toSeq
  79. assert A.toSeq == [a0, a1, a2]
  80. assert B[float].toSeq == [B[float].b0, B[float].b1]
  81. for a in enumFullRange(E): yield a
  82. func symbolName*[T: OrdinalEnum](a: T): string =
  83. ## Returns the symbol name of an enum.
  84. runnableExamples:
  85. type B = enum
  86. b0 = (10, "kb0")
  87. b1 = "kb1"
  88. b2
  89. let b = B.low
  90. assert b.symbolName == "b0"
  91. assert $b == "kb0"
  92. static: assert B.high.symbolName == "b2"
  93. const names = enumNames(T)
  94. names[a.ord - T.low.ord]