The problem with prefix codes (or paired identical tokens) is one of scoping. I'll pull an example of this (specifically paired identical tokens) out of the realm of Unix-alike shell scripts.
The traditional way of getting the output of a command in a shell into another command is the use of the "backtick" (`). You wrap the command in backticks to get the string that the command would otherwise print to your screen. So, for example, `date -I` would put the current date in the "international" format into your command. So if I wanted to scan a file for any instances of today's date in it, I'd use a command line like this: grep -i `date -I` my_file.txt
There are several problems with this (for an example of these, check the actual text I had to type above to get it looking the way it does…), but chief among them is that you can't nest. I can't take the output of a command and put it into another command whose output I want to use. foo `bar`baz`` doesn't work, for example. This unnecessarily limits your abilities to compose higher-order constructs in your text.
The solution to this in the Unix-alike shell world was to deprecate the use of backticks and instead use the $(...) construct. Because there's a clear opening token and a clear closing token, it's easy to build nested structures. The original scanning one would now be grep -i $(date -I) my_file.txt, for example. And the one that doesn't work would become foo $(bar$(baz)). It's clearer to the eye where the replacement is going to be in the first example, and the second example is actually possible. (Incidentally, this also makes it easier to write the parser, so a developer who opposes balanced tokens is of dubious competence in my books.)
Of course this still leaves us with the problem of how to identify replacements. The MUSHcode solution is, as is typical for that family of half-baked software, utterly idiotic. Using % as the introductory token doesn't work because % is a character people use a lot. As in saying "75% off!", a problem I face after well over a decade of playing MUSHes still. (It doesn't help that various bits of softcode deal with this inconsistently so sometimes I have to use %% and sometimes I don't in an incoherent mess.) Too, using square brackets is also pretty idiotic. We may not use square brackets as often in English as we'd use, say, the percentage sign, but we still do use them.
What a good system really needs is something that's consistent and different from English.
So here's the outline of what I propose:
First, use a compound, balanced-pair approach for EVERYTHING. Period. Yes it means you type a bit more when you're accessing, say, a variable name. Suck it up. For substitution codes, open with, say "$(" and close with ")". So what in MUSH would be "%n" becomes "$(n)". (Let's not quibble over whether it should be $(...) or %{...} here. This is illustrating a concept, not detailed specifics.) For function invocation and substitution I'd suggest a different pairing be used, just to make it visually clear to the reader that different behaviour is intended. So "[random_select(this|that|the other)]" in a hypothetical MUSHing substitution could be instead "${random_select(this|that|the other)}".
Now my reasoning for these:
- First, $( and ${ (or whatever opening tokens are chosen) are not exactly commonly used tokens in English expressions. This means you reduce ambiguity and the resulting disambiguation (like MUSHcode's irritating, and inconsistent, requirement for %% when you want to talk about sales promotions). About the only time you're going to have to escape these is if you're directly talking about the code itself.
- To my eyes $(n) stands out a lot more from the text than %n does. It calls out that a substitution is being made and this makes it perfectly clear precisely what is being substituted and where it fits.
- The parser is easier to write when you have a clear and consistent single character that begins a substitution.
- It's easy to mix and match at need, as well as nesting at need. Consider a piece of code that can randomly affect the invoker or the target: ${name(${select_from([$(n), $(1)]})} is bonked on the head!
- It is also, by virtue of being properly tokenized, parsed, and executed code instead of simple-minded string substitutions all the way down, code that's able to be formatted. In situ, not passed through external formatting/deformatting tools.