From c2f0cbf0d97fd1c9a2390af969138a3aea9c8bd8 Mon Sep 17 00:00:00 2001 From: lchenut Date: Fri, 10 Jun 2022 01:29:07 +0200 Subject: [PATCH] Add enum utilities and add enum with holes support (#115) --- stew/objects.nim | 55 ++++++++++---------- tests/test_objects.nim | 111 +++++++++++++++++++++++++++++++---------- 2 files changed, 109 insertions(+), 57 deletions(-) diff --git a/stew/objects.nim b/stew/objects.nim index 3410b583..cbf561ca 100644 --- a/stew/objects.nim +++ b/stew/objects.nim @@ -1,5 +1,6 @@ import - macros + macros, + sequtils template init*(lvalue: var auto) = mixin init @@ -67,26 +68,28 @@ proc baseType*(obj: RootObj): cstring = proc baseType*(obj: ref RootObj): cstring = obj[].baseType -when false: - # TODO: Implementing this doesn't seem possible at the moment. - # - # When given enum like: - # - # type WithoutHoles2 = enum - # A2 = 2, B2 = 3, C2 = 4 - # - # ...the code below will print: - # - # EnumTy - # Empty - # Sym "A2" - # Sym "B2" - # Sym "C2" - # - macro hasHoles*(T: type[enum]): bool = - let t = getType(T)[1] - echo t.treeRepr - return newLit(true) +macro enumRangeInt64*(a: type[enum]): untyped = + ## This macro returns an array with all the ordinal values of an enum + let + values = a.getType[1][1..^1] + valuesOrded = values.mapIt(newCall("int64", it)) + newNimNode(nnkBracket).add(valuesOrded) + +macro hasHoles*(T: type[enum]): bool = + # As an enum is always sorted, just substract the first and the last ordinal value + # and compare the result to the number of element in it will do the trick. + let len = T.getType[1].len - 2 + + quote: `T`.high.ord - `T`.low.ord != `len` + +proc contains*[I: SomeInteger](e: type[enum], v: I): bool = + when I is uint64: + if v > int.high.uint64: + return false + when e.hasHoles(): + v.int64 in enumRangeInt64(e) + else: + v.int64 in e.low.int64 .. e.high.int64 func checkedEnumAssign*[E: enum, I: SomeInteger](res: var E, value: I): bool = ## This function can be used to safely assign a tainted integer value (coming @@ -94,14 +97,7 @@ func checkedEnumAssign*[E: enum, I: SomeInteger](res: var E, value: I): bool = ## if the integer value is within the acceped values of the enum and `false` ## otherwise. - # TODO: Enums with holes are not supported yet - # static: doAssert(not hasHoles(E)) - - when I is SomeSignedInt or low(E).int > 0: - if value < I(low(E)): - return false - - if value > I(high(E)): + if value notin E: return false res = E value @@ -113,4 +109,3 @@ func isZeroMemory*[T](x: T): bool = if b != 0: return false return true - diff --git a/tests/test_objects.nim b/tests/test_objects.nim index b64a854a..78386ee7 100644 --- a/tests/test_objects.nim +++ b/tests/test_objects.nim @@ -70,23 +70,81 @@ suite "Objects": T6 is DistinctBar T6 isnot Bar - when false: - # TODO: Not possible yet (see objects.nim) - test "hasHoles": - type - WithoutHoles = enum - A1, B1, C1 + test "enumRangeInt64": + type + WithoutHoles = enum + A1, A2, A3 + WithoutHoles2 = enum + B1 = 4, B2 = 5, B3 = 6 + WithHoles = enum + C1 = 1, C2 = 3, C3 = 5 - WithoutHoles2 = enum - A2 = 2, B2 = 3, C2 = 4 + check: + enumRangeInt64(WithoutHoles) == [ 0'i64, 1, 2 ] + enumRangeInt64(WithoutHoles2) == [ 4'i64, 5, 6 ] + enumRangeInt64(WithHoles) == [ 1'i64, 3, 5 ] - WithHoles = enum - A3, B3 = 2, C3 - check: - hasHoles(WithoutHoles2) == false - hasHoles(WithoutHoles) == false - hasHoles(WithHoles) == true + test "contains": + type + WithoutHoles = enum + A1, A2, A3 + WithoutHoles2 = enum + B1 = 4, B2 = 5, B3 = 6 + WithHoles = enum + C1 = 1, C2 = 3, C3 = 5 + WithoutHoles3 = enum + D1 = -1, D2 = 0, D3 = 1 + WithHoles2 = enum + E1 = -5, E2 = 0, E3 = 5 + + check: + 1 in WithoutHoles + 5 notin WithoutHoles + 1 notin WithoutHoles2 + 5 in WithoutHoles2 + 1 in WithHoles + 2 notin WithHoles + 6 notin WithHoles + 5 in WithHoles + 1.byte in WithoutHoles + 4294967295'u32 notin WithoutHoles3 + -1.int8 in WithoutHoles3 + -4.int16 notin WithoutHoles3 + -5.int16 in WithHoles2 + 5.uint64 in WithHoles2 + -12.int8 notin WithHoles2 + int64.high notin WithoutHoles + int64.high notin WithHoles + int64.low notin WithoutHoles + int64.low notin WithHoles + int64.high.uint64 * 2 notin WithoutHoles + int64.high.uint64 * 2 notin WithHoles + + + test "hasHoles": + type + EnumWithOneValue = enum + A0 + + WithoutHoles = enum + A1, B1, C1 + + WithoutHoles2 = enum + A2 = 2, B2 = 3, C2 = 4 + + WithHoles = enum + A3, B3 = 2, C3 + + WithBigHoles = enum + A4 = 0, B4 = 2000, C4 = 4000 + + check: + hasHoles(EnumWithOneValue) == false + hasHoles(WithoutHoles) == false + hasHoles(WithoutHoles2) == false + hasHoles(WithHoles) == true + hasHoles(WithBigHoles) == true test "checkedEnumAssign": type @@ -96,42 +154,41 @@ suite "Objects": AnotherEnum = enum A2 = 2, B2, C2 + EnumWithHoles = enum + A3, B3 = 3, C3 var e1 = A1 e2 = A2 + e3 = A3 check: checkedEnumAssign(e1, 2) e1 == C1 - - check: not checkedEnumAssign(e1, 5) e1 == C1 - - check: checkedEnumAssign(e1, 0) e1 == A1 - - check: not checkedEnumAssign(e1, -1) e1 == A1 - check: checkedEnumAssign(e2, 2) e2 == A2 - - check: not checkedEnumAssign(e2, 5) e2 == A2 - - check: checkedEnumAssign(e2, 4) e2 == C2 - - check: not checkedEnumAssign(e2, 1) e2 == C2 + checkedEnumAssign(e3, 4) + e3 == C3 + not checkedEnumAssign(e3, 1) + e3 == C3 + checkedEnumAssign(e3, 0) + e3 == A3 + not checkedEnumAssign(e3, -1) + e3 == A3 + test "isZeroMemory": type Foo = object