This is a read-only snapshot of the ComputerCraft forums, taken in April 2020.
hannoman's profile picture

Problem with imports: os.loadAPI after shell.run

Started by hannoman, 25 November 2014 - 01:02 PM
hannoman #1
Posted 25 November 2014 - 02:02 PM
Hello everyone (first forum post, yay ^^),

I've got a problem in understanding how the different code sources get integrated into the global environment.
My problem is in the following scenario:

-- file: "base"
function printTable(tab, depth, indent, indentAdd)
   [...]
end


-- file: "startup"
shell.run("base")
(I like to export some functions directly in the global namespace, especially the ones i use often from the lua shell. So i run them with shell.run from the startup file.)


-- file: "imTest"
print("imTest started.")
printTable({hans="wurst", foo="bar"})

After that in a lua prompt:
Running "imTest" with shell.run works fine,
lua> shell.run("imTest")
imTest started.
hans = wurst
foo = bar
true

but running it with os.loadAPI, which runs the whole file too, results in an error:
lua> os.loadAPI("imTest")
imTest started.
imTest:3: attempt to call nil.

I can't figure out what is the reason for this, as a peek on the part of "bios.lua" where "os.loadAPI" is defined

local tAPIsLoading = {}
function os.loadAPI( _sPath )
	local sName = fs.getName( _sPath )
	if tAPIsLoading[sName] == true then
		printError( "API "..sName.." is already being loaded" )
		return false
	end
	tAPIsLoading[sName] = true
		
	local tEnv = {}					  -- !!
	setmetatable( tEnv, { __index = _G } )		  -- !!
	local fnAPI, err = loadfile( _sPath )
	if fnAPI then
		setfenv( fnAPI, tEnv )		  -- !!
		fnAPI()
	else
		printError( err )
		tAPIsLoading[sName] = nil
		return false
	end
	
	local tAPI = {}
	for k,v in pairs( tEnv ) do
		tAPI[k] =  v
	end
	
	_G[sName] = tAPI	
	tAPIsLoading[sName] = nil
	return true
end

seems to indicate that the API being loaded should have access to _G through the metatable. And as far as i can see _G should already contain "printTable" at the time of os.loadAPI("imTest") getting called.

Any insights are greatly appreciated!
Thanks,
hanno
Lyqyd #2
Posted 25 November 2014 - 04:00 PM
Note that it uses the name of the file provided as the key when inserting the environment table of the loaded API into _G. You'd need to call imTest.printTable.
ElvishJerricco #3
Posted 25 November 2014 - 05:50 PM
First off, global functions are generally a bad idea. But I won't get into why here. As for the cause of your problem, when a program is run via shell.run or os.run, it gets a global environment whose __index is _G, but whose __newindex isn't. So although they can access globals, they don't write globals to _G. You'd have to do


function _G.printTable(...)

end
hannoman #4
Posted 28 November 2014 - 06:07 PM
First off, global functions are generally a bad idea. But I won't get into why here. As for the cause of your problem, when a program is run via shell.run or os.run, it gets a global environment whose __index is _G, but whose __newindex isn't. So although they can access globals, they don't write globals to _G. You'd have to do


function _G.printTable(...)

end

Ok, that actually fixed it… :huh:/> Thanks!

I think i've found the misunderstandings that lead to my problem (I'll elaborate a bit here to clarify it for me and add a bit of value to this thread ;)/>):
The environment of the shell (=: shellEnv) is not the "real" global environment (=: _G), but an environment which only inherits from _G.
  • All programs run from that shell (and through shell.run) read from shellEnv (and _G) and write (their global names) to shellEnv.
  • os.loadAPI on the other hand always reads and writes to _G.
shellEnv is shared between all programs that were run from one shell (inluding the lua interpreter), therefore subsequent calls of shell.run can read global values which were set before through shell.run, as they reside in shellEnv. (Error 1: I always thought shellEnv == _G)

The soluton works like this:
By using "function _G.funcname" you trigger getmetatable(shellEnv).__index("_G") which fetches _G._G (== _G) and then write funcname into a field of the correct global environment.
(Error 2: I always thought "_G" would be merely a synonym for the environment. Therefore being translated internally and _G == getfenv() would hold true.)


Thank you very much! :D/>