lua-users home
lua-l archive

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


Ram Firestone wrote:
Twice before I have hacked the lua IO library (liolib) to optionally read and write from functions instead of IO descriptors. I did
this so that I could get the IO library to work in a windowing environment. Since I have changed jobs I no longer have this old code
and I am about to hack it again. However before I do I thought I would check to see if there is some better or standardized way of
doing this now. If not, I'll go ahead and hack it again. After doing it twice before it will probably only take me a few hours now
:-P
Also if any lua support guys are listening I was wondering if it might be better to just add this to the library at some point. The
way I hacked it before the library worked exactly the way it used to unless you registered IO functions with it, in which case it
would switch over to using them. I'm not sure if this fits the vision or not but it seems convenient. Just a thought. Ram


It is possible to hijack the IO in the standard DLLs using pure Lua plus a little help from C. Attached is my Lua script that does this (its a re-implementation of the standard lua.c in Lua). All you need to provide is the m.puts, m.gets and m.setmetatable functions.

--
Regards,

Dave Nichols
Match-IT Limited
Tel: 0845 1300 510
Fax: 0845 1300 610
mailto:dave.nichols@make247.co.uk
http://www.make247.co.uk

Email Disclaimer: The contents of this electronic mail message and any attachments (collectively "this message") are confidential, possibly privileged and intended only for its addressee ("the addressee"). If received in error, please delete immediately without disclosing its contents to anyone. Neither the sender nor its management or employees will in any way be responsible for any advice, opinion, conclusion or other information contained in this message or arising from it's disclosure.
--{{{  history

--11/08/06 DCN Created
--05/09/06 DCN Fix results returning problem from _G.console

--}}}
--{{{  description

--provide a console facility for use in Match-IT
--that uses a Clarion window for the UI

--}}}

m = require'match_it'

--{{{  override file: operations for stdin/out/err

local function name(self)
  if self == io.stdin  then return 'stdin'  end
  if self == io.stdout then return 'stdout' end
  if self == io.stderr then return 'stderr' end
  return tostring(self)
end

local inmeta = {}
inmeta.__index    = inmeta

inmeta.close      = function(self) return nil,'attempt to close '..name(self) end  --not allowed to close
inmeta.flush      = function(self) return true end                                 --flush is a no-op
inmeta.setvbuf    = function(self) return true end                                 --setvbuf is a no-op
inmeta.seek       = function(self) return nil,'attempt to seek '..name(self) end   --seek is not allowed
inmeta.__tostring = function(self) return 'file ('..name(self)..')' end
inmeta.lines      = function(self) return function() return m.gets(-1) end end

--{{{  function inmeta.read(self,...)

function inmeta.read(self,...)

  if #{...} == 0 then return m.gets(-1) end  --read to eol

  local results = {}
  local file = {}
  local line, char
  for arg,v in ipairs{...} do

    if type(v) == 'number' then
      results[arg] = m.gets(v)       --read n chars, nb: n=0 means test for eof

    elseif type(v) == 'string' then
      if string.sub(v,1,2) == '*l' then
        results[arg] = m.gets(-1)      --read to eol

      elseif string.sub(v,1,2) == '*a' then
        file = {}
        line = 0
        repeat
          line = line + 1
          file[line] = m.gets(-1)      --read to eol
        until not file[line]
        results[arg] = table.concat(file,'\n')

      elseif string.sub(v,1,2) == '*n' then
        line = ''
        char = m.gets(1)
        while char and (char == '' or char == ' ' or char == '\t') do --skip white space
          char = m.gets(1)
        end
        while char and char ~= '' and char ~= ' ' and char ~= '\t' do --carry on until white space
          line = line..char
          char = m.gets(1)
        end
        results[arg] = tonumber(line)

      else
        assert(false,'bad argument #'..arg..' (invalid format)')

      end
    else
      assert(false,'bad argument #'..arg..' (expected string, got '..type(v)..')')
    end
  end

  return unpack(results)

end

--}}}

m.setmetatable(io.stdin,inmeta)

local outmeta = {}
outmeta.__index    = outmeta

outmeta.close      = function(self) return nil,'attempt to close '..name(self) end --not allowed to close
outmeta.flush      = function(self) return true end                                --flush is a no-op
outmeta.setvbuf    = function(self) return true end                                --setvbuf is a no-op
outmeta.seek       = function(self) return nil,'attempt to seek '..name(self) end  --seek is not allowed
outmeta.__tostring = function(self) return 'file ('..name(self)..')' end

--{{{  function outmeta.write(self,...)

function outmeta.write(self,...)
  for arg,v in ipairs{...} do
    if type(v) == 'number' then
      m.puts(string.format('%.14g',v))
    elseif type(v) == 'string' then
      m.puts(v)
    else
      assert(false,'bad argument #'..arg..' (expected string, got '..type(v)..')')
    end
  end
  return true
end

--}}}

m.setmetatable(io.stdout,outmeta)
m.setmetatable(io.stderr,outmeta)

--}}}

