diff --git a/changelog.md b/changelog.md index 7569bf1332..6edcb5b562 100644 --- a/changelog.md +++ b/changelog.md @@ -50,6 +50,9 @@ - Dot calls combined with explicit generic instantiations can now be written as ``x.y[:z]``. ``x.y[:z]`` that is transformed into ``y[z](x)`` in the parser. - ``func`` is now an alias for ``proc {.noSideEffect.}``. +- In order to make ``for`` loops and iterators more flexible to use Nim now + supports so called "for-loop macros". See + the `manual `_ for more details. ### Language changes diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index 85fe3d7934..f90e5c5f96 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -702,11 +702,46 @@ proc isTrivalStmtExpr(n: PNode): bool = return false result = true +proc handleForLoopMacro(c: PContext; n: PNode): PNode = + let iterExpr = n[^2] + if iterExpr.kind in nkCallKinds: + # we transform + # n := for a, b, c in m(x, y, z): Y + # to + # m(n) + let forLoopStmt = magicsys.getCompilerProc("ForLoopStmt") + if forLoopStmt == nil: return + + let headSymbol = iterExpr[0] + var o: TOverloadIter + var match: PSym = nil + var symx = initOverloadIter(o, c, headSymbol) + while symx != nil: + if symx.kind in {skTemplate, skMacro}: + if symx.typ.len == 2 and symx.typ[1] == forLoopStmt.typ: + if match == nil: + match = symx + else: + localError(n.info, errGenerated, msgKindToString(errAmbiguousCallXYZ) % [ + getProcHeader(match), getProcHeader(symx), $iterExpr]) + symx = nextOverloadIter(o, c, headSymbol) + + if match == nil: return + var callExpr = newNodeI(nkCall, n.info) + callExpr.add newSymNode(match) + callExpr.add n + case match.kind + of skMacro: result = semMacroExpr(c, callExpr, callExpr, match, {}) + of skTemplate: result = semTemplateExpr(c, callExpr, match, {}) + else: result = nil + proc semFor(c: PContext, n: PNode): PNode = - result = n checkMinSonsLen(n, 3) var length = sonsLen(n) + result = handleForLoopMacro(c, n) + if result != nil: return result openScope(c) + result = n n.sons[length-2] = semExprNoDeref(c, n.sons[length-2], {efWantIterator}) var call = n.sons[length-2] if call.kind == nkStmtListExpr and isTrivalStmtExpr(call): @@ -772,7 +807,7 @@ proc typeSectionTypeName(n: PNode): PNode = else: result = n if result.kind != nkSym: illFormedAst(n) - + proc typeSectionLeftSidePass(c: PContext, n: PNode) = # process the symbols on the left side for the whole type section, before diff --git a/lib/system.nim b/lib/system.nim index 1d1b2d2d21..9695278280 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -4172,3 +4172,8 @@ when not defined(js): magic: "Slice".} proc toOpenArray*(x: string; first, last: int): openarray[char] {. magic: "Slice".} + + +type + ForLoopStmt* {.compilerProc.} = object ## special type that marks a macro + ## as a `for-loop macro`:idx: diff --git a/tests/macros/tforloop_macro1.nim b/tests/macros/tforloop_macro1.nim new file mode 100644 index 0000000000..a8f45c7ac4 --- /dev/null +++ b/tests/macros/tforloop_macro1.nim @@ -0,0 +1,44 @@ +discard """ + output: '''0 1 +1 2 +2 3 +0 1 +1 2 +2 3 +0 1 +1 2 +2 3 +3 5''' +""" + +import macros + +macro mymacro(): untyped = + result = newLit([1, 2, 3]) + +for a, b in mymacro(): + echo a, " ", b + +macro enumerate(x: ForLoopStmt): untyped = + expectKind x, nnkForStmt + # we strip off the first for loop variable and use + # it as an integer counter: + result = newStmtList() + result.add newVarStmt(x[0], newLit(0)) + var body = x[^1] + if body.kind != nnkStmtList: + body = newTree(nnkStmtList, body) + body.add newCall(bindSym"inc", x[0]) + var newFor = newTree(nnkForStmt) + for i in 1..x.len-3: + newFor.add x[i] + # transform enumerate(X) to 'X' + newFor.add x[^2][1] + newFor.add body + result.add newFor + +for a, b in enumerate(items([1, 2, 3])): + echo a, " ", b + +for a2, b2 in enumerate([1, 2, 3, 5]): + echo a2, " ", b2