123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195 |
- #
- #
- # Nim's Runtime Library
- # (c) Copyright 2020 Nim contributors
- #
- # See the file "copying.txt", included in this
- # distribution, for details about the copyright.
- #
- import macros
- from typetraits import OrdinalEnum, HoleyEnum
- when defined(nimPreviewSlimSystem):
- import std/assertions
- # xxx `genEnumCaseStmt` needs tests and runnableExamples
- macro genEnumCaseStmt*(typ: typedesc, argSym: typed, default: typed,
- userMin, userMax: static[int], normalizer: static[proc(s :string): string]): untyped =
- # Generates a case stmt, which assigns the correct enum field given
- # a normalized string comparison to the `argSym` input.
- # string normalization is done using passed normalizer.
- # NOTE: for an enum with fields Foo, Bar, ... we cannot generate
- # `of "Foo".nimIdentNormalize: Foo`.
- # This will fail, if the enum is not defined at top level (e.g. in a block).
- # Thus we check for the field value of the (possible holed enum) and convert
- # the integer value to the generic argument `typ`.
- let typ = typ.getTypeInst[1]
- let impl = typ.getImpl[2]
- expectKind impl, nnkEnumTy
- let normalizerNode = quote: `normalizer`
- expectKind normalizerNode, nnkSym
- result = nnkCaseStmt.newTree(newCall(normalizerNode, argSym))
- # stores all processed field strings to give error msg for ambiguous enums
- var foundFields: seq[string] = @[]
- var fStr = "" # string of current field
- var fNum = BiggestInt(0) # int value of current field
- for f in impl:
- case f.kind
- of nnkEmpty: continue # skip first node of `enumTy`
- of nnkSym, nnkIdent: fStr = f.strVal
- of nnkAccQuoted:
- fStr = ""
- for ch in f:
- fStr.add ch.strVal
- of nnkEnumFieldDef:
- case f[1].kind
- of nnkStrLit: fStr = f[1].strVal
- of nnkTupleConstr:
- fStr = f[1][1].strVal
- fNum = f[1][0].intVal
- of nnkIntLit:
- fStr = f[0].strVal
- fNum = f[1].intVal
- else:
- let fAst = f[0].getImpl
- if fAst.kind == nnkStrLit:
- fStr = fAst.strVal
- else:
- error("Invalid tuple syntax!", f[1])
- else: error("Invalid node for enum type `" & $f.kind & "`!", f)
- # add field if string not already added
- if fNum >= userMin and fNum <= userMax:
- fStr = normalizer(fStr)
- if fStr notin foundFields:
- result.add nnkOfBranch.newTree(newLit fStr, nnkCall.newTree(typ, newLit fNum))
- foundFields.add fStr
- else:
- error("Ambiguous enums cannot be parsed, field " & $fStr &
- " appears multiple times!", f)
- inc fNum
- # finally add else branch to raise or use default
- if default == nil:
- let raiseStmt = quote do:
- raise newException(ValueError, "Invalid enum value: " & $`argSym`)
- result.add nnkElse.newTree(raiseStmt)
- else:
- expectKind(default, nnkSym)
- result.add nnkElse.newTree(default)
- macro enumFullRange(a: typed): untyped =
- newNimNode(nnkCurly).add(a.getType[1][1..^1])
- macro enumNames(a: typed): untyped =
- # this could be exported too; in particular this could be useful for enum with holes.
- result = newNimNode(nnkBracket)
- for ai in a.getType[1][1..^1]:
- assert ai.kind == nnkSym
- result.add newLit ai.strVal
- iterator items*[T: HoleyEnum](E: typedesc[T]): T =
- ## Iterates over an enum with holes.
- runnableExamples:
- type
- A = enum
- a0 = 2
- a1 = 4
- a2
- B[T] = enum
- b0 = 2
- b1 = 4
- from std/sequtils import toSeq
- assert A.toSeq == [a0, a1, a2]
- assert B[float].toSeq == [B[float].b0, B[float].b1]
- for a in enumFullRange(E): yield a
- func span(T: typedesc[HoleyEnum]): int =
- (T.high.ord - T.low.ord) + 1
- const invalidSlot = uint8.high
- proc genLookup[T: typedesc[HoleyEnum]](_: T): auto =
- const n = span(T)
- var ret: array[n, uint8]
- var i = 0
- assert n <= invalidSlot.int
- for ai in mitems(ret): ai = invalidSlot
- for ai in items(T):
- ret[ai.ord - T.low.ord] = uint8(i)
- inc(i)
- return ret
- func symbolRankImpl[T](a: T): int {.inline.} =
- const n = T.span
- const thres = 255 # must be <= `invalidSlot`, but this should be tuned.
- when n <= thres:
- const lookup = genLookup(T)
- let lookup2 {.global.} = lookup # xxx improve pending https://github.com/timotheecour/Nim/issues/553
- #[
- This could be optimized using a hash adapted to `T` (possible since it's known at CT)
- to get better key distribution before indexing into the lookup table table.
- ]#
- {.noSideEffect.}: # because it's immutable
- let ret = lookup2[ord(a) - T.low.ord]
- if ret != invalidSlot: return ret.int
- else:
- var i = 0
- # we could also generate a case statement as optimization
- for ai in items(T):
- if ai == a: return i
- inc(i)
- raise newException(IndexDefect, $ord(a) & " invalid for " & $T)
- template symbolRank*[T: enum](a: T): int =
- ## Returns the index in which `a` is listed in `T`.
- ##
- ## The cost for a `HoleyEnum` is implementation defined, currently optimized
- ## for small enums, otherwise is `O(T.enumLen)`.
- runnableExamples:
- type
- A = enum # HoleyEnum
- a0 = -3
- a1 = 10
- a2
- a3 = (20, "f3Alt")
- B = enum # OrdinalEnum
- b0
- b1
- b2
- C = enum # OrdinalEnum
- c0 = 10
- c1
- c2
- assert a2.symbolRank == 2
- assert b2.symbolRank == 2
- assert c2.symbolRank == 2
- assert c2.ord == 12
- assert a2.ord == 11
- var invalid = 7.A
- doAssertRaises(IndexDefect): discard invalid.symbolRank
- when T is Ordinal: ord(a) - T.low.ord.static
- else: symbolRankImpl(a)
- func symbolName*[T: enum](a: T): string =
- ## Returns the symbol name of an enum.
- ##
- ## This uses `symbolRank`.
- runnableExamples:
- type B = enum
- b0 = (10, "kb0")
- b1 = "kb1"
- b2
- let b = B.low
- assert b.symbolName == "b0"
- assert $b == "kb0"
- static: assert B.high.symbolName == "b2"
- type C = enum # HoleyEnum
- c0 = -3
- c1 = 4
- c2 = 20
- assert c1.symbolName == "c1"
- const names = enumNames(T)
- names[a.symbolRank]
|