To pick a random example from the build helpers for "CScape" interpolation of some generated C code:
emit {
#define ${MAYBE PREFIX}INCLUDE_PARAMS_OF_${NATIVE-NAME} \
$[Items]; \
assert(Get_Series_Info(level_->varlist, HOLD))
}
-
The use of
${}
(instead of$()
or$<>
) means that the result of the expression should be turned into a valid C identifier name... so dashes are converted to underscores, etc. -
The use of all capitals in the
${}
escaping means that the strings generated by the expressions evaluated should be made all uppercase. -
The use of
$[]
means that items is an array, and its elements should be printed one line at a time...repeating the boilerplate leading and trailing on each line (in this case an indent on the left, and a semicolon and backslash on the right)
The template looks something like the result:
#define INCLUDE_PARAMS_OF_IF \
DECLARE_PARAM(1, return); \
USED(ARG(return)); \
DECLARE_PARAM(2, condition); \
DECLARE_PARAM(3, branch); \
assert(Get_Series_Info(level_->varlist, HOLD))
Without interpolation, we fall back on LOAD-able code... where spaces and quotes are required by the language itself. This starts to lose the ability to keep track of actual spaces in the interpolated thing, plus you keep having to start and stop string delimiters on the string portions.
I'm not quite sure how it would come together dialected via regular code, but it would drift away from looking like C code, at best it might look like:
emit [
"#define " <c> (MAYBE PREFIX) "INCLUDE_PARAMS_OF_" <c> (NATIVE-NAME) " \"
" " @[Items] "; \"
" assert(Get_Series_Info(level_->varlist, HOLD))"
]
I'd be hard-pressed to say the spacing was correct on inspection. We've lost the intuition about where the unspaced parts are. You can imagine it getting worse when you're building unspaced material inside a string literal. Strings can simply be the least noisy medium when you want to see something that looks close to the result.
Anyway, with strings carrying binding, we wouldn't have to do what we do today... which is actually pass the variables (that don't live in LIB) in a block to emit:
emit [prefix native-name items] { ; <-- ack
#define ${MAYBE PREFIX}INCLUDE_PARAMS_OF_${NATIVE-NAME} \
$[Items]; \
assert(Get_Series_Info(level_->varlist, HOLD))
}
So I look forward to getting rid of that.
And it’s even easier in Rebol than it is in Haskell, because there’s already a single built-in function to do everything for you:
>> x: 10 y: "foo" == "foo" >> print ajoin ["Scopes? " x " " x " " x " " y " " y " " y] Scopes? 10 10 10 foo foo foo >> foo: func [x] [local: 20 ajoin ["The sum is " (x + local)]] >> foo 30 == "The sum is 50"
I strongly prefer this approach over string concatenation, since by using sensible data structures it integrates much better with the rest of the language. (It also reduces the risk of errors from malformed strings, and potentially the equivalent of SQL injection attacks.)
Note that Ren-C has DELIMIT (and UNSPACED, SPACED) instead of AJOIN... which hopefully you'll like even better.