Search code examples
luaenvironment-variablesenvironmentevaluation

Evaluating expression in Lua in an Environment


This question has some reference to the question Evaluating expression in Lua in Mathematics Environment The following code works.

tbl = {}
tbl.sin = math.sin
tbl.cos = math.cos

function mathEval(exp)
  return load("return " .. exp, exp, "t", tbl)()
end

print(mathEval("sin(0)"))
print(mathEval("sin(0)+cos(1)+2^2"))

However, the following code does not work.

tbl = {}
tbl.sin = math.sin
tbl.cos = math.cos

function mathEval(exp)
  return load("return " .. tostring(exp), tostring(exp), "t", tbl)()
end

print(mathEval(sin(0)))
print(mathEval(sin(0)+cos(1)+2^2))

I want to evaluate expressions without using quotes. How can that be done?


Solution

  • The problem with the line print(mathEval(sin(0)+cos(1)+2^2)) is that the argument of mathEval is evaluated before mathEval runs, so evaluating the variables sin and cos can not be deferred to the environment of mathEval; that is, mathEval gets a value, and no expression to evaluate at all!

    First of all, one option to evaluate such mathematical expressions without the use of mathEval would be to simply temporarily change your environment:

    local prev_env = _ENV -- this is needed to restore the environment later on
    _ENV = tbl -- enter custom environment
    local result = sin(0)+cos(1)+2^2
    _ENV = prev_env -- restore environment
    print(result)
    

    if you want mathEval as a convenience helper, you'll have to pass the expression as a function returning the value to the expression such that calling the function will evaluate the expression; this allows you to defer the initialization. You'll have to use a powerful function called setfenv which allows you to change the environment of func; this was unfortunately removed in favor of _ENV in Lua 5.2 and later. The code then becomes trivial:

    local function mathEval(func)
        setfenv(func, tbl)
        return func
    end
    mathEval(function() return sin(0)+cos(1)+2^2 end)
    

    setfenv can be replicated in Lua 5.2 using the debug library, since Lua internally implements _ENV as an upvalue, as shown by Leafo:

    local function setfenv(fn, env)
      local i = 1
      while true do
        local name = debug.getupvalue(fn, i)
        if name == "_ENV" then
          debug.upvaluejoin(fn, i, (function()
            return env
          end), 1)
          break
        elseif not name then
          break
        end
    
        i = i + 1
      end
    
      return fn
    end