Load Lua function chunk, modify environment in 5.2 with sandbox - lua

This question is similar to Modify Lua Chunk Environment, but with a twist.
I want to load strings as reusable Lua functions, where they all share the same sandbox for evaluation, but where each invocation I can pass in different values for the variables.
For example, say I want to evaluate the equation round(10*w/h) + a * b, with round, w, and h all values in a common sandbox environment, but where a and b are values that I want to modify each time I evaluate the equation.
This question shows how to dynamically change the environment for a compiled function. However, it does so setting the ENTIRE environment for the function, without the fallback sandbox that I have.
What's an efficient way to achieve my goals? Note that I almost only care about time needed to evaluate the function, not the setup time.
The way I have things currently, users write CSS expressions like:
box {
left: #x;
width: viewwidth - #w;
}
...where #x and #w are attributes of the box element (the 'local' variables) and viewwidth is a sheet-level variable set up elsewhere (my sandbox). Each of the property values—the portion after the :—is parsed out as a string to be compiled into a Lua function. They use normal Lua syntax, except where I currently swap the # with _el. in order to dereference the element table.
For answers to this question it is acceptable to keep this same syntax and require a differentiation between the local and sheet variables, BUT it is also acceptable to have a solution the gets rid of the # symbols and treats all variables the same.

