I haven't been completely thrilled about being able to put raised errors in parameter packs. But initially, I allowed it:
>> block: []
== []
>> pack [take block, 10 + 20]
; first in pack of length 2
== ~make error! [
type: 'Script
id: 'nothing-to-take
message: "Can't TAKE, no value available (consider TRY TAKE)"
near: '[take [] **]
where: '[take reduce pack console]
file: ~null~
line: 1
]~ ; anti
Honestly this seems wrong. I allow you to PACK a PACK, and that's okay. But glossing over the error at time of packing I do not like.
I much prefer:
>> pack [take block, 10 + 20]
** Script Error: Can't TAKE, no value available (consider TRY TAKE)
Only One Use Case: MAXMATCH-D (and MAXMATCH-C)
In practice, only one place was making use of the ability to put raised errors in packs:
maxmatch-D: combinator [ ; "(D)efault"
{Match maximum of two rules, keeping side effects of both if match}
return: "Result of the longest match (favors first parser if equal)"
[any-value? pack?]
parser1 [action?]
parser2 [action?]
<local> result1' result2' remainder1 remainder2
][
[~^result1'~ remainder1]: parser1 input except e -> [
pack [raise e, null]
]
[~^result2'~ remainder2]: parser2 input except e -> [
pack [raise e, null]
]
if raised? unmeta result2' [ ; parser2 didn't succeed
if raised? unmeta result1' [
return unmeta result1' ; neither succeeded
]
] else [ ; parser2 succeeded
any [
raised? unmeta result1'
(index of remainder1) < (index of remainder2)
] then [
remainder: remainder2
return unmeta result2'
]
]
remainder: remainder1
return unmeta result1'
]
Two parsers here are called with two results, and this tries to handle the case of when one "result" is a raised error...putting it into the same slot where a result would be.
The problem is that the EXCEPT statement is producing an expression that is targeting a pack. And in the first slot of that pack is a potentially-anything-value... anything but a raised error, that is. If you try to cheat and put a non-raised error in that slot, how would you know the parser you called wasn't just generating an ERROR! value to pass around? You need another state.
No Other Choice... Or... Is There?
The second slot is a series position. You could poke a plain error there, and by virtue of seeing the remainder1
is an ERROR! and not an ANY-SERIES! you'll know that parser1 raised an error.
So instead of:
[~^result1'~ remainder1]: parser1 input except e -> [
pack [raise e, null]
]
You'd do:
[^result1' remainder1]: parser1 input except e -> [
pack [~, e] ; say result1 is trash, and remainder1 is e
]
Your variable names are a bit weird here, because it's actually remainder-or-error-1
. But you've built an expression that targets the pack.
But why are we being so stingy about variables? Why not expand the pack to three items?
[^result1' remainder1 /error1]: parser1 input except e -> [
pack [~, ~, e]
]
(You have to use the /error1
syntax if the non-erroring parser case returns a pack with only two items in it. The slash means you're okay with fewer items in the source pack and it will set error1 to null in that case.)
Or... why are we bothering to make the error case use EXCEPT? You have TRAP for definitional errors as well. It returns NULL if no raised error, and the non-raised form of the ERROR! if there was one:
>> trap [1 + 2]
== ~null~ ; anti
>> trap [1 / 0]
== make error! [
type: 'Math
id: 'zero-divide
message: "attempt to divide by zero"
near: '[1 / 0 **]
where: '[/ entrap trap console]
file: ~null~
line: 1
]
Leveraging that gives you basically the cleanest code you could ask for:
error1: trap [[^result1' remainder1]: parser1 input]
(Let me point out that the ability to intercept a definitional error coming from PARSER1 by having it "pass through" a SET-BLOCK! (or a SET-WORD!) is a feature to enable precisely this scenario. None of the assignments will be performed. And that is the limit of how far it will jump and be allowed to TRAP before it will be promoted to a failure. If you're not up to speed on definitional errors, read up on them, because they are absolutely critical to coherent error interception.)
So Forget About Raised Errors in PACK!
It's a terrible idea. I'm making PACK abrupt fail if you try to put a raised error in it.
Compare the original maxmatch-D to this one using TRAP:
maxmatch-D: combinator [ ; "(D)efault"
{Match maximum of two rules, keeping side effects of both if match}
return: "Result of the longest match (favors first parser if equal)"
[any-value? pack?]
parser1 [action?]
parser2 [action?]
<local> error1 error2 result1' result2' remainder1 remainder2
][
error1: trap [[^result1' remainder1]: parser1 input]
error2: trap [[^result2' remainder2]: parser2 input]
if error2 [ ; parser2 didn't succeed
if error1 [
return raise error1 ; neither succeeded
]
] else [ ; parser2 succeeded
any [
error1
(index of remainder1) < (index of remainder2)
] then [
remainder: remainder2
return unmeta result2'
]
]
remainder: remainder1
return unmeta result1'
]
Ren-C gives you power and flexibility to solve your problems in better ways... take advantage of that!