Lua script functions

I recently added Lua scripting to my data-driven component system (which is itself written in Lua). I wanted a script file to behaves like a function call (as opposed to a module).

Lua compiles a script into a “chunk”, which is treated as a function with a variable number of arguments, and may return a value (or multiple values, another useful Lua feature). This differs from using Lua’s require() function, which compiles the script and then runs it.

The ellipsis “...” operator is used to access script arguments [introduced in Lua 5.1]. Assigning the ellipsis operator to local variables makes them act like function arguments, e.g. a simple script which adds two numbers could be written like this:

local x,y = ...
return x + y

When compiled into a chunk, that script is equivalent to the following function:

function(x,y)
  return x + y
end

The script can be loaded and compiled with loadfile():

local f = assert( loadfile( "addTwoNumbers.lua" ) )

After load, the script function f can be called just like any other function:

local z = f(19,23)
print(z) --> 42

This is mentioned briefly in Programming in Lua (PiL) and can be cobbled together from information in the Lua 5.1 Reference Manual, but documentation is a bit light. PiL contains many more details about require() and modules, which are great for exposing a set of related functionality, but are cumbersome overkill for light scripting.

In my data-driven system, the same script is likely to be requested many times by different components, so I added a cache to avoid multiple compilation of the same file. The first time a script is requested, it is compiled and the function is stored in the cache. Later requests for the same script return the function from the cache.

This script functionality is provided as a data-driven component within the component system. For convenience when writing very short scripts, I also added another component which compiles a string instead of a script (it uses a cache, as well).

2 Comments

  • uki says:

    wouldn’t it be easier to just write a callable module?
    local _M = {}
    function _M.__call(x, y) return x+y end
    return _M

    • John Giors says:

      Short answer: No.

      Long answer:

      Because I can think of no other reason, I assume your question is raised because it looks like a lot of work to keep adding lines like the following for each function:

      local f = assert( loadfile( "addTwoNumbers.lua" ) )

      However, that is just an example of usage. In the situation where I am using this, the loader code is generic and only needed to be written once. Now, inside the data-driven system, the following entry inside a data-driven table would create a function:

      f = {_func="local x,y=... ; return x + y"}

      That’s much simpler than creating a callable module. And, since this is data driven, tools can easily parse and manipulate the function as a string. The data-driven system also supports placing (generally longer) functions in a separate file.

      This is a nice format for user-defined scripts. All users need to know is that local a,b,etc = ... is used to accept args, and that value(s) may be returned from the function. They don’t need to know (nor worry) about boilerplate module code.

      There are several other reasons for not using modules in this case, but the two primary reasons are:

      1. Module code is designed for use with require, which means the user would be obligated to use files, without the option for embedded strings.
      2. I’m using a sandbox, which I don’t think can be done with require.

      BTW, the sample you gave needs to set the metatable, and generally, modules are set as the function environment. It would look more like this:

      local _M = {}
      _M.__index = _M
      setmetatable(_M,_M)
      setfenv(1,_M)
      function _M.__call(x, y) return x+y end
      return _M

Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>