lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]

On 05/02/15 06:35 PM, Sean Conner wrote:
  Some background:  At work, I'm in the process of writing some regression
tests for our product.  We have a text file describing all our test cases. 
It's a tab-delimeted file---each line describes a test, and each field on a
line is a value to use.  As an example:

#id	use-a	use-b	datum1	datum2
1.0001	false	false	polyphyletically	pursue
1.0002	false	true	penchute	dentata
1.0003	true	false	advectitious	flinkite
1.0004	true	true	hansel	galvanocontractility

  I would like to use a construct such as:

	for testcase in next_test_cases("master-list") do
	  -- process test case
	  -- here, just print out the data

  So, I need an interator.  Not a problem.  But here is my question. 
According to the Lua documenation:

	A for statement like

	     for var_1, ···, var_n in explist do block end

	is equivalent to the code: 

	       local f, s, var = explist
	       while true do
	         local var_1, ···, var_n = f(s, var)
	         if var_1 == nil then break end
	         var = var_1

	Note the following:

	* explist is evaluated only once. Its results are an iterator
	  function, a state, and an initial value for the first iterator
	* f, s, and var are invisible variables. The names are here for
	  explanatory purposes only.

	* You can use break to exit a for loop.

	* The loop variables var_i are local to the loop; you cannot use
	  their values after the for ends. If you need these values, then
	  assign them to other variables before breaking or exiting the

  Okay, so I write my iterator:

	function next_test_case(cases)
	  local nextline,state,line = io.lines(cases)
	  return function()
	    line = nextline(state,line)
	    if not line then
	      return nil
	    local nextfield,fs,fv = line:gmatch("[^\t]+")
	    local res  = {}     = nextfield(fs,fv)
	    res.use_a  = nextfield(fs,
	    res.use_b  = nextfield(fs,res.use_a)
	    res.datum1 = nextfield(fs,res.use_b)
	    res.datum2 = nextfield(fs,res.datum1)
	    return res

  But in playing around, I notice that io.lines() and string.gmatch()
actually only return a function; they do *not* return a state and an initial
variable value.  Assuming that, I can rewrite my iterator as:

	function next_test_case(cases)
	  local nextline = io.lines(cases)

	  return function()
	    local line = nextline()
	    if not line then
	      return nil
	    local nextfield = line:gmatch("[^\t]+") 
	    return {  
	      id     = nextfield(),
	      use_a  = nextfield(),
	      use_b  = nextfield(),
	      datum1 = nextfield(),
	      datum2 = nextfield()

  It's (in my opinion) cleaner and easier to read.  Now my question:  can I
rely upon this behavior of io.lines() and string.gmatch() to only return a
single function?  I tested it on Lua 5.1, 5.2, 5.3 and LuaJIT, and in each
case, only a function was returned.  Am I relying upon an implementation
detail?  Should I use the first version, where I pass in the state and value
each time, to be "safe"?


According to the manual:

"Returns an *iterator function*"

I believe it is safe to assume "iterator function" means it returns only a function that returns the next match or nil at the end. (If it said just "an iterator", THEN it wouldn't be safe to assume so)

Same goes for io.lines and a few others:

See also:

"The generic for statement works over functions, called iterators. On each iteration, the iterator function is called to produce a new value, stopping when this new value is nil."
"explist is evaluated only once. Its results are an *iterator function*, a state, and an initial value for the first iterator variable."

The manual for ipairs also talks about an iterator function:
"Returns three values (an *iterator function*, the table t, and 0)"
Disclaimer: these emails are public and can be accessed from <TODO: get a non-DHCP IP and put it here>. If you do not agree with this, DO NOT REPLY.