Consider some simple code that used to "work" (in of course only the simplest of cases)
>> parse [word: 10] [
let word: set-word! let val: integer! (
set word val
)
]
We're getting some unbound values by structural extraction. But now that structural extraction doesn't propagate bindings... how do we look those values up in an environment?
We'd get the wrong answer if we said set (inside [] word) val... that would try to bind the "word" word to the LET variable from the rule. I made it conflict just to stress the point that the processing code is not the right environment to be looking up values in the data most of the time.
When PARSE is doing the processing (and recursions in our data for us), we're cut out of the loop on binding.
Solution Tactics
You can use the <input>
TAG! combinator to get the input, and if there were an IN combinator you could do this yourself... handling recursions
>> parse [[word: 10]] [
let i: <input>
subparse in (i) block! [ ; make subparse input propagate specifier
let sub: <input>
let word: set-word! let val: integer! (
set (in sub word) val
)
]
]
Making this a little easier might be a combinator for capturing the parse state object, for getting the input more easily at any time.
>> parse [[word: 10]] [
let s: <state>
subparse in (s.input) block! [ ; subparse changes s.input
let word: set-word! let val: integer! (
set (in s.input word) val
)
]
]
Certainly some pain involved here. Perhaps @bradrn can appreciate the reason why propagating binding through structure automatically seemed necessary so things like this worked "like magic".
But it was bad magic. If the structural operations presume ideas about binding, that ties our hands in the interpretation of binding for the input block. We have [[word: 10]] now, but what if we wanted something like [let word [word: 10]]? It's up to the parse of this "dialect" to decide the bindings, not have it automatic. It's only the refusal of the automaticness allowing the LET in PARSE above to be implemented!
Though actually in this simple case, you could just say:
>> parse [[word: 10]] [
subparse in <input> block! [ ; make subparse input propagate specifier
let word: in <input> set-word! let val: integer! (
set (in sub word) val
)
]
]
Even briefer, a TAG! combinator <in>
that means in <input>
:
parse [[word: 10]] [
subparse <in> block! [
let word: <in> set-word! let val: integer! (
set word val
)
]
]
Not too arduous, and you have the necessary hook points for alternative binding interpretation when you need it. And if you're just processing code structurally, you don't have to worry about it.
(Note: Trying this I remembered that TAG! combinators haven't been set up to take arguments. Should they be able to? Maybe not... none do at the moment, and it seems a reasonable policy to say they don't. If not a TAG! then what should this be? It could be the behavior of the @
operator... which is a bit incongruous with how @word
etc. are handled in PARSE, but lines up sort of with wanting to capture the current sense of binding on the next argument. Something to think about, I'm calling it *in*
as a placeholder just to move along)
Other Places This Pops Up
If you're writing something like a FOR-EACH loop, and you want to get the bindings of things, you can look the thing up in an environment that you have on hand:
>> block: [word: 10]
>> for-each [word val] block [
set (in block word) val
]
>> word
== 10
It's manual, but it works. But what if the block were literal, and you didn't have access to it?
>> for-each [word val] [word: 10] [
set (??? word) val
]
Where this may be pointing is that instead of trying to imagine weirdly designed FOR-EACH variants that incorporate binding, it may be that you should think in terms of PARSE as the tool for when you want to enumerate with binding...