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)