From 1b478430126a45693eb65bd746d98a62334a1d79 Mon Sep 17 00:00:00 2001 From: Mark Spanbroek Date: Sun, 7 Mar 2021 08:56:35 +0100 Subject: [PATCH] Lift operators so that they can be used on options and results --- questionable/options.nim | 41 +++++++++++++++++++++++++++++++++ questionable/results.nim | 44 ++++++++++++++++++++++++++++++++++++ testmodules/options/test.nim | 29 ++++++++++++++++++++++++ testmodules/result/test.nim | 29 ++++++++++++++++++++++++ 4 files changed, 143 insertions(+) diff --git a/questionable/options.nim b/questionable/options.nim index f33532f..f252b98 100644 --- a/questionable/options.nim +++ b/questionable/options.nim @@ -31,3 +31,44 @@ template `=?`*[T](name: untyped{nkIdent}, option: ?T): bool = template name: T {.used.} = option.unsafeGet() option.isSome +template liftPrefix(_: type Option, operator: untyped) = + + template `operator`*(a: ?typed): ?typed = + type T = type(`operator`(a.unsafeGet)) + if x =? a: + `operator`(x).some + else: + T.none + +template liftInfix(_: type Option, operator: untyped) = + + template `operator`*(a: ?typed, b: ?typed): ?typed = + type T = type(`operator`(a.unsafeGet, b.unsafeGet)) + if x =? a and y =? b: + `operator`(x, y).some + else: + T.none + + template `operator`*(a: ?typed, b: typed): ?typed = + type T = type(`operator`(a.unsafeGet, b)) + if x =? a: + `operator`(x, b).some + else: + T.none + +Option.liftPrefix(`-`) +Option.liftPrefix(`+`) +Option.liftPrefix(`@`) +Option.liftInfix(`*`) +Option.liftInfix(`/`) +Option.liftInfix(`div`) +Option.liftInfix(`mod`) +Option.liftInfix(`shl`) +Option.liftInfix(`shr`) +Option.liftInfix(`+`) +Option.liftInfix(`-`) +Option.liftInfix(`&`) +Option.liftInfix(`<=`) +Option.liftInfix(`<`) +Option.liftInfix(`>=`) +Option.liftInfix(`>`) diff --git a/questionable/results.nim b/questionable/results.nim index 3c56c58..1716ee4 100644 --- a/questionable/results.nim +++ b/questionable/results.nim @@ -33,3 +33,47 @@ template `|?`*[T](value: ?!T, fallback: T): T = template `=?`*[T](name: untyped{nkIdent}, value: ?!T): bool = template name: T {.used.} = value.unsafeGet() value.isOk + +template liftPrefix(_: type Result, operator: untyped) = + + template `operator`*(a: ?!typed): ?!typed = + type T = type(`operator`(a.unsafeGet)) + if x =? a: + `operator`(x).success + else: + T.failure(a.error) + +template liftInfix(_: type Result, operator: untyped) = + + template `operator`*(a: ?!typed, b: ?!typed): ?!typed = + type T = type(`operator`(a.unsafeGet, b.unsafeGet)) + if x =? a and y =? b: + `operator`(x, y).success + elif a.isErr: + T.failure(a.error) + else: + T.failure(b.error) + + template `operator`*(a: ?!typed, b: typed): ?!typed = + type T = type(`operator`(a.unsafeGet, b)) + if x =? a: + `operator`(x, b).success + else: + T.failure(a.error) + +Result.liftPrefix(`-`) +Result.liftPrefix(`+`) +Result.liftPrefix(`@`) +Result.liftInfix(`*`) +Result.liftInfix(`/`) +Result.liftInfix(`div`) +Result.liftInfix(`mod`) +Result.liftInfix(`shl`) +Result.liftInfix(`shr`) +Result.liftInfix(`+`) +Result.liftInfix(`-`) +Result.liftInfix(`&`) +Result.liftInfix(`<=`) +Result.liftInfix(`<`) +Result.liftInfix(`>=`) +Result.liftInfix(`>`) diff --git a/testmodules/options/test.nim b/testmodules/options/test.nim index 980f2a2..21b511c 100644 --- a/testmodules/options/test.nim +++ b/testmodules/options/test.nim @@ -56,3 +56,32 @@ suite "optionals": let a = 42.some if a =? a: check a == 42 + + test "unary operator `-` works for options": + check -(-42.some) == 42.some + check -(int.none) == int.none + + test "other unary operators work for options": + check +(42.some) == 42.some + check @([1, 2].some) == (@[1, 2]).some + + test "binary operator `+` works for options": + check 40.some + 2.some == 42.some + check 40.some + 2 == 42.some + check int.none + 2 == int.none + check 40.some + int.none == int.none + check int.none + int.none == int.none + + test "other binary operators work for options": + check 21.some * 2 == 42.some + check 84'f.some / 2'f == 42'f.some + check 84.some div 2 == 42.some + check 85.some mod 43 == 42.some + check 0b00110011.some shl 1 == 0b01100110.some + check 0b00110011.some shr 1 == 0b00011001.some + check 44.some - 2 == 42.some + check "f".some & "oo" == "foo".some + check 40.some <= 42 == true.some + check 40.some < 42 == true.some + check 40.some >= 42 == false.some + check 40.some > 42 == false.some diff --git a/testmodules/result/test.nim b/testmodules/result/test.nim index 04b5bd7..11baac5 100644 --- a/testmodules/result/test.nim +++ b/testmodules/result/test.nim @@ -63,3 +63,32 @@ suite "result": test "catch can be used to convert exceptions to results": check parseInt("42").catch == 42.success check parseInt("foo").catch.error of ValueError + + test "unary operator `-` works for results": + check -(-42.success) == 42.success + check -(int.failure(error)) == int.failure(error) + + test "other unary operators work for results": + check +(42.success) == 42.success + check @([1, 2].success) == (@[1, 2]).success + + test "binary operator `+` works for results": + check 40.success + 2.success == 42.success + check 40.success + 2 == 42.success + check int.failure(error) + 2 == int.failure(error) + check 40.success + int.failure(error) == int.failure(error) + check int.failure(error) + int.failure(error) == int.failure(error) + + test "other binary operators work for results": + check (21.success * 2 == 42.success) + check (84'f.success / 2'f == 42'f.success) + check (84.success div 2 == 42.success) + check (85.success mod 43 == 42.success) + check (0b00110011.success shl 1 == 0b01100110.success) + check (0b00110011.success shr 1 == 0b00011001.success) + check (44.success - 2 == 42.success) + check ("f".success & "oo" == "foo".success) + check (40.success <= 42 == true.success) + check (40.success < 42 == true.success) + check (40.success >= 42 == false.success) + check (40.success > 42 == false.success)