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

[Lua][Error] Attempt to index ? (a nil value)

Started by Shazz, 09 May 2013 - 04:42 PM
Shazz #1
Posted 09 May 2013 - 06:42 PM
Hello, I was just trying to make a simple 'include' function, here is the code for it:

function include(localP)
   path = shell.resolve(localP)
   local f = fs.open(path, 'r')
   local str = f.readAll()
   f.close()

   local tbl = loadstring(str)()
   return tbl
end

local updater = include('apis/updater')

Here is the apis/updater file:
local runningDir = shell.getRunningProgram()
return {
	testF = function()
		return 'hello world'
	end,
}

For some reason, it seems that it doesn't let me access the Shell API.
Error:
string:1: attempt to index ? (a nil value)

Any help would be nice. I know there is the os.loadAPI function, but I don't want to use that.
Bubba #2
Posted 09 May 2013 - 07:29 PM
Your problem lies here:

local tbl = loadstring(str)()

loadstring is given a limited environment, and therefore you'll be unable to access the shell API from it.
Shazz #3
Posted 09 May 2013 - 07:34 PM
Yeah, I figured it had something to do with loadstring() since this is my first time using it.
Is there any way that I can approach the include function in a way that allows me to still use all the APIs?
H4X0RZ #4
Posted 09 May 2013 - 07:36 PM
I think you can use setfenv() but I don't understand it, so… I can't help :D/>/>
Bubba #5
Posted 09 May 2013 - 07:42 PM
Yeah, I figured it had something to do with loadstring() since this is my first time using it.
Is there any way that I can approach the include function in a way that allows me to still use all the APIs?

As Freack says, you can use setfenv to set your environment of the function returned by loadstring.


local function include(f)
  --[[local file = fs.open(f, "r")
  local c = file.readAll()
  file.close()]] There's no need for all of this. Just use loadfile.

  local tEnv = {} --Create the environment we'll store the file functions in
  setmetatable(tEnv, {__index = _G}) --Set the __index metamethod of tEnv so that when we try to call something that doesn't exist in the loadfile env (shell.getRunningProgram, for example), it will access the values in _G instead
  local api = loadfile(f)
  setfenv(api, tEnv) --Go ahead and set the environment of the loaded code to our tEnv
  api() --Run it
  return tEnv --Return the table
end
Shazz #6
Posted 09 May 2013 - 08:10 PM
-snip-

Still get the same error for some reason.
Bubba #7
Posted 09 May 2013 - 08:11 PM
-snip-

Still get the same error for some reason.

Really? That's strange. I tried it and it's working fine for me.

Here's the code again in case I typed something up wrong:

function include(_f)
  local f = shell.resolve(_f)
  if not fs.exists(f) then
	return false, "That file does not exist!"
  end

  local tEnv = {}
  setmetatable(tEnv, {__index = _G})
  local loaded = loadfile(f)
  setfenv(loaded, tEnv)
  loaded()

  return tEnv
end


local test = include "/test2" --Here's my test: it worked perfectly fine for me

for i,v in pairs(test) do
  print(i..": "..tostring(v))
end
H4X0RZ #8
Posted 09 May 2013 - 08:19 PM
-snip-

Still get the same error for some reason.

Really? That's strange. I tried it and it's working fine for me.

Here's the code again in case I typed something up wrong:

function include(_f)
  local f = shell.resolve(_f)
  if not fs.exists(f) then
	return false, "That file does not exist!"
  end

  local tEnv = {}
  setmetatable(tEnv, {__index = _G})
  local loaded = loadfile(f)
  setfenv(loaded, tEnv)
  loaded()

  return tEnv
end


local test = include "/test2" --Here's my test: it worked perfectly fine for me

for i,v in pairs(test) do
  print(i..": "..tostring(v))
end
There's a little typo in the third line:
"_f" not "f" ;)/>
Shazz #9
Posted 09 May 2013 - 08:20 PM
There's a little typo in the third line:
"_f" not "f" ;)/>

That's intentional, read the line above it.

@Bubba: Did you put shell.getRunningProgram() (or any other Shell API function) in your test file?
H4X0RZ #10
Posted 09 May 2013 - 08:22 PM
Oh yea, now i see it.

I have to read better ^_^/>
Bubba #11
Posted 09 May 2013 - 08:34 PM
There's a little typo in the third line:
"_f" not "f" ;)/>

That's intentional, read the line above it.

@Bubba: Did you put shell.getRunningProgram() (or any other Shell API function) in your test file?

SpoilerYup. It worked fine. Here's the contents of my test2 file:

function test()
  print(shell.getRunningProgram())
end

And the output:

test: function: 2cc7d960

Oh hang on a sec. I didn't actually try the loaded function and now I'm seeing that it's not working *facepalm*. Hang on a second while I debug this.
Shazz #12
Posted 09 May 2013 - 08:37 PM
Try running the test function.

local test = include('/test2')
test.test()
Bubba #13
Posted 09 May 2013 - 08:40 PM
Okay, I'm really derping today. You can easily load shell yourself by doing this:

local tEnv = {shell=shell}

But I'm not 100% sure why the setmetatable bit isn't working as it should. Oh well, doing the above code fixes the problem.
Shazz #14
Posted 09 May 2013 - 08:42 PM
Okay, I'm really derping today. You can easily load shell yourself by doing this:

local tEnv = {shell=shell}

But I'm not 100% sure why the setmetatable bit isn't working as it should. Oh well, doing the above code fixes the problem.

Yeah, I just noticed that too. Thanks, I'll try to find a better way around it if I can.