Let's say you have script like this:
Rebol [
File: %quitter.r
]
print "I'm a quitter!"
quit
print "This doesn't run"
In Rebol2 if you run it from a prompt with do %quitter
, it will print I'm a quitter
and immediately exit the interpreter. Red does the same thing. (though you have to change the header from "Rebol []" to "Red []")
But in R3-Alpha, you get:
>> do %quitter.r
Script: "Untitled" Version: none Date: none
I'm a quitter
== 0
There's nuances about QUIT in R3-Alpha as well, that you can see in the HELP:
USAGE:
QUIT /return value /now
DESCRIPTION:
Stops evaluation and exits the interpreter.
QUIT is a native value.
REFINEMENTS:
/return -- Returns a value (to prior script or command shell)
value -- Note: use integers for command shell
/now -- Quit immediately
While Red and Rebol2's /RETURN only took INTEGER!, R3-Alpha could take any other value...which can be returned to the calling script.
It also offers a /NOW, in case you really did mean you wanted to exit the system entirely.
"/NOW" Not Being the Default Makes Sense
"Quitting" a script shouldn't casually force exit from the entire interpreter when you DO it. So I agree with R3-Alpha's choice on that.
But I think I prefer moving the /NOW functionality into a different routine, to convey just how different that is. sys.util/exit
seems like a good way to say it. That's a function that breaks the boundaries of what a more polite routine like QUIT can do.
QUIT Should Be Able To Return Arbitrary Values
I think that allowing arbitrary values to be returned to the caller via DO instead of just integers makes a lot of sense. Rebol2 and Red both let a script return that arbitrary value if it just fell out the bottom of the script... so why shouldn't you be able to do that with a QUIT parameter?
(Though actually, I'm not sure that arbitrary values should just fall out the bottom of a DO script. This would seem to leak values in a lot of cases that weren't intended... showing information in the console which is irrelevant (at best) or a "security leak" (at worst). The default should likely be nothing (~ antiform)... which is interpreted as the script succeeding by default (0 exit code). If you want the script to synthesize a value, you need to explicitly QUIT with that value...same as with RETURN in FUNC)
But arbitrary values gives us the question of conflating arbitrary integer return results with exit codes. You might have a script which wants to return an integer as a count of something, and any non-zero counts could be interpreted as the script having failed. But if you call this script from the command line, it shouldn't be treated as if it failed.
It seems clear to me that this is a job for definitional errors! QUIT needs to differentiate between returning values "as is" (all of which should represent success if called from the console) or returning raised errors, with the exit code embedded. This would allow a DO of a script that raised an error to correctly fail by default, if you didn't handle the error with an EXCEPT.
What Interface Should QUIT Use?
We need a way to say quit with 1 as raised error vs. quit with 1 as ordinary value. I think that the former is probably the more typical meaning. And I'll mention that I don't like /RETURN as the name of any refinement that isn't dealing with function RETURN.
It's probably best it's made to be arity-1. quit 0
isn't that much more to type than just quit
, and reinforces that you have a choice there.
If you said QUIT 0 that would translate into just returning nothing, so you'd get nothing displayed if you called the script from a DO. But you'd get a 0 exit status from calling it at the command line.
By default you could only pass it integers, but some refinement would switch it over to meaning you want to quit with a value. Probably something like quit/value 1
would do the trick. That would give back the literal value 1 to a DO that called it, and look like success with exit status 0 if you called it at the command line. (Maybe the option to print the result should be offered as a command-line switch, so command-line programs have at least some way to get a non-integer value out of a script that catered to DO from other scripts?)
Big Thought: Definitional QUIT
Right now catching QUIT is a bit of a black art.
It seems to me that the way this should work is that each QUIT is definitional, throwing back to the DO or IMPORT that rigged up the module structure and ran it.
When you're running code from the console, that too should have its own QUIT...that just quits the console. That way, when it gets caught the console can know it caught just the one that was intended for it, and can tidy up. Not only that, the interface to that QUIT can offer an arity-0 form without affecting the rest of the system. (Slightly confusing perhaps, but people will figure it out.)
There are some strange consequences of this. It means that every time you run an evaluation in the console, you're dealing with a different QUIT--it's a QUIT that is specifically scoped to that console evaluation. It can always be found in the same place (e.g. the QUIT variable of the user context)...but it will be refreshed each time there is a new evaluation to be caught.
Further, it means modules can't QUIT after their initial execution. This actually makes a lot of sense... who would process that module's QUIT when it isn't running on the stack? You can only quit a module while you're initializing it...after that point, your only option is to SYS.UTIL/EXIT and terminate the whole interpreter...because there's nowhere else to meaningfully tie a quit to! Good stuff.
This All Looks Quite Good
Seems to fix a lot of suboptimal things related to quitting we've had going on.
The code that tried to guess whether non-integer values meant success or not was a very early example I encountered of "hmmm...this doesn't seem right" ... it will be nice to rip that out and operate on the basis of isotopic rigor!