diff --git a/questionable/indexing.nim b/questionable/indexing.nim new file mode 100644 index 0000000..04155ca --- /dev/null +++ b/questionable/indexing.nim @@ -0,0 +1,41 @@ +import std/macros + +macro `?`*(expression: typed, brackets: untyped{nkBracket}): untyped = + # chain is of shape: expression?[index] + let index = brackets[0] + quote do: + type T = typeof(`expression`[`index`]) + try: + `expression`[`index`].some + except KeyError: + T.none + +macro `?`*(expression: typed, infix: untyped{nkInfix}): untyped = + # chain is of shape: expression?left `operator` right + let left = infix[1] + infix[1] = quote do: `expression`?`left` + infix + +macro `?`*(expression: typed, bracket: untyped{nkBracketExpr}): untyped = + # chain is of shape: expression?left[right] + let left = bracket[0] + bracket[0] = quote do: `expression`?`left` + bracket + +macro `?`*(expression: typed, dot: untyped{nkDotExpr}): untyped = + # chain is of shape: expression?left.right + let left = dot[0] + dot[0] = quote do: `expression`?`left` + dot + +macro `?`*(expression: typed, call: untyped{nkCall}): untyped = + let procedure = call[0] + if procedure.kind == nnkDotExpr: + # chain is of shape: expression?left.right(arguments) + let (left, right) = (procedure[0], procedure[1]) + call[0] = right + call.insert(1, quote do: `expression`?`left`) + call + else: + call.expectKind(nnkBracketExpr) + nil diff --git a/questionable/options.nim b/questionable/options.nim index ecbed7b..23d958f 100644 --- a/questionable/options.nim +++ b/questionable/options.nim @@ -1,12 +1,14 @@ import std/options import std/macros import ./chaining +import ./indexing import ./operators include ./errorban export options export chaining +export indexing template `?`*(T: typed): type Option[T] = Option[T] diff --git a/testmodules/options/test.nim b/testmodules/options/test.nim index 6a13240..9798732 100644 --- a/testmodules/options/test.nim +++ b/testmodules/options/test.nim @@ -1,5 +1,6 @@ import std/unittest import std/sequtils +import std/tables import std/sugar import pkg/questionable @@ -87,6 +88,25 @@ suite "optionals": if var a =? int.none: fail + test "?[] can be used for indexing tables without raising KeyError": + let table = @{"a": 1, "b": 2}.toTable + check table?["a"] == 1.some + check table?["c"] == int.none + + test "?[] can be followed by calls, operators and indexing": + let table = @{"a": @[41, 42]}.toTable + check table?["a"].isSome + check table?["a"].isSome() + check table?["a"][0] == 41.some + check table?["a"]?.len.get == 2 + check table?["a"]?.len.get.uint8.uint64 == 2'u64 + check table?["a"]?.len.get() == 2 + check table?["a"]?.len.get().uint8.uint64 == 2'u64 + check table?["a"]?.deduplicate()[0]?.uint8?.uint64 == 41'u64.some + check table?["a"]?.len + 1 == 3.some + check table?["a"]?.deduplicate()[0] + 1 == 42.some + check table?["a"]?.deduplicate.map(x => x) == @[41, 42].some + test "=? evaluates optional expression only once": var count = 0 if a =? (inc count; 42.some):