[Date Prev][Date Next][Thread Prev][Thread Next]
[Date Index]
[Thread Index]
- Subject: Re: Lua in a windowing enviroment
- From: Dave Nichols <dave.nichols@...>
- Date: Fri, 20 Oct 2006 17:40:26 +0100
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
--}}}