├── config.lua ├── readme.md ├── luaish.lua └── lua.lua /config.lua: -------------------------------------------------------------------------------- 1 | --- Reads configuration files into a Lua table. 2 | -- Understands INI files, classic Unix config files, and simple 3 | -- delimited columns of values. 4 | -- 5 | -- # test.config 6 | -- # Read timeout in seconds 7 | -- read.timeout=10 8 | -- # Write timeout in seconds 9 | -- write.timeout=5 10 | -- #acceptable ports 11 | -- ports = 1002,1003,1004 12 | -- 13 | -- -- readconfig.lua 14 | -- require 'pl' 15 | -- local t = config.read 'test.config' 16 | -- print(pretty.write(t)) 17 | -- 18 | -- ### output ##### 19 | -- { 20 | -- ports = { 21 | -- 1002, 22 | -- 1003, 23 | -- 1004 24 | -- }, 25 | -- write_timeout = 5, 26 | -- read_timeout = 10 27 | -- } 28 | -- 29 | -- See the Guide for further @{06-data.md.Reading_Configuration_Files|discussion} 30 | -- 31 | -- Dependencies: none 32 | -- @module pl.config 33 | 34 | local type,tonumber,ipairs,io, table = _G.type,_G.tonumber,_G.ipairs,_G.io,_G.table 35 | 36 | local function split(s,re) 37 | local res = {} 38 | local t_insert = table.insert 39 | re = '[^'..re..']+' 40 | for k in s:gmatch(re) do t_insert(res,k) end 41 | return res 42 | end 43 | 44 | local function strip(s) 45 | return s:gsub('^%s+',''):gsub('%s+$','') 46 | end 47 | 48 | local function strip_quotes (s) 49 | return s:gsub("['\"](.*)['\"]",'%1') 50 | end 51 | 52 | local config = {} 53 | 54 | --- like io.lines(), but allows for lines to be continued with '\'. 55 | -- @param file a file-like object (anything where read() returns the next line) or a filename. 56 | -- Defaults to stardard input. 57 | -- @return an iterator over the lines, or nil 58 | -- @return error 'not a file-like object' or 'file is nil' 59 | function config.lines(file) 60 | local f,openf,err 61 | local line = '' 62 | if type(file) == 'string' then 63 | f,err = io.open(file,'r') 64 | if not f then return nil,err end 65 | openf = true 66 | else 67 | f = file or io.stdin 68 | if not file.read then return nil, 'not a file-like object' end 69 | end 70 | if not f then return nil, 'file is nil' end 71 | return function() 72 | local l = f:read() 73 | while l do 74 | -- does the line end with '\'? 75 | local i = l:find '\\%s*$' 76 | if i then -- if so, 77 | line = line..l:sub(1,i-1) 78 | elseif line == '' then 79 | return l 80 | else 81 | l = line..l 82 | line = '' 83 | return l 84 | end 85 | l = f:read() 86 | end 87 | if openf then f:close() end 88 | end 89 | end 90 | 91 | --- read a configuration file into a table 92 | -- @param file either a file-like object or a string, which must be a filename 93 | -- @param cnfg a configuration table that may contain these fields: 94 | -- 101 | -- @return a table containing items, or nil 102 | -- @return error message (same as @{config.lines} 103 | function config.read(file,cnfg) 104 | local f,openf,err 105 | cnfg = cnfg or {} 106 | local function check_cnfg (var,def) 107 | local val = cnfg[var] 108 | if val == nil then return def else return val end 109 | end 110 | local t = {} 111 | local top_t = t 112 | local variablilize = check_cnfg ('variabilize',true) 113 | local list_delim = check_cnfg('list_delim',',') 114 | local convert_numbers = check_cnfg('convert_numbers',true) 115 | local trim_space = check_cnfg('trim_space',true) 116 | local trim_quotes = check_cnfg('trim_quotes',false) 117 | local ignore_assign = check_cnfg('ignore_assign',false) 118 | local keysep = check_cnfg('keysep','=') 119 | local keypat = keysep == ' ' and '%s+' or '%s*'..keysep..'%s*' 120 | 121 | local function process_name(key) 122 | if variablilize then 123 | key = key:gsub('[^%w]','_') 124 | end 125 | return key 126 | end 127 | 128 | local function process_value(value) 129 | if list_delim and value:find(list_delim) then 130 | value = split(value,list_delim) 131 | for i,v in ipairs(value) do 132 | value[i] = process_value(v) 133 | end 134 | elseif convert_numbers and value:find('^[%d%+%-]') then 135 | local val = tonumber(value) 136 | if val then value = val end 137 | end 138 | if type(value) == 'string' then 139 | if trim_space then value = strip(value) end 140 | if trim_quotes then value = strip_quotes(value) end 141 | end 142 | return value 143 | end 144 | 145 | local iter,err = config.lines(file) 146 | if not iter then return nil,err end 147 | for line in iter do 148 | -- strips comments 149 | local ci = line:find('%s*[#;]') 150 | if ci then line = line:sub(1,ci-1) end 151 | -- and ignore blank lines 152 | if line:find('^%s*$') then 153 | elseif line:find('^%[') then -- section! 154 | local section = process_name(line:match('%[([^%]]+)%]')) 155 | t = top_t 156 | t[section] = {} 157 | t = t[section] 158 | else 159 | line = line:gsub('^%s*','') 160 | local i1,i2 = line:find(keypat) 161 | if i1 and not ignore_assign then -- key,value assignment 162 | local key = process_name(line:sub(1,i1-1)) 163 | local value = process_value(line:sub(i2+1)) 164 | t[key] = value 165 | else -- a plain list of values... 166 | t[#t+1] = process_value(line) 167 | end 168 | end 169 | end 170 | return top_t 171 | end 172 | 173 | return config 174 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ## A better REPL for Lua 2 | 3 | luaish is based on [lua.lua](http://lua-users.org/wiki/LuaInterpreterInLua) which is a Lua interpreter front-end written in Lua by David Manura. 4 | 5 | /mnt/extra/luaish$ lua52 lua.lua -h 6 | usage: lua.lua [options] [script [args]]. 7 | Available options are: 8 | -e stat execute string 'stat' 9 | -l name require library 'name' 10 | -i enter interactive mode after executing 'script' 11 | -v show version information 12 | -- stop handling options 13 | - execute stdin and stop handling options 14 | 15 | Starting from this good working point, I first modified `lua.lua` to be 5.2 compatible, and added 'readline-like' support using [lua-linenoise](https://github.com/hoelzro/lua-linenoise) which is a linenoise binding by Rob Hoelz. Although only a few hundred lines of C, linenoise is more than capable for straightforward line editing and history. And it has enough tab completion support for our purposes. 16 | 17 | So, say if you have typed 'st' then will give you the only matching Lua global, which is 'string'. If you now enter '.' , will cycle through all the available `string` table functions. 18 | 19 | This also works with objects (such as strings). After 's:r' will complete 's:rep' for us: 20 | 21 | > s = '#' 22 | > = s:rep(10) 23 | "##########" 24 | 25 | There is also a few shortcuts defined, so 'fn' gives 'function', and 'rt' gives 'return'. 26 | 27 | luaish makes the command history available in the usual way, and saves it in the `~/luai-history` file. Anything you put in the `~/luairc.lua` file will be loaded at startup. 28 | 29 | luaish uses [luaposix](https://github.com/rrthomas/luaposix) for directory iteration and setting the process environment. 30 | 31 | There is an _optional_ dependency on [Microlight](https://github.com/stevedonovan/Microlight), which is only used to provide some table-dumping abilities to the REPL: 32 | 33 | > = {1,2;one=1} 34 | {1,2,one=1} 35 | 36 | ## Shell Mode 37 | 38 | It can be irritating to have to switch between the Lua interactive prompt and the shell, as separate programs. However, Lua would make a bad shell, in the same way (arguably) that Bash makes a poor programming language. 39 | 40 | Any line begining with '.' is assumed to be a shell command: 41 | 42 | > .tail luaish.lua 43 | local luarc = home..'/.luairc.lua' 44 | local f = io.open(luarc,'r') 45 | if f then 46 | f:close() 47 | dofile(luarc) 48 | else 49 | print 'no ~/.luairc.lua found' 50 | end 51 | 52 | return lsh 53 | > .ls 54 | luaish.lua lua.lua readme.md 55 | 56 | In this shell sub-mode, tab completion switches to _working with paths_. In the above case, I typed '.tail l' and tabbed twice to get '.tail luaish.lua'. 57 | 58 | `cd` is available, but is a _pseudo-command_. It changes the current working directory for the whole session, and updates the title bar of the terminal window. It acts rather like the `pushd` command, so that the pseudo-command `back` goes back to the directory you came from. 59 | 60 | > .cd ../lua/Penlight 61 | ../lua/Penlight 62 | > .ls 63 | github Penlight stevedonovan.github.com 64 | > .back 65 | /mnt/extra/luaish 66 | > .cd ../lua 67 | > .lua hello.lua 68 | hello dolly! 69 | > .l hello.lua 70 | hello dolly! 71 | 72 | 'l' is another pseudo-command, which is equivalent to the Lua call `dofile 'hello.lua`; thereafter '.l' will load the last named file. 73 | 74 | Note that this works as expected: 75 | 76 | > .export P=$(pwd) 77 | > .echo $P 78 | /mnt/extra/luaish 79 | 80 | But, given that luaish is just creating a subshell for commands, how can this command modify the environment of luaish? How this is done is discussed next. 81 | 82 | ## Shell and Lua Mode communication 83 | 84 | If a shell command ends with a '| - ' then 'fun' is assumed to be a function in the global table 'luaish'. The predefined '>' function sets the global with the given name to the output, as a Lua table: 85 | 86 | > .ls -1 | -> ls 87 | > = ls 88 | {"luaish.lua","lua.lua","readme.md"} 89 | 90 | Another built-in function is 'lf', which presents numbered output lines, You can then refer to the line as '$n' 91 | 92 | > .ls -1 | -lf 93 | 1 luaish.lua 94 | 2 lua.lua 95 | 3 readme.md 96 | > .head -n 2 $3 97 | ## A better REPL for Lua 98 | 99 | Any Lua globals are also expanded: 100 | 101 | > P = 'hello' 102 | > .echo $P $(pwd) 103 | hello /mnt/extra/luaish 104 | 105 | The 'ls -1 |-lf' pattern is common enough that an _alias_ is provided: 106 | 107 | > .dir *.lua 108 | 1 luaish.lua 109 | 2 lua.lua 110 | 111 | which is defined so: 112 | 113 | luaish.add_alias('dir','ls -1 %s |-lf') 114 | 115 | Nothing fancy goes on here; any arguments to the alias are passed to the command directly. 116 | 117 | Now the implementation of 'export' can be explained. There is a built-in function which uses [luaposix](https://github.com/rrthomas/luaposix)'s `setenv` function: 118 | 119 | function luaish.lsetenv (f) 120 | local line = f:read() 121 | local var, value = line:match '^(%S+) "(.-)"$' 122 | posix.setenv(var,value) 123 | end 124 | 125 | (Note that these functions are passed a file object for reading from the shell process) 126 | 127 | Here is the long way of using 'lsetenv': 128 | 129 | >.export P=$(pwd) && echo P "$P" | -lsetenv 130 | 131 | And that's exactly what the builtin-command 'export' outputs when you say: 132 | 133 | >.export P=$(pwd) 134 | 135 | Lua string values can be passed to the shell as expanded globals, but there's also an equivalent '| -' mechanism for pumping data into a shell command: 136 | 137 | > t = {'one','two','three'} 138 | > .-print t | sort 139 | one 140 | three 141 | two 142 | 143 | Again, 'print' is a function in the 'luaish' table; these functions work exactly like the others, except they write to their file object. Here is a simplified implementation: 144 | 145 | function luaish.print (f,name) 146 | for _,line in ipairs(_G[name]) do 147 | f:write(line,'\n') 148 | end 149 | end 150 | 151 | The purpose of `~/.luairc.lua` is to let people define their own Lua filters and aliases, as well as preloading useful libraries. 152 | 153 | ## More Possibilities 154 | 155 | luaish is currently in the 'executable proposal' stage of development, for people to try out and play with the possibilities. It could do with some refactoring, so that a person may use it only as a linenoise-equiped Lua prompt, or even use that old dog `readline` itself. Rob Hoelz and myself will be looking at how to build a more generalized and extendable framework. 156 | 157 | Currently, you may _either_ use the 'push input' or 'pop output' forms, but not together. Since `popen2` can be implemented using luaposix, this restriction can be lifted, and we _can_ have a general mechanism for pumping Lua data through a shell filter: 158 | 159 | > . -print idata | sort | -> sdata 160 | 161 | -------------------------------------------------------------------------------- /luaish.lua: -------------------------------------------------------------------------------- 1 | local have_ml,ml = pcall(require, 'ml') 2 | local posix = require 'posix' 3 | local linenoise = require 'linenoise' 4 | local lsh = { tostring = tostring } 5 | local append = table.insert 6 | 7 | io.write "luaish (c) Steve Donovan 2012\n" 8 | 9 | local our_completions, our_history = {} 10 | local our_line_handlers, our_shortcuts = {} ,{} 11 | 12 | local function is_pair_iterable(t) 13 | local mt = getmetatable(t) 14 | return type(t) == 'table' or (mt and mt.__pairs) 15 | end 16 | 17 | local function lua_candidates(line) 18 | -- identify the expression! 19 | local i1,i2 = line:find('[.:%w_]+$') 20 | if not i1 then return end 21 | local front,partial = line:sub(1,i1-1), line:sub(i1) 22 | local prefix, last = partial:match '(.-)([^.:]*)$' 23 | local t, all = _G 24 | if #prefix > 0 then 25 | local P = prefix:sub(1,-2) 26 | all = last == '' 27 | for w in P:gmatch '[^.:]+' do 28 | t = t[w] 29 | if not t then return end 30 | end 31 | end 32 | prefix = front .. prefix 33 | local res = {} 34 | local function append_candidates(t) 35 | for k,v in pairs(t) do 36 | if all or k:sub(1,#last) == last then 37 | append(res,prefix..k) 38 | end 39 | end 40 | end 41 | local mt = getmetatable(t) 42 | if is_pair_iterable(t) then 43 | append_candidates(t) 44 | end 45 | if mt and is_pair_iterable(mt.__index) then 46 | append_candidates(mt.__index) 47 | end 48 | return res 49 | end 50 | 51 | local function completion_handler(c,s) 52 | local cc 53 | for pat, cf in pairs(our_completions) do 54 | if s:match(pat) then 55 | cc = cf(s) 56 | if not cc then return end 57 | end 58 | end 59 | for sc,value in pairs(our_shortcuts) do 60 | local idx = #s - #sc + 1 61 | if s:sub(idx)==sc then 62 | cc = {s:sub(1,idx-1)..value} 63 | break 64 | end 65 | end 66 | if not cc then 67 | cc = lua_candidates(s) 68 | end 69 | if cc then 70 | for _,name in ipairs(cc) do 71 | linenoise.addcompletion(c,name) 72 | end 73 | end 74 | end 75 | 76 | function lsh.set_tostring(ts) 77 | local old_tostring = lsh.tostring 78 | lsh.tostring = ts 79 | return old_tostring 80 | end 81 | 82 | function lsh.set_shortcuts(shortcuts) 83 | our_shortcuts = shortcuts 84 | end 85 | 86 | function lsh.add_line_handler (h) 87 | append(our_line_handlers, h) 88 | end 89 | 90 | function lsh.add_completion(pat,cf) 91 | our_completions[pat] = cf 92 | end 93 | 94 | local file_list = {} 95 | 96 | function lsh.add_to_file_list(file) 97 | append(file_list,file) 98 | print(('%2d %s'):format(#file_list,file)) 99 | end 100 | 101 | function lsh.reset_file_list() 102 | file_list = {} 103 | end 104 | 105 | function lsh.saveline (s) 106 | linenoise.historyadd(s) 107 | linenoise.historysave(our_history) 108 | end 109 | 110 | function lsh.readline(prmt) 111 | local line, err = linenoise.linenoise(prmt) 112 | if not line then 113 | if err == 'cancel' then 114 | print '' 115 | line = linenoise.linenoise(prmt) 116 | else 117 | return nil 118 | end 119 | end 120 | if #file_list > 0 and line then 121 | line = line:gsub('%$(%d+)',function(num) 122 | num = tonumber(num) 123 | return file_list[num] or 'que?' 124 | end) 125 | lsh.lines = file_list 126 | end 127 | return line 128 | end 129 | 130 | function lsh.checkline(b) 131 | for _,h in ipairs(our_line_handlers) do 132 | local res = h(b) 133 | if res then 134 | lsh.saveline(b) 135 | return false -- we handled this, keep reading 136 | end 137 | end 138 | return true -- line for Lua interpreter 139 | end 140 | 141 | -------- Lua output filters -------- 142 | -- a shell command like '.ls | sort |=name' 143 | -- will be put through a Lua function called 'name' in the luaish global table. 144 | -- This function is passed the file object from popen 145 | 146 | -- this filter prints lines with line numbers, and the lines can 147 | -- subsequently be accessed with $n from the prompt (all modes) 148 | function lsh.lf (f) 149 | lsh.reset_file_list() 150 | for line in f:lines() do 151 | lsh.add_to_file_list(line) 152 | end 153 | end 154 | 155 | -- this filter is used to implement the built-in command export 156 | function lsh.lsetenv (f) 157 | local line = f:read() 158 | local var, value = line:match '^(%S+) "(.-)"$' 159 | posix.setenv(var,value) 160 | end 161 | 162 | lsh ['>'] = function(f,name) 163 | local res = {} 164 | for line in f:lines() do 165 | append(res,line) 166 | end 167 | _G[name] = res 168 | end 169 | 170 | lsh.print = function(f,name) 171 | local val = _G[name] 172 | if not val then 173 | io.write(("'%s' is not a Lua global\n"):format(name)) 174 | return 175 | end 176 | for _,line in ipairs(_G[name]) do 177 | f:write(line,'\n') 178 | end 179 | end 180 | 181 | local function at(s,i) 182 | return s:sub(i,i) 183 | end 184 | 185 | local push, pop = append, table.remove 186 | local dirstack = {} 187 | 188 | local function is_directory(path) 189 | return posix.stat(path,'type') == 'directory' 190 | end 191 | 192 | function path_candidates(line) 193 | local i1,front,path,name 194 | i1 = line:find '%S+$' 195 | if not i1 then return end 196 | front, path = line:sub(1,i1-1), line:sub(i1) 197 | i1 = path:find '[.%w%-]*$' 198 | if not i1 then return end 199 | path,name = path:sub(1,i1-1), path:sub(i1) 200 | local fullpath, sc, dpath = path,at(path,1),path 201 | if sc == '~' then 202 | path = os.getenv 'HOME'..'/'..path:sub(3) 203 | fullpath = path 204 | elseif sc ~= '/' then 205 | fullpath = posix.getcwd() 206 | if path ~= '' then 207 | fullpath = fullpath ..'/'..path 208 | else 209 | path = '.' 210 | dpath = '' 211 | end 212 | end 213 | if not is_directory(fullpath) then return end 214 | local res = {} 215 | local all = name == '' 216 | for _,f in ipairs(posix.dir(path)) do 217 | if all or f:sub(1,#name)==name then 218 | push(res,front..dpath..f) 219 | end 220 | end 221 | table.sort(res,function(a,b) 222 | return #a < #b 223 | end) 224 | return res 225 | end 226 | 227 | local function set_title(msg) 228 | msg = msg or posix.getcwd() 229 | io.write("\027]2;luai "..msg.."\007") 230 | end 231 | 232 | local function change_directory(dir) 233 | posix.chdir(dir) 234 | set_title(posix.getcwd()) 235 | print(dir) 236 | end 237 | 238 | local function back() 239 | local odir = pop(dirstack) 240 | if odir then 241 | change_directory(odir) 242 | else 243 | print 'dir stack is empty' 244 | end 245 | end 246 | 247 | local last_command 248 | 249 | local function exec (line) 250 | local i1,_,filter,at_start 251 | i1,_,lfilter = line:find '|%s*%-(.+)$' 252 | if not i1 then 253 | at_start = true 254 | _,i1,lfilter = line:find '%s*%-([^|]+)|' 255 | end 256 | if i1 then 257 | if at_start then 258 | line = line:sub(i1+1) 259 | else 260 | line = line:sub(1,i1-1) 261 | end 262 | local lfun,arg = lfilter:match '(%S+)%s+(.-)%s*$' 263 | if not lfun then 264 | lfun = lfilter 265 | end 266 | if not lsh[lfun] then 267 | io.write (lfun,' is not a Lua filter\n') 268 | return true 269 | end 270 | local f = io.popen(line,at_start and 'w' or 'r') 271 | local ok, res = pcall(lsh[lfun],f,arg) 272 | f:close() 273 | if not ok then 274 | io.write(res,'\n') 275 | end 276 | return true 277 | else 278 | os.execute(line) 279 | end 280 | return true 281 | end 282 | 283 | local alias = {} 284 | 285 | function lsh.add_alias(name,cmd) 286 | alias[name] = cmd 287 | end 288 | 289 | local function expand_alias(cmd,args) 290 | return alias[cmd]:format(args) 291 | end 292 | 293 | local function expand_lua_globals(line) 294 | return line:gsub('%$(%a+)',function(name) 295 | local val = _G[name] 296 | if val then return tostring(val) 297 | else return '$'..name 298 | end 299 | end) 300 | end 301 | 302 | function shell_command_handler (line) 303 | if at(line,1) == '.' then 304 | line = line:gsub('^%.%s*','') 305 | line = expand_lua_globals(line) 306 | local cmd,args = line:match '^(%S+)(.*)$' 307 | if not args then 308 | io.write 'bad command syntax\n' 309 | return true 310 | end 311 | if alias[cmd] then 312 | line = expand_alias(cmd,args) 313 | cmd,args = line:match '^(%a+)(.*)$' 314 | end 315 | args = args:gsub('^%s*','') 316 | if cmd == 'cd' then 317 | local arg = args:match '^(%S+)' 318 | local _,k = arg:find '^%-+$' 319 | if k then 320 | arg = arg:gsub('%-','../',k) 321 | end 322 | arg = arg:gsub('^~',os.getenv 'HOME') 323 | if not is_directory(arg) then 324 | arg = posix.dirname(arg) 325 | end 326 | push(dirstack,posix.getcwd()) 327 | change_directory(arg) 328 | elseif cmd == 'l' then 329 | if args == '' then 330 | args = last_command 331 | end 332 | if not args then 333 | print 'no script file specified' 334 | return true 335 | end 336 | dofile(args) 337 | last_command = args 338 | elseif cmd == 'back' then 339 | back() 340 | elseif cmd == 'h' then 341 | return exec(('tail %s |-lf'):format(our_history)) 342 | elseif cmd == 'export' then 343 | local var = args:match '^(.-)=' 344 | local cmd = (('%s && echo %s \\"$%s\\" |-lsetenv'):format(args,var,var)) 345 | return exec(cmd) 346 | else -- plain shell command 347 | return exec(line) 348 | end 349 | return true 350 | end 351 | end 352 | 353 | local home = os.getenv 'HOME' 354 | linenoise.setcompletion(completion_handler) 355 | our_history = home..'/.luai-history' 356 | linenoise.historyload(our_history) 357 | lsh.add_line_handler(shell_command_handler) 358 | lsh.add_completion('^%.',path_candidates) 359 | 360 | -- microlight isn't essential, but it gives you better 361 | -- output; tables will be printed out 362 | if have_ml then 363 | lsh.set_tostring(ml.tstring) 364 | _G.ml = ml 365 | end 366 | local ok 367 | ok,_G.config = pcall(require,'config') 368 | 369 | lsh.set_shortcuts { 370 | fn = "function ", 371 | rt = "return ", 372 | } 373 | 374 | _G.posix = posix 375 | _G.luaish = lsh -- global for rc file 376 | 377 | lsh.add_alias('dir','ls -1 %s |-lf') 378 | lsh.add_alias('locate','locate %s |-lf') 379 | 380 | local luarc = home..'/.luairc.lua' 381 | local f = io.open(luarc,'r') 382 | if f then 383 | f:close() 384 | dofile(luarc) 385 | else 386 | --print 'no ~/.luairc.lua found' 387 | end 388 | 389 | return lsh 390 | -------------------------------------------------------------------------------- /lua.lua: -------------------------------------------------------------------------------- 1 | -- lua.lua - Lua 5.1 interpreter (lua.c) reimplemented in Lua. 2 | -- 3 | -- WARNING: This is not completed but was quickly done just an experiment. 4 | -- Fix omissions/bugs and test if you want to use this in production. 5 | -- Particularly pay attention to error handling. 6 | -- 7 | -- (c) David Manura, 2008-08 8 | -- Licensed under the same terms as Lua itself. 9 | -- Based on lua.c from Lua 5.1.3. 10 | -- Improvements by Shmuel Zeigerman. 11 | 12 | -- Variables analogous to those in luaconf.h 13 | local LUA_INIT = "LUA_INIT" 14 | local LUA_PROGNAME = "lua" 15 | local LUA_PROMPT = "> " 16 | local LUA_PROMPT2 = ">> " 17 | local function LUA_QL(x) return "'" .. x .. "'" end 18 | 19 | local lua51 = _VERSION:match '5%.1$' 20 | -- Variables analogous to those in lua.h 21 | local LUA_RELEASE, LUA_COPYRIGHT, eof_ender 22 | if lua51 then 23 | LUA_RELEASE = "Lua 5.1.4" 24 | LUA_COPYRIGHT = "Copyright (C) 1994-2008 Lua.org, PUC-Rio" 25 | eof_ender = LUA_QL("") 26 | else 27 | LUA_RELEASE = "Lua 5.2.0" 28 | LUA_COPYRIGHT = "Copyright (C) 1994-2011 Lua.org, PUC-Rio" 29 | eof_ender = '' 30 | end 31 | local EXTRA_COPYRIGHT = "lua.lua (c) David Manura, 2008-08" 32 | 33 | -- Note: don't allow user scripts to change implementation. 34 | -- Check for globals with "cat lua.lua | luac -p -l - | grep ETGLOBAL" 35 | 36 | local _G = _G 37 | local assert = assert 38 | local collectgarbage = collectgarbage 39 | local loadfile = loadfile 40 | local loadstring = loadstring or load 41 | local pcall = pcall 42 | local rawget = rawget 43 | local select = select 44 | local tostring = tostring 45 | local type = type 46 | local unpack = unpack or table.unpack 47 | local xpcall = xpcall 48 | local io_stderr = io.stderr 49 | local io_stdout = io.stdout 50 | local io_stdin = io.stdin 51 | local string_format = string.format 52 | local string_sub = string.sub 53 | local os_getenv = os.getenv 54 | local os_exit = os.exit 55 | 56 | 57 | local progname = LUA_PROGNAME 58 | 59 | -- Use external functions, if available 60 | local lua_stdin_is_tty = function() return true end 61 | local setsignal = function() end 62 | 63 | local function print_usage() 64 | io_stderr:write(string_format( 65 | "usage: %s [options] [script [args]].\n" .. 66 | "Available options are:\n" .. 67 | " -e stat execute string " .. LUA_QL("stat") .. "\n" .. 68 | " -l name require library " .. LUA_QL("name") .. "\n" .. 69 | " -i enter interactive mode after executing " .. 70 | LUA_QL("script") .. "\n" .. 71 | " -v show version information\n" .. 72 | " -- stop handling options\n" .. 73 | " - execute stdin and stop handling options\n" 74 | , 75 | progname)) 76 | io_stderr:flush() 77 | end 78 | 79 | local our_tostring = tostring 80 | 81 | local tuple = table.pack or function(...) 82 | return {n=select('#', ...), ...} 83 | end 84 | 85 | local using_lsh,lsh 86 | 87 | local function our_print (...) 88 | local args = tuple(...) 89 | for i = 1,args.n do 90 | io.write(our_tostring(args[i]),'\t') 91 | end 92 | _G._ = args[1] 93 | io.write '\n' 94 | end 95 | 96 | local function saveline(s) 97 | if using_lsh then 98 | lsh.saveline(s) 99 | end 100 | end 101 | 102 | local function getline(prmt) 103 | if using_lsh then 104 | return lsh.readline(prmt) 105 | else 106 | io_stdout:write(prmt) 107 | io_stdout:flush() 108 | return io_stdin:read'*l' 109 | end 110 | end 111 | 112 | local function l_message (pname, msg) 113 | if pname then io_stderr:write(string_format("%s: ", pname)) end 114 | io_stderr:write(string_format("%s\n", msg)) 115 | io_stderr:flush() 116 | end 117 | 118 | local function report(status, msg) 119 | if not status and msg ~= nil then 120 | msg = (type(msg) == 'string' or type(msg) == 'number') and tostring(msg) 121 | or "(error object is not a string)" 122 | l_message(progname, msg); 123 | end 124 | return status 125 | end 126 | 127 | local function traceback (message) 128 | local tp = type(message) 129 | if tp ~= "string" and tp ~= "number" then return message end 130 | local debug = _G.debug 131 | if type(debug) ~= "table" then return message end 132 | local tb = debug.traceback 133 | if type(tb) ~= "function" then return message end 134 | return tb(message, 2) 135 | end 136 | 137 | local function docall(f, ...) 138 | local tp = {...} -- no need in tuple (string arguments only) 139 | local F = function() return f(unpack(tp)) end 140 | setsignal(true) 141 | local result = tuple(xpcall(F, traceback)) 142 | setsignal(false) 143 | -- force a complete garbage collection in case of errors 144 | if not result[1] then collectgarbage("collect") end 145 | return unpack(result, 1, result.n) 146 | end 147 | 148 | function dofile(name) 149 | local f, msg = loadfile(name) 150 | if f then f, msg = docall(f) end 151 | return report(f, msg) 152 | end 153 | 154 | local function dostring(s, name) 155 | local f, msg = loadstring(s, name) 156 | if f then f, msg = docall(f) end 157 | return report(f, msg) 158 | end 159 | 160 | local function dolibrary (name) 161 | return report(docall(_G.require, name)) 162 | end 163 | 164 | local function print_version() 165 | l_message(nil, LUA_RELEASE .. " " .. LUA_COPYRIGHT.."\n"..EXTRA_COPYRIGHT) 166 | end 167 | 168 | local function getargs (argv, n) 169 | local arg = {} 170 | for i=1,#argv do arg[i - n] = argv[i] end 171 | if _G.arg then 172 | local i = 0 173 | while _G.arg[i] do 174 | arg[i - n] = _G.arg[i] 175 | i = i - 1 176 | end 177 | end 178 | return arg 179 | end 180 | 181 | local function get_prompt (firstline) 182 | -- use rawget to play fine with require 'strict' 183 | local pmt = rawget(_G, firstline and "_PROMPT" or "_PROMPT2") 184 | local tp = type(pmt) 185 | if tp == "string" or tp == "number" then 186 | return tostring(pmt) 187 | end 188 | return firstline and LUA_PROMPT or LUA_PROMPT2 189 | end 190 | 191 | local function fetchline(firstline) 192 | return getline(get_prompt(firstline)) 193 | end 194 | 195 | local function incomplete (msg) 196 | if msg then 197 | if string_sub(msg, -#eof_ender) == eof_ender then 198 | return true 199 | end 200 | end 201 | return false 202 | end 203 | 204 | 205 | local function pushline (firstline) 206 | local b, fine = true 207 | repeat 208 | b = fetchline(firstline) 209 | if not b then return end -- no input 210 | if using_lsh then 211 | fine = lsh.checkline(b) 212 | end 213 | until fine 214 | if firstline and string_sub(b, 1, 1) == '=' then 215 | return "return " .. string_sub(b, 2) -- change '=' to `return' 216 | else 217 | return b 218 | end 219 | end 220 | 221 | 222 | local function loadline () 223 | local b = pushline(true) 224 | if not b then return -1 end -- no input 225 | local f, msg 226 | while true do -- repeat until gets a complete line 227 | f, msg = loadstring(b, "=stdin") 228 | if not incomplete(msg) then break end -- cannot try to add lines? 229 | local b2 = pushline(false) 230 | if not b2 then -- no more input? 231 | return -1 232 | end 233 | b = b .. "\n" .. b2 -- join them 234 | end 235 | 236 | saveline(b) 237 | 238 | return f, msg 239 | end 240 | 241 | 242 | local function dotty () 243 | local oldprogname = progname 244 | progname = nil 245 | using_lsh,lsh = pcall(require, 'luaish') 246 | if using_lsh then 247 | our_tostring = lsh.tostring 248 | else 249 | print('problem loading luaish:',lsh) 250 | end 251 | while true do 252 | local result 253 | local status, msg = loadline() 254 | if status == -1 then break end 255 | if status then 256 | result = tuple(docall(status)) 257 | status, msg = result[1], result[2] 258 | end 259 | report(status, msg) 260 | if status and result.n > 1 then -- any result to print? 261 | status, msg = pcall(our_print, unpack(result, 2, result.n)) 262 | if not status then 263 | l_message(progname, string_format( 264 | "error calling %s (%s)", 265 | LUA_QL("print"), msg)) 266 | end 267 | end 268 | end 269 | io_stdout:write"\n" 270 | io_stdout:flush() 271 | progname = oldprogname 272 | end 273 | 274 | 275 | local function handle_script(argv, n) 276 | _G.arg = getargs(argv, n) -- collect arguments 277 | local fname = argv[n] 278 | if fname == "-" and argv[n-1] ~= "--" then 279 | fname = nil -- stdin 280 | end 281 | local status, msg = loadfile(fname) 282 | if status then 283 | status, msg = docall(status, unpack(_G.arg)) 284 | end 285 | return report(status, msg) 286 | end 287 | 288 | 289 | local function collectargs (argv, p) 290 | local i = 1 291 | while i <= #argv do 292 | if string_sub(argv[i], 1, 1) ~= '-' then -- not an option? 293 | return i 294 | end 295 | local prefix = string_sub(argv[i], 1, 2) 296 | if prefix == '--' then 297 | if #argv[i] > 2 then return -1 end 298 | return argv[i+1] and i+1 or 0 299 | elseif prefix == '-' then 300 | return i 301 | elseif prefix == '-i' then 302 | if #argv[i] > 2 then return -1 end 303 | p.i = true 304 | p.v = true 305 | elseif prefix == '-v' then 306 | if #argv[i] > 2 then return -1 end 307 | p.v = true 308 | elseif prefix == '-e' then 309 | p.e = true 310 | if #argv[i] == 2 then 311 | i = i + 1 312 | if argv[i] == nil then return -1 end 313 | end 314 | elseif prefix == '-l' then 315 | if #argv[i] == 2 then 316 | i = i + 1 317 | if argv[i] == nil then return -1 end 318 | end 319 | else 320 | return -1 -- invalid option 321 | end 322 | i = i + 1 323 | end 324 | return 0 325 | end 326 | 327 | 328 | local function runargs(argv, n) 329 | local i = 1 330 | while i <= n do if argv[i] then 331 | assert(string_sub(argv[i], 1, 1) == '-') 332 | local c = string_sub(argv[i], 2, 2) -- option 333 | if c == 'e' then 334 | local chunk = string_sub(argv[i], 3) 335 | if chunk == '' then i = i + 1; chunk = argv[i] end 336 | assert(chunk) 337 | if not dostring(chunk, "=(command line)") then return false end 338 | elseif c == 'l' then 339 | local filename = string_sub(argv[i], 3) 340 | if filename == '' then i = i + 1; filename = argv[i] end 341 | assert(filename) 342 | if not dolibrary(filename) then return false end 343 | end 344 | i = i + 1 345 | end end 346 | return true 347 | end 348 | 349 | 350 | local function handle_luainit() 351 | local init = os_getenv(LUA_INIT) 352 | if init == nil then 353 | return -- status OK 354 | elseif string_sub(init, 1, 1) == '@' then 355 | dofile(string_sub(init, 2)) 356 | else 357 | dostring(init, "=" .. LUA_INIT) 358 | end 359 | end 360 | 361 | 362 | local import = _G.import 363 | if import then 364 | lua_stdin_is_tty = import.lua_stdin_is_tty or lua_stdin_is_tty 365 | setsignal = import.setsignal or setsignal 366 | LUA_RELEASE = import.LUA_RELEASE or LUA_RELEASE 367 | LUA_COPYRIGHT = import.LUA_COPYRIGHT or LUA_COPYRIGHT 368 | _G.import = nil 369 | end 370 | 371 | if _G.arg and _G.arg[0] and #_G.arg[0] > 0 then progname = _G.arg[0] end 372 | local argv = {...} 373 | handle_luainit() 374 | local has = {i=false, v=false, e=false} 375 | local script = collectargs(argv, has) 376 | if script < 0 then -- invalid args? 377 | print_usage() 378 | os_exit(1) 379 | end 380 | if has.v then print_version() end 381 | local status = runargs(argv, (script > 0) and script-1 or #argv) 382 | if not status then os_exit(1) end 383 | if script ~= 0 then 384 | status = handle_script(argv, script) 385 | if not status then os_exit(1) end 386 | else 387 | _G.arg = nil 388 | end 389 | if has.i then 390 | dotty() 391 | elseif script == 0 and not has.e and not has.v then 392 | if lua_stdin_is_tty() then 393 | print_version() 394 | dotty() 395 | else dofile(nil) -- executes stdin as a file 396 | end 397 | end 398 | --------------------------------------------------------------------------------