( iteration control stack We create two new stacks - a small stack to hold the "current" value of the loop, or the "i" stack, and a larger stack to hold any extra state, as well as the cp of a word that moves to the next value, which we call the "next" stack. With these two new stacks, we can create a generic loop construct for iterating over streaming values. Not only that, but those values can be arbitrarily filtered and transformed simply by pushing a new value onto the iter-next stack which calls out to the previous one. ) uservar itop 4 cells userallot { userhere @ } const itop-init uservar nexttop 16 cells userallot { userhere @ } const nexttop-init ' task-init :chain >r itop-init itop r@ !far nexttop-init nexttop r@ !far r ( i -- v ) POP AX SHL AX 1 # MOV BX @[ SS: r MOV BX @[ SS: r@ @] INC BX INC BX MOV @[ SS: r ( v -- ) POP AX MOV BX @[ SS: r@ @] DEC BX DEC BX MOV @[ SS: i nexttop :push >next : r ; { ( Because we dereference pointers on the return stack, we must run this from the caller's segment. Copy the definition into the host segment. ) : EACH_ r ; : each ' EACH_ , here 0 , ; immediate : continue ' GOTO_ , dup cell - , ; immediate : more ['] continue here swap ! ; immediate :timm each t, EACH_ patchpt ; : CONTINUE t, GOTO_ dup cell - w>t ; :timm continue CONTINUE ; :timm more CONTINUE patch!t ; } 0 var, cancelled : cancel 1 cancelled ! :| nextdrop get-next if idrop then 0 cancelled ! 0 swap |; >next ; : nothing :| 0 1 |; >next ; : single >i :| nextdrop :| idrop 0 1 |; >next 1 1 |; >next ; : times ( n -- ) >i :| i 1 then 1 |; >next ; : links ( p -- ) dup if >i :| i 1 then 1 |; >next else nothing then ; : +for? ( n -- f ) i 1 then ; : for ( start lim -- ) >next 1- >i :| 1 +for? 2 |; >next ; : for+ ( start lim inc -- ) >next >next 1 nextpeek - >i :| 2 nextpeek +for? 3 |; >next ; ( Mapping is complex because iterators use the i-stack to store their own state - when asking for the next value, we must restore the previous value. However, we do not want to touch the i-stack until the iterator has run, in case it is an empty iterator with no values. We want to handle this using a minimum of next-stack space; ideally never more than two slots. The user defines a mapping iterator by defining a word or no-name that passes an anonymous function to "map" and returning. "map" must assume that the current i value sits below the mapper on the next-stack and the iterator to remap sits below that. "initial-map" assumes a mapper is below it on the stack with no initial i value, and the iterator to remap sits below that. It queries the iterator to ensure it's not empty, and then sets up the environment to allow the mapper to continue working. ) : initial-map ( -- f c ) nextdrop next i >next >next get-next drop drop next >next ( remove the fake iterator ) 2 + 1 swap ( add mapper to count and return success ) else drop drop 0 0 then ; : map ( cp -- f c ) i ( cpnext cp: restore i to previous value ) get-next if ( cpnext cp c ) >rot i >next i >next 2 + 1 swap else >rot drop drop 0 swap then ; : >map ( mapper -- ) >next ' initial-map >next ; : filter ( cp -- f c ) >r next 1 swap 1+ rdrop return then ( filter hit -- f c ) drop ( cpnext ) else ( no more items ) swap drop 0 swap rdrop return then again ; : .all each i [ key 0 lit ] + draw-char more ; : doubled :| ' 2* map |; >map ;