lua-users home
lua-l archive

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


It was thus said that the Great Gavin Holt once stated:
> Hi,
> 
> This is my first post, please be gentle. I am trying to make a simple
> imitation of AWK in Lua 5.1 using a PC.

  [ snip ]
 
> I would be grateful for some advice about how to manage the ORS. You
> will see that for the last source line I avoid appending the ORS. However,
> if the last line doesn't generate any output I can't get back the previously
> output ORS!
> 
  You could try the following (simplified to make it easier to follow):

        awkENV.ORS = "" -- initially nothing

        for _,pattern i pairs(instructions) do
          if pattern[1](...) then
            local output = pattern[2](...)
            if output then
              -- write output
              -- write awkENV.ORS
              awkENV.ORS = instructions.ORS or "\n" -- set ORS for next
interation
            end
          end
        end

  You always send the ORS, but the first time through, it's an empty string.
On each subsequent pass, it's set to the value you want.  Yes, it's an extra
assignment on each pass, but it avoids quite a bit of tricky logic, so I
think it's a win overall.

> For a generalized approach I would also value some advice about creating
> an iterator to process a line at a time, while preserving the ability to  
> send a string or table or file name.

  An interator is a function that returns a function and some state data.
Something like:

function next_record(source,separator)
  separator = separator or "\n"
  
  -- -----------------------------------------------------------------------
  -- our state is the source table; var is the record count.  We can't just
  -- use next() because the traveral of keys is unordered, even for
  -- numerical indicies.  So let's just do it ourselves.
  -- -----------------------------------------------------------------------
  
  local next_from_table(state,var)
    var = var + 1
    if state[var] then
      return var,state[var]
    end
  end
  
  -- -----------------------------------------------------------------------
  -- Here, our state is a function that returns the next record, and again,
  -- var is just the record count.
  -- -----------------------------------------------------------------------
  
  local next_from_file_or_string(state,var)
    var = var + 1
    local rec = state()
    if rec then
      return var,state()
    end
  end
  
  -- -----------------------------------------------------------------------
  -- This is used to handle error cases---there's nothing to iterate, so it
  -- just exits.
  -- -----------------------------------------------------------------------
  
  local next_nothing()
    return nil
  end
  
  -- ---------------------------------------------
  -- See section 2.4.5 (Lua 5.1)
  --             3.3.5 (Lua 5.2)
  --             3.3.5 (Lua 5.3)
  -- for more information about iterators
  -- ---------------------------------------------
  
  if type(source) == 'table' then
      return next_from_table,source,0
  
  -- ----------------------------------------------------------------------
  -- For a string, if we can open it, treat it as a file.  If the input
  -- separator is "\n", use io.lines() for state (a function that will
  -- return the next line); otherwise, read the entire input, and use
  -- str:gmatch() (a function that will return the next match) as the state. 
  -- If we can't open it, assume a non-filename, and use str:gmatch() as the
  -- state.
  -- -----------------------------------------------------------------------
  
  elseif type(source) == 'string' then
    if file_exists(source) then
      if separator == '\n' then
        return next_from_file_or_string,io.lines(source),0
      else
        local f = io.open(source,"r")
        if f then
          local d = f:read("*a")
          f:close()
          return next_from_file_or_string,d:gmatch("[^" .. separator .. "]+"),0
        else
          return next_nothing
        end
      end
    end
    
    return next_from_file_or_string,source:gmatch("[^" .. separator .. "]+"),0
    end
  
  -- ---------------------------------
  -- For any other source, just bail
  -- ---------------------------------
  
  else
    return next_nothing
  end
end

  The code is untested, but it *should* work (or be close enough to get it
working).  It's to be used like:

	-- source can be a table, a filename or a string
	
	for recnum,record in next_record(source,"\n") do
	  ...
	end
	
  -spc (Hope this helps some)