There's efficiency to be gained when you compose your parse rules ahead of time. But what if the rule depends on other things, and you want to generate a rule to splice in that gets composed in every time it's run?
>> num: 1
>> char: #"a"
>> did parse ["a" "bb" "ccc"] [
some [
into [:(
rule: reduce [num char]
num: num + 1
char: char + 1
rule
)]
] end
]
== #[true]
(Note: You shouldn't need the block outside the GET-GROUP! for the INTO, but at the moment INTO isn't aware of these get groups in its immediate argument...so they have to go in a block rule. It will be updated to recognize them directly.)
If the expression inside the GET-GROUP! evaluates to a null, it splices nothing into the rule stream. With so many null-signaling routines to choose from, that's rather cool.
>> did parse "aaabbbcccddd" [
some "a"
:(case [1 > 2 [[some "x"]] 4 > 3 [[some "b"]]])
some "c"
:(switch 1 + 2 [
4 [[some "z"]] 3 + 2 [[some "q"]]
])
some "d"
end
]
== #[true]
Though you can return BLOCK! rules to splice, you can also just return single WORD!s. At the moment, returning a | is disabled, though that might be relaxed based on feedback and allowed to mean "skip to next rule" (but clearly this is going to be different from a | in the source, as being literally in the source is a prerequisite to participating in the skipping.)
The (normal) there would be a no-op. It sees a single group in the rule, so it invokes the evaluator and ignores the result. You're ignoring ([some "b"]) the evaluation product...so no-op.
The getter there should run the code and splice, so in this case that just makes [some "c"] the next rule.
Hence this should fail, the b's won't match.
I'll do some work on the retriggering of groups. It's a little weird because you have a step where if it's a GET-GROUP! you have to process it beforehand to see if it's a WORD!, but then if it's a word you have to retrigger it to see if it's a group (!). I guess that just means do group processing if necessary, do word lookup if necessary, then loop up to do group processing if necessary.
Will have to make sure there's a cancellation opportunity if you somehow produce an infinite loop on this.
I proposed another I-think-awesome feature in chat, which is that LOGIC! #[true] be a no-op in parse (same as BLANK!) while #[false] not match anything.
parse "abc" ["a" :(true) (-- "this prints") :(false) to end (-- "this doesn't")]
foo?: true parse "abc" [foo? "a" (foo?: false) "b" foo? (-- "doesn't print")]
This eliminates the need for PARSE's IF construct. IF would take a condition as a parameter, in a GROUP!, and continue the parse if the condition were truthy and cease matching if it were falsey. But that was one conditional construct, with the oddity that it was "single-arity". So it created questions about what its branch would be, where was the EITHER or SWITCH, etc.
Now instead of if (x = 10) you can just say :(x = 10), and you have all the power this implies. If you want to stop a parse from continuing a match, you can do so just by having whatever you put in that GET-GROUP! evaluate to false! Should you feel that's too obtuse, you might also write :(if x != 10 [false]) and it would work just as well, using null to proceed matching with an opt-out vs. by returning true.
And if you had if (condition) before and it was a logic, you can just say condition !
A major benefit in my eyes is that FALSE can take the place of FAIL--as FAIL has now come to be tightly associated with raising errors. I'd been worried about how to fix that, and this takes care of it.
Of course, as with INTEGER! and BLANK! and WORD! for keywords, you won't be able to match literal logics any more with TRUE or FALSE. You'll need lit :(condition):, lit #[true], etc. This is par for the course.
I was confused by it, because I thought that IF was arity 2 and was running the rule after it. But it doesn't run any rule at all, it just directs continue-or-not.
Here's the one example I have of me using it, in the implementation of FUNCTION going over the spec to look at the locals and args. I had to basically hack up EITHER by using if (var) [...] | if (not var) [...]
if (var) [
set var: any-word! (
append exclusions var ; exclude args/refines
append new-spec var
)
|
set other: [block! | text!] (
append/only new-spec other ; spec notes or data type blocks
)
]
|
if (not var) [
set var: set-word! ( ; locals legal anywhere
append exclusions var
append new-spec var
var: _
)
]
Complex code is going to be tricky no matter how you read it. But I think it's more clear by just breaking into normal code, and getting to use an EITHER to pick your rules:
:(either var [[
set var: any-word! (
append exclusions var ; exclude args/refines
append new-spec var
)
|
set other: [block! | text!] (
append/only new-spec other ; spec notes or data type blocks
)
]][[
set var: set-word! ( ; locals legal anywhere
append exclusions var
append new-spec var
var: _
)
]])
Now the sky's the limit on your conditional constructs.
This further tightens the notion that PARSE never sees what you put in a single GROUP!...it always executes and gets discarded, while double groups always splice. That's been a major sticking point for me in the past, and we're killing off the things that used it... IF, RETURN... bonus!
Once upon a time, I believed that doubling brackets up was ugly, and hence there needed to be an IF/ONLY that would not evaluate branches.
@MarkEye didn't think /ONLY was better. Over time, I changed my mind to think /ONLY wasn't worth it, and double brackets weren't so bad. Good, even. You say what you mean, and that's what it looks like.
Eventually the idioms can grow on you, and you find other ways to deal with it.
:(either var '[
set var: any-word! (
append exclusions var ; exclude args/refines
append new-spec var
)
|
set other: [block! | text!] (
append/only new-spec other ; spec notes or data type blocks
)
] '[
set var: set-word! ( ; locals legal anywhere
append exclusions var
append new-spec var
var: _
)
])
There's a bit of a problem here that lit-THING! has to become THING! in the evaluator, and lit-lit-THING! has to become lit-THING!... in a fairly regimented way. So it might not be able to work.
You can't do this anymore if you expect binding to work on those blocks. Keyword recognition (like SOME and THRU) is done literally, so you can do some things without binding...but if you have any variables you want to refer to that won't work. You need to use e.g. $[some "a"] or just [some "a"], which are synonyms in this context.
There's no technical reason we couldn't continue to have this done by "GET-GROUP"--which is really a CHAIN! with a BLANK! at the head and a GROUP! in the second slot. I just don't like it anymore. (To be honest, I never really liked it very much in the first place.)
So we need another answer.
THE-GROUP! can't be the answer, because THE means match the thing literally:
So in its group form, @(...) means synthesize a value from an expression and then match that exact value.
I don't think anything fits particularly well here symbolically in the current model. There's no reason it couldn't just be done with a keyword... I've likened this to PARSE's version of REEVALUATE (REEVAL), so maybe it could be called REPARSE...better suggestions welcome:
>> parse [a b b a] ['a reparse (if 1 = 1 '[some 'b]) 'a]
== a
Leading slash would be an option, were we to consider it to be parallel to asking to run things in the evaluator, and that would make some sense:
>> parse [a b b a] ['a /(if 1 = 1 '[some 'b]) 'a]
== a
Action combinators are very useful--and in general perhaps more useful than retriggering groups as rules.
We'll also have FENCE!...and I don't know what FENCE! is going to do. But I don't think it should be doing this.
Maybe It's Time To ((Go Back To Doubled Groups))
When the feature was first conceived, it used doubled-up groups.
>> parse [a b b a] ['a ((if 1 = 1 '[some 'b])) 'a]
== a
It wasn't bad, just weird. And looking back on it, it wasn't terrible...definitely has some advantages.
Maybe we'll go with that. This might call for the idea that the GROUP! combinator subdispatches if it notices asymmetric patterns, so you can put in the combinator map things like (| |) and it will assume you want any group that adheres to that pattern to go through that dispatch if it exists. So (()) would be searched for if it fit that pattern...