CC: Tweaked

CC: Tweaked

42M Downloads

Issue detecting if script is being loaded as a module vs being run

TheElementalOfDestruction opened this issue ยท 4 comments

commented

MC Version: 1.12.2
CC Version: 1.82.3

According to this stack overflow answer (which I have confirmed myself to work), you should be able to tell if the code is being loaded ( i.e. require('script') or being run directly.

To test that this actually works, you can create two files in the same folder: a.lua and b.lua. Here is how they are defined:

a.lua

print(pcall(debug.getlocal, 4, 1))

b.lua

a = require('./a')

Remember these two files, because we are going to be using them multiple times.

Now, if you were to open a lua interpreter, this is the output you would get:

C:\...> lua a.lua
false   bad argument #1 to 'debug.getlocal' (level out of range)
C:\...> lua b.txt
true    nil

However, if you were to repeat this same process on a CC: Tweeked computer, this is what would happen:

> a.lua
true    nil
> b.lua
true    nil

See the problem here?

Can you do anything to fix this?

commented

So the code in the above SO question makes lots of assumptions about how the Lua interpreter works, which don't really hold up in ComputerCraft.

The above code pretty much asks "is this the first function the whole Lua VM runs?", which is only true within CC's internal start up code. We actually need to ask "was this run via shell.run?".

I wrote this code a while back, which can be used to detect if the program was run with require, os.loadAPI, or shell.run. It does rely on a couple of Lua 5.1 features, so doesn't work on 5.2+, but should be sufficient for CC:

local function get_type()
  if not shell then return "os.loadAPI" end
  if not package then return "shell.run" end -- Compat for old versions of CC

  -- Locate the sentinel value, and check that it's currently within the table
  -- somewhere.
  package.preload["__sentinel"] = function() return package.loaded["__sentinel"] end
  local sentinel = require("__sentinel")
  for k, v in pairs(package.loaded) do
    if k ~= "__sentinel" and v == sentinel then return "require" end
  end

  return "shell.run"
end

print(get_type())

a = require('./a')

On a side note, you shouldn't be loading modules like this. require works a bit like Python or Java's import though no relative import. You need to pass in a dot-separated path, such as require("a") or require("cc.expect"), not a file path.

commented

That was a specific example to ensure it imported the exact file I wanted. I usually have the issue of require("a") not working at all if the file is created using edit a because the default path is /, and the require function starts at /rom/

commented

The require function should start relative to the current program's directory, so assuming a and b are in the same directory everything should work fine.

However, require is a little broken when used within the lua REPL, as the current program resides within rom/programs (so it tries to find rom/programs/b instead). require should print out the list of paths it tried, so it should be possible to work out what's going wrong for you.

I should probably fix the REPL behaviour TBH, it's confused far too many people.

This one's been fixed for a while. Woops.

commented

You can edit the require path if I remember correctly.