Yieldable For Loops |
|
for
loop is not allowed to yield; it makes it messy to write simple responder loops where the iterator might be, for example, an asychronous input function.
Instead of just being able to write:
for msg in eachmsg() do -- handle msg end -- end of messages, clean up
you need:
repeat local msg = getmsg() if msg == nil then break end -- handle msg until false -- end of messages, clean up
However, it is very simple to get the first code sample to work. It is only necessary to split the TFORLOOP
VM operation into two opcodes. The first one sets up an ordinary Lua call and then falls into the OP_CALL implementation. The following op code does a conditional move and branch based on the first value returned by the first op code.
Some very rough testing seems to show that performance is actually slightly improved by this change, although the results are not definitive. I suppose that is because the VM can handle the call without recursing, making up for the overhead of an extra opcode.
At any rate, the patch is at [dead link].
(Also available here[1], taken from Google Code Search cache[2].)
Here's a test program. The key function here is responder
, which shows the yieldable for
in action. Test output follows the code
local yield, resume, create, costatus = coroutine.yield, coroutine.resume, coroutine.create, coroutine.status local function input(prompt) local inf, outf = io.stdin, io.stderr return function() outf:write(prompt," ") return inf:read() end end -- These could be quite a bit more complex function eachmsg() return yield end -- This isn't actually used in this demo, but it could be :) getmsg = coroutine.yield -- This would probably be more complicated in a real app, too. function responder(name) local n = 0 print(name.." is born!") for msg in eachmsg() do n = n + 1 if msg == "goodbye" then break else print(name.." heard "..msg) end end print(name.." departs this vale of tears, after listening to "..n.." utterances") end function driver() local cmd = {} local kids = {} -- the commands we understand function cmd.quit() print "Exiting!" for _, kid in pairs(kids) do resume(kid) end return false end function cmd.kill(arg) local _, _, who = string.find(arg, "(%w+)") if not who then return "Kill who?" elseif not kids[who] then return who.."? I don't know any "..who else local status, result = resume(kids[who]) kids[who] = nil if status then return else return result end end end function cmd.spawn(arg) local _, _, who = string.find(arg, "(%w+)") if not who then return "Spawn who?" elseif kids[who] then return who .. " already exists" else kids[who] = create(responder) local status, result = resume(kids[who], who) if not status then kids[who] = nil return result end end end function cmd.list() print"Currently active:" for k in pairs(kids) do print(" "..k) end end -- main loop starts here -- for msg in input("->") do local _, _, verb, rest = string.find(msg, "%s*(%w+)%s*(.*)") if cmd[verb] then local res = cmd[verb](rest) if res then print(res) elseif res == false then return end elseif kids[verb] then local status, result = coroutine.resume(kids[verb], rest) if not status then print(verb.." exited with error "..result) kids[verb] = nil elseif coroutine.status(kids[verb]) ~= "suspended" then print(verb.." decided to go away") kids[verb] = nil end else print "I don't understand what you're talking about" end end end
Sample run:
> driver() -> list Currently active: -> spawn bob bob is born! -> spawn sally sally is born! -> bob hi bob heard hi -> sally hi sally heard hi -> bob meet sally bob heard meet sally -> fred hi I don't understand what you're talking about -> spawn fred fred is born! -> list Currently active: sally fred bob -> fred how are you fred heard how are you -> fred goodbye fred departs this vale of tears, after listening to 2 utterances fred decided to go away -> kill bob bob departs this vale of tears, after listening to 2 utterances -> sally ? sally heard ? -> spawn sue sue is born! -> quit Exiting! sally departs this vale of tears, after listening to 2 utterances sue departs this vale of tears, after listening to 0 utterances
-- RiciLake