5 Years Have Gone By... How Much Better Can We Do?
Foundationally better... 
whitespace: [some [space | newline]]
suit-order: [♣ ♦ ♥ ♠]
direction-order: [N E S W]
rank-order: [2 3 4 5 6 7 8 9 T J Q K A]
one-hand-rule: [
let suit: ('♣)
collect [repeat 4 [
repeat 13 [
let rank: any @rank-order ; RANK-ORDER is literals, not rules
keep (join word! [suit, rank = 'T then [10] else [rank]])
]
inline (
suit: select suit-order suit
if suit ["."] else $[ahead [whitespace | <end>]]
)
]]
|
reject "Invalid Hand information in PBN"
]
pbn-to-hands: func [
"Convert portable bridge notation to BLOCK!-structured hands"
return: [object!]
pbn [text!]
<local> start direction
][
let hands: parse pbn [gather [
opt whitespace ; We allow leading whitespace, good idea?
[
start: any @direction-order ; DIRECTION-ORDER is literals, not rules
| reject "PBN must start with N, E, S, or W"
]
direction: (start)
[":" | reject "PBN second character must be `:`"]
[repeat 4 [
emit (direction): one-hand-rule ( ; e.g. [emit N: ...]
direction: (select direction-order direction) else [
first direction-order
]
)
opt whitespace ; Is more than one space between hands ok?
]
|
reject "PBN must have 4 hand definitions"
]]
assert [direction = start] ; skipping around should have cycled
return hands
]
And it works!
>> pbn-to-hands --[
N:QJ6.K652.J85.T98 873.J97.AT764.Q4 K5.T83.KQ9.A7652 AT942.AQ4.32.KJ3
]--
== #[object! [
N: '[♣Q ♣J ♣6 ♦K ♦6 ♦5 ♦2 ♥J ♥8 ♥5 ♠10 ♠9 ♠8]
E: '[♣8 ♣7 ♣3 ♦J ♦9 ♦7 ♥A ♥10 ♥7 ♥6 ♥4 ♠Q ♠4]
S: '[♣K ♣5 ♦10 ♦8 ♦3 ♥K ♥Q ♥9 ♠A ♠7 ♠6 ♠5 ♠2]
W: '[♣A ♣10 ♣9 ♣4 ♣2 ♦A ♦Q ♦4 ♥3 ♥2 ♠K ♠J ♠3]
]
You Can Use LET In PARSE
It might seem like a minor feature, but the underpinnings that allow a combinator to expand the environment required lots of advances.
GROUP! Rule Handling Became More Homogeneous
UPARSE has an overarching policy that rules synthesize values, and a BLOCK! will synthesize the last value synthesized by the rules within it.
I became more comfortable with GROUP! synthesizing values always. It's not like the values are discarded sometimes, and heeded other times.
This means that the style preferred by @rgchris, where you can just say keep (...)
without needing to decorate to say something like keep :[...]
, has won out. My paranoia about the decoration being needed to say "this isn't a value-discarding case of the group" no longer applies (to the extent it was ever worth worrying about).
As for retriggering rules, I'm happy right now with that being done via inline (...)
. It all fits together. So instead of:
:(
suit: select suit-order suit
if suit ["."] else '[ahead [whitespace | end]]
)
You say:
inline (
suit: select suit-order suit
if suit ["."] else [[ahead [whitespace | <end>]]]
)
Note that I chose to instead make the branch $, to get a bound version of a "soft quoted" branch:
inline (
suit: select suit-order suit
if suit ["."] else $[ahead [whitespace | <end>]]
)
You can't just use a quote there, because the unquoted block you got back from the branch would be unbound. It's a matter of taste I guess if you like this better than putting the block in a block... but it's faster, because the evaluator does not get invoked on the block. And I think it looks clearer, once you have a basic understanding of what's going on with binding.
The [repeat 4 ...] Instead Of [4 ...] Is Much Better
No qualms about that change.
If you're in a context where you want 4 to do a REPEAT 4 implicitly, that's possible. UPARSE is easy to customize with custom combinators, and we should make it even easier to do off the cuff (as easy as RebindableSyntax in the evaluator).
Uses GATHER Instead Of Assigning Hands
The original code did:
let hands: make object! [N: E: S: W: null]
parse [... hand: collect [...] (hand/(direction): hand ...) ...]
The new code does:
hands: parse [gather [... emit (direction): collect ...]]
It's much more powerful to synthesize the object from the rule--instead of pre-creating the object and passing it to the rule to have its fields assigned. Obviously it makes the rule easier to reuse. But also it means that the rule can make decisions about the shape and structure of the object.
There's only one little detail here, which is that in today's world (and maybe tomorrow's) the order of fields in an object matters in some places. By not pre-creating the object, the field order of the generated object will be different based on what order the fields are emitted. Here that might be exactly what you want--the order of fields in the object will reflect the order of hands in the original notation. But it's something to consider.
JOIN WORD! For The Win
Instead of:
keep (as word! unspaced [suit either rank = #"T" [10] [rank]])
This has:
keep (join word! [suit, rank = 'T then [10] else [rank]])
I like the THEN/ELSE rhythm better than I like EITHER, but that's a matter of taste.
Also, matching a literal WORD! or INTEGER! rule in a string gives you the original rule (unquoted) as a product now, not a character:
>> parse "a" ['a]
== a
>> parse "10" ['10]
== 10
So this gets a bit cleaner.
ANY And Literal Matching With @PINNED!
The new meaning of the ANY rule is more like the ANY in the regular evaluator. It implicitly considers there to be a |
between the elements of the block you pass it, so it's picking out of a set of alternates.
By default those alternates are rules, not literal values to match against. So if you're trying to match literal things, you have to put them in quotes:
>> parse [a a b a b c c] [some any ['a 'b 'c]]
== c
But we can use a "PINNED" block (@[...]
) as a signal that we want a literal match:
>> parse [a a b a b c c] [some any @[a b c]]
== c
If your block of options are in a variable, and you don't have your block of options decorated, you can use an operator to add the @ ... that operator is called PIN 
>> suit-order: [♣ ♦ ♥ ♠]
>> parse [♣ ♦ ♦ ♥ ♠ ♣] [some any (pin suit-order)]
== ♣
Or you could put the @
on the data array itself:
>> suit-order: @[♣ ♦ ♥ ♠]
>> parse [♣ ♦ ♦ ♥ ♠ ♣] [some any (suit-order)]
== ♣
But I offered another option, which is to use a pinned word at the reference site, and it will then assume you mean you wanted to treat what it looks up to as literal data:
>> suit-order: [♣ ♦ ♥ ♠]
>> parse [♣ ♦ ♦ ♥ ♠ ♣] [some any @suit-order]
== ♣
(If you just said any suit-order
then it would only accept plain blocks, and treat it as parse rules with no literal recognition... the dialect subtleties here are complex, but I think that's for the best.)
REJECT Instead Of PANIC
reject "message"
saves you on parentheses vs. (panic "message")
...
But the difference is much greater than that. REJECT, by being cooperative with PARSE, returns the error from PARSE itself, so you can react to it if you want. But also--it can encode information about what the state of the parse was at that moment into the error.
This idea that PARSE will by default give you a good diagnostic--that the console can break down for you and report visually--will hopefully make a big difference. This way the default of leaving error handling off your PARSE will be translated into a PANIC at that point, and you'll get much better information.