diff --git a/assigns.nimble b/assigns.nimble index 38cc867..dbd1501 100644 --- a/assigns.nimble +++ b/assigns.nimble @@ -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" diff --git a/src/assigns.nim b/src/assigns.nim index e8a8459..e4a0a42 100644 --- a/src/assigns.nim +++ b/src/assigns.nim @@ -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 diff --git a/src/assigns/impl.nim b/src/assigns/impl.nim index 406dcd8..428c294 100644 --- a/src/assigns/impl.nim +++ b/src/assigns/impl.nim @@ -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 @@ -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`. @@ -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]`. @@ -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 = diff --git a/src/assigns/syntax.nim b/src/assigns/syntax.nim index f7bc844..7f6ff9d 100644 --- a/src/assigns/syntax.nim +++ b/src/assigns/syntax.nim @@ -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, @@ -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. diff --git a/src/assigns/tap.nim b/src/assigns/tap.nim index 84ab154..5c8b351 100644 --- a/src/assigns/tap.nim +++ b/src/assigns/tap.nim @@ -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) @@ -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) @@ -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) diff --git a/tests/test_naive_match.nim b/tests/test_naive_match.nim index f5802e1..69524b7 100644 --- a/tests/test_naive_match.nim +++ b/tests/test_naive_match.nim @@ -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) diff --git a/tests/test_tap.nim b/tests/test_tap.nim index 28e28a5..d973651 100644 --- a/tests/test_tap.nim +++ b/tests/test_tap.nim @@ -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] @@ -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