While we don't have the C source code for Rebol 1.0, we do have the %rebol.r
initialization file:
Rebol 1.0.2 Initialization File (October 7, 1998) · GitHub
It would seem that if there were an ability to pack source code in with the executable, they would have done it...given that the Quick Start says "do not modify rebol.r
. If you accidentally do modify rebol.r
, reinstall it."
Hence this is probably the entire portion of Rebol 1.0 that's written in Rebol, e.g. the whole "Mezzanine".
It says:
;;; Note: Code in rebol.r runs in the system context. The system
;;; context [has] all the built in bindings of the user context, but also
;;; has extra bindings to allow rebol to be bootstrapped. Many of the
;;; rebol functions available in user code are actually written in
;;; terms of simpler rebol natives, or in terms of special
;;; system natives.
;;; REBOL reserves the right to change the system natives at any time,
;;; so you shouldn't depend on them for portable code.
Remarks On Contents, In No Particular Order
FUNC Definition
func: make function! [args body] [make function! :args :body]
I found it a bit interesting that the User Guide talked about how FUNC was defined, as an illustrative example, of an important thing for users to know about. (It turns out there's a brief mention in the Rebol2 User's Guide, but the Rebol 1.0 Guide writes it up twice, probably on accident.)
It's worth pointing out that there's big questions even in this seemingly simple definition. Such as, should a function copy its arguments or body? What should this do?
body: [print "Hello"]
foo: func [] body
append body [print "Goodbye"]
bar: func [] body
Does foo print just "Hello", or does it print "Hello" and "Goodbye"?
I don't yet know what Rebol1 did, but Rebol2's MAKE FUNCTION! would not copy the body. So FUNC would do a deep copy as the "higher level" operator, before passing it to MAKE FUNCTION!.
But during bootstrap, it used a definition of FUNC that didn't copy the body, for performance reasons...which it switched over to the copying implementation at the end of boot.
Some version of these crazy optimizations are on the table for future Ren-C.
PRINT, PRIN, PROBE
write-block-or-element:
make function! [port element] [
do
if block? :element [:write-block] else [:form-to-port]
:port
:element]
write-block: func [port block] [ ; !!! needs work
foreach element :block [form-to-port :port :element form-to-port :port " "]
]
prin: func [value] [
if block? :value [write-block output-port reduce :value]
else [form-to-port output-port :value]
exit
]
print: func [value] [
prin :value
linefeed-port output-port
exit
]
probe: func [value] [
prin " PROBE --> "
send output-port :value
linefeed-port output-port
:value
]
Weird. (and prints a space after every element, so you get a space at the end of the line vs. just delimited between, etc.)
At least one interesting aspect of this is to see the rigid "EXIT" at the end to make sure that PRIN and PRINT don't leak a result on accident. Things like this feel like a vindication of Ren-C's requirement to use a RETURN in order to give back a result from FUNC (but not LAMBDA).
IS? as TO-LOGIC
is?: func [value] [not not :value]
This was called TRUE? in Rebol2, and I very much disliked the ambiguity of that vs. testing to see if a value was = #[true]
the LOGIC! literal.
I wrote something up about how DID could be the opposite of NOT (which even goes together as DIDN'T for DID NOT). But due to some various shades of meaning the current state is that it means THEN? and DIDN'T means ELSE? as prefix tests for the trigger conditions that would run THEN or ELSE. It needs thought.
Anyway, interesting to see the choice of IS?
here.
A Recursive Folding ANY and ALL
any: func [block] [
eval-one block
make function! [value rest] [
if not value [any rest]
else [value]
]
make function! [value] [value]
]
all: func [block] [
eval-one block
make function! [value rest] [
if is? value [all rest]
else [false]
]
make function! [value] [value]
]
So this is based on a function called EVAL-ONE, that takes a list and two functions. It isn't defined in %rebol.r and isn't in the reference guide either. But it's a right fold with early termination.
One can definitely imagine the Joe Marshall and Carl friction on this ("why are you making all these usermode functions and calls, why not just use a loop?").
While there's a time and a place for this, I do think that if you are starting to push out into the usermode layers and finding this mentality is driving it...you're going to end up with something that isn't hitting the mark that Rebol was aiming at.
Why Is PICK So Weird?
pick: func [series index] [
do make function! [offset] [
if (:offset + index? :series) <= 1
[none]
else [do make function! [disp] [
if (length? :disp) = 0
[none]
else
[&peek :disp 0]
] skip :series if :offset < 0 [:offset] else [:offset - 1]
]
] if logic? :index [if :index [1] else [2]] else [:index]
]
My guess here is that the pattern:
do make function! [arg] [...code with arg...] value-for-arg
...is probably some holdover from before USE existed. Or maybe USE is just an abstraction built on functions, and so it's done this way for optimization. I dunno.
Poor-Man's EXPORT
;;; These functions can be defined in terms of system natives that are
;;; not available in the user context. Since we made the functions in
;;; this context, the values of the words in the body are relative to
;;; this context. But we place the functions in the user context so
;;; that the users can call them. This allows the user to call the
;;; system natives through a defined API in a controlled manner.
user-functions: [
dir? [file] [do func [info] [info/dir?] info? :file]
size? [file] [do func [info] [info/size] info? :file]
...
]
foreach [name args body] user-functions [
context-set user-context name func args body
]
So the comment says what's going on here, it's the attempt to push functions out into the user context when they're implemented in terms of functions that aren't available in the user context. I'm not sure what's not available (these implementations of DIR? and FILE? are based on INFO?, is that not exported to the user context?)
As far as I know, there's nothing like this in Rebol2 (there's no separate user-context
from a system-context
, is there?) Interesting if that was something that disappeared in Rebol2 and came back in Rebol3.
More Modularization: EVAL-REDUCE Takes Context
if not none? REBOL/script [
if exists? REBOL/script [
do make function! [] [
top-level-continuation: :return
if not REBOL/silent [
linefeed
prin "Loading script "
print REBOL/script]
eval-reduce [do REBOL/script] user-context
]
]
]
Rebol1 seems to have been working with modularization ideas, because even during startup, the script you pass on the command line is run via something called EVAL-REDUCE that takes a parameter of where to do the evaluation.
So definitely a shame that Rebol2 seems to have moved away from the idea that evaluations needed to be done in a context.
CATCH is defined in terms of CATCH-FUNC
We know from the user-functions
exporting that this:
catch [word block] [catch-func func reduce [word] :block]
Is actually:
catch: func [word block] [catch-func func reduce [word] :block]
Since there's no type checking, there's a :BLOCK
GET-WORD! just to be sure it's not a function, I guess? And then it's FUNC's job to do a check in its implementation. But then, why not :WORD
just to be sure WORD! isn't a function you're calling? (I like pointing this out, due to Ren-C's better answers to this issue...avoiding the "pox of documenting what you don't know")
So the idea of using functions as proxies for "virtual binding" is the old way. What's going on here is that the block contains code that wants to be bound to whatever the throw construct is, and so that block is made the body of a function, that you call and pass the thing you want bound to that name as the argument. (COLLECT+KEEP worked this way). But it's undesirable, because it means you've lost the fluidity of having the currency of a structural BLOCK!...replaced with the black box of a function just because you wanted to bind something.
I'm pretty sure this CATCH mechanic (being called a "continuation") is stackful and can't do anything too bizarre, but I'd like an executable to try and ensure that.