Interactive Lua

lua-users home

ilua is utility that gives you an extended interactive prompt which offers more features than standard Lua. It is implemented in pure Lua. See also CompleteWithReadline.

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, PUC-Rio
> = 10+20
> 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, PUC-Rio
"quit" to end
> 10+20
> t = {10,20,30}
> t
> _
> m = {alice=1,john=2,betty=3}
> m
> 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)
  -- if compiled ok, then evaluate the chunk
  if not err then
      err = evaluate(chunk)
  -- if there was any error, print it out
  if err then

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
> ilua.table_options {clever = false}
> s

Command-line options

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, PUC-Rio
"quit" to end
> sin(1.2)

-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, 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, 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)
> 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)

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.

Controlling Output Precision

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
> ilua.precision(0,2)
> t
> {10,20,30}
> ilua.precision()
> t

Customizing ilua

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
> math
 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)
> 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!
        return nil
        return line

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
> 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.

Further Work

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.

Source Code

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.


RecentChanges · preferences
edit · history
Last edited June 14, 2011 5:28 pm GMT (diff)