GET-GROUP!s in PARSE mean "execute and splice as rule"


#1

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.)


2018 Retrospective: Elevating the Art
#2

Love both of these features. Definitely useful immediately for me.


#3

Two PARSE cases to consider:

normal: lit ([some "b"])
getter: lit :([some "c"])
parse "bbbccc" [some [(normal) | getter]]

#4

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.


#5

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.

@rgchris has summarized:

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.


#6

Cleaner, more concise. I like!
I haven't used the conditional parse keywords very much, so I'm looking into digging into it with this nicer approach.


#7

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!


#8

Thumbs up on the GET-GROUP!s here-- but in the improved code, why are those EITHER blocks doubled up?


#9

Because the GET-GROUP! wants to return a BLOCK! that's a parse rule. Without the inner block, it would try to run the PARSE code...in DO.

parse "aaa" [:(if true [some "a"])]  ; gives error, SOME is unset, etc.

parse "aaa" [:(if true [[some "a"]])]  ; works

If you're writing code like this and that bothers you, then stylistically you could space it out.

parse "aaa" [ :(if true [ [some "a"] ]) ]  ; works

Or put it in a variable, etc.


#10

Ah ah ah, yes, thanks. Makes perfect sense, and was in some other examples earlier.


#11

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.


#12

Do you think this would be clearer?

:(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: _
    )
])

#13

I like the LIT-BLOCK! form too. It's not a huge difference but it is cleaner here.


#14

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.

UPDATE: Soft-quoting branches makes this work, and is an experiment in progress


#15

Agreed. Doesn't seem like the juice is worth the squeeze, so to speak.


#16

One thing I notice about the evaluative parity of literal blocks and regular blocks is that it can act as a comment, of sorts:

 either condition [  ; code fragment producing parse rule
      blah blah blah
      '[some "a"]
 ][
     blah blah blah
     '[thru <terminal>]
 ]

It might be clearer that you are evaluating to that block as a value, and it's not a parameter to blah blah blah.

So it may have some kind of idiomatic usage.