I wasn't initially thrilled about being able to put ERROR! antiforms in parameter packs:
>> block: []
== []
>> pack [take block, 10 + 20]
; first in pack of length 2
== \~&[warning! [
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
]]~\ ; antiform (error!)
The alternative would be to panic immediately
>> pack [take block, 10 + 20]
** Script Error: Can't TAKE, no value available (consider TRY TAKE)
First Use Case: MAXMATCH-D (and MAXMATCH-C)
In the beginning, 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 [fail e, null]
]
[^result2 remainder2]: parser2 input except e -> [
pack [fail e, null]
]
if error? ^result2 [ ; parser2 didn't succeed
if error? ^result1 [
return ^result1 ; neither succeeded
]
] else [ ; parser2 succeeded
any [
error? ^result1
(index of remainder1) < (index of remainder2)
] then [
remainder: remainder2
return ^result2
]
]
remainder: remainder1
return ^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 an ERROR! antiform, that is. If you try to cheat and put a non-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 non-antiform WARNING! there, and by virtue of seeing the remainder1
is an WARNING! and not an ANY-SERIES! you'll know that parser1 raised an ERROR!.
So instead of:
[^result1 remainder1]: parser1 input except e -> [
pack [fail e, null]
]
You'd do:
[^result1 remainder1]: parser1 input except e -> [
pack [~, e] ; say result1 is tripwire, and remainder1 is e
]
Your variable names are a bit weird here, because it's actually remainder-or-warning-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 :warning1]: parser1 input except e -> [
pack [~, ~, e]
]
(You have to use the :warning1
syntax if the non-erroring parser case returns a pack with only two items in it, or it will be unset. Leading colon means you're okay with fewer items in the source pack and it will set warning1 to null in that case.)
Or... why are we bothering to make the error case use EXCEPT? You have RESCUE for definitional errors as well. It returns NULL if no raised error, and the non-raised form of the ERROR! if there was one:
>> rescue [1 + 2]
== \~null~\ ; antiform
>> rescue [1 / 0]
== &[warning! [
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:
warning1: rescue [[^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 RESCUE 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 You Can Avoid Raised Errors in PACK!
Compare the original maxmatch-D to this one using RESCUE:
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> warning1 warning2 ^result1 ^result2 remainder1 remainder2
][
warning1: rescue [[^result1 remainder1]: parser1 input]
warning2: rescue [[^result2 remainder2]: parser2 input]
if error2 [ ; parser2 didn't succeed
if warning1 [
return fail warning1 ; neither succeeded
]
] else [ ; parser2 succeeded
any [
warning1
(index of remainder1) < (index of remainder2)
] then [
remainder: remainder2
return ^result2
]
]
remainder: remainder1
return ^result1
]
Does the existence of alternative methods suggest ERROR! in PACK! should be illegal?