The R3C Branch ("Chris's Rebol" or "Rebol 3 Conservative")

@rgchris has been between a rock and a hard place... being a Rebol-style code believer with a large corpus, who hasn't been particularly served by either Red or Ren-C. Unwilling to jump ship for Python or other non-Rebol languages, this has kept him rather stuck in a Rebol2 world.

To make the best of a bad situation, he has put forth the idea of branching Ren-C circa two years ago. The intent is not to stay there long term, but to believe that the dynamism of Ren-C is such that future versions will be able to emulate this point if desired (considering I've stated that emulation of Rebol2 is a goal, this would be even easier).

I have mixed feelings about this plan, but his participation is valuable enough to be worth compromising to try it out. So I've patched R3C to be buildable with more up-to-date compilers in "rigorous" mode (I don't like building without all warnings on, and don't feel like having to install old GCC or MSVC to build an older branch).

I've also done a few patches to try and make R3C closer to mainline. So far:

  • COLLECT and KEEP in PARSE - This was a strongly-requested feature by @rgchris, and as such it doesn't make sense to have implemented it and leave it out of R3C. The syntax is different than Red's version, per his request, and it does rollback...which Red also does not.

  • Permissive Group Invisibility - At first I thought this idea wouldn't be useful. But the more I used "invisibles", the more I wanted to be able to group them like any other value. forum post

  • ENBIN and DEBIN - There are many ways to encode integers as binary or decode them. Rebol's attempt to have a "standard" TO BINARY! for integers and TO INTEGER! of a binary wound up being a major pain point in the 32-bit to 64-bit conversion for integers. This would only get worse with arbitrary precision numbers...the next frontier. These dialects may not be the perfect design, but their weird names make them easy to find...and they're pretty good power tools for the purpose. forum post

  • TAKE returning NULL - originally TAKE of an empty block was an error in Ren-C, to try and be "safe"... so you had to use a lower-level TAKE* that allowed it. While it was one of the earliest examples of CHAIN mechanics--and offered benefit by testing that--it was probably not so great. forum post

  • PRINT NEWLINE is allowed - PRINT's attempt to avoid people getting blindsided by print value where value turns out to be something like [format hard drive] led it to be more prickly, so you know it's not the kind of thing to be used with ANY-VALUE!. The compromise of only taking BLOCK! and TEXT! helped keep it from being a generic debug utility...making it a known quantity. Regardless of this, being able to say PRINT NEWLINE looks nice...and have that mean printing one (and only one) newline. forum post

One of the big goals is having feedback. Here are a few things I pointed out that I haven't patched in yet, but consider review of the issues to be important:

  • THEN returns its branch result unmodified - While this is a fairly minor change, it's probably more significant than it seems. forum post

  • VOID! assignments allowed in SET-WORD! and SET-PATH! - This was debated by the Rebol community historically (and has come up on the Red Gitter). It's one of those things where the fundamental "safety" value is debatable, while it creates a very clear inconvenience. Ren-C's rethinking of VOID! as a "hot potato" that hangs around and isn't really a problem unless you try to actually use it helps distinguish it from truly "unsetting" a variable, so the verbal paradox of "setting to an unset" isn't as jarring. forum post

  • Skippable COMPOSE marking - Chris hasn't liked this, but I think the convolutions you have to do to avoid it are bad. One need not use it if one doesn't like it, but I think you shoot yourself in the foot by not having it. forum post

Really here, the idea is to get engagement on the design...and find out what answers would be satisfying to all involved for the main branch. So the measure of that engagement will drive how much I can afford to work on accommodating bugfixes and feature patches onto the branch. But it seems that this is more useful than Redbol--at least to him--at this point in time.

The biggest deviations from Ren-C are:

  • No change to syntax* or range of datatypes from Rebol 3 Alpha (renaming notwithstanding, e.g. STRING! -> TEXT!)

  • Mostly backward-compatible changes to PARSE grammar, with the following changes from the Rebol 3 Alpha PARSE:

    • Returns position following last match.

    • COLLECT/KEEP (the aforementioned—based on Ren-C, not Red's implementation).

There are other things I'm exploring that would ease a transition from Rebol 2 (or other similar interpreters) once I've had a chance to evaluate the test suite.

* based on target usage, permitting leading '@' email values may be an exception here

It would be very instructive, @rgchris, if you kept a running commentary as you work about things you hit that you like or dislike... or maybe are indifferent to. Also links you read in order to get up to speed that are more-or-less helpful. This forum (and this thread, even) could be a place for that running commentary.

I'd be interested to know things like whether you are warming up to the COMPOSE change with (( )) to splice, and not splicing otherwise. That's something I'm very partial to, and I think the "fatter, multiplicity" syntax for it has nice semiotics for what it does.

But as I've mentioned, it's the ability to customize these things that's of the essence. Looking at Redbol's implementation for COMPOSE, for instance (ignore the weird name for APPLIQUE, it's weird on purpose because it's unlikely to be wanted for anything else):

function [
    value "Ren-C composes ANY-ARRAY!:"
    /deep "Ren-C recurses into PATH!s:"
    /into ""
        [any-array! any-string! binary!]
    if not block? :value [return :value]  ; `compose 1` is `1` in Rebol2

    composed: applique 'compose [
        value: :value
        deep: deep

        ; The predicate is a function that runs on whatever is generated
        ; in the COMPOSE'd slot.  If you put it in a block, that will
        ; splice but protect its contents from splicing (the default).
        ; We add the twist that VOID!s ("unset") won't compose in Rebol2.
        ;    rebol2> type? either true [] []
        ;    == unset!
        ;    rebol2> compose [(either true [] [])]
        ;    == []  ; would be a #[void] in Ren-C
        predicate: either only [:enblock-devoid] [:devoid]

    either into [insert into composed] [composed]

So the point is to build modular functions that are easily bent to what you want, without having to dig in and hack on C code.