So I've become annoyed at the global by default style in Lua. I'm trying to write a program which will make all programs that run after it incapable of creating global variables. When they try to, that variable will be set to the function environment of that program. I've come up with this, and it seems to work, but for some reason is throwing an error in [edit: 9] in the ComputerCraft rom/programs/edit. When I run a test program,
a = 1
print(a)
it works fine and prevents the global variable from being created while still allowing it to be accessed by that program, but it doesn't work for other programs. I've tried doing _G.a, local a, and other things, but all of them work. Does anyone have any ideas why it might not work on other programs?
local oldload = loadfile
function _G.loadfile(str)
local func = oldload(str)
local env = {}
env._G = env
setmetatable(env, {__index = _G, __newindex =
function(table, var, val)
rawset(env, var, val)
end})
setfenv(func, env)
return func
end
Lua is designed to be an embedded language. This means that the ultimate arbiter is the host language, C.
One Lua script can achieve dominance over another via sandboxing techniques. You didn't fully sandbox your scripts. You changed loadfile, but you didn't change load or dofile.
But that doesn't matter. Why? Because in terms of dominance, C always wins. See, C doesn't call the Lua loadfile function. Well, it can obviously, but it usually doesn't. Instead it calls the Lua API luaL_loadfile.
Lua code can establish a sandbox for other Lua code that it directly loads. But a Lua-based sandbox has no effect on C code, unless that code is deliberately designed to live in a Lua sandbox. And most C libraries aren't.
What this means is that, once you decide to run in a C execution environment that you can't control, your Lua sandbox means nothing. The C code can, and in many cases will, load scripts outside of your control and give them whatever environments that it wants.
And there's nothing you can do about it from Lua. The only way to resolve this is to modify the Lua .dll itself to establish your sandbox.
I found out the solution. It turns out that the shell API, an API used in many ComputerCraft programs, was not in _G, thus was not accessible when I applied the sandbox. Here's my new, functional code:
local oldload = load
function _G.load(str, arg1, arg2, arg3)
local func = oldload(str, arg1, arg2, arg3)
local env = getfenv(func)
if (env == _G) then
env = {}
env._G = env
end
setmetatable(env, {__index = _G, __newindex = function(table, var, val) rawset(env, var, val) end})
setfenv(func, env)
return func
end
Related
This is a question about closures in Lua. I stumbled across a problem (and workaround) while trying to make an object registrar object as follows:
tracker = {
objList = {},
myRegister = function(self, obj)
table.insert(self.objList, obj)
return "hello"
end,
myInit = function(self)
local i, obj
for i, obj in ipairs(self.objList) do
obj:init()
end
end,
}
-- Note: As written, this does *not* work.
-- It *will* work if I separate the line into two parts as follows:
-- local myvar
-- myvar = tracker:myRegister({
local myvar = tracker:myRegister({
init = function(self)
-- This will generate an error complaining that myvar
-- is a global variable with a "nil" value
print("myvar = " .. myvar)
end,
})
tracker:myInit()
It seems that if I declare the local variable, "myvar", in the same statement which creates a closure, then the local variable is not accessible from the closure. If, however, I merely assign to an already-existing local variable, then that variable is accessible from the closure.
Obviously, I know how to fix this: Just declare myvar separately.
My question, however, is this: Why is this necessary? Is this by design, or is it a bug in the compiler and/or virtual machine? If it's by design, where is it documented? I'm particularly interested in whether this behavior has other implications, and what those might be, and I'm hoping that the documentation (again assuming this is the intended behavior) will shed some light on this.
Yes, this is the intended behavior.
It is documented in the Lua manual §3.5 – Visibility Rules
This feature allows you to write the following code:
print"Beginning of log"
do
local print =
function(...)
print(os.date"%T", ...) -- Here you're invoking global "print"
end
-- inside this do-end block "print" is automatically adding current time
print"Some event"
print"Another event"
end
print"End of log"
In other words, while the shadowing object is being created, the original object is still accessible.
This is quite useful.
I'm struggling with the "global" aspect of functions as it relates to modules. Maybe someone here could tell me if this example would work, explain why and then tell me the right way to do it.
If I have two modules:
f1.lua
local mod = T{}
function mod.print_msg(msg)
print(msg)
end
return mod
f2.lua
local mod = T{}
function mod.print_hello()
msgmod.print_msg('Hello')
end
return mod
and both are called in a "main" file
msgmod = assert(loadfile(file_path .. 'f1.lua'))()
himod = assert(loadfile(file_path .. 'f2.lua'))()
himod.print_hello()
Would print_hello still work if called from f2 or would I need to loadfile() f1.lua in f2?
It would work if called after the msgmod = ... has been executed (in any file), but not before. This is a confusing situation due to the usage of globals.
Typically, you do not want to use globals like this in modules. You should handle dependencies using require just as you would #include them in C++. So, f2.lua, which wants to use print_msg defined in f1.lua, might look like this:
local f1 = require('f1')
local mod = T{}
function mod.print_hello()
f1.print_msg('Hello')
end
return mod
You should also use require in your main file (and get in the habit of making everything local):
local msgmod = require('f1')
local himod = require('f2')
himod.print_hello()
Note that we could have omitted the first line, since we aren't actually using f1 in main, and f2 will require it automatically when we require f2. Unlike loadfile, require automatically caches loaded modules such that they are loaded only once. Again, require is almost always what you want to use.
The general pattern for writing modules is to require all dependency modules into locals, then use them as you like to implement your module functions:
local dep1 = require('dep1')
local dep2 = require('dep2')
...
local mod = {}
function mod.foo ()
return dep1.bar(dep2.bazz())
end
return mod
I'm writting a basic event handler in lua which uses some code located in another module
require "caves"
script.on_event({defines.events.on_player_dropped_item}, function(e)
caves.init_layer(game)
player = game.players[e.player_index]
caves.move_down(player)
end
)
but whenever the event is triggered i get following error
attempt to index global 'caves' (a nil value)
why is this and how do i solve it?
You open up the module in question and see what it exports (which global variables are assigned and which locals are returned in the bottom of the file). Or pester the mod author to create interface.
Lua require(filename) only looks up a file filename.lua and runs it, which stands for module initialization. If anything is returned by running the file, it is assigned into lua's (not-so) hidden table (might as well say, a cache of the require function), if nothing is returned but there was no errors, the boolean true is assigned to that table to indicate that filename.lua has already been loaded before. The same true is returned to the variable to the left of equals in the caves = require('caves').
Anything else is left up to author's conscience.
If inside the module file functions are written like this (two variants shown):
init_layer = function(game)
%do smth
end
function move_down(player)
%do smth
end
then after call to require these functions are in your global environment, overwriting your variables with same names.
If they are like this:
local init_layer = function(game)
%do smth
end
local function move_down(player)
%do smth
end
then you won't get them from outside.
Your code expects that the module is written as:
caves = {
init_layer = function(game)
%do smth
end
}
caves.move_down=function(player)
%do smth
end
This is the old way of doing modules, it is currently moved away, but not forbidden. Many massive libraries like torch still use it because you'd end up assigning them to the same named globals anyway.
Кирилл's answer is relevant to newer style:
local caves={
%same as above
}
%same as above
return caves
We here cannot know more about this. The rest is up to you, lua scripts are de-facto open-source anyways.
Addendum: The event_handler is not part of lua language, it is something provided by your host program in which lua is embedded and the corresponding tag is redundant.
You should consult your software documentation on what script.on_event does in this particular case it is likely does not matter, but in general the function that takes another function as argument can dump it to string and then try to load it and run in the different environment without the upvalues and globals that the latter may reference.
require() does not create global table automatically, it returns module value to where you call this function. To access module via global variable, you should assign it manually:
local caves = require "caves"
I'm working with a program that has standard Lua 5.1 embedded and am trying to write a module I can call functions from but have had no avail.
The current environment is quite picky, and if I make mistakes the scripts will break but will not get any errors, so here we go:
I have something like this (inside moduletests.lua):
local _ = {}
function _.prints()
HUD.AddUpdateBoxText("Hello World!", 200) --Equivalent to print()
end
I would then attempt to require this and call it with:
mts = require 'moduletests' --seems to cause no issues
mts.prints() --breaks the scripts
Normally if the require function is incorrect, the scripts will break. The scripts work fine with the require, so I assume it is correct, but on any attempt to call the print function it will break.
On the other hand, I have another lua module installed and have been able to successfully require it and call a function and it is what I modeled my module after. This is the module's code.
Here is how I used it:
moses = require 'moses' --Works
local bok = moses.isInteger(6)
HUD.AddUpdateBoxText(tostring(bok), 700); --Works, outputs "true"
And this worked fine, perfectly as intended. Can someone tell me what is different or wrong with my module, or have any suggestions on how I could make a better functional version of this?
Thank you to everyone, I sincerely appreciate the help!
Much appreciated!
In Lua modules, you have to return something. The reason your code isn't working is because you are trying to call a method from whatever is returned by the module, but since nothing is being returned, an error can only be expected. Try returning the table:
local ar = {}
function ar.prints()
HUD.AddUpdateBoxText("Hello World!", 200) --Equivalent to print()
end
return ar
I recently got into Computer Craft (Mod for Minecraft) you can code the computers with lua.
I want to go "water status" and it will load "water" and then set a variable to "status" or any other word/string in its place so I can use it for anything. I guess you would call that a parameter?
os.run( environment, prgmpath, arguments )
I don't understand what environment is. prgmpath is water and the argument would be status?
I'm just unsure how to grab those arguments from that.
So yeah, I'm quite confused =/
Tried to explain it best I could, thanks,
Jazza
After searching around, I think I found my answer.
lua water arg1
Goes in the command line
derp = arg[2]
Goes in the file?
EDIT: After lurking around some more, I found out that:
derp = ...
print(derp)
In the file and:
file hi
It printed hi, so I guess that works, but I can't seem to add any more D=
os.run is an extension to the os library written specifically for that mod. according to the documentation on the wiki:
environment is the metatable to set up the state for the script you're running at prgmpath
arguments is whatever you want to pass to the code you're calling located in the script at prgmpath
so basically, if you had some code set up to do something specific in path/to/file.lua, but it depended on some outside state, you'd set up that state in your calling file, and pass the environment (or a subset of it) to the code in file.lua by passing it as a table to the first param in os.run().
arguments is supposed to be a table of arguments you wanted to pass to the function you'd be calling in file.lua. so if in file.lua you had...
function doSomething(arg1, arg2, arg3)
...
end
you'd pass arg1, arg2, and arg3 to doSomething by creating a table and passing it like this...
local args = {"arg1Val", {}, 1234}
os.run({}, '/path/to/file.lua', args)
os.run would then set up an empty environment for function doSomething() in file.lua, and pass the 3 values in args to the function.
make sense?