Simulated Non Local In Strings

lua-users home
wiki

Simulating non-local variables in compiled files or strings

By default the files or strings previously compiled by using loadfile and loadstring, respectively, and executed afterwards directly or by using pcall, work in the global environment. Their effects in the caller are only possible through the global table _G and the corresponding return var_list. In the following we work with the string case.

Therefore, the local and non-local variables of the caller function or chunk are not inherited by the string. Besides, non-local variables do not exist for the string (and we cannot use the string as a closure).

However we could wish to use and/or modify some non-local variable inside the string. The only way of doing it is through the design of a function that prepares a new environment with a table (now a global one inside the string) as a proxy with the required non-local variables. These must be used inside the string as if they were global, but with some prefix. We chose _U. Inside the function defined as a string they will be the fields of a global table _U. We also need some functions of the debug library (this is a necessary drawback) with the aim of modify the upvalues in the return.

Therefore, the use of non-local variables is simulated, but it works.

We need a function, callstring, with arguments:

The return of callstring is a status with nil in case of error or true if all is correct, and the return of proc.

Let put a simple example:

s = [[ _U.c = 11; _U.d = 22; return ... ]]
proc = loadstring(s)

local c = 1

function example (proc)
   local d = 2
   local result = {callstring(proc, {c = c, d = d}}, nil, "a", "b")}}
   print(d)       -- possibly modified inside proc
   return result  -- a table with the status and the results of proc
end

example(proc)
print(c)          -- possibly modified inside proc

In this, c is a non-local variable in example because it is used inside example (if not Lua 'forgot' it for the moment). On the other hand, d is a local variable in example. Both behave like upvalues inside proc: they can be accessed and modified with the names _U.c and _U.d. Even more, the modified values are transferred to example.

The key is that the table _U will have two fields _U.c and _U.d, initialised with the values of c and d, respectively. Besides, by using c and d in the invocation of callstring we assure that both variables work like upvalues inside proc.

Other fields passed in _U to callstring are treated as local variables inside proc. In the calling:

callstring(proc, {c = c, d = d, x = "xx"}}, nil, "a", "b")

x is not an upvalue, because it does not exist in some place in the caller closure. In this case _U.x takes the value "xx" in the string, and it behaves like a local variable inside the string.

It is time to present callstring.

-- Calling a function obtained from a string or file
-- with simulated upvalues
--
-- Arguments:
-- proc    loaded string or file
-- _U      is a new local and non-local table;
--         if it is nil then we suppose it {}}
-- func    is the function from we invoked 'callstring';
--         can be nil if the invocation is not a tail return
-- ...     the arguments to string calling

function callstring (proc, _U, func, ...)

   _U = _U or {}}
   if type(_U) ~= "table" then
      return nil, "Second argument of callstring must be a table or nil"
   end

   -- determine the calling function
   func = func or debug.getinfo(2, "f").func

   -- count _U fields
   local nt = 0
   for _, _ in pairs(_U) do nt = nt+1 end

   if func == nil and nt ~= 0 then
      return nil, "Callstring invoked in a tail call cannot evaluate string"
   end

   -- both _G and _U are passed as global in the new environment
   -- the direct indexing of _G is permitted for accessing (not modifying)

   local newgt = {_U = _U}}
   setmetatable(newgt, {__index = _G}})
   setfenv(proc, newgt)

   -- proc is executed with arguments
   local result = {proc(...)}}

   -- when _U is {}} (no upvalues) the return is always possible 
   -- (even in a tail call)
   if nt == 0 then return true, unpack(result) end

   -- modify local and non-local variables of the calling routine
   -- (adapted from PIL2 chapter 23)
   for n, v in pairs(_U) do
      local found = false
      -- non-local variable
      for i = 1, math.huge do
         local m, _ = debug.getupvalue(func, i)
         if not m then break end
         if m == n then
            found = true
            debug.setupvalue(func, i, v)
            break
         end
      end
      if not found then
         -- local variable
         local ipos
         for i = 1, math.huge do
            local m, _ = debug.getlocal(2, i)
            if not m then break end
            if m == n then ipos = i end
            -- the last found is the correct
         end
         if ipos then debug.setlocal(2, ipos, v) end
      end
   end

   return true, unpack(result)
end

A more elaborate example of calling could be:

local c = 1
local h = 2
g = 3   -- global

local s = [[
     local f = 99           -- local inside the string
     _U.c = 11              -- non-local of something
     _U.h = 22              -- internal (not passed in table)
     _U.z = 4               -- internal (not passed in table)
     _U.d = 77              -- local of something
     _G.zzz = table.concat({...}}, "+") -- global of new creation
     print("_U.x = ", _U.x) -- internal (because in table does not
                            -- correspond to any local/non-local)
     _G.g = 33              -- previously existent global
     _G.v = f               -- global of new creation
     return 10*g, 10*v      -- return
]]

-- loading the string
local proc, msg = loadstring(s)

function something (proc)
   local d = 3

   if proc == nil then error(msg) end

   local r = {callstring(proc, {c = c, d = d, x = "xx"}}, nil, "a", "b")}}

   -- printing a local modified inside the string as an upvalue
   print("d = ", d)

   return unpack(r)
end

local r = {something(proc)}}

if not r[1] then
   print("error in callstring: " .. r[2])
else
   k1 = r[2]
   k2 = r[3]
end

print(c, h, g, v, z)
print(k1, k2)
print(zzz)

The output of this program is:

_U.x =          xx                  --< internal variable in the string
d =     77                          --< local variable in something
11      2       33      99      nil --< local, local, global, global, global
330     990                         --< return of string
a+b                                 --< global zzz modified inside the string

As a conclusion, it will be good if Lua executed compiled strings and files as other 'normal' function. But, for the moment we have to 'circumvent' Lua with some constructions like the one presented.

-- JulioFernandez

See Also


RecentChanges · preferences
edit · history
Last edited March 23, 2007 5:26 am GMT (diff)