Skip to content

Commit

Permalink
implement break for check failure, needs define for match
Browse files Browse the repository at this point in the history
  • Loading branch information
metagn committed Sep 6, 2024
1 parent e951fae commit 8122d96
Show file tree
Hide file tree
Showing 7 changed files with 163 additions and 50 deletions.
2 changes: 1 addition & 1 deletion assigns.nimble
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,6 @@ task docs, "build docs for all modules":

task tests, "run tests for multiple backends":
when declared(runTests):
runTests(backends = {c, js, nims}, optionCombos = @[""])
runTests(backends = {c, js, nims}, optionCombos = @["", "-d:assignsMatchBreakpoint"])
else:
echo "tests task not implemented, need nimbleutils"
4 changes: 2 additions & 2 deletions src/assigns.nim
Original file line number Diff line number Diff line change
Expand Up @@ -98,5 +98,5 @@
## assignment. You can use the `implementAssign` and `implementAssignExported` templates as a
## shorthand for declaring these overloads.

import assigns/[syntax, tupleindex, impl]
export syntax, tupleindex, impl.assign
import assigns/[syntax, tupleindex, impl, tap]
export syntax, tupleindex, impl.assign, tap
40 changes: 30 additions & 10 deletions src/assigns/impl.nim
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,28 @@ type
AssignBoundError* = object of AssignError
## error for failed bound checks in assignments

template assignCheckDefaultFail*(body) =
body

template assignCheckFail*(body) =
## if `assignCheckBreakpoint` is declared, calls it,
## otherwise falls back to `body`
when declared(assignCheckBreakpoint):
assignCheckBreakpoint(body)
else:
body

template assignCheckEqual*(a, b): untyped =
## template for equality checks in assignments
if a != b:
raise newException(AssignEqualityError, "expected " & astToStr(b) & ", got " & astToStr(a))
assignCheckFail:
raise newException(AssignEqualityError, "expected " & astToStr(b) & ", got " & astToStr(a))

template assignCheckNotEqual*(a, b): untyped =
## template for non-equality checks in assignments
if a == b:
raise newException(AssignEqualityError, "did not expect " & astToStr(b) & ", got " & astToStr(a))
assignCheckFail:
raise newException(AssignEqualityError, "did not expect " & astToStr(b) & ", got " & astToStr(a))

template assignCheckType*(a, b): untyped =
## template for type checks in assignments
Expand All @@ -43,32 +56,38 @@ template assignCheckNotType*(a, b): untyped =
template assignCheckContains*(a, b): untyped =
## template for equality checks in assignments
if a notin b:
raise newException(AssignContainsError, "expected " & astToStr(a) & " to be in " & astToStr(b))
assignCheckFail:
raise newException(AssignContainsError, "expected " & astToStr(a) & " to be in " & astToStr(b))

template assignCheckNotContains*(a, b): untyped =
## template for non-equality checks in assignments
if a in b:
raise newException(AssignContainsError, "did not expect " & astToStr(a) & " to be in " & astToStr(b))
assignCheckFail:
raise newException(AssignContainsError, "did not expect " & astToStr(a) & " to be in " & astToStr(b))

template assignCheckLess*(a, b): untyped =
## template for non-equality checks in assignments
if not (a < b):
raise newException(AssignBoundError, "expected " & astToStr(a) & " to be less than " & astToStr(b))
assignCheckFail:
raise newException(AssignBoundError, "expected " & astToStr(a) & " to be less than " & astToStr(b))

template assignCheckLessEqual*(a, b): untyped =
## template for non-equality checks in assignments
if not (a <= b):
raise newException(AssignBoundError, "expected " & astToStr(a) & " to be less than or equal to " & astToStr(b))
assignCheckFail:
raise newException(AssignBoundError, "expected " & astToStr(a) & " to be less than or equal to " & astToStr(b))

template assignCheckGreater*(a, b): untyped =
## template for non-equality checks in assignments
if not (a > b):
raise newException(AssignBoundError, "expected " & astToStr(a) & " to be greater than " & astToStr(b))
assignCheckFail:
raise newException(AssignBoundError, "expected " & astToStr(a) & " to be greater than " & astToStr(b))

template assignCheckGreaterEqual*(a, b): untyped =
## template for non-equality checks in assignments
if not (a >= b):
raise newException(AssignBoundError, "expected " & astToStr(a) & " to be greater than or equal to " & astToStr(b))
assignCheckFail:
raise newException(AssignBoundError, "expected " & astToStr(a) & " to be greater than or equal to " & astToStr(b))

template openAssign*(lhs, rhs: NimNode, ak: AssignKind = akLet): NimNode =
## Creates a node that calls an open symbol `assign` with `lhs` and `rhs`.
Expand Down Expand Up @@ -205,7 +224,8 @@ when not defined(assignsDisableOptionAssign):
template assignCheckOption*(a): untyped =
## template for equality checks in assignments
if not a.isSome:
raise newException(AssignOptionError, "option " & astToStr(a) & " was not Some")
assignCheckFail: # this will generate `break` for `tap`
raise newException(AssignOptionError, "option " & astToStr(a) & " was not Some")