--redefine standard stuff to use file:read/write
--{{{  function _G.print(...)

--print stuff to stdout (nb: not to default output)

function _G.print(...)
  for i,v in ipairs{...} do
    if i > 1 then io.stdout:write('\t') end
    io.stdout:write(tostring(v))
  end
  io.stdout:write('\n')
end

--}}}
--{{{  function _G.debug.debug()

function _G.debug.debug()
  local cmd, ok, msg
  repeat
    io.stderr:write("Lua_debug> ")
    cmd = io.stdin:read()
    if not cmd or cmd == 'cont' then return end
    ok, msg = pcall(loadstring,cmd,"=(debug command)")
    if not ok then
      io.stderr:write(msg)
      io.stderr:write('\n')
    else
      ok, msg = pcall(msg)
      if not ok then
        io.stderr:write(msg)
        io.stderr:write('\n')
      end
    end
  until false
end

--}}}
--{{{  function _G.io.close(file)

function _G.io.close(file)
  if file then
    return file:close()
  else
    return io.output():close()
  end
end

--}}}
--{{{  function _G.io.flush()

function _G.io.flush()
  return io.output():flush()
end

--}}}
--{{{  function _G.io.lines(filename)

local _io_lines  = io.lines    --note original 'cos we need to use it

function _G.io.lines(filename)
  if filename then
    return _io_lines(filename)
  else
    return io.input():lines()
  end
end

--}}}
--{{{  function _G.io.read(...)

function _G.io.read(...)
  return io.input():read(...)
end

--}}}
--{{{  function _G.io.write(...)

function _G.io.write(...)
  return io.output():write(...)
end

--}}}

