After re-reading these posts a few times… I think I understand what you’re getting at now.
To double-check my understanding, let me try to summarise what happens in the example:
- RULE is a block containing instances of ONE and TWO, intended to be substituted for their values (which are parsing rules)
- ONE and TWO are defined within the block, with their matched character being COMPOSEd into their definitions
- Similarly, RULE also needs to be COMPOSEd into the block: specifically, it ends up nested within a larger PARSE rule
- But at the same time, it needs any instances of ONE or TWO to refer to the just-defined parse rules, rather than any bindings it might already have
- However, the ‘easy’ options don’t work in this case:
- UNUSE can’t run after ONE and TWO are defined, because by that point the code is within a dialect which has no clue how to UNUSE a block
- But my earlier implementation of UNUSE can’t run before ONE and TWO are defined, because it relies on being able to gather up their definitions
- Therefore, this requires an UNUSE which can do the ‘hole-punching’ without prior knowledge of what the names will be bound to.
I’ll note that my earlier implementation of UNUSE actually can work if the code is rewritten to use two levels of COMPOSE:
do compose [
let one: (char)
let two: [repeat 2 (char)]
rule: (rule)
parse (string) compose [comment "your code here" (unuse [one two] rule)]
]
To my mind this is slightly easier to comprehend, but I can see how it would get annoying quickly.
(Incidentally, this also puts the final nail in the coffin for the way I originally suggested implementing UNUSE using FUNC — that one relied function application to collect the new bindings, and that doesn’t work in dialects either!)