ensonic
ensonic

Reputation: 3440

lua environments and modules

Lets assume I have a module:

-- env.lua

local env = {}

function env.resolve(str)
  print("mod", _ENV)

  if _resolve_path ~= nil then
    return _resolve_path(str)
  else
    error("bad env")
  end
end

return env

and some code using it:

-- sandbox demo
-- run as: lua env-test.lua

env = require('env')

function _resolve_path(path)
  return "/" .. path
end

print("before main()")
print("", _ENV)
print("", env.resolve("test"))

local sandbox
do
  local _ENV = {
    print = print,
    env = env,
    _resolve_path = function (path)
      return "/chroot/" .. path
    end
  }
  function sandbox()
    print("from sandbox()")
    print("", _ENV)
    print("", env.resolve("test"))
  end
end

sandbox()

print("after main()")
print("", _ENV)
print("", env.resolve("test"))

What I'd like to achieve is that env.resolve() from sandbox() would use the custom _resolve_path function from the environment. It see that the environment is not applied to code called from the sandboxed function though. The goal is to augument how some modules behave depending on where they are called from. E.g. having sandbox{1,2,3}() with different local _resolve_path() functions.

Upvotes: 4

Views: 746

Answers (1)

Adam
Adam

Reputation: 3103

When you load your module with require it is bound to the global environment. Once a function is created in an environment it has that environment for its entire lifetime.

Prior to Lua 5.2 you could use set/getfenv to alter the environment, but environments are now lexical. The environment can only be changed with the debug library by changing the _ENV upvalue.

So, how can you run the same function within different environments? You can pass in the environment as a parameter:

function env.resolve(str, _ENV)
  print("mod", _ENV)
  if _resolve_path ~= nil then
    return _resolve_path(str)
  else
    error("bad env")
  end
end

Where you then call resolve like:

env.resolve('test', _ENV)

Or, if you would prefer the environment didn't have to be specified for every resolve call you can bind a resolve function to each new environment:

-- env.lua
local print = print
local error = error
local env = {}

-- this is the actual resolve function that takes the environment as a parameter
local function resolve_env(str, _ENV)
  print("mod", _ENV)
  if _resolve_path ~= nil then
    return _resolve_path(str)
  else
    error("bad env")
  end
end

-- this is the module (ie. global) resolve
function env.resolve(str)
  return resolve_env(str, _ENV)
end

-- this function binds a resolve function to a sandbox environment
function env.bind(_ENV)
  _ENV.env = {
    resolve = function(str)
      return resolve_env(str, _ENV)
    end
  }
  return _ENV
end

return env

The sandbox can now setup a bound resolve:

-- sandbox.lua
env = require 'env'

function _resolve_path(path)
  return "/" .. path
end

print("before main()")
print("", _ENV)
print("", env.resolve("test"))

local sandbox; do
  local _ENV = env.bind{
    print = print,
    _resolve_path = function (path)
      return "/chroot/" .. path
    end
  }

  function sandbox()
    print("from sandbox()")
    print("", _ENV)
    print("", env.resolve("test"))
  end
end

sandbox()

print("after main()")
print("", _ENV)
print("", env.resolve("test"))

Which will yield the result:

$ lua sandbox.lua
before main()
        table: 00612f40
mod     table: 00612f40
        /test
from sandbox()
        table: 0061c7a8
mod     table: 0061c7a8
        /chroot/test
after main()
        table: 00612f40
mod     table: 00612f40
        /test

Upvotes: 3

Related Questions