Hi Toby,
Thank-you for pointing out the current infelicities
of implementing these entirely reasonable cases.
Though I'm not entirely convinced that "pretty hard"
is a entirely fair descriptor. ;-)
For example, here's a reasonably straightforward implementation
for that final "comma-separated list of numbered blocks" example:
use Keyword::Declare;
keyword tryall (Num|Block|Comma @args) {
# Check that the args are in num/block/comma sequence...
for (my $i=0; $i < @args; $i+=3) {
return "BEGIN{ die 'Syntax error in tryall' }"
if $args[$i] !~ /\d/
|| $args[$i+1] !~ /^\{/
|| $i+2 < @args && $args[$i+2] !~ /,/
}
# Build the replacement code...
my @num = grep /\d/, @args;
my @blk = grep /\{/, @args;
return join '//',
map { "eval { say 'Trying $num[$_]'; $blk[$_] } " }
0..$#num;
}
# Now test...
tryall
1 { say 'one'; die },
2 { say 'two'; die },
3 { say 'three'; die },
4 { die },
5 { say 'five' },
6 { say 'six' }
Admittedly, though that's not exactly "hard" to implement,
it's also not nearly as easy and declarative as I'd like it to be.
Obviously, the first step is to add a :sep attribute on keyword parameters,
so that syntaxes like your simple list of comma-separated (un-numbered)
blocks
could be specified as:
keyword tryall (Block @blocks :sep(',')) {
return join '//', map { "eval $_ " } @blocks;
}
I will certainly look at doing that.
But for a comma-separated list of numbers-then-blocks, we're rapidly
moving into a realm where a keyword's parameter list has to be
as powerful as a full grammar...which I'm frankly nervous about tackling.
Thinking about it further, I realized that the key to simplifying this
particular case (and, I suspect, most others too) lies in providing the
ability to specify parameter types that match compound elements
(such as a number followed by a block) and which then allow us
to extract those individual elements, preferably by name.
To that end, I'm going to look at possibly supporting regexes (including
keytyped named regexes) that include named captures. When a keyword
detects an argument using a regex (or keytype), it will return that
argument as an object, rather than as a simple string. That object will
have a stringification overloading that simply returns the entire match
(as currently), but will also have a hash-lookup operator that returns
the various named captures the regex made (and, I suppose, an
array-lookup operator to return numbered captures as well).
So then we could simply write:
keytype NumThenBlock is /
(?<num> (?&PerlNumber) )
(?&PerlOWS)
(?<block> (?&PerlBlock) )
/x;
keyword tryall (NumThenBlock @blocks :sep(',')) {
return join '//',
map { "eval { say 'Trying $_->{num}'; $_->{block} } " }
@blocks;
}
Please have a think about this proposed solution yourself
and let me know if you feel it would adequately meet
your future keyword-declaring needs. :-)
If so, I will look at releasing a new version of the module
as soon as I can implement these extra features.
Damian