You can now create your own amazingly powerful Rebol natives in plain C, powered by the new binding, in a way that is OUT OF THIS WORLD.
Here's a full C program using the Ren-C libRebol
The mechanics heavily rely on Pure Virtual Binding II, and having it look so clean is due to macro tricks involving shadowed variables as a proxy for knowing the C function stack:
#define LIBREBOL_SPECIFIER (&specifier)
#include "rebol.h"
typedef RebolValue Value;
typedef RebolSpecifier Specifier;
typedef RebolBounce Bounce;
static Specifier* specifier = nullptr; // default inherit of LIB
void Subroutine(void) {
rebElide(
"assert [action? :print]",
"print {Subroutine() has original ASSERT and PRINT!}"
);
}
const char* Sum_Plus_1000_Spec = "[ \
{Demonstration native that shadows ASSERT and PRINT} \
assert [integer!] \
print [integer!] \
]";
Bounce Sum_Plus_1000_Impl(Specifier* specifier)
{
Value* hundred = rebValue("fourth [1 10 100 1000]");
Subroutine();
return rebValue("print + assert +", rebR(hundred));
}
int main() {
rebStartup();
Value* action = rebFunction(Sum_Plus_1000_Spec, &Sum_Plus_1000_Impl);
rebElide(
"let sum-plus-1000: @", action,
"print [{Sum Plus 1000 is:} sum-plus-1000 5 15]"
)
rebRelease(action);
rebShutdown();
return 0;
}
This outputs:
Subroutine() has original ASSERT and PRINT!
Sum Plus 1000 is 1020
If you use C++, It Gets Niftier, But Same Internals!
-
Raw strings R"(...)" mean you don't need backslashes
-
Lambdas mean you don't need to name your implementation function
-
Variadic Template Packing allows custom conversions to Value* from
int
(no rebI() needed!) or any other datatype! Add your own converters for any C++ class!Value* action = rebFunction(R"([ {Demonstration native that shadows ASSERT and ADD} assert [integer!] add [integer!] ])", [](Specifier specifier) -> Bounce { int thousand = Subroutine(); return rebValue("add + assert +", thousand); });
But it's better than that because we can make Value a smart pointer that automatically gets released when the last reference goes away. RenCpp did that, but we can do it much more lightweight in libRebol...coming soon!
It's a very elegant bridge, working without resorting to FFI or similar.
The smarts of the API macros like rebElide()
and rebValue()
is that they pick up the specifier by name that you give, so you don't have to pass it every time. When you're inside your native's implementation, the
shadowing of the argument overrides the global variable.
And of course being to do this at all hinges on throwing out the playbook from Rebol's historical binding, and doing something coherent and useful.
The Function Gets a Definitional Return. But...Why?
So you might think there's no good reason to have a definitional return. Because how would you ever run it?
const char* Illegal_Return_Spec = "[ \
{Showing that you can't use RETURN in an API Call} \
arg [integer!] \
]";
Bounce Illegal_Return_Impl(Specifier* specifier)
{
rebElide("return arg + 1000");
DEAD_END;
}
When you call rebElide()
, it crosses the API boundary and the C code is still on the stack. You can't unwind across it... unless you use longjmp or exceptions, and that's very thorny and brittle.
But Ren-C has Continuations
Note that the function you supply to do the native's work doesn't return a Value*
, it returns something called a Bounce
.
Bounce
is a superset of Value*
, that includes the ability to encode other instructions. One of those instructions is to ask the evaluator to do more work on the C function's behalf--even though it's no longer on the stack--before returning a value. You can ask to be called back again after that work is done (rebContinue()
)...or you can just transfer control to some additional code and let what it does be the answer (rebDelegate()
).
And within that code, it can use the definitional RETURN to deliver the value to the caller of your native!
const char* Working_Return_Spec = "[ \
{Showing that you *can* use RETURN in an API Continuation} \
return: [tag!] \
arg [integer!] \
];
Bounce Working_Return_Impl(Specifier* specifier)
{
int bigger = rebUnboxInteger(arg) + 1000; // whatever C processing
return rebDelegate(
"if", rebI(bigger), "> 10000 [return <big>]",
"print {It wasn't big!}",
"return <small>"
);
}
I believe this is one of the most clever language bridging ideas ever made - bringing still more uniqueness to Rebol's already very unique offering. And of course, C++ can throw in many improvements (not needing rebI(...) and just using integers directly and getting values, lifetime management for API handles with smart pointers so you don't need to rebRelease() them, etc. etc.
So much is enabled by this new binding, it's light years ahead of what we're used to.