Add stew/enums and enumStrValues{Array,Seq}

This commit is contained in:
Zahary Karadjov 2022-07-02 14:31:39 +03:00
parent 4cab7b0879
commit 1f67530aa0
No known key found for this signature in database
GPG Key ID: C8936F8A3073D609
5 changed files with 209 additions and 156 deletions

58
stew/enums.nim Normal file
View File

@ -0,0 +1,58 @@
import
macros, sequtils
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 enumStrValuesArrayImpl(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("$", it))
newNimNode(nnkBracket).add(valuesOrded)
# TODO: This should be a proc returning a lent view over the
# const value. This will ensure only a single instace
# of the array is generated.
template enumStrValuesArray*(E: type[enum]): auto =
const values = enumStrValuesArrayImpl E
values
# TODO: This should be a proc returning a lent view over the
# const value. This will ensure only a single instace
# of the sequence is generated.
template enumStrValuesSeq*(E: type[enum]): seq[string] =
const values = @(enumStrValuesArray E)
values
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.
if value notin E:
return false
res = E value
return true

View File

@ -1,6 +1,11 @@
import
macros,
sequtils
macros, sequtils
import
enums
export
enums
template init*(lvalue: var auto) =
mixin init
@ -68,41 +73,6 @@ proc baseType*(obj: RootObj): cstring =
proc baseType*(obj: ref RootObj): cstring =
obj[].baseType
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.
if value notin E:
return false
res = E value
return true
func isZeroMemory*[T](x: T): bool =
# TODO: iterate over words here
for b in cast[ptr array[sizeof(T), byte]](unsafeAddr x)[]:

View File

@ -20,6 +20,7 @@ import
test_byteutils,
test_ctops,
test_endians2,
test_enums,
test_io2,
test_keyed_queue,
test_sorted_set,

143
tests/test_enums.nim Normal file
View File

@ -0,0 +1,143 @@
import
unittest, typetraits,
../stew/enums
suite "enums":
test "enumRangeInt64":
type
WithoutHoles = enum
A1, A2, A3
WithoutHoles2 = enum
B1 = 4, B2 = 5, B3 = 6
WithHoles = enum
C1 = 1, C2 = 3, C3 = 5
check:
enumRangeInt64(WithoutHoles) == [ 0'i64, 1, 2 ]
enumRangeInt64(WithoutHoles2) == [ 4'i64, 5, 6 ]
enumRangeInt64(WithHoles) == [ 1'i64, 3, 5 ]
test "enumStringValues":
type
RegularEnum = enum
A1, A2, A3
EnumWithHoles = enum
C1 = 1, C2 = 3, C3 = 5
StringyEnum = enum
A = "value A"
B = "value B"
check:
enumStrValuesArray(RegularEnum) == ["A1", "A2", "A3"]
enumStrValuesArray(EnumWithHoles) == ["C1", "C2", "C3"]
enumStrValuesArray(StringyEnum) == ["value A", "value B"]
enumStrValuesSeq(RegularEnum) == @["A1", "A2", "A3"]
enumStrValuesSeq(EnumWithHoles) == @["C1", "C2", "C3"]
enumStrValuesSeq(StringyEnum) == @["value A", "value B"]
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
SomeEnum = enum
A1, B1, C1
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
not checkedEnumAssign(e1, 5)
e1 == C1
checkedEnumAssign(e1, 0)
e1 == A1
not checkedEnumAssign(e1, -1)
e1 == A1
checkedEnumAssign(e2, 2)
e2 == A2
not checkedEnumAssign(e2, 5)
e2 == A2
checkedEnumAssign(e2, 4)
e2 == C2
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

View File

@ -70,125 +70,6 @@ suite "Objects":
T6 is DistinctBar
T6 isnot Bar
test "enumRangeInt64":
type
WithoutHoles = enum
A1, A2, A3
WithoutHoles2 = enum
B1 = 4, B2 = 5, B3 = 6
WithHoles = enum
C1 = 1, C2 = 3, C3 = 5
check:
enumRangeInt64(WithoutHoles) == [ 0'i64, 1, 2 ]
enumRangeInt64(WithoutHoles2) == [ 4'i64, 5, 6 ]
enumRangeInt64(WithHoles) == [ 1'i64, 3, 5 ]
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
SomeEnum = enum
A1, B1, C1
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
not checkedEnumAssign(e1, 5)
e1 == C1
checkedEnumAssign(e1, 0)
e1 == A1
not checkedEnumAssign(e1, -1)
e1 == A1
checkedEnumAssign(e2, 2)
e2 == A2
not checkedEnumAssign(e2, 5)
e2 == A2
checkedEnumAssign(e2, 4)
e2 == C2
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