lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


On Friday 05 May 2006 10:47 pm, Raymond Jacobs wrote:
> I am trying to do all of this from C,
> so that example doesn't really help me,
> I especially don't know what __index is =/
>
> as I understand it, i want to set my environment to a, 'temporary' one,
> load the code, and then set it back (so i get all of my nice global
> variables and registred C functions and such)
> is that right?  and if so how would I acomplish that in C?

in fact, it's easiest to do all from Lua, if you REALLY need it from C, you 
could do a prototype in Lua and then port it to C.

i'll try to explain most of the concepts here, first in Lua, and then try to 
show how to port it to C.

first, when you load a file, what you get is a function representing the whole 
chunk. assume that the file defines some functions, like this:

----- script.lua --------

function foo (x)
  return x * 2
end

function bar (t)
  return foo(t.x)
end

--------------------

this Lua chunk in fact is two separate assignment statements:

----- script.lua -------

foo = function (x)
  return x*2
end

bar = function (t)
  return foo (t.x)
end

---------------------

to get the functions 'foo' and 'bar' defined, you have to execute the chunk 
you get when loading the file:

--- script loader (in lua) ----

local chunk = loadfile ("script.lua")      -- loads/compiles the lua chunk
chunk()    -- executes the chunk
foo(2)      -- calls a script function

------------------------------

if you do that, the script would set two global functions (foo and bar) in the 
global environment.  that's what you want to avoid.  to do that, just change 
the environment for the chunk function, like this:

--- script loader (in lua) ----

local scriptenv = {}   -- a new table (one for each script)
local chunk = loadfile ("script.lua")      -- loads/compiles the lua chunk
setfenv (chunk, scriptenv)    -- sets the 'global' env for the script
chunk()    -- executes the chunk

scriptenv.foo(2)      -- calls a script function

-----------------------------

now, the foo and bar variables won't be set in the 'real' global environment, 
but in a new 'private' global space.  you can use a new one for each script, 
or maybe if you have several groups you could use a different environment for 
each group.  scripts within one group would 'see' the same environment and 
could share global variables or functions.

but..... the localized environment is empty, the scripts can't access any 
function then don't define.  not even 'standard' ones, like print(), or 
open(), or any utility function you've defined for use.

the 'easy' solution is to make the private environment 'inherit' from the real 
global environment.  enter metatables

a metatable is a table associated with another object.  it defines some 
properties, like how to fetch and set for extra fields.  specifically, we 
need here the '__index' property.  if you set the '__index' field of a 
metatable, then it'll be used when trying to fetch a nonexistant key from the 
object that uses this metatable.  __index can contain either a function to be 
called to fetch the key, or (easier to use) a table to use as a 'fallback'.

to make the private environment inherit from the global one:

--- script loader (in lua) ----

local scriptenv = {}   -- a new table (one for each script)
local scriptenv_mt = {__index=_G}  -- the metatable for scriptenv
setmetatable (scriptenv, scriptenv_mt)

local chunk = loadfile ("script.lua")      -- loads/compiles the lua chunk
setfenv (chunk, scriptenv)    -- sets the 'global' env for the script
chunk()    -- executes the chunk

scriptenv.foo(2)      -- calls a script function

-----------------------------

now, when some code in the script tries to fetch a 'global' function (like 
print()), it won't find it in the environment, so Lua would look in the 
metatable, follow a link to the 'real' environment, and find it there.

this way, you can expose the whole 'global' environment, but at the same time 
protect it from being modified.  any 'global' changes the script does are 
limited to its private environment.

you can refine this a little if you want to define a small subset of 
functionality.  for example, you might not want the scripts being able to 
open files, or you want them to use a special version of print(), that sends 
data to your system, instead of going to the screen:

---------------------------------
local cage = {
  -- import some packages:
  string = string,  coroutine = coroutine,
  table = table, math = math,
  
  -- some 'global' functions:
  next = next, ipairs = ipairs, pairs = pairs,
  require = require, type = type,
  tonumber = tonumber, tostring = tostring,
  unpack = unpack, 
  setmetatable = setmetatable,
  getmetatable = getmetatable,

  -- modified global functions:
  print = myprint,
  error = myerror

  -- my own api:
  move = move
  kill = kill
}

local mt = {__index=cage}

function scriptloader (scriptname)
  local scriptenv = {}
  setmetatable (scriptenv, mt)
  
  local chunk  = loadfile (scriptname)
  setfenv (chunk, scriptenv)
  
  chunk ()

  return scriptenv
end
---------------------------

here you define a specific subset of Lua functionality, to be available to 
your scripts.  then, each script has it's own environment, that 'inherits' 
from this "cage".  the scriptloader() function loads a script file and 
executes it into this new private environment, and returns it.  if the script 
defines 'global' functions, they will be registered only in this new 
environment, without 'polluting' the real global environment.

you can have a table with the new environments, like this:

-------------
allscripts  = {}

allscripts[1] = scriptloader ("script.lua")
allscripts[2] = scriptloader ("secondscript.lua")

script[1].foo(x)      -- call the loaded function from first script
script[2].foo(x)      -- from second script
--------------

now......... you want to do this from C?  the easiest way is to just call 
scriptloader()

-- 
Javier

Attachment: pgpqP_Xjrp4kO.pgp
Description: PGP signature