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.
Related
In javascript we can do the following:
var someString = `some ${myVar} string`
I have the following lua code, myVar is a number that needs to be in the square brackets:
splash:evaljs('document.querySelectorAll("a[title*=further]")[myVar]')
Function that fits you description is string.format:
splash:evaljs(string.format('document.querySelectorAll("a[title*=further]")[%s]', myVar))
It is not as verbose as ${}. It is more of a good old (and hated) sprintf.
I have written an emulation of Python's f'' strings, which is a set of functions you can hide inside a require file. So, if you like Python's f'' strings, this may be what you're looking for.
(If anyone finds errors, please notify.)
It's quite big compared to the other solution, but if you hide the bulk in a library, then its use is more compact and readable, IMO.
With this library you can do the following, for example:
require 'f_strings'
a = 12345
print(f'Number: {a}, formatted with two decimals: {a::%.2f}')
-- Number: 12345, formatted with two decimals: 12345.00
Note the use of Lua string.format formatting codes, and the use of double colon (instead of Python's single colon) for format specifiers because of Lua's use of colon for methods.
I have extracted only the relevant functions from a larger library. Although some optimizations may be possible for this specific use case, I leave them unchanged as they are general purpose and may also be useful for other purposes.
And here's the required library (placed somewhere in your Lua libraries folder):
-- f_strings.lua ---
unpack = table.unpack or unpack
--------------------------------------------------------------------------------
-- Escape special pattern characters in string to be treated as simple characters
--------------------------------------------------------------------------------
local
function escape_magic(s)
local MAGIC_CHARS_SET = '[()%%.[^$%]*+%-?]'
if s == nil then return end
return (s:gsub(MAGIC_CHARS_SET,'%%%1'))
end
--------------------------------------------------------------------------------
-- Returns iterator to split string on given delimiter (multi-space by default)
--------------------------------------------------------------------------------
function string:gsplit(delimiter)
if delimiter == nil then return self:gmatch '%S+' end --default delimiter is any number of spaces
if delimiter == '' then return self:gmatch '.' end
if type(delimiter) == 'number' then --break string in equal-size chunks
local index = 1
local ans
return function()
ans = self:sub(index,index+delimiter-1)
if ans ~= '' then
index = index + delimiter
return ans
end
end
end
if self:sub(-#delimiter) ~= delimiter then self = self .. delimiter end
return self:gmatch('(.-)'..escape_magic(delimiter))
end
--------------------------------------------------------------------------------
-- Split a string on the given delimiter (comma by default)
--------------------------------------------------------------------------------
function string:split(delimiter,tabled)
tabled = tabled or false --default is unpacked
local ans = {}
for item in self:gsplit(delimiter) do
ans[#ans+1] = item
end
if tabled then return ans end
return unpack(ans)
end
--------------------------------------------------------------------------------
function copy(t) --returns a simple (shallow) copy of the table
if type(t) == 'table' then
local ans = {}
for k,v in next,t do ans[ k ] = v end
return ans
end
return t
end
--------------------------------------------------------------------------------
function eval(expr,vars)
--evaluate a string expression with optional variables
if expr == nil then return end
vars = vars or {}
assert(type(expr) == 'string','String expected as 1st arg')
assert(type(vars) == 'table','Variable table expected as 2nd arg')
local env = {abs=math.abs,acos=math.acos,asin=math.asin,atan=math.atan,
atan2=math.atan2,ceil=math.ceil,cos=math.cos,cosh=math.cosh,
deg=math.deg,exp=math.exp,floor=math.floor,fmod=math.fmod,
frexp=math.frexp,huge=math.huge,ldexp=math.ldexp,log=math.log,
max=math.max,min=math.min,modf=math.modf,pi=math.pi,pow=math.pow,
rad=math.rad,random=math.random,randomseed=math.randomseed,
sin=math.sin,sinh=math.sinh,sqrt=math.sqrt,tan=math.tan,
tanh=math.tanh}
for name,value in pairs(vars) do env[name] = value end
local a,b = pcall(load('return '..expr,nil,'t',env))
if a == false then return nil,b else return b end
end
--------------------------------------------------------------------------------
-- f'' formatted strings like those introduced in Python v3.6
-- However, you must use Lua style format modifiers as with string.format()
--------------------------------------------------------------------------------
function f(s)
local env = copy(_ENV) --start with all globals
local i,k,v,fmt = 0
repeat
i = i + 1
k,v = debug.getlocal(2,i) --two levels up (1 level is this repeat block)
if k ~= nil then env[k] = v end
until k == nil
local
function go(s)
local fmt
s,fmt = s:sub(2,-2):split('::')
if s:match '%b{}' then s = (s:gsub('%b{}',go)) end
s = eval(s,env)
if fmt ~= nil then
if fmt:match '%b{}' then fmt = eval(fmt:sub(2,-2),env) end
s = fmt:format(s)
end
return s
end
return (s:gsub('%b{}',go))
end
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()
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
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
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