So DO in Rebol and Red of a string has classically been what we might think of today as:
do in system.contexts.user transcode string
Sample behavior:
rebol2>> var: 10
== 10
rebol2>> do "var: var + 20 print [var]"
30
rebol2>> var
== 30
So not only could it see VAR, it could change it.
This seems obviously bad in a modularized world, in the sense that if you DO a string somewhere besides the console (e.g. in a module) you're getting no effects where you likely intend them, and causing effects somewhere else.
Ren-C addresses this by isolating the DO (as it would a module), which seems like the only sane idea. If you have a string representing code and want to run it in the current context, you need a binding operation, e.g.:
do in [] transcode string
Quirks With Header Handling In String Input
If you DO a string with a header, it appears to be handled:
rebol2>> do "Rebol [] print {Hello}"
Hello
BUT it's actually not being handled in Rebol2. It's just that historically, Rebol has been the system object:
rebol2>> words-of rebol
== [version build product core components words...]
So the DO was actually evaluating Rebol to an OBJECT!, ignoring it, then evaluating a block, and ignoring that. To see that this is what's happening:
rebol2>> unprotect 'Rebol
rebol2>> Rebol: does [print "Boo"]
rebol2>> do "Rebol [] print {Hello}"
Boo!
Hello
R3-Alpha changed this, so that if you do a string starting with the word "Rebol" then it will interpret the subsequent block as a header, and use that to guide the loading process of the string... which might mean interpreting it into something besides a block (e.g. a module):
r3-alpha>> do {REBOL [Type: Module Options: [isolate]] xyz: <hi>}
Module: "Untitled" Version: none Date: none
r3-alpha>> xyz
** Script error: xyz has no value
There's a quirk in that if you do a BLOCK! directly instead of a string, you get the old REBOL-evaluates-as-an-OBJECT! ignoring the block behavior (issue 2372)
r3-alpha>> do [REBOL [Type: Module Options: [isolate]] xyz: <hi>]
== <hi>
r3-alpha>> xyz
== <hi>
To help mitigate this, the word "Rebol" is defined to generate an error by default under evaluation in Ren-C (the system object is SYSTEM or alias as SYS)
In any case, the idea here is that since headers are used to dictate how code is loaded and bound, if you have a BLOCK! it has already gone through that process.
DO of a TEXT! Should Require A Header
All Rebol2 files processed by LOAD and DO of a FILE! (or URL!) are supposed to have a header:
rebol2>> write %code.r "print {Hello World}"
rebol2>> do %code.r
** Syntax Error: Script is missing a REBOL header
R3-Alpha doesn't enforce that, which was a step backwards in many people's opinions (including mine).
r3-alpha>> write %code.r "print {Hello World}"
r3-alpha>> do %code.r
Script: none Version: none Date: none
Hello World
If you have code without a header in a file, then it's your job to TRANSCODE that yourself into a block and do whatever binding or processing you need to do to get it to run.
But considering that, it seems to me that DO of a TEXT! should need a header, and be a synonym for DO LOAD of the text. Having a header would help reinforce that you really are running this code like a script in its isolated environment. And it means that if you got your script data from some random place, you don't have to write it to a file just to get DO to process the header.
Splitting DO and EVAL Is Likely Important
When you add all this up, it may suggest we should separate DO and EVAL completely (which was a long-running opinion of @earl from back in the day).
eval block is a little bit longer than do block but not by much.
One benefit could be that if we shift away from assuming that DO is something that runs just Rebol code, we can say that it gets its knowledge from the file itself. This could come from the extension (e.g. do %some-file.js could run javascript code, do %some-file.css could incorporate CSS).
Another potentially good argument is that the amount of parameterization for DO could roughly wind up matching the amount of parameterization for IMPORT. This might suggest even that DO be arity-2 in all cases instead of having a refinement form of DO/ARGS. That would allow for a content type override if you were DO-ing a string:
do css-source-text [Type: 'css]
Though single-arity DO is pretty entrenched. Maybe DO of a BLOCK! does expect that block to have a header structure, so you could add that header:
do compose [Rebol [Type: 'css] (css-source-text)]
; -or-
do compose [CSS [<options for merging CSS in ReplPad>] (css-source-text)]
do compose [Rebol [] (spread transcode rebol-source-text)]
Regardless, DO of a TEXT! Needs a Header
This strikes me as non-negotiable, so I went ahead to with this, and hit a few issues.
DO-ing text directly with no intermediate processing step is a rarely needed operation, so it doesn't come up much. But I did trip over two issues:
Issue: --do On The Command Line
First thing I hit was the question of the --do "your code here"
command line option. This is a little annoying, because you don't have the option of saying --do [your code here]
at that particular place.
I can attest to the fact that I'd be annoyed if I had to put a header there. But then... what if I wanted a header's influence... settings to direct the execution of that code?
This again seems to suggest a split of --eval and --do. Use --do if you have a header, and --eval if you don't. Since EVAL wouldn't take strings, the idea that the command line presumes you want the string bound in the "default sense" (e.g. what would happen if you typed in the console) seems fine.
It may be that DO of a BLOCK! running Rebol code is just so ingrained that it isn't changeable. If that's the case, then saying --do
takes the block interpretation of the string you pass it is probably not a big issue, and then --do {do {Rebol [] ...}}
can be how you inject a header on the command line.
Issue: DOES for doing code
This isn't really about the DO-needs-a-header-on-string, but another thing I noticed pertaining to a potential DO/EVAL split.
DOES is a shorthand for defining a LAMBDA with no arguments.
>> foo: does [1 + 2]
>> foo
== 3
For reasons of consistency, Ren-C extended it so it would run anything DO would process:
>> foo: does %your-script.r
>> foo
Running your script...
That's hardly an important feature (foo: does [do %your-script.r]
is easy enough to say if it ever actually comes up, which it has not.) But it raises a question about using the word DOES in light of a DO and EVAL split. DOES contains the word DO and might suggest a connection with DO's behavior and not EVAL's.
Perhaps RUN could be the script-oriented runner, with DO keeping the role of EVAL... though right now I'm using RUN for executing frames and gathering arguments at the callsite (a kind of inline APPLY). That would need another name.
Perhaps EVALS is not so bad as an alternative to DOES? :-/
>> foo: evals [print "hi"]
>> foo
hi
No, it's bad. I'm not a fan.
Or maybe the inconsistency that DOES has little relation to DO is not a big deal. But inconsistency bothers me (which is why I made does x a synonym for lambda [] [do x] for all X, despite likely uselessness for non-block X.)