# this is a copy paste implementation of github.com/krux02/ast_pattern_matching # Please provide bugfixes upstream first before adding them here. import macros, strutils, tables export macros when isMainModule: template debug(args: varargs[untyped]): untyped = echo args else: template debug(args: varargs[untyped]): untyped = discard const nnkIntLiterals* = nnkCharLit..nnkUInt64Lit nnkStringLiterals* = nnkStrLit..nnkTripleStrLit nnkFloatLiterals* = nnkFloatLit..nnkFloat64Lit proc newLit[T: enum](arg: T): NimNode = newIdentNode($arg) proc newLit[T](arg: set[T]): NimNode = ## does not work for the empty sets result = nnkCurly.newTree for x in arg: result.add newLit(x) type SomeFloat = float | float32 | float64 proc len[T](arg: set[T]): int = card(arg) type MatchingErrorKind* = enum NoError WrongKindLength WrongKindValue WrongIdent WrongCustomCondition MatchingError = object node*: NimNode expectedKind*: set[NimNodeKind] case kind*: MatchingErrorKind of NoError: discard of WrongKindLength: expectedLength*: int of WrongKindValue: expectedValue*: NimNode of WrongIdent, WrongCustomCondition: strVal*: string proc `$`*(arg: MatchingError): string = let n = arg.node case arg.kind of NoError: "no error" of WrongKindLength: let k = arg.expectedKind let l = arg.expectedLength var msg = "expected " if k.len == 0: msg.add "any node" elif k.len == 1: for el in k: # only one element but there is no index op for sets msg.add $el else: msg.add "a node in" & $k if l >= 0: msg.add " with " & $l & " child(ren)" msg.add ", but got " & $n.kind if l >= 0: msg.add " with " & $n.len & " child(ren)" msg of WrongKindValue: let k = $arg.expectedKind let v = arg.expectedValue.repr var msg = "expected " & k & " with value " & v & " but got " & n.lispRepr if n.kind in {nnkOpenSymChoice, nnkClosedSymChoice}: msg = msg & " (a sym-choice does not have a strVal member, maybe you should match with `ident`)" msg of WrongIdent: let prefix = "expected ident `" & arg.strVal & "` but got " if n.kind in {nnkIdent, nnkSym, nnkOpenSymChoice, nnkClosedSymChoice}: prefix & "`" & n.strVal & "`" else: prefix & $n.kind & " with " & $n.len & " child(ren)" of WrongCustomCondition: "custom condition check failed: " & arg.strVal proc failWithMatchingError*(arg: MatchingError): void {.compileTime, noReturn.} = error($arg, arg.node) proc expectValue(arg: NimNode; value: SomeInteger): void {.compileTime.} = arg.expectKind nnkLiterals if arg.intVal != int(value): error("expected value " & $value & " but got " & arg.repr, arg) proc expectValue(arg: NimNode; value: SomeFloat): void {.compileTime.} = arg.expectKind nnkLiterals if arg.floatVal != float(value): error("expected value " & $value & " but got " & arg.repr, arg) proc expectValue(arg: NimNode; value: string): void {.compileTime.} = arg.expectKind nnkLiterals if arg.strVal != value: error("expected value " & value & " but got " & arg.repr, arg) proc expectValue[T](arg: NimNode; value: pointer): void {.compileTime.} = arg.expectKind nnkLiterals if value != nil: error("Expect Value for pointers works only on `nil` when the argument is a pointer.") arg.expectKind nnkNilLit proc expectIdent(arg: NimNode; strVal: string): void {.compileTime.} = if not arg.eqIdent(strVal): error("Expect ident `" & strVal & "` but got " & arg.repr) proc matchLengthKind*(arg: NimNode; kind: set[NimNodeKind]; length: int): MatchingError {.compileTime.} = let kindFail = not(kind.card == 0 or arg.kind in kind) let lengthFail = not(length < 0 or length == arg.len) if kindFail or lengthFail: result.node = arg result.kind = WrongKindLength result.expectedLength = length result.expectedKind = kind proc matchLengthKind*(arg: NimNode; kind
# Compatible with ranger 1.4.2 through 1.6.*
#
# Automatically change the directory in bash after closing ranger
#
# This is a bash function for .bashrc to automatically change the directory to
# the last visited one after ranger quits.
# To undo the effect of this function, you can type "cd -" to return to the
# original directory.

function ranger-cd {
    tempfile='/tmp/chosendir'
    /usr/bin/ranger --choosedir="$tempfile" "${@:-$(pwd)}"
    test -f "$tempfile" &&
    if [ "$(cat -- "$tempfile")" != "$(echo -n `pwd`)" ]; then
        cd -- "$(cat "$tempfile")"
    fi
    rm -f -- "$tempfile"
}