--lua.c implemented in Lua
--{{{  function _G.console(...)

--15/08/06 DCN @@TBD@@ how to allow a Ctrl-C to interrupt things
--16/08/06 DCN But this is a general problem when running errant Lua scripts in M-IT

--console behaves like pcall,
--i.e. it'll return true+results if OK, or nil+error message if not

function _G.console(args)

  local LUA_RELEASE   = "Lua 5.1.1"
  local LUA_COPYRIGHT = "Copyright (C) 1994-2006 Lua.org, PUC-Rio"
  local PROMPT        = '> '
  local PROMPT2       = '>> '

  --{{{  local function print_usage(args)
  
  local function print_usage(args)
    io.stderr:write("\n"..
                    "Args: "..(args or '').."\n"..
                    "Usage: console('[options] [script [args]]')\n"..
                    "Available options are:\n"..
                    "  -e stat  execute string 'stat'\n"..
                    "  -l name  require library 'name'\n"..
                    "  -i       enter interactive mode after executing 'script'\n"..
                    "  -v       show version information\n"..
                    "  --       stop handling options\n"..
                    "no arguments is the same as \"-i\"\n"..
                    "arguments with embedded spaces must be quoted using \"\n")
  end
  
  --}}}
  --{{{  local function l_message(msg)
  
  local function l_message(msg)
    io.stderr:write(msg..'\n')
  end
  
  --}}}
  --{{{  local function report(status,msg)
  
  --report OK or bad result
  --status is nil for an error, then the msg is the error
  --returns its input params in all cases
  
  local function report(status,msg)
    if not status then
      collectgarbage('collect')
      if msg then
        if not tostring(msg) then
          l_message('(error object is not a string)')
        else
          l_message(msg)
        end
      else
        l_message('(error with no message)')
      end
    end
    return status,msg
  end
  
  --}}}
  --{{{  local function print_version()
  
  local function print_version()
    l_message('Lua console: '..LUA_RELEASE.."  "..LUA_COPYRIGHT)
  end
  
  --}}}
  --{{{  local function getargs(args,n)
  
  local function getargs(args,n)
    local results = {}
    for i,v in ipairs(args) do
      if i >= n then results[#results+1]=v end
    end
    results.n = #results
    return results
  end
  
  --}}}
  --{{{  local function docall(func,err,...)
  
  --NB: The return from here is false,error message
  --    or true,{results}
  --    i.e. always 2 things but the 2nd thing is a table on success (possibly empty)
  
  local function docall(func,err,...)
    if func then
      local arg = {...}
      local results = {xpcall(function() return func(unpack(arg)) end,debug.traceback)}
      local status  = results[1]
      table.remove(results,1)
      if status then
        return status, results
      else
        return report(status, results[1])
      end
    else
      return report(func,err)
    end
  end
  
  --}}}
  --{{{  local function dofile(name,...)
  
  local function dofile(name,...)
    local func, err = loadfile(name)
    if func then
      return docall(func,nil,...)
    else
      return report(func,err)
    end
  end
  
  --}}}
  --{{{  local function dostring(s,name)
  
  local function dostring(s,name)
    return docall(loadstring(s,name))
  end
  
  --}}}
  --{{{  local function dolibrary(name)
  
  local function dolibrary(name)
    return docall(require,'require not defined',name)
  end
  
  --}}}
  --{{{  local function readline(prompt)
  
  local function readline(prompt)
    if prompt then io.stdout:write(prompt) end
    return io.stdin:read("*l")
  end
  
  --}}}
  --{{{  local function incomplete(status,func)
  
  local function incomplete(status,func)
    if not func and string.find(status,".*near '%<eof%>'$") then return true end
    return false
  end
  
  --}}}
  --{{{  local function loadline()
  
  local function loadline()
    local line2, func, status
    local line = readline(PROMPT)
    if not line then return nil,nil end --no input
    if string.sub(line,1,1) == '=' then
      line = "return "..string.match(line,"^=(.*)")  --map '=' to 'return'
    end
    repeat --until get complete line
      func,status = loadstring(line,"=stdin")
      if not incomplete(status,func) then return func,status end
      line2 = readline(PROMPT2)
      if not line2 then return nil,nil end --no more input
      line = line..line2
    until false
  end
  
  --}}}
  --{{{  local function dotty()
  
  local function dotty()
    local func, status, results, ok, msg
    repeat
      func,status = loadline()
      if not func then
        if not status then break end       --end of input
        report(func,status)                --syntax error
      else
        status,results = docall(func)
        if status and #results > 0 then
          ok,msg = pcall(print,unpack(results))
          if not ok then
            l_message("error calling 'print' ("..msg..")")
          end
        end
      end
    until false
    --io.stdout:write('\n')
    return
  end
  
  --}}}
  --{{{  local function handle_args(args)
  
  --args is: [options] [script [args]]
  --Available options are:
  --  -e stat  execute string 'stat'
  --  -l name  require library 'name'
  --  -i       enter interactive mode after executing 'script'
  --  -v       show version information
  --  --       stop handling options
  --no arguments is the same as -i
  
  local function handle_args(args)
  
    if (args or '') == '' then print_version(); return true,true,nil end  --no arguments, go interactive
  
    --{{{  put all the args 'words' in t{}
    
    local s = args .. ' '        -- ending space
    local t = {}        -- table to collect fields
    local fieldstart = 1
    repeat
      -- next field is quoted? (start with '"'?)
      if string.find(s, '^"', fieldstart) then
        local a, c
        local i  = fieldstart
        repeat
          -- find closing quote
          a, i, c = string.find(s, '"("?)', i+1)
        until c ~= '"'    -- quote not followed by quote?
        if not i then error('unmatched "') end
        local f = string.sub(s, fieldstart+1, i-1)
        table.insert(t, (string.gsub(f, '""', '"')))
        fieldstart = string.find(s, ' ', i) + 1
      else                -- unquoted; find next space
        local nexti = string.find(s, ' ', fieldstart)
        table.insert(t, string.sub(s, fieldstart, nexti-1))
        fieldstart = nexti + 1
      end
    until fieldstart > string.len(s)
    
    --}}}
  
    local i, ii, opt, interactive, status, results, filename, v
  
    --{{{  validate and process immediate options
    
    i = 0
    
    while i < #t do
      i = i + 1; v = t[i]
      if string.sub(v,1,1) ~= '-' then break end  --not an option
      opt = string.sub(v,1,2)
      if opt == '--' then
        if opt ~= v          then print_usage(args); return false,true,'bad -- option: '..args end
        break
      elseif opt == '-v' then
        print_version()
      elseif opt == '-i' then
        interactive = true
      elseif opt == '-e' then
        i = i + 1; opt = t[i]
        if (opt or '') == '' then print_usage(args); return false,true,'bad -e option: '..args end
        --delay this
      elseif opt == '-l' then
        i = i + 1; opt = t[i]
        if (opt or '') == '' then print_usage(args); return false,true,'bad -l option: '..args end
        --delay this
      else
                                  print_usage(args); return false,true,'unknown option: '..args
      end
    end
    
    --}}}
    --{{{  process delayed options
    
    i = 0
    
    while i < #t do
      i = i + 1; v = t[i]
      ii = i
      if string.sub(v,1,1) ~= '-' then ii = ii - 1; break end  --not an option
      opt = string.sub(v,1,2)
      if opt == '--' then
        break
      elseif opt == '-v' then
        --already done this
      elseif opt == '-i' then
        --already done this
      elseif opt == '-e' then
        i = i + 1; opt = t[i]
        status, results = dostring(opt,"=<command line>")
        if not status then return status,interactive,results end
      elseif opt == '-l' then
        i = i + 1; opt = t[i]
        status, results = dolibrary(opt)
        if not status then return status,interactive,results end
      end
    end
    
    --}}}
  
    if t[ii+1] then
      filename = t[ii+1]
      _G.arg = getargs(t,ii+2)
      status,results = dofile(filename,unpack(_G.arg))
      if not status then return status,interactive,results end
    else
      status = true
    end
  
    return status,interactive,results
  
  end
  
  --}}}
  --{{{  local function handle_luainit()
  
  local function handle_luainit()
    local status,err
    if LUA_INIT and LUA_INIT ~= '' then
      if string.sub(LUA_INIT,1,1) == '@' then
        return dofile(string.gsub(LUA_INIT,'@','',1))
      else
        return dostring(LUA_INIT,"=LUA_INIT")
      end
    else
      return true
    end
  end
  
  --}}}

  local interactive, status, results

  status, results = handle_luainit()              ; if not status then error(results) end

  status, interactive, results = handle_args(args); if not status then error(results) end

  if interactive then dotty() end

  _ARGS    = args                        --so can see them interactively as a debug aid
  _RESULTS = results                     --when coming back in here after executing a statement

  return unpack(results or {})

end

--}}}