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

[Lua][Error] Calling Shell within an API [WORKAROUND]

Started by TemporalWolf, 13 March 2012 - 11:11 AM
TemporalWolf #1
Posted 13 March 2012 - 12:11 PM
I'm trying to offload the bulk of my terminal formatting to an API which begins with

tos (API)

function splash(num)
shell.run("clear");
--rest of the program
end

My main program calls tos.splash(2);

whenever I run it I get:
tos:2: attempt to index ? (a nil value)

if I remove the shell.run it runs with no issues (just doesn't clear the screen, as you would expect).

I'm guessing it's having trouble seeing the other APIs from within an API, so I tried

function splash(num)
os.loadAPI("shell");
shell.run("clear");
--rest of the program
end
which throws the same line 2 error. I also tried os.loadAPI("rom/apis/shell"); with no dice.

Am I missing something or is this just not allowed?
Espen #2
Posted 13 March 2012 - 12:26 PM
"clear" is a program which runs these two lines of code:
term.clear()
term.setCursorPos( 1, 1 )
So just use that instead of running a program from within a program. It's much more direct this way. :mellow:/>/>
Advert #3
Posted 13 March 2012 - 12:28 PM
Did you do os.loadAPI("tos")?

You can also use splash() (tos.splash() may* error (i don't think it will, but it may)) inside your tos api:

function splash() end
function usesSplash()
 ...
 splash()
 ...
end


You will have to os.loadAPI("splash") every time you reboot your computer, so you can put os.loadAPI("splash") at the top of it.
TemporalWolf #4
Posted 13 March 2012 - 01:16 PM
"clear" is a program which runs these two lines of code:
term.clear()
term.setCursorPos( 1, 1 )
So just use that instead of running a program from within a program. It's much more direct this way. :mellow:/>/>

That didn't work the first time I did it, then I realized why: I've been adding semi-colons to the end. Without semi-colons that works fine, but even shell.run("clear") (without semicolon) doesn't work.

Your solution is effective. If anyone knows why shell.run won't work, I'd love to know. EDIT: I updated the title to include that a work around has been posted. I'd like to see a true solution (as I'd like to be able to make shell.run calls within an API) if anyone knows it.

Did you do os.loadAPI("tos")?

You can also use splash() (tos.splash() may* error (i don't think it will, but it may)) inside your tos api:

function splash() end
function usesSplash()
...
splash()
...
end


You will have to os.loadAPI("splash") every time you reboot your computer, so you can put os.loadAPI("splash") at the top of it.

I don't follow… are you suggesting os.loadAPI("tos") then calling it with splash() (vice tos.splash()?).

The tos.splash(num) call works without issue, it is only shell.run that is throwing errors.
RestfulMonad #5
Posted 27 March 2012 - 08:07 AM
I'm still on 1.3, but unless things have changed significantly, I think I know why calling shell from inside an API being loaded will fail - the shell isn't a regular API.

Normal APIs end up in the 'global table' called _G. If you look at os.loadAPI in bios.lua you'll see that loadAPI works by running the API code in a separate environment via setfenv and dumping the modified bits of the environment into a new table that becomes the table named after the API. However, shell is not in this list.

Looking at rom/programs/shell you can see that the shell API is implemented as a regular variable inside this program. Programs run via the shell then inherit this variable via the same environment changing mechanism. So because os.loadAPI is at a lower level than the shell, the shell is not available to APIs.

There are two workarounds that I can see to this issue:
  1. Modify the shell to install itself as a real API somehow. I have no idea how difficult this would be as I haven't really given the shell program a good look and I don't know the design considerations for making the shell a regular program.
  2. Add an extra parameter to all your API calls that require shell access to take the shell object. This muddies your interface, but it should obviously work.
I don't think you can cache the shell table without running the risk of potentially weird things happening if you ever end up nesting shells. As an example, if you store the value of shell in program foo, then run a nested shell and execute program bar that uses the cached shell to call getRunningProgram() you should see "rom/programs/shell" instead of "bar" as from the original shell's perspective the currently running program is the subshell.

Here's a quick demonstration:

Save as "hack":

local shell = {}
function setShell(newShell)
  shell = newShell
end
function whoami()
  print(shell.getRunningProgram())
end

Save as "test"

hack.setShell(shell)
hack.whoami()

shell.run("shell")

Save as "test2"

hack.whoami()

shell.exit()

From a lua prompt, run os.loadAPI("hack") then run "test" followed by "test2". The first program stores its shell object inside the hack API then spawns a subshell. The second program asks the hack API to print the result of getRunningProgram() from the cached shell object, then exits the subshell. The output should be:


CraftOS 1.3
> test
test
CraftOS 1.3
> test2
rom/programs/shell
>
Wolvan #6
Posted 27 March 2012 - 08:25 AM
Thx for that info! I really wondered why shell.run("clear") didn't work anymore
Espen #7
Posted 27 March 2012 - 03:41 PM
That didn't work the first time I did it, then I realized why: I've been adding semi-colons to the end. Without semi-colons that works fine, but even shell.run("clear") (without semicolon) doesn't work.
Hmm, that's odd, because semicolons at the end shouldn't affect the code in any way.
According to the Lua 5.1 Manual - Section 2.4.1 ("Chunks"):
Each statement can be optionally followed by a semicolon