# This binds Ctrl-O to ranger-cd:
bind '"\C-o":"ranger-cd\C-m"'
mtList` if elseBranch != nil: if outerErrorSym != nil: outerStmtList.add quote do: let `outerErrorSym` = @`errorSymbols` `elseBranch` else: outerStmtList.add elseBranch else: if errorSymbols.len == 1: # there is only one of branch and no else branch # the error message can be very precise here. let errorSym = errorSymbols[0] outerStmtList.add quote do: failWithMatchingError(`errorSym`) else: var patterns: string = "" for i in beginBranches ..< endBranches: let ofBranch = args[i] let pattern = ofBranch[0] patterns.add pattern.repr patterns.add "\n" let patternsLit = newLit(patterns) outerStmtList.add quote do: error("Ast pattern mismatch: got " & `astSym`.lispRepr & "\nbut expected one of:\n" & `patternsLit`, `astSym`) let lengthLit = newLit(localsArrayLen) result = quote do: block `outerBlockLabel`: let `astSym` = `astExpr` var `localsArraySym`: array[`lengthLit`, NimNode] `outerStmtList` debug result.repr proc recursiveNodeVisiting*(arg: NimNode, callback: proc(arg: NimNode): bool) = ## if `callback` returns true, visitor continues to visit the ## children of `arg` otherwise it stops. if callback(arg): for child in arg: recursiveNodeVisiting(child, callback) macro matchAstRecursive*(ast: NimNode; args: varargs[untyped]): untyped = # Does not recurse further on matched nodes. if args[^1].kind == nnkElse: error("Recursive matching with an else branch is pointless.", args[^1]) let visitor = genSym(nskProc, "visitor") let visitorArg = genSym(nskParam, "arg") let visitorStmtList = newStmtList() let matchingSection = genSym(nskLabel, "matchingSection") let localsArraySym = genSym(nskVar, "locals") let branchError = genSym(nskVar, "branchError") var localsArrayLen = 0 for ofBranch in args: ofBranch.expectKind(nnkOfBranch) ofBranch.expectLen(2) let pattern = ofBranch[0] let code = ofBranch[1] code.expectkind(nnkStmtList) let stmtList = newStmtList() let matchingBranch = genSym(nskLabel, "matchingBranch") let numLocalsUsed = generateMatchingCode(visitorArg, pattern, 0, matchingBranch, branchError, localsArraySym, stmtList) localsArrayLen = max(localsArrayLen, numLocalsUsed) stmtList.add code stmtList.add nnkBreakStmt.newTree(matchingSection) visitorStmtList.add quote do: `branchError`.kind = NoError block `matchingBranch`: `stmtList` let resultIdent = ident"result" let visitingProc = bindSym"recursiveNodeVisiting" let lengthLit = newLit(localsArrayLen) result = quote do: proc `visitor`(`visitorArg`: NimNode): bool = block `matchingSection`: var `localsArraySym`: array[`lengthLit`, NimNode] var `branchError`: MatchingError `visitorStmtList` `resultIdent` = true `visitingProc`(`ast`, `visitor`) debug result.repr ################################################################################ ################################# Example Code ################################# ################################################################################ when isMainModule: static: let mykinds = {nnkIdent, nnkCall} macro foo(arg: untyped): untyped = matchAst(arg, matchError): of nnkStmtList(nnkIdent, nnkIdent, nnkIdent): echo(88*88+33*33) of nnkStmtList( _( nnkIdentDefs( ident"a", nnkEmpty, nnkIntLit(intVal = 123) ) ), _, nnkForStmt( nnkIdent(strVal = "i"), nnkInfix, `mysym` @ nnkStmtList ) ): echo "The AST did match!!!" echo "The matched sub tree is the following:" echo mysym.lispRepr #else: # echo "sadly the AST did not match :(" # echo arg.treeRepr # failWithMatchingError(matchError[1]) foo: let a = 123 let b = 342 for i in a ..< b: echo "Hallo", i static: var ast = quote do: type A[T: static[int]] = object ast = ast[0] ast.matchAst(err): # this is a sub ast for this a findAst or something like that is useful of nnkTypeDef(_, nnkGenericParams( nnkIdentDefs( nnkIdent(strVal = "T"), `staticTy`, nnkEmpty )), _): echo "`", staticTy.repr, "` used to be of nnkStaticTy, now it is ", staticTy.kind, " with ", staticTy[0].repr ast = quote do: if cond1: expr1 elif cond2: expr2 else: expr3 ast.matchAst: of {nnkIfExpr, nnkIfStmt}( {nnkElifExpr, nnkElifBranch}(`cond1`, `expr1`), {nnkElifExpr, nnkElifBranch}(`cond2`, `expr2`), {nnkElseExpr, nnkElse}(`expr3`) ): echo "ok" let ast2 = nnkStmtList.newTree( newLit(1) ) ast2.matchAst: of nnkIntLit( 1 ): echo "fail" of nnkStmtList( 1 ): echo "ok" ast = bindSym"[]" ast.matchAst(errors): of nnkClosedSymChoice(strVal = "[]"): echo "fail, this is the wrong syntax, a sym choice does not have a `strVal` member." of ident"[]": echo "ok" const myConst = 123 ast = newLit(123) ast.matchAst: of _(intVal = myConst): echo "ok" macro testRecCase(ast: untyped): untyped = ast.matchAstRecursive: of nnkIdentDefs(`a`,`b`,`c`): echo "got ident defs a: ", a.repr, " b: ", b.repr, " c: ", c.repr of ident"m": echo "got the ident m" testRecCase: type Obj[T] {.inheritable.} = object name: string case isFat: bool of true: m: array[100_000, T] of false: m: array[10, T] macro testIfCondition(ast: untyped): untyped = let literals = nnkBracket.newTree ast.matchAstRecursive: of `intLit` @ nnkIntLit |= intLit.intVal > 5: literals.add intLit let literals2 = quote do: [6,7,8,9] doAssert literals2 == literals testIfCondition([1,6,2,7,3,8,4,9,5,0,"123"])