I came up with 6 techniques for accomplishing the goal. The bottom of this post benchmarks their performance for evaluation speed. The fastest techniques require differentiating the local variables from sandbox variables in code:
local SANDBOX = {
w=1920,
h=1080,
round=function(n) return math.floor(n+0.5) end
}
local CODE = "round(10*w/h) + #a * #b"
function compile(code, sandbox)
local munged = 'local _el=... return '..code:gsub('#', '_el.')
return load(munged, nil, nil, sandbox)
end
local f = compile(CODE, SANDBOX)
print(f{a=1, b=2}) --> 20
print(f{a=3, b=4}) --> 30
If you don't want to differentiate the changing variables from those in the sandbox, or don't want to use a fragile sub() like the above, the next fastest is to mutate the __index of your locals and then pass it as the environment. You can wrap this in a helper function to make it easier:
local CODE = "round(10*w/h) + a * b"
function compile(code, sandbox)
local meta = {__index=sandbox}
return {meta=meta, f=load('_ENV=... return '..code)}
end
function eval(block, locals)
return block.f(setmetatable(locals, block.meta))
end
local f = compile(CODE, SANDBOX)
print(eval(f, {a=1, b=2})) --> 20
print(eval(f, {a=3, b=4})) --> 30
Here is the benchmark results of all the techniques below. Note that the fastest one can be even faster because unlike all other techniques the compile function returns a function that can be invoked directly, instead of wrapped in an evaluation helper script:
scope as a table, 2 : 0.9s (0.22µs per eval)
scope as a table : 1.1s (0.27µs per eval)
Use __ENV, change scope meta : 1.3s (0.32µs per eval)
blank env, change meta of scope : 1.6s (0.41µs per eval)
copy values over environment : 2.8s (0.70µs per eval)
setfenv, change scope meta : 3.0s (0.74µs per eval)
local SANDBOX = {
w = 1920,
h = 1080,
round = function(n) return math.floor(n+0.5) end
}
local TESTS = {
{env={a=1, b=2}, expected=18+2},
{env={a=4, b=3}, expected=18+12},
{env={a=9, b=7}, expected=18+63},
{env={a=4, b=5}, expected=18+20},
}
-- https://leafo.net/guides/setfenv-in-lua52-and-above.html
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
local techniques = {
["copy values over environment"]={
code="round(10*w/h) + a*b",
setup=function(code, fallback)
local env = setmetatable({},{__index=fallback})
return {env=env,func=load("return "..code,nil,nil,env)}
end,
call=function(block, kvs)
for k,v in pairs(block.env) do block.env[k]=nil end
for k,v in pairs(kvs) do block.env[k]=v end
return block.func()
end
},
["blank env, change meta of scope"]={
code="round(10*w/h) + a*b",
setup=function(code, fallback)
local kvsmeta = {__index=fallback}
local envmeta = {}
local env = setmetatable({},envmeta)
return {envmeta=envmeta,meta=meta,kvsmeta=kvsmeta,func=load("return "..code,nil,nil,env)}
end,
call=function(block, kvs)
block.envmeta.__index=kvs
setmetatable(kvs, block.kvsmeta)
return block.func()
end
},
["setfenv, change scope meta"]={
code="round(10*w/h) + a*b",
setup=function(code, fallback)
return {meta={__index=fallback}, func=load("return "..code)}
end,
call=function(block, kvs)
setmetatable(kvs,block.meta)
setfenv(block.func, kvs)
return block.func()
end
},
["Use __ENV, change scope meta"]={
code="round(10*w/h) + a*b",
setup=function(code, fallback)
local meta = {__index=fallback}
return {meta=meta, func=load("_ENV=... return "..code)}
end,
call=function(block, kvs)
return block.func(setmetatable(kvs, block.meta))
end
},
["scope as a table"]={
-- NOTE: requires different code than all other techniques!
code="round(10*w/h) + _el.a * _el.b",
setup=function(code, fallback)
local env = setmetatable({},{__index=fallback})
return {env=env,func=load("return "..code,nil,nil,env)}
end,
call=function(block, kvs)
block.env._el=kvs
return block.func()
end
},
["scope as a table, 2"]={
-- NOTE: requires different code than all other techniques!
code="round(10*w/h) + _el.a * _el.b",
setup=function(code, fallback)
return load("local _el=... return "..code,nil,nil,fallback)
end,
call=function(func, kvs)
return func(kvs)
end
},
}
function validate()
for name,technique in pairs(techniques) do
local f = technique.setup(technique.code, SANDBOX)
for i,test in ipairs(TESTS) do
local actual = technique.call(f, test.env)
if actual~=test.expected then
local err = ("%s test #%d expected %d but got %s\n"):format(name, i, test.expected, tostring(actual))
io.stderr:write(err)
error(-1)
end
end
end
end
local N = 1e6
function benchmark(setup, call)
for name,technique in pairs(techniques) do
local f = technique.setup(technique.code, SANDBOX)
local start = os.clock()
for i=1,N do
for i,test in ipairs(TESTS) do
technique.call(f, test.env)
end
end
local elapsed = os.clock() - start
print(("%-33s: %.1fs (%.2fµs per eval)"):format(name, elapsed, 1e6*elapsed/#TESTS/N))
end
end
validate()
benchmark()

Related

OOP Help - How do I get this code block to recognize one of its arguments?

I'm learning Lua and how to implement OOP. Trying out a test example of an object seems to return one of the argument of the object as 'null' despite being assigned one.
function Character(Name, Level, Class) --Constructor
return {GetName = T.GetName, GetLevel = T.GetLevel, GetClass = T.GetClass}
end
end
-- Snippets
Player = Character("Bob", 1, "Novice")
When I try printing Player.GetName() it returns null instead of Bob. Where have I gone wrong?
Here is the full code.
OOP in Lua takes a bit more than what you have done there, you will want to make use of metatables and upvalues.
-- How you could define your character structure.
local Character = {}
function Character.GetName(self)
return self.name
end
function Character.new(Name, Level, Class)
local _meta = {}
local _private = {}
_private.name = Name
_private.level = Level
_private.class = Class
_meta.__index = function(t, k) -- This allows access to _private
return rawget(_private, k) or rawget(Character, k)
end
_meta.__newindex = function(t, k, v) -- This prevents the value from being shaded
if rawget(_private, k) or rawget(Character, k) then
error("this field is protected")
else
rawset(t, k, v)
end
end
return setmetatable({}, _meta) --return an empty table with our meta methods implemented
end
This creates a local table _private when you create a new instance of a Character. That local table is an upvalue to the _meta.__index and it cannot be accessed outside the scope of the Character.new function. _private can be accessed when __index is called because it is an upvalue.
-- How to use the character structure
player = Character.new("Bob", 10, "Novice")
npc = Character.new("Alice", 11, "Novice")
print(player:GetName())
I use player:GetName(), but in all honesty you can just do player.name as well.
Resources for more on this topic:
http://tutorialspoint.com/lua/lua_metatables.htm
http://lua-users.org/wiki/ObjectOrientationTutorial

quoting troubles in Lua

I am trying to make a quote function in lua, so i can use the arguments as strings but without quotes or access them in some environment. Much like in the second comment on this question
w = print
function test()
local function _ix( _ , k )
w( " _ix \\ " , _ , k )
local v = rawget( _G , k )
w( " <-- " , k )
return k
end
local _ = setmetatable( {} , { __index = _ix } )
local function q( _ ) return _ end
q = setfenv( q , _ )
return q
end
So, when I run it:
q = test()
w( "q( uno )" , q( uno ) )
It doesn't even call the __index metamethod:
---------- Capture Output ----------
q( uno ) nil
> Terminated with exit code 0.
So, what I'm doing wrong?
If I'm understanding correctly, then what you're trying to do doesn't make much sense. uno will be looked up in the environment that q is called in, not with. In your example it's like calling q(nil). The example from the other question works because they're working in the same, global environment.
You can use write a helper function to intercept your current environments nil-lookups, but it must be called preemptively in any environment you want to use these nil-to-string lookups.
local function intercept (tab)
setfenv(2, setmetatable(tab or {}, {
__index = function (_, key)
return key
end
}))
end
And you'll need an environment cloning function, unless you want to manually create your sandboxes every time, else you'll probably mess up parent environments (e.g., _G). You could move this logic inside of intercept for a cleaner function call, but with less flexibility.
local function clone_current_env ()
local env = {}
for key, value in pairs(getfenv(2)) do
env[key] = value
end
return env
end
Using them together, you can cause nil lookups in whichever environment you're in to become strings.
intercept(clone_current_env())
print(type(foo), type(bar)) --> string string
This is some ugly metaprogramming, and I don't really know why you'd want to write code like this, except as a proof of concept.
A full example.
DEMO
local function clone (tab)
local new = {}
for key, value in pairs(tab) do
new[key] = value
end
return new
end
local function enable_nil_strings ()
setfenv(2, setmetatable(clone(getfenv(2)), {
__index = function (env, key)
return key
end
}))
end
local function disable_nil_strings()
setmetatable(getfenv(2), nil)
end
-----------------------------------------------------
print(type(foo), type(bar)) --> nil nil
enable_nil_strings()
print(type(foo), type(bar)) --> string string
disable_nil_strings()
print(type(foo), type(bar)) --> nil nil
Finally, arguably the best way to implement this would be to simply wrap around an execution context:
local function with_nil_strings (context, ...)
local env = {}
for key, value in pairs(getfenv(2)) do
env[key] = value
end
setfenv(
context,
setmetatable(env, {
__index = function (_, key) return key end
})
)
context(...)
end
print(type(foo)) --> nil
with_nil_strings(function ()
print(type(foo)) --> string
end)
print(type(foo)) --> nil

How to save boolean conditions and evaluate later

I need to create a structure. The structure must contain an array of "boolean conditions". Something like this:
function ReturnStructure ()
local structure = {
{A < 10},
{B == "smth"},
{FunctionReturnsTrueOrFalse(params)},
--...
}
return structure
end
structure = ReturnStructure()
print(structure[1][1]) -- prints true or false depending on the value of A
In fact these tables contain true or false, not conditions, because when we call function ReturnStructure and it creates a local table structure, all conditions in the fields will be executed. I want to create a structure whose fields will contain not boolean values, but something that I can execute (when I want to do it) and get a boolean value. I can achieve this by using anonymous functions:
function ReturnStructure ()
local structure = {
{function() return A < 10 end},
{function() return B == "smth" end},
{FunctionReturnsTrueOrFalse, params}, -- I don't call function in this line, a just put its adress and parameters to table.
--...
}
return structure
end
structure = ReturnStructure()
print(structure[1][1]) -- prints function: 0x109bdd0
print(structure[1][1]()) -- prints true or false. I execute condition in this string.
So, there is a code which works as I want it to, but it seems very ugly.
I want to hear some ideas on how to create a simpler and more beautiful table, without printing function () return ... in every field. I think that I should use a simple OOP implementation to create my structure as an object, but I don't know how to do it. I also will be happy to get some references to methods, implementations, articles etc., which can help me to find some ideas.
I want to hear some ideas on how to create a simpler and more beautiful table, without printing function () return ... in every field.
There aren't. If Lua had C#'s lambda syntax, you could write:
local structure = {
() => A < 10,
() => B == "smth",
() => FunctionReturnsTrueOrFalse(params),
But Lua likes to keep things small and simple, to avoid adding size and complexity to the language and its implementation, so we have one syntax for one function type.
You could store them as strings, then compile and run them later, but that's choosing form over function. You don't want to be invoking the compiler unnecessarily.
local structure = {
'A < 10',
'B == "smth"',
'FunctionReturnsTrueOrFalse(params)',
So your original solution is better. I don't particularly like the way that the first two items defer evaluating their arguments, while your third example evaluates the parameters at compile time. It would be more consistent to treat the FunctionReturnsTrueOrFalse the same
local structure = {
function() return A < 10 end,
function() return B == "smth" end,
function() return FunctionReturnsTrueOrFalse(param1) end,
This also means you don't need to put them in tables. Each is just a function you call, which simplifies the calling code as well.
If you really wanted to evaluate FunctionReturnsTrueOrFalse's arguments at compile time, I'd write a utility routine to build a closure from a function and its arguments:
local function bind(f, ...)
local args = {...}
return function() f(unpack(args)) end
end
Then use that to bind the function to its args:
local structure = {
function() return A < 10 end,
function() return B == "smth" end,
bind(FunctionReturnsTrueOrFalse, param1, param2, param3),
Then everything in your table is simply a function so you don't need special handling
function ReturnStructure ()
local structure = {
{'A < 10'},
{'B == "smth"'},
{FunctionReturnsTrueOrFalse, 'parameter'},
}
local function call_me(f, ...)
return (type(f)=='function' and f or
assert((load or loadstring)('return '..f)))(...)
end
return setmetatable({}, {
__index =
function(t,k)
if structure[k] then
return call_me((table.unpack or unpack)(structure[k]))
end
end,
__newindex = function(t,k,v) structure[k] = v end
})
end
A = 2
B = "anything"
function FunctionReturnsTrueOrFalse(par)
return #par > 5
end
structure = ReturnStructure()
print(structure[1]) -- true
print(structure[2]) -- false
print(structure[3]) -- true
structure[4] = {'1==0'}
print(structure[4]) -- false

Access local variable by name

With globals you can use _G[name] to access the global variable name if you have a string "name":
function setGlobal(name, val)
_G[name] = val
end
If you have
-- module.lua
local var1
local var2
there is no _L that would allow you to do the equivalent for locals:
function setLocal(name, val)
_L[name] = val -- _L doesn't exist
end
Is there another way that you could access a local variable by string representing its name?
You can use debug.getlocal() and debug.setlocal() in the debug library:
function setLocal(name, val)
local index = 1
while true do
local var_name, var_value = debug.getlocal(2, index)
if not var_name then break end
if var_name == name then
debug.setlocal(2, index, val)
end
index = index + 1
end
end
Test:
local var1
local var2
setLocal("var1", 42)
print(var1)
Output: 42
I strongly recommend not using getLocal, it's a function in the debug library which should never be used in official commercial uses because it affects performance and opens huge vulnerabilities for hackers to exploit! Never depend on debug functions for your logic.
If you really need this, then why not define a dictionary _L, then:
local _L = {}
_L.var1 = ...
_L.var2 = ...
The pattern above is not against the rules of Lua's design.

Can I extract stack values from a parent function in Lua?

My game engine pushes a value on to the lua stack as a parameter to a function and then invokes it using lua_pcall. The lua code will run and call additional lua functions. Eventually this lua code will invoke a C function. Is it possible for this function to retrieve the value that was originally pushed on to the stack?
Its like this:
<engine function A>
pushes parameter value X on to stack for lua
<lua func>
<lua func>
<lua func>
<engine function B>
can I extract the values X that was pushed by function A here?
Yes, with a combination of getinfo, getlocal and getupvalue you can get all that information (you can even change those values using set* functions).
Here is a fragment from MobDebug that returns stack information along with a table of locals and upvalues at each level. The variables at each level will be indexed in the same order they appear in the code (starting from parameters). For each get* function you can use their C equivalents (lua_getinfo, lua_getlocal, and lua_getupvalue), but the logic should be exactly the same.
local function stack(start)
local function vars(f)
local func = debug.getinfo(f, "f").func
local i = 1
local locals = {}
while true do
local name, value = debug.getlocal(f, i)
if not name then break end
if string.sub(name, 1, 1) ~= '(' then locals[name] = {value, tostring(value)} end
i = i + 1
end
i = 1
local ups = {}
while func and true do -- check for func as it may be nil for tail calls
local name, value = debug.getupvalue(func, i)
if not name then break end
ups[name] = {value, tostring(value)}
i = i + 1
end
return locals, ups
end
local stack = {}
for i = (start or 0), 100 do
local source = debug.getinfo(i, "Snl")
if not source then break end
table.insert(stack, {
{source.name, source.source, source.linedefined,
source.currentline, source.what, source.namewhat, source.short_src},
vars(i+1)})
if source.what == 'main' then break end
end
return stack
end

Resources