Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add enum utilities and add enum with holes support #115

Merged
merged 6 commits into from
Jun 9, 2022
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 enumRangeOrd*(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("ord", 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 enumRangeOrd(e).mapIt(it.int64)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using mapIt here would be a bit inefficient as it would allocate a sequence just to perform the check. It would be better to directly produce an int64 array in enumRangeOrd.

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 "enumRangeOrd":
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:
enumRangeOrd(WithoutHoles) == [ 0, 1, 2 ]
enumRangeOrd(WithoutHoles2) == [ 4, 5, 6 ]
enumRangeOrd(WithHoles) == [ 1, 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