local z=string.find local e=string.sub local K=string.match local D=require("\112\108\046\117\116\105\108\115")local O=D.unpack local n,t,b=pairs,type,tonumber local P=D.patterns local A=io local c={}function c.alltokens(K,D,O)local n=K()local t=1 return function()while n do local b,P=z(n,D,t)if b then t=P+1 local z=e(n,b,P)if O then z=O(z)end return z else n=K()t=1 end end return nil end end local f=c.alltokens function c.create_getter(z)if z then if t(z)=="\115\116\114\105\110\103"then local e=D.split(z,"\n")local K,O=0,#e return function()K=K+1 if K>O then return nil end return e[K]end else if not z.read then error("\110\111\116 \097 \102\105\108\101-\108\105\107\101 \111\098\106\101\099\116")end return function()return z:read()end end else return A.read end end function c.numbers(z)return f(c.create_getter(z),"\040"..(P.FLOAT.."\041"),b)end function c.words(z)return f(c.create_getter(z),"\037\119\043")end local function a(z,...)local e={...}for K=1,#e,1 do local D=b(e[K])if D==nil then if not z then return nil,e[K]end else e[K]=D end end return e end function c.fields(z,e,b,P)local A local f local U=c.create_getter(b)local T=P and P.no_fail local p=P and P.no_convert if not e or e==" "then e="\037\115"A="\037\115\043"f="\037\115\042"else A=e f=""end local i=0 if t(z)=="\116\097\098\108\101"then for z,e in n(z)do if e>i then i=e end end else i=z z={}for e=1,i,1 do z[#z+1]=e end end local B="\091\094"..(e.."\093\042")local X=1 for e=1,i,1 do if z[X]==e then X=X+1 f=f..("\040"..(B.."\041"))else f=f..B end if e<i then f=f..A end end local F=1 return function()local z,e,n repeat z=U()F=F+1 if not z then return nil end if p then e={K(z,f)}else e,n=a(T,K(z,f))if not e then D.quit("\108\105\110\101 "..(F-1 ..("\058 \099\097\110\110\111\116 \099\111\110\118\101\114\116 \'"..(n.."\' \116\111 \110\117\109\098\101\114"))))end end until#e>0 return O(e)end end return c
--- Iterators for extracting words or numbers from an input source.
--
--    require 'pl'
--    local total,n = seq.sum(input.numbers())
--    print('average',total/n)
--
-- _source_ is defined as a string or a file-like object (i.e. has a read() method which returns the next line)
--
-- See @{06-data.md.Reading_Unstructured_Text_Data|here}
--
-- Dependencies: `pl.utils`
-- @module pl.input
local strfind = string.find
local strsub = string.sub
local strmatch = string.match
local utils = require 'pl.utils'
local unpack = utils.unpack
local pairs,type,tonumber = pairs,type,tonumber
local patterns = utils.patterns
local io = io

local input = {}

--- create an iterator over all tokens.
-- based on allwords from PiL, 7.1
-- @func getter any function that returns a line of text
-- @string pattern
-- @string[opt] fn  Optionally can pass a function to process each token as it's found.
-- @return an iterator
function input.alltokens (getter,pattern,fn)
    local line = getter()  -- current line
    local pos = 1           -- current position in the line
    return function ()      -- iterator function
        while line do         -- repeat while there are lines
          local s, e = strfind(line, pattern, pos)
          if s then           -- found a word?
            pos = e + 1       -- next position is after this token
            local res = strsub(line, s, e)     -- return the token
            if fn then res = fn(res) end
            return res
          else
            line = getter()  -- token not found; try next line
            pos = 1           -- restart from first position
          end
        end
        return nil            -- no more lines: end of traversal
   end
end
local alltokens = input.alltokens

-- question: shd this _split_ a string containing line feeds?

--- create a function which grabs the next value from a source. If the source is a string, then the getter
-- will return the string and thereafter return nil. If not specified then the source is assumed to be stdin.
-- @param f a string or a file-like object (i.e. has a read() method which returns the next line)
-- @return a getter function
function input.create_getter(f)
    if f then
        if type(f) == 'string' then
            local ls = utils.split(f,'\n')
            local i,n = 0,#ls
            return function()
                i = i + 1
                if i > n then return nil end
                return ls[i]
            end
        else
            -- anything that supports the read() method!
            if not f.read then error('not a file-like object') end
            return function() return f:read() end
        end
    else
        return io.read  -- i.e. just read from stdin
    end
end

--- generate a sequence of numbers from a source.
-- @param f A source
-- @return An iterator
function input.numbers(f)
    return alltokens(input.create_getter(f),
        '('..patterns.FLOAT..')',tonumber)
end

--- generate a sequence of words from a source.
-- @param f A source
-- @return An iterator
function input.words(f)
    return alltokens(input.create_getter(f),"%w+")
end

local function apply_tonumber (no_fail,...)
    local args = {...}
    for i = 1,#args do
        local n = tonumber(args[i])
        if  n == nil then
            if not no_fail then return nil,args[i] end
        else
            args[i] = n
        end
    end
    return args
end

--- parse an input source into fields.
-- By default, will fail if it cannot convert a field to a number.
-- @param ids a list of field indices, or a maximum field index
-- @string delim delimiter to parse fields (default space)
-- @param f a source @see create_getter
-- @tab opts option table, `{no_fail=true}`
-- @return an iterator with the field values
-- @usage for x,y in fields {2,3} do print(x,y) end -- 2nd and 3rd fields from stdin
function input.fields (ids,delim,f,opts)
  local sep
  local s
  local getter = input.create_getter(f)
  local no_fail = opts and opts.no_fail
  local no_convert = opts and opts.no_convert
  if not delim or delim == ' ' then
      delim = '%s'
      sep = '%s+'
      s = '%s*'
  else
      sep = delim
      s = ''
  end
  local max_id = 0
  if type(ids) == 'table' then
    for i,id in pairs(ids) do
      if id > max_id then max_id = id end
    end
  else
    max_id = ids
    ids = {}
    for i = 1,max_id do ids[#ids+1] = i end
  end
  local pat = '[^'..delim..']*'
  local k = 1
  for i = 1,max_id do
    if ids[k] == i then
      k = k + 1
      s = s..'('..pat..')'
    else
      s = s..pat
    end
    if i < max_id then
      s = s..sep
    end
  end
  local linecount = 1
  return function()
    local line,results,err
    repeat
        line = getter()
        linecount = linecount + 1
        if not line then return nil end
        if no_convert then
            results = {strmatch(line,s)}
        else
            results,err = apply_tonumber(no_fail,strmatch(line,s))
            if not results then
                utils.quit("line "..(linecount-1)..": cannot convert '"..err.."' to number")
            end
        end
    until #results > 0
    return unpack(results)
  end
end

return input

