Initial version of questionable

Syntactic sugar for std/options, pkg/result and pkg/stew
This commit is contained in:
Mark Spanbroek 2021-03-05 21:07:48 +01:00
commit 81ed9b652c
21 changed files with 259 additions and 0 deletions

5
.editorconfig Normal file
View File

@ -0,0 +1,5 @@
[*]
indent_style = space
insert_final_newline = true
indent_size = 2
trim_trailing_whitespace = true

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
*
!*/
!*.*
nimbledeps

1
.tool-versions Normal file
View File

@ -0,0 +1 @@
nim 1.4.4

3
questionable.nim Normal file
View File

@ -0,0 +1,3 @@
import ./questionable/options
export options

10
questionable.nimble Normal file
View File

@ -0,0 +1,10 @@
version = "0.1.0"
author = "Questionable Authors"
description = "Elegant optional types"
license = "MIT"
task test, "Runs the test suite":
for module in ["options", "result", "stew"]:
withDir "testmodules/" & module:
exec "nimble install -d -y"
exec "nimble test -y"

View File

@ -0,0 +1,8 @@
## Include this file to indicate that your module does not raise Errors.
## Disables compiler hints about unused declarations in Nim < 1.4.0
when (NimMajor, NimMinor, NimPatch) >= (1, 4, 0):
{.push raises:[].}
else:
{.push raises: [Defect].}
{.hint[XDeclaredButNotUsed]: off.}

33
questionable/options.nim Normal file
View File

@ -0,0 +1,33 @@
import std/options
include ./errorban
export options
template `?`*(T: typed): type Option[T] =
Option[T]
template `.?`*(option: ?typed, field: untyped{nkIdent}): ?untyped =
type T = type option.get.field
if option.isSome:
option.unsafeGet().field.some
else:
T.none
template `[]`*(option: ?typed, index: typed): ?typed =
type T = type option.get[index]
if option.isSome:
option.unsafeGet()[index].some
else:
T.none
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

35
questionable/results.nim Normal file
View File

@ -0,0 +1,35 @@
import ./resultsbase
include ./errorban
export resultsbase
template `?!`*(T: typed): type Result[T, ref CatchableError] =
Result[T, ref CatchableError]
template success*[T](value: T): ?!T =
ok(?!T, value)
template 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)
else:
err(?!T, error(value))
template `[]`*(value: ?!typed, index: typed): ?!typed =
type T = type value.get[index]
if value.isOk:
ok(?!T, value.unsafeGet()[index])
else:
err(?!T, error(value))
template `|?`*[T](value: ?!T, fallback: T): T =
value.valueOr(fallback)
template `=?`*[T](name: untyped{nkIdent}, value: ?!T): bool =
template name: T {.used.} = value.unsafeGet()
value.isOk

View File

@ -0,0 +1,8 @@
template tryImport(module) = import module
when compiles tryimport pkg/result:
import pkg/result/../results
else:
import pkg/stew/results
export results

View File

@ -0,0 +1 @@
--path:"../.."

View File

View File

@ -0,0 +1,58 @@
import std/unittest
import pkg/questionable
suite "optionals":
test "?Type is shorthand for Option[Type]":
check (?int is Option[int])
check (?string is Option[string])
check (?seq[bool] is Option[seq[bool]])
test ".? can be used for chaining optionals":
let a: ?seq[int] = @[41, 42].some
let b: ?seq[int] = seq[int].none
check a.?len == 2.some
check b.?len == int.none
check a.?len.?uint8 == 2'u8.some
check b.?len.?uint8 == uint8.none
test "[] can be used for indexing optionals":
let a: ?seq[int] = @[1, 2, 3].some
let b: ?seq[int] = seq[int].none
check a[1] == 2.some
check a[^1] == 3.some
check a[0..1] == @[1, 2].some
check b[1] == int.none
test "|? can be used to specify a fallback value":
check 42.some |? 40 == 42
check int.none |? 42 == 42
test "=? can be used for optional binding":
if a =? int.none:
check false
if b =? 42.some:
check b == 42
else:
check false
while a =? 42.some:
check a == 42
break
while a =? int.none:
check false
break
test "=? can appear multiple times in conditional expression":
if a =? 42.some and b =? "foo".some:
check a == 42
check b == "foo"
else:
check false
test "=? works with variable hiding":
let a = 42.some
if a =? a:
check a == 42

View File

@ -0,0 +1,7 @@
version = "0.1.0"
author = "Questionable Authors"
description = "Questionable tests for std/option"
license = "MIT"
task test, "Runs the test suite":
exec "nim c -f -r test.nim"

View File

@ -0,0 +1 @@
--path:"../.."

View File

View File

@ -0,0 +1,65 @@
import std/unittest
import std/strutils
import pkg/questionable/results
suite "result":
let error = newException(CatchableError, "error")
test "?!Type is shorthand for Result[Type, ref CatchableError]":
check (?!int is Result[int, ref CatchableError])
check (?!string is Result[string, ref CatchableError])
check (?!seq[bool] is Result[seq[bool], ref CatchableError])
test ".? can be used for chaining results":
let a: ?!seq[int] = @[41, 42].success
let b: ?!seq[int] = seq[int].failure error
check a.?len == 2.success
check b.?len == int.failure error
check a.?len.?uint8 == 2'u8.success
check b.?len.?uint8 == uint8.failure error
test "[] can be used for indexing optionals":
let a: ?!seq[int] = @[1, 2, 3].success
let b: ?!seq[int] = seq[int].failure error
check a[1] == 2.success
check a[^1] == 3.success
check a[0..1] == @[1, 2].success
check b[1] == int.failure error
test "|? can be used to specify a fallback value":
check 42.success |? 40 == 42
check int.failure(error) |? 42 == 42
test "=? can be used for optional binding":
if a =? int.failure(error):
check false
if b =? 42.success:
check b == 42
else:
check false
while a =? 42.success:
check a == 42
break
while a =? int.failure(error):
check false
break
test "=? can appear multiple times in conditional expression":
if a =? 42.success and b =? "foo".success:
check a == 42
check b == "foo"
else:
check false
test "=? works with variable hiding":
let a = 42.success
if a =? a:
check a == 42
test "catch can be used to convert exceptions to results":
check parseInt("42").catch == 42.success
check parseInt("foo").catch.error of ValueError

View File

@ -0,0 +1,9 @@
version = "0.1.0"
author = "Questionable Authors"
description = "Questionable tests for pkg/result"
license = "MIT"
requires "result"
task test, "Runs the test suite":
exec "nim c -f -r test.nim"

1
testmodules/stew/nim.cfg Normal file
View File

@ -0,0 +1 @@
--path:"../.."

View File

View File

@ -0,0 +1 @@
include ../result/test

View File

@ -0,0 +1,9 @@
version = "0.1.0"
author = "Questionable Authors"
description = "Questionable tests for pkg/stew"
license = "MIT"
requires "stew"
task test, "Runs the test suite":
exec "nim c -f -r test.nim"