diff --git a/questionable/chaining.nim b/questionable/chaining.nim new file mode 100644 index 0000000..809a36b --- /dev/null +++ b/questionable/chaining.nim @@ -0,0 +1,33 @@ +import std/macros + +template `.?`*(option: typed, identifier: untyped{nkIdent}): untyped = + option ->? option.unsafeGet.identifier + +macro `.?`*(option: typed, infix: untyped{nkInfix}): untyped = + let left = infix[1] + infix[1] = quote do: `option`.?`left` + infix + +macro `.?`*(option: typed, bracket: untyped{nkBracketExpr}): untyped = + let left = bracket[0] + bracket[0] = quote do: `option`.?`left` + bracket + +macro `.?`*(option: typed, dot: untyped{nkDotExpr}): untyped = + let left = dot[0] + dot[0] = quote do: `option`.?`left` + dot + +macro `.?`*(option: typed, call: untyped{nkCall}): untyped = + let procedure = call[0] + if call.len > 1: + if procedure.kind == nnkDotExpr: + let (inner, outer) = (procedure[0], procedure[1]) + call[0] = outer + call.insert(1, quote do: `option`.?`inner`) + call + else: + call.insert(1, quote do: `option`.unsafeGet) + quote do: `option` ->? `call` + else: + quote do: `option`.?`procedure` diff --git a/questionable/operators.nim b/questionable/operators.nim new file mode 100644 index 0000000..83a4552 --- /dev/null +++ b/questionable/operators.nim @@ -0,0 +1,12 @@ +template liftUnary*(T: type, operator: untyped) = + + template `operator`*(a: T): untyped = + a ->? `operator`(a.unsafeGet()) + +template liftBinary*(T: type, operator: untyped) = + + template `operator`*(a: T, b: T): untyped = + (a, b) ->? `operator`(a.unsafeGet, b.unsafeGet) + + template `operator`*(a: T, b: typed): untyped = + a ->? `operator`(a.unsafeGet(), b) diff --git a/questionable/options.nim b/questionable/options.nim index cce8b3b..b262ba9 100644 --- a/questionable/options.nim +++ b/questionable/options.nim @@ -1,54 +1,39 @@ import std/options +import ./chaining +import ./operators include ./errorban export options +export chaining template `?`*(T: typed): type Option[T] = Option[T] -template `.?`*(option: ?typed, field: untyped{nkIdent}): ?untyped = - type T = type option.get.field +template `->?`*(option: ?typed, expression: untyped): untyped = + type T = type expression if option.isSome: - option.unsafeGet().field.some + expression.some else: T.none +template `->?`*(options: (?typed, ?typed), expression: untyped): untyped = + type T = type expression + if options[0].isSome and options[1].isSome: + expression.some + else: + T.none + +template `=?`*[T](name: untyped{nkIdent}, option: ?T): bool = + template name: T {.used.} = option.unsafeGet() + option.isSome + template `|?`*[T](option: ?T, fallback: T): T = if option.isSome: option.unsafeGet() else: fallback -template `=?`*[T](name: untyped{nkIdent}, option: ?T): bool = - template name: T {.used.} = option.unsafeGet() - option.isSome - -template liftUnary(_: type Option, operator: untyped) = - - template `operator`*(a: ?typed): ?typed = - type T {.used.} = type(`operator`(a.unsafeGet)) - if x =? a: - `operator`(x).some - else: - T.none - -template liftBinary(_: 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.liftUnary(`-`) Option.liftUnary(`+`) Option.liftUnary(`@`) diff --git a/questionable/results.nim b/questionable/results.nim index 98eaf86..fff6cb2 100644 --- a/questionable/results.nim +++ b/questionable/results.nim @@ -1,5 +1,6 @@ -import ./options import ./resultsbase +import ./options +import ./operators include ./errorban @@ -8,18 +9,27 @@ export resultsbase template `?!`*(T: typed): type Result[T, ref CatchableError] = Result[T, ref CatchableError] -template success*[T](value: T): ?!T = +proc success*[T](value: T): ?!T = ok(?!T, value) -template failure*(T: type, error: ref CatchableError): ?!T = +proc failure*(T: type, error: ref CatchableError): ?!T = err(?!T, error) -template `.?`*(value: ?!typed, field: untyped{nkIdent}): ?!untyped = - type T = type value.get.field - if value.isOk: - ok(?!T, value.unsafeGet().field) +template `->?`*(option: ?!typed, expression: untyped): untyped = + type T = type expression + if option.isErr: + T.failure(option.error) else: - err(?!T, error(value)) + expression.success + +template `->?`*(options: (?!typed, ?!typed), expression: untyped): untyped = + type T = type expression + if options[0].isErr: + T.failure(options[0].error) + elif options[1].isErr: + T.failure(options[1].error) + else: + expression.success template `|?`*[T](value: ?!T, fallback: T): T = value.valueOr(fallback) @@ -34,33 +44,6 @@ proc toOption*[T,E](value: Result[T,E]): ?T = else: T.none -template liftUnary(_: type Result, operator: untyped) = - - template `operator`*(a: ?!typed): ?!typed = - type T {.used.} = type(`operator`(a.unsafeGet)) - if x =? a: - `operator`(x).success - else: - T.failure(a.error) - -template liftBinary(_: 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.liftUnary(`-`) Result.liftUnary(`+`) Result.liftUnary(`@`) diff --git a/testmodules/options/test.nim b/testmodules/options/test.nim index 4f86016..a3077bd 100644 --- a/testmodules/options/test.nim +++ b/testmodules/options/test.nim @@ -1,5 +1,6 @@ import std/unittest import std/sequtils +import std/sugar import pkg/questionable suite "optionals": @@ -16,6 +17,21 @@ suite "optionals": check b.?len == int.none check a.?len.?uint8 == 2'u8.some check b.?len.?uint8 == uint8.none + check a.?len() == 2.some + check b.?len() == int.none + check a.?distribute(2).?len() == 2.some + check b.?distribute(2).?len() == int.none + + test ".? chain can be followed by . calls and operators": + let a = @[41, 42].some + check a.?len.get == 2 + check a.?len.get.uint8.uint64 == 2'u64 + check a.?len.get() == 2 + check a.?len.get().uint8.uint64 == 2'u64 + check a.?deduplicate()[0].?uint8.?uint64 == 41'u64.some + check a.?len + 1 == 3.some + check a.?deduplicate()[0] + 1 == 42.some + check a.?deduplicate.map(x => x) == @[41, 42].some test "[] can be used for indexing optionals": let a: ?seq[int] = @[1, 2, 3].some diff --git a/testmodules/result/test.nim b/testmodules/result/test.nim index 862f7a8..ecd4f32 100644 --- a/testmodules/result/test.nim +++ b/testmodules/result/test.nim @@ -1,6 +1,7 @@ import std/unittest import std/sequtils import std/strutils +import std/sugar import pkg/questionable import pkg/questionable/results @@ -20,6 +21,21 @@ suite "result": check b.?len == int.failure error check a.?len.?uint8 == 2'u8.success check b.?len.?uint8 == uint8.failure error + check a.?len() == 2.success + check b.?len() == int.failure error + check a.?distribute(2).?len() == 2.success + check b.?distribute(2).?len() == int.failure error + + test ".? chain can be followed by . calls and operators": + let a = @[41, 42].success + check (a.?len.get == 2) + check (a.?len.get.uint8.uint64 == 2'u64) + check (a.?len.get() == 2) + check (a.?len.get().uint8.uint64 == 2'u64) + check (a.?deduplicate()[0].?uint8.?uint64 == 41'u64.success) + check (a.?len + 1 == 3.success) + check (a.?deduplicate()[0] + 1 == 42.success) + check (a.?deduplicate.map(x => x) == @[41, 42].success) test "[] can be used for indexing optionals": let a: ?!seq[int] = @[1, 2, 3].success @@ -115,23 +131,23 @@ suite "result": # chaining: let amount = works().?deduplicate.?len - check amount == 2.success + check (amount == 2.success) # fallback values: let value = fails() |? @[] - check value == newSeq[int](0) + check (value == newSeq[int](0)) # lifted operators: let sum = works()[3] + 40 - check sum == 42.success + check (sum == 42.success) # catch let x = parseInt("42").catch - check x == 42.success + check (x == 42.success) let y = parseInt("XX").catch check y.isErr # Conversion to Option let converted = works().toOption - check converted == @[1, 1, 2, 2, 2].some + check (converted == @[1, 1, 2, 2, 2].some)