Skip to content

Commit

Permalink
Add enum utilities and add enum with holes support (#115)
Browse files Browse the repository at this point in the history
  • Loading branch information
lchenut authored Jun 9, 2022
1 parent 412a691 commit c2f0cbf
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 57 deletions.
55 changes: 25 additions & 30 deletions stew/objects.nim
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import
macros
macros,
sequtils

template init*(lvalue: var auto) =
mixin init
Expand Down Expand Up @@ -67,41 +68,36 @@ 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
## from untrusted source) to an enum variable. The function will return `true`
## 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
Expand All @@ -113,4 +109,3 @@ func isZeroMemory*[T](x: T): bool =
if b != 0:
return false
return true

111 changes: 84 additions & 27 deletions tests/test_objects.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down

0 comments on commit c2f0cbf

Please sign in to comment.