macro assign*[T](lhs; rhs: Option[T], kind: static AssignKind = akLet): untyped =
## The library's builtin overload of `assign` for `Option[T]`.
Expand Down Expand Up @@ -241,7 +261,7 @@ template implementAssign*(T; body) {.dirty.} =
LinkedList[int](leaf: 2, next:
LinkedList[int](leaf: 3, next: nil)))

import ./syntax
import assigns/syntax
x | [y | [z | _]] := a
doAssert (x, y, z) == (1, 2, 3)
macro assign(lhs; rhs: T, kind: static AssignKind): untyped =
Expand Down
62 changes: 43 additions & 19 deletions src/assigns/syntax.nim
Original file line number Diff line number Diff line change
Expand Up @@ -83,14 +83,13 @@ template `:=?`*(a, b, body): untyped =
## let a = some(3)
## some(n) :=? a:
## doAssert n == 3
var assignFinished = false
try:
a := b
assignFinished = true
body
except AssignError:
if assignFinished:
raise
block match:
template assignCheckBreakpoint(checkBody) {.redefine, used.} =
break match
`a` := `b`
template assignCheckBreakpoint(checkBody) {.redefine, used.} =
checkBody
`body`

macro `:=?`*(a, b, body, elseBranch): untyped =
## Executes `body` if ``a ::= b`` doesn't give a runtime error,
Expand All @@ -107,17 +106,42 @@ macro `:=?`*(a, b, body, elseBranch): untyped =
## else:
## doAssert false
let elseExpr = if elseBranch.kind == nnkElse: elseBranch[0] else: elseBranch
result = quote:
var assignFinished = false
try:
`a` := `b`
assignFinished = true
`body`
except AssignError:
if assignFinished:
raise
else:
`elseExpr`
when not defined(assignsMatchBreakpoint):
result = quote:
var assignFinished = false
try:
`a` := `b`
assignFinished = true
`body`
except AssignError:
if assignFinished:
raise
else:
`elseExpr`
else:
result = quote:
const isExpr = compiles:
let x = `body`
when isExpr:
var res: typeof(`body`)
block match:
block success:
template assignCheckBreakpoint(checkBody) {.redefine, used.} =
break success
`a` := `b`
template assignCheckBreakpoint(checkBody) {.redefine, used.} =
checkBody
when isExpr:
res = `body`
else:
`body`
break match
when isExpr:
res = `elseExpr`
else:
`elseExpr`
when isExpr:
res

macro unpackArgs*(args, routine): untyped =
## Injects unpacking assignments into the body of a given routine.
Expand Down
61 changes: 44 additions & 17 deletions src/assigns/tap.nim
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,25 @@ proc lhsToVal(n: NimNode, context = None): NimNode =
else:
result = nil

proc transformTap(e: NimNode, body, elseBody: NimNode): NimNode =
proc breakingBreakpoint(label: NimNode): NimNode =
# template assignCheckBreakpoint(body) = break tapSuccess
newProc(
name = ident"assignCheckBreakpoint",
params = [newEmptyNode(), newIdentDefs(ident"body", ident"untyped")],
body = newTree(nnkBreakStmt, label),
procType = nnkTemplateDef,
pragmas = newTree(nnkPragma, ident"redefine", ident"used"))

proc defaultBreakpoint(): NimNode =
# template assignCheckBreakpoint(body) = body
newProc(
name = ident"assignCheckBreakpoint",
params = [newEmptyNode(), newIdentDefs(ident"body", ident"untyped")],
body = ident"body",
procType = nnkTemplateDef,
pragmas = newTree(nnkPragma, ident"redefine", ident"used"))

proc transformTap(e: NimNode, body, elseBody, successLabel: NimNode): NimNode =
if e.kind in nnkCallKinds and e[0].eqIdent"in" and e.len == 3:
let forVal = e[1]
let forValSimple = trySimpleForVar(forVal)
Expand All @@ -123,42 +141,44 @@ proc transformTap(e: NimNode, body, elseBody: NimNode): NimNode =
result.add(body)
elif e.kind in nnkCallKinds and e[0].eqIdent"result" and e.len == 2:
var val = e[1]
if val.kind == nnkAsgn:
if val.kind in {nnkAsgn, nnkExprEqExpr}:
let a = val[0]
let b = val[1]
val = newNimNode(nnkInfix, val)
val.add(ident":=")
val.add(a)
val.add(b)
if val.kind in nnkCallKinds and (val[0].eqIdent":=" or val[0].eqIdent":=?"):
if val.kind in nnkCallKinds and (val[0].eqIdent":=" or val[0].eqIdent":=?" or val[0].eqIdent"=?"):
let lhsVal = lhsToVal(val[1])
if lhsVal.isNil:
error("cannot get result value from " & val[1].repr, val[1])
else:
val[1] = newTree(nnkVarTy, val[1])
result = newStmtList(transformTap(val, body, elseBody), lhsVal)
result = newStmtList(transformTap(val, body, elseBody, successLabel), lhsVal)
else:
result = newStmtList(body, val)
elif e.kind in nnkCallKinds and e[0].eqIdent":=?" and e.len == 3:
result = copy e
result.add body
if not elseBody.isNil:
result.add elseBody
elif e.kind in nnkCallKinds and e[0].eqIdent"filter" and e.len == 2:
result = newNimNode(nnkIfStmt, e)
var branch = newNimNode(nnkElifBranch, e[1])
branch.add(e[1])
branch.add(body)
result.add(branch)
if false: # else: continue
branch = newNimNode(nnkElse, e[1])
branch.add(newNimNode(nnkContinueStmt, e[1]))
result.add branch
elif e.kind == nnkAsgn:
elif e.kind in {nnkAsgn, nnkExprEqExpr}:
result = newNimNode(nnkInfix, e)
result.add(ident":=")
result.add(e[0])
result.add(e[1])
result = newStmtList(result, body)
elif e.kind in nnkCallKinds and (e[0].eqIdent":=?" or e[0].eqIdent"=?") and e.len == 3:
result = newNimNode(nnkInfix, e)
result.add(ident":=")
result.add(e[1])
result.add(e[2])
result = newStmtList(breakingBreakpoint(successLabel), result, defaultBreakpoint(), body)
elif e.kind == nnkStmtList:
result = body
for i in countdown(e.len - 1, 0):
result = transformTap(e[i], result, elseBody, successLabel)
else:
result = newStmtList(e, body)

Expand Down Expand Up @@ -193,15 +213,22 @@ proc tapImpl(nodes: NimNode): NimNode =
elseBody.insert(0, value)
dec finalIndex
value = nodes[finalIndex]
let topLabel = genSym(nskLabel, "tap")
let successLabel = if elseBody.isNil: topLabel else: genSym(nskLabel, "tapSuccess")
result = value
for i in countdown(finalIndex - 1, 0):
result = transformTap(nodes[i], result, elseBody)
result = transformTap(nodes[i], result, elseBody, successLabel)
if not elseBody.isNil:
result = newBlockStmt(successLabel,
newStmtList(
result,
newTree(nnkBreakStmt, topLabel)))
result = newStmtList(result, elseBody)
result = newBlockStmt(topLabel, result)
if exceptBranches.len != 0 or not finallyBody.isNil:
result = newTree(nnkTryStmt, result)
for x in exceptBranches: result.add(x)
if not finallyBody.isNil: result.add(finallyBody)
let label = genSym(nskLabel, "tap")
result = newBlockStmt(label, result)

macro tap*(nodes: varargs[untyped]): untyped =
result = tapImpl(nodes)
35 changes: 35 additions & 0 deletions tests/test_naive_match.nim
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,38 @@ test "naive match works":
fizzbuzz(4) == "4"
fizzbuzz(5) == "Buzz"
fizzbuzz(15) == "FizzBuzz"

proc flatMap[T](x: Option[Option[T]]): Option[T] =
match x:
of Some(Some(val)):
result = some(val)
else:
result = none(T)

when not defined(assignsMatchBreakpoint):
proc flatMapRec[T](x: Option[T]): auto =
match x:
of Some(val):
when val is Option:
result = flatMapRec(val)
else:
result = some(val)
else:
result = none(typeof(flatMapRec(x).get))

check:
flatMap(some some 3) == some(3)
flatMap(some none int) == none(int)
flatMap(none(Option[int])) == none(int)
flatMap(some some some 3) == some(some 3)
flatMap(some some none int) == some none(int)
flatMap(some none(Option[int])) == none(Option[int])

when not defined(assignsMatchBreakpoint):
check:
flatMapRec(some some 3) == some(3)
flatMapRec(some none int) == none(int)
flatMapRec(none(int)) == none(int)
flatMapRec(some some some 3) == some(3)
flatMapRec(some some none int) == none(int)
flatMapRec(some none(Option[int])) == none(int)
9 changes: 8 additions & 1 deletion tests/test_tap.nim
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ test "basic test":
s[i] = i + 1
check x == @[1, 2, 3, 4, 5]
var s: seq[int]
tap a := 5, i in 1 .. a, filter i mod 2 != 0:
tap a = 5, i in 1 .. a, filter i mod 2 != 0:
s.add(i)
check s == @[1, 3, 5]

Expand All @@ -35,3 +35,10 @@ test "matching":
else:
branch = 2
check branch == 2
branch = 0
tap some(a) =? y:
branch = 1
check a == 5
else:
branch = 2
check branch == 2

0 comments on commit 8122d96

Please sign in to comment.