nim-taskpools/taskpools/instrumentation/contracts.nim

114 lines
3.7 KiB
Nim

# Weave
# Copyright (c) 2019 Mamy André-Ratsimbazafy
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import macros, os, strutils
{.used.}
# A simple design-by-contract API
# ----------------------------------------------------------------------------------
# Everything should be a template that doesn't produce any code
# when WV_Asserts is not defined.
# Those checks are controlled by a custom flag instead of
# "--boundsChecks" or "--nilChecks" to decouple them from user code checks.
# Furthermore, we want them to be very lightweight on performance
# TODO auto-add documentation
proc inspectInfix(node: NimNode): NimNode =
## Inspect an expression,
## Returns the AST as string with runtime values inlined
## from infix operators inlined.
# TODO: pointer and custom type need a default repr
# otherwise we can only resulve simple expressions
proc inspect(node: NimNode): NimNode =
case node.kind:
of nnkInfix:
return newCall(
bindSym"&",
newCall(
bindSym"&",
newCall(ident"$", inspect(node[1])),
newLit(" " & $node[0] & " ")
),
newCall(ident"$", inspect(node[2]))
)
of {nnkIdent, nnkSym}:
return node
of nnkDotExpr:
return quote do:
when `node` is pointer or
`node` is ptr or
`node` is (proc):
toHex(cast[ByteAddress](`node`) and 0xffff_ffff)
else:
$(`node`)
of nnkPar:
result = nnkPar.newTree()
for sub in node:
result.add inspect(sub)
else:
return node.toStrLit()
return inspect(node)
macro assertContract(
checkName: static string,
predicate: untyped) =
let lineinfo = lineinfoObj(predicate)
let file = extractFilename(lineinfo.filename)
var strippedPredicate: NimNode
if predicate.kind == nnkStmtList:
assert predicate.len == 1, "Only one-liner conditions are supported"
strippedPredicate = predicate[0]
else:
strippedPredicate = predicate
let debug = "\n Contract violated for " & checkName & " at " & file & ":" & $lineinfo.line &
"\n " & $strippedPredicate.toStrLit &
"\n The following values are contrary to expectations:" &
"\n "
let values = inspectInfix(strippedPredicate)
let myID = quote do:
when declared(myID):
$myID()
else:
"N/A"
result = quote do:
{.noSideEffect.}:
when compileOption("assertions"):
assert(`predicate`, `debug` & $`values` & " [Worker " & `myID` & "]\n")
elif defined(WV_Asserts):
if unlikely(not(`predicate`)):
raise newException(AssertionError, `debug` & $`values` & '\n')
# A way way to get the caller function would be nice.
template preCondition*(require: untyped) =
## Optional runtime check before returning from a function
assertContract("pre-condition", require)
template postCondition*(ensure: untyped) =
## Optional runtime check at the start of a function
assertContract("post-condition", ensure)
template ascertain*(check: untyped) =
## Optional runtime check in the middle of processing
assertContract("transient condition", check)
# Sanity checks
# ----------------------------------------------------------------------------------
when isMainModule:
proc assertGreater(x, y: int) =
postcondition(x > y)
# We should get a nicely formatted exception
assertGreater(10, 12)