# experimental coroutine / generator implementation # this is different from tasks, which have private rs AND ds # coroutines are represented by # - a return stack (current state) # - a cr switcher xt # - a boot xt # since cr's are quite meaninless without circular # refs, the declaration is 2pass: # declare the switcher xt using 'cr' or 'gen' # start the coroutine using the 'start' word # the body is just an ordinary xt. # if a coroutine is invoked from the interpreter, # the only way to yield control back to the interpreter # is by calling 'cr-quit'. # recursion is not allowed for coroutines. a cr # calling itself has no effect. # recursion of body xts is allowed, since it is # nothing more than ordinary recursion. # these are symmetric coroutines: control is passed # directly to another routine upon invocation. # (generators are a special form and require different # call sequence. see below.) # the 'master' coroutine (console) can be invoked using cr-quit provide :cr () variable! generators # generator stack () variable! master # empty master coroutine state variable current # current coroutine master current ! # set master to be current # transfer control to coroutine : transfer 1 pack swapreturn current @ ! # swap out current and save ( ) ( cr.body )R r current ! # set new current r> @> swapreturn drop ;; # get new current and swap in # reset coroutine / generator state : halt () generators ! # kill generator stack master transfer ;; # exit to master (interpreter) # raw threaded code for coroutine loop create mainloop ] begin r try execute recover drop "COROUTINE: " . last-error . .n halt endtry again [ : make-stack mainloop 2 pack ; # create initial coroutine stack from xt "( xt -- rs )\tConvert xt to coroutine rs." , : link-stack link-buffer () , ; : link-coroutine link-stack does> pass transfer [ : coroutine read link-coroutine ; "Create a (code-less) coroutine switcher word with an empty context. Parses name." , # generators. # like coroutines, only they can return to caller using gen-yield # for generators : yield generators pop transfer ;; # transfer control to previous generator : call current @ generators push transfer ;; # call a generator : link-generator link-stack does> pass call [ : generator read link-generator ; : _ postpone yield ; immediate # small shortcut # starting: # a coroutine/generator, represented by an xt that is bound # to the task struct (return stack) can be initialized using the # 'start' word # restarting and all should be done by the user. : start >r make-stack r> xt>body ! ; "( xt.code xt.cr -- )\tInitialize a coroutine/generator xt.cr with xt.code" , # it is possible to use 'anonymous' coroutines/generators # by using 'make-stack' 'call' and 'transfer' # i.e. : minimalistic multitasker () variable! tasks # task list defer handle # task handler : keep tasks queue ; # keep task # public interface : party tasks @> () tasks ! # get tasks { ' keep is handle # set default handler >r rsp call r> # call coroutine handle } for-each ; # handle it : spawn make-stack tasks push ; "( xt -- )\tAdd xt to stack of generators." , : kill try tasks pop drop recover drop endtry ; "( -- )\tKill last added generator." , : stop ' drop is handle yield ; "( -- )\tStop this task." , : killall () tasks ! ; # what about going the next step: redesign tasks # see paul graham's "on lisp", chapter # a task is a RS/DS pair + wait expression + priority