Interactive Lua |
|
A good feature of Lua is that you can work with the language interactively, as if you are having a conversation with the interpreter. Just as conversation is the best way to learn a human language, it is very helpful to try out ideas interactively. (Or, you just want a better calculator on your computer ;)). The standalone lua interpreter will enter interactive mode if there is no script specified, or if the -i
option has been specified.
However, there are some limitations about the standard interactive prompt; if you wish to evaluate an expression, you must prefix it with '='. The interpreter translates this as 'return ' so that you are always entering a valid chunk of Lua code. Also, tables are not printed out, unless there is an explicit __tostring
in their metatables:
$ lua Lua 5.1.2 Copyright (C) 1994-2007 Lua.org, PUC-Rio > = 10+20 30 > t = {10,20,30} > = t table: 003DB790
This is a little awkward, and not what users of other interactive languages might expect. With ilua, expressions do not need a '=', and tables are printed out as nicely as possible. The value of the last expression is always available in the '_' variable.
$ lua ilua.lua ILUA: Lua 5.1.2 Copyright (C) 1994-2007 Lua.org, PUC-Rio "quit" to end > 10+20 30 > t = {10,20,30} > t {10,20,30} > _ {10,20,30} > m = {alice=1,john=2,betty=3} > m {betty=3,john=2,alice=1} > x+1 [string "local"]:1: variable 'x' is not declared > quit
Notice that variables must be declared before they are used; ilua
operates in strict mode. Any assignment to a variable (including nil) will declare it.
The fact remains that 10+20
is not a valid Lua statement. The technique I use is experimental compilation - first assume the user has typed an expression, and if that fails, attempt to compile as a statement. This shows the basic logic:
function eval_lua(line) -- is it an expression? local err,chunk = compile('_pretty_print('..line..')') if err then -- otherwise, a statement? err,chunk = compile(line) end -- if compiled ok, then evaluate the chunk if not err then err = evaluate(chunk) end -- if there was any error, print it out if err then print(err) end end
We get precise control of how values are printed out using the global _pretty_print
; its most important job is expanding tables. It is necessary to watch out for a few runaway situations. The table may be too large, or too deeply nested; it may contain circular references. ilua
deals with this by specifying a default length and depth; the function ilua.table_options
can be used to change the maximum length and depth (defaults to 20 and 7 respectively). For example, ilua.table_options {limit=100}
would allow you to print longer tables. It detects any table which has been previously printed, or is currently being printed. These are currently rendered as '{<self>}'. For example, calling require
for a well-defined library will return this kind of table, which has a circular reference _M
.
> require 'utils' {hex=function: 00487548,_PACKAGE='',printf=function: 003DD3F0,choose=function: 04878D8,import=function: 00487888,quit=function: 00486678,dump=function: 004875 0,_NAME='utils',timer=function: 00487638,_M={<self>},fprintf=function: 00485310 readf=function: 004878F8}
By default, ilua
tries to be clever about printing tables. I use a simple-minded scheme to decide whether they are list-like or map-like; sometimes it gets things wrong because not all tables are classifiable as one or the other. You can switch off the cleverness with table_options
:
> s = {1,2,bonzo='dog',felix='cat'} > s {1,2} > ilua.table_options {clever = false} > s {[1]=1,[2]=2,felix='cat',bonzo='dog'}
Before processing any options or filenames, ilua
attempts to load 'ilua-defs.lua' using require
, so it can be anywhere on your package path. This is a useful place to put any Lua code you want to load each time.
ilua
has several command-line flags. -l
works like the same option for Lua; it preloads the indicated library. -L
loads the library and brings all its functions into the global namespace.
$ lua ilua.lua -Lmath ILUA: Lua 5.1.2 Copyright (C) 1994-2007 Lua.org, PUC-Rio "quit" to end > sin(1.2) 0.93203908596723
-L
can bite you badly so it will warn of any collisions found:
$ lua ilua.lua -Lutils -Ltest ILUA: Lua 5.1.2 Copyright (C) 1994-2007 Lua.org, PUC-Rio "quit" to end warning: test.printf overwrites utils.printf warning: test.unpack overwrites global unpack
The -t
option will save a transcript of your session to a log file. If no file is specified, it uses 'ilua.log'. Since this option expects a parameter, it must be the last option on the line:
$ lua ilua.lua -t saving transcript "ilua.log" ILUA: Lua 5.1.2 Copyright (C) 1994-2007 Lua.org, PUC-Rio "quit" to end > 2.3*math(1.2) [string "local"]:1: attempt to call global 'math' (a table value) > 2.3*math.sin(1.2) 2.1436898977246 > quit $ cat ilua.log ! ilua -t > 2.3*math(1.2) [string "local"]:1: attempt to call global 'math' (a table value) > 2.3*math.sin(1.2) 2.1436898977246
Another way to get a log is -T
, which automatically generates a log file with the name ilua_yyyy_mm_dd_HH_MM.log
.
Importing a table into the global namespace is generally useful for testing, so ilua.import
is available. For example, ilua.import(require 'lfs')
will bring in the lfs
library and make all its functions global.
Sometimes it's useful to control the number of decimal places for floating-point output. ilua.precision
lets you set the width and precision for numbers (use a width of 0 if you don't particularly care). 'Integers' are treated slightly differently. To reset to the usual representation, just call ilua.precision
with no arguments.
> t = {sin(1),sin(1.5),sin(5.2)} > t {0.8414709848079,0.99749498660405,-0.88345465572015} > ilua.precision(0,2) > t {0.84,1.00,-0.88} > {10,20,30} {10,20,30} > ilua.precision() > t {0.8414709848079,0.99749498660405,-0.88345465572015}
Sometimes it's useful to change how ilua
prints out a particular type. ilua.print_handler
can be used to set a handler for a particular type:
> ilua.print_handler('function',function(f) return 'fun' end) > math.sin fun > math {log=fun,max=fun,acos=fun,huge=1.#INF,ldexp=fun,pi=3.1415926535898,cos=fun,tanh=fun, pow=fun,deg=fun,tan=fun,cosh=fun,sinh=fun,random=fun,randomseed=fun,frexp=fun, ceil=fun,floor=fun,rad=fun,abs=fun,sqrt=fun ... }
As mentioned, ilua
operates in strict mode. However, you can specify a handler that gets called if a variable cannot be found. This example makes operating system environment variables available as if they were read-only Lua variables:
> ilua.global_handler(function(s) return os.getenv(s) end) > COMSPEC 'C:\WINDOWS\system32\cmd.exe' > WINDIR 'C:\WINDOWS' > s [string "local"]:1: variable 's' is not declared
The final customization allows your code to see each line entered by the user. For instance, you can put this in your 'ilua-defs.lua' file:
ilua.line_handler(function (line) if line:sub(1,1) == '.' then -- a shell command! os.execute(line:sub(2)) return nil else return line end end)
Now if you type a line like '. ls' or '. dir' it will regard the rest of the line as a OS command! ilua
continues processing if you pass back a string, so you also have an opportunity to filter that string in some way.
You cannot enter multiline statements in ilua
, unlike with regular lua
:
> for i=1,5 do print(i) end 1 2 3 4 5 > for i=1,10 do [string "local"]:1: 'end' expected near '<eof>'
In practice this is not usually a problem, since one can always bring in code using dofile(filename)
at any stage. It would not be difficult to solve this, if there's any need.
It will be interesting to integrate ilua
with the PlutoLibrary for true 'workspace' persistence. This is a very productive mode offered by some language systems where you can save your session state, complete with functions and data, to be reloaded later.
ilua.lua
is available at Files:wiki_insecure/users/steved/ilua.lua.
An interactive GUI version of ilua (lconsole) that uses LuaInterface, is available here:
It has a useful little code pane where you can define multi-line functions and have them automatically saved.