I did a little twist to UPARSE so that when a WORD! is not recognized from the keywords list, it dispatches to a "WORD! combinator". The spec is pretty simple, it looks like:
word!-combinator: combinator [
return: "Result of running combinator from fetching the WORD!"
[<opt> <invisible> any-value!]
value [word!]
<local> result
]
So it has the usual (implicit) parameters of the INPUT, and the job of synthesizing a return result and the REMAINDER of how much input is left to process. But since it's a datatype combinator it receives the WORD! to process as the value
parameter
So what it does is:
-
Errors if the word looks up to NULL, because I'm pretty sure we've decided that...as being what's valuable about the ~unset~/NULL/BLANK! distinction.
-
Acts as a no-op and synthesizes a ~void~ isotope if it's a BLANK!, which makes it act just like an empty block rule would. Again, because I think we've decided that is the best behavior...this ties into the value of the "3 intents"
-
For combinators that don't need parameters it will delegate to them. I'm not sure how many types to allow here, but have decided INTEGER! variables in PARSE are too obfuscating...and really, there wouldn't be any parser here to pass to the integer combinator to call N times (the rules aren't a parameter, and not available here).
So with a combinator to handle these non-keyword-WORD!s in hand...I got the idea that wrapping the default WORD! combinator with ENCLOSE could be an elegant replacement for %load-parse-tree.r.
The TRACKED-WORD! Combinator
Don't belabor the details of the following code too much.
It just adds a little prelude that marks the position of the stack it's gathering at the beginning, and pushes a line describing the word that is fetching the rule. The epilogue notices success or failure...and on failure pops everything added since the mark (including the line the prelude pushed). On success it pushes a line on the stack that says what part of the input was spanned.
tracked-word!: enclose :default-combinators.(word!) func [
f [frame!]
<static> indent (0)
][
let input: f.input ; save to use after DO F
let remainder: f.remainder ; (same)
let mark: tail stack ; save where the stack was
append stack spaced [collect [repeat indent [keep tab]], f.value, "["]
indent: me + 1
let result': ^(do f)
indent: me - 1
if result' <> null [ ; rule fetched by word succeeded
let consumed: mold copy/part input get remainder
append stack spaced [
collect [repeat indent [keep tab]], "] =>" consumed
]
] else [clear mark] ; roll back stack
return unmeta result'
]
(I'm sorry if the ^ and unmeta are confusing but they are the easiest way to handle the piping of things like unsets and void around...)
Now Let's Make a PARSE Variant That Uses It
We didn't HIJACK the actual WORD! combinator, because then all UPARSEs would do this. We created a new function that can act as a combinator (since it has the same interface)
Now let's make a PARSETREE function that uses a combinator set that includes it:
tracked-combinators: copy default-combinators
tracked-combinators.(word!): :tracked-word!
trackparse*: specialize :uparse [combinators: tracked-combinators]
trackparse: enclose :trackparse* func [f [frame!]] [
stack: copy []
do f also [
for-each line stack [print line]
clear stack
return ~none~ ; suppress visibility of successful result
]
]
Let's Try It Out!
>> foo-rule: [some "f"]
>> bar-rule: [some "b" foo-rule]
>> trackparse "fff" [foo-rule]
foo-rule [
] => "fff"
>> trackparse "bbbfff" [bar-rule]
bar-rule [
foo-rule [
] => "fff"
] => "bbbfff"
>> trackparse "bbbfffbbbfff" [2 bar-rule]
bar-rule [
foo-rule [
] => "fff"
] => "bbbfff"
bar-rule [
foo-rule [
] => "fff"
] => "bbbfff"
We can keep going...
>> meta-rule: [some bar-rule]
>> trackparse "bbbfffbbbfff" [meta-rule]
meta-rule [
bar-rule [
foo-rule [
] => "fff"
] => "bbbfff"
bar-rule [
foo-rule [
] => "fff"
] => "bbbfff"
] => "bbbfffbbbfff"
Neat! But is it ACTUALLY Working?
Well, er, no. And it goes back to the same issue that COLLECT had.
The problem is that what we care about for keeping or rejecting is contextually based on who asked, not based on the mere success or failure of the rules themselves.
So we're back to that same point where it's like we are just running code unconditionally in a GROUP!, with no concern about whether the overall rule succeeds or not.
>> trackparse "fffyyy" [foo-rule some "x" | foo-rule some "y"]
foo-rule [
] => "fff"
foo-rule [
] => "fff"
So @Brett, do you agree that the desired output there should have just been one FOO-RULE?
Either way, I hope you think this is kind of impressive. You can try the above in the web REPL...