114 lines
3.7 KiB
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)
|