1 package Parse::Method::Signatures;
4 use MooseX::Types::Moose qw/
5 ArrayRef HashRef ScalarRef CodeRef Int Str ClassName
9 use Moose::Util::TypeConstraints;
10 use Parse::Method::Signatures::ParamCollection;
11 use Parse::Method::Signatures::Types qw/
12 PositionalParam NamedParam UnpackedParam
17 use namespace::clean -except => 'meta';
18 our $VERSION = '1.003012';
21 our $DEBUG = $ENV{PMS_DEBUG} || 0;
23 # Setup what we need for specific PPI subclasses
24 @PPI::Token::EOF::ISA = 'PPI::Token';
26 class_type "PPI::Document";
27 class_type "PPI::Element";
41 has 'signature_class' => (
44 default => 'Parse::Method::Signatures::Sig',
47 has 'param_class' => (
50 default => 'Parse::Method::Signatures::Param',
53 has 'type_constraint_class' => (
56 default => 'Parse::Method::Signatures::TypeConstraint',
59 has 'type_constraint_callback' => (
62 predicate => 'has_type_constraint_callback',
65 has 'from_namespace' => (
68 predicate => 'has_from_namespace'
73 isa => 'PPI::Document',
78 # A bit dirty, but we set this with local most of the time
81 isa => 'PPI::Element',
89 Class::MOP::load_class($_)
90 for map { $self->$_ } qw/
98 # Skip leading whitespace
100 unless $ppi->significant;
104 my ($self, $args) = @_;
107 push @traits, $args->{ variable_name } ? 'Bindable' : 'Placeholder'
108 if !exists $args->{unpacking};
109 push @traits, $args->{ named } ? 'Named' : 'Positional';
110 push @traits, 'Unpacked::' . $args->{unpacking}
111 if exists $args->{unpacking};
113 return $self->param_class->new_with_traits(traits => \@traits, %{ $args });
116 override BUILDARGS => sub {
119 return { input => $_[0] } if @_ == 1 and !ref $_[0];
127 my $input = substr($self->input, $self->offset);
128 my $doc = PPI::Document->new(\$input);
130 # Append the magic EOF Token
131 $doc->add_element(PPI::Token::EOF->new(""));
133 # Annoyingly "m($x)" gets treated as a regex operator. This isn't what we
134 # want. so replace it with a Word, then a list. The way we do this is by
135 # taking the operator off the front, then reparsing the rest of the content
136 # This will look the same (so wont affect anything in a code block) but is
137 # just store different token wise.
138 $self->_replace_regexps($doc);
140 # ($, $x) parses the $, as a single var. not what we want. FIX UP
141 # While we're att it lets fixup $: $? and $!
142 $self->_replace_magic($doc);
144 # (Str :$x) yields a label of "Str :"
145 # (Foo Bar :$x) yields a label of "Bar :"
146 $self->_replace_labels($doc);
148 # This one is actually a bug in PPI, rather than just an odity
149 # (Str $x = 0xfF) parses as "Oxf" and a word of "F"
150 $self->_fixup_hex($doc);
155 sub _replace_regexps {
156 my ($self, $doc) = @_;
159 foreach my $node ( @{ $doc->find('Token::Regexp') || [] } ) {
160 my $str = $node->content;
162 next REGEXP unless defined $node->{operator};
164 # Rather annoyingly, there are *no* methods on Token::Regexp;
165 my ($word, $rest) = $str =~ /^(\Q@{[$node->{operator}]}\E)(.*)$/s;
167 my $subdoc = PPI::Document->new(\$rest);
168 my @to_add = reverse map { $_->remove } $subdoc->children;
169 push @to_add, new PPI::Token::Word($word);
170 # insert_after restricts what you can insert.
171 # $node->insert_after($_) for @to_add;
172 $node->__insert_after($_) for @to_add;
180 my ($self, $doc) = @_;
182 foreach my $node ( @{ $doc->find('Token::Magic') || [] } ) {
183 my ($op) = $node->content =~ /^\$([,?:!)])$/ or next;
185 $node->insert_after(new PPI::Token::Operator($op));
186 $node->insert_after(new PPI::Token::Cast('$'));
191 sub _replace_labels {
192 my ($self, $doc) = @_;
194 foreach my $node ( @{ $doc->find('Token::Label') || [] } ) {
195 my ($word, $ws) = $node->content =~ /^(.*?)(\s+)?:$/s or next;
197 $node->insert_after(new PPI::Token::Operator(':'));
198 $node->insert_after(new PPI::Token::Whitespace($ws)) if defined $ws;
199 $node->insert_after(new PPI::Token::Word($word));
205 my ($self, $doc) = @_;
207 foreach my $node ( @{ $doc->find('Token::Number::Hex') || [] } ) {
208 my $next = $node->next_token;
209 next unless $next->isa('PPI::Token::Word')
210 && $next->content =~ /^[0-9a-f]+$/i;
212 $node->add_content($next->content);
219 my $ppi = $self->ppi_doc->first_token;
221 if ($ppi->class eq 'PPI::Token::Word' && exists $LEXTABLE{"$ppi"}) {
222 bless $ppi, "PPI::Token::LexSymbol";
223 $ppi->{lex} = $LEXTABLE{"$ppi"};
233 # invocant: param ':'
235 # params: param COMMA params
241 $self = $self->new(@_) unless blessed($self);
243 $self->assert_token('(');
248 my $param = $self->param;
250 if ($param && $self->ppi->content eq ':') {
251 # That param was actualy the invocant
252 $args->{invocant} = $param;
253 croak "Invocant cannot be named"
254 if NamedParam->check($param);
255 croak "Invocant cannot be optional"
256 if !$param->required;
257 croak "Invocant cannot have a default value"
258 if $param->has_default_value;
260 croak "Invocant must be a simple scalar"
261 if UnpackedParam->check($param) || $param->sigil ne '$';
263 $self->consume_token;
264 $param = $self->param;
269 push @$params, $param;
271 my $greedy = $param->sigil ne '$' ? $param : undef;
272 my $opt_pos_param = !$param->required;
274 while ($self->ppi->content eq ',') {
275 $self->consume_token;
277 my $err_ctx = $self->ppi;
278 $param = $self->param;
279 $self->error($err_ctx, "Parameter expected")
282 my $is_named = NamedParam->check($param);
284 if ($param->required && $opt_pos_param) {
285 $self->error($err_ctx, "Invalid: Required positional param " .
286 " found after optional one");
289 croak "Invalid: Un-named parameter '" . $param->variable_name
291 . $greedy->variable_name . "'\n";
295 push @$params, $param;
296 $opt_pos_param = $opt_pos_param || !$param->required;
297 $greedy = $param->sigil ne '$' ? $param : undef;
301 $self->assert_token(')');
302 $args->{params} = $params;
304 my $sig = $self->signature_class->new($args);
312 # (OPTIONAL|REQUIRED)?
317 # where: WHERE <code block>
321 # var : COLON label '(' var_or_unpack ')' # label is classish, with only /a-z0-9_/i allowed
325 # var_or_unpack : '[' param* ']' # should all be required + un-named
326 # | '{' param* '}' # Should all be named
334 unless (blessed($self)) {
335 $self = $self->new(@_) unless blessed($self);
339 # Also used to check if a anything has been consumed
340 my $err_ctx = $self->ppi;
346 $self->_param_typed($param);
348 $self->_param_opt_or_req(
349 $self->_param_labeled($param)
350 || $self->_param_named($param)
351 || $self->_param_variable($param)
352 || $self->_unpacked_param($param)
353 ) or ($err_ctx == $self->ppi and return)
354 or $self->error($err_ctx);
356 $self->_param_default($param);
357 $self->_param_constraint_or_traits($param);
359 $param = $self->create_param($param);
364 ? ($param, $self->remaining_input)
368 sub _param_opt_or_req {
369 my ($self, $param) = @_;
371 return unless $param;
373 if ($self->ppi->class eq 'PPI::Token::Operator') {
374 my $c = $self->ppi->content;
376 $param->{required} = 0;
377 $self->consume_token;
378 } elsif ($c eq '!') {
379 $param->{required} = 1;
380 $self->consume_token;
387 sub _param_constraint_or_traits {
388 my ($self, $param) = @_;
390 while ($self->_param_where($param) ||
391 $self->_param_traits($param) ) {
399 my ($self, $param) = @_;
401 return unless $self->ppi->isa('PPI::Token::LexSymbol')
402 && $self->ppi->lex eq 'WHERE';
404 $self->consume_token;
406 $param->{constraints} ||= [];
408 my $ppi = $self->ppi;
410 $self->error($ppi, "Block expected after where")
411 unless $ppi->class eq 'PPI::Token::Structure'
412 && $ppi->content eq '{';
414 # Go from token to block
417 $ppi->finish or $self->error($ppi,
418 "Runaway '" . $ppi->braces . "' in " . $self->_parsing_area(1), 1);
420 push @{$param->{constraints}}, $ppi->content;
422 $self->_set_ppi($ppi->finish);
423 $self->consume_token;
428 my ($self, $param) = @_;
429 return unless $self->ppi->isa('PPI::Token::LexSymbol')
430 && $self->ppi->lex eq 'TRAIT';
432 my $op = $self->consume_token->content;
434 $self->error($self->ppi, "Error parsing parameter trait")
435 unless $self->ppi->isa('PPI::Token::Word');
437 $param->{param_traits} ||= [];
439 push @{$param->{param_traits}}, [$op, $self->consume_token->content];
444 my ($self, $param) = @_;
447 $self->ppi->content eq ':' &&
448 $self->ppi->next_token->isa('PPI::Token::Word');
450 $self->consume_token;
452 $self->error($self->ppi, "Invalid label")
453 if $self->ppi->content =~ /[^-\w]/;
456 $param->{required} = 0;
457 $param->{label} = $self->consume_token->content;
459 $self->assert_token('(');
460 $self->_unpacked_param($param)
461 || $self->_param_variable($param)
462 || $self->error($self->ppi);
464 $self->assert_token(')');
469 sub _unpacked_param {
470 my ($self, $param) = @_;
472 return $self->bracketed('[', \&unpacked_array, $param) ||
473 $self->bracketed('{', \&unpacked_hash, $param);
477 my ($self, $param) = @_;
480 $self->ppi->content eq ':' &&
481 $self->ppi->next_token->isa('PPI::Token::Symbol');
483 $param->{required} = 0;
485 $self->consume_token;
487 my $err_ctx = $self->ppi;
488 $param = $self->_param_variable($param);
490 $self->error($err_ctx, "Arrays or hashes cannot be named")
491 if $param->{sigil} ne '$';
497 my ($self, $param) = @_;
503 $tc = $self->type_constraint_class->new(
505 ( $self->has_type_constraint_callback
506 ? (tc_callback => $self->type_constraint_callback)
509 ( $self->has_from_namespace
510 ? ( from_namespace => $self->from_namespace )
514 $param->{type_constraints} = $tc;
520 my ($self, $param) = @_;
522 return unless $self->ppi->content eq '=';
524 $self->consume_token;
526 $param->{default_value} =
527 $self->_consume_if_isa(qw/
528 PPI::Token::QuoteLike
533 PPI::Token::ArrayIndex
535 $self->bracketed('[') ||
536 $self->bracketed('{')
537 or $self->error($self->ppi);
539 $param->{default_value} = $param->{default_value}->content;
543 sub _param_variable {
544 my ($self, $param) = @_;
546 my $ppi = $self->ppi;
547 my $class = $ppi->class;
548 return unless $class eq 'PPI::Token::Symbol'
549 || $class eq 'PPI::Token::Cast';
551 if ($class eq 'PPI::Token::Symbol') {
552 $ppi->symbol_type eq $ppi->raw_type or $self->error($ppi);
554 $param->{sigil} = $ppi->raw_type;
555 $param->{variable_name} = $self->consume_token->content;
557 $param->{sigil} = $self->consume_token->content;
564 my ($self, $list, $param) = @_;
567 while ($self->ppi->content ne '}') {
568 my $errctx = $self->ppi;
570 or $self->error($self->ppi);
572 $self->error($errctx, "Cannot have positional parameters in an unpacked-array")
573 if $p->sigil eq '$' && PositionalParam->check($p);
576 last if $self->ppi->content eq '}';
577 $self->assert_token(',');
579 $param->{params} = $params;
580 $param->{sigil} = '$';
581 $param->{unpacking} = 'Hash';
586 my ($self, $list, $param) = @_;
589 while ($self->ppi->content ne ']') {
590 my $watermark = $self->ppi;
591 my $param = $self->param
592 or $self->error($self->ppi);
594 $self->error($watermark, "Cannot have named parameters in an unpacked-array")
595 if NamedParam->check($param);
597 $self->error($watermark, "Cannot have optional parameters in an unpacked-array")
598 unless $param->required;
600 push @$params, $param;
602 last if $self->ppi->content eq ']';
603 $self->assert_token(',');
605 $param->{params} = $params;
606 $param->{sigil} = '$';
607 $param->{unpacking} = 'Array';
612 my ($self, $required) = @_;
614 my $ident = $self->_ident;
616 $ident or ($required and $self->error($self->ppi)) or return;
618 return $self->_tc_union(
619 $self->bracketed('[', \&_tc_params, $ident)
624 # Handle parameterized TCs. e.g.:
627 # Dict["foo bar", Baz]
629 my ($self, $list, $tc) = @_;
631 my $new = PPI::Statement::Expression::TCParams->new($tc->clone);
633 return $new if $self->ppi->content eq ']';
635 $new->add_element($self->_tc_param);
637 while ($self->ppi->content =~ /^,|=>$/ ) {
639 my $op = $self->consume_token;
640 $self->_stringify_last($new) if $op->content eq '=>';
642 $new->add_element($self->tc(1));
648 # Valid token for individual component of parameterized TC
652 (my $class = $self->ppi->class) =~ s/^PPI:://;
653 return $self->consume_token->clone
654 if $class eq 'Token::Number' ||
655 $class =~ /^Token::Quote::(?:Single|Double|Literal|Interpolate)/;
661 my ($self, $tc) = @_;
663 return $tc unless $self->ppi->content eq '|';
665 my $union = PPI::Statement::Expression::TCUnion->new;
666 $union->add_element($tc);
667 while ( $self->ppi->content eq '|' ) {
669 $self->consume_token;
670 $union->add_element($self->tc(1));
676 # Stringify LHS of fat comma
677 sub _stringify_last {
678 my ($self, $list) = @_;
679 my $last = $list->last_token;
680 return unless $last->isa('PPI::Token::Word');
682 # Is this conditional on the content of the word?
683 bless $last, "PPI::Token::StringifiedWord";
687 # Handle the boring bits of bracketed product, then call $code->($self, ...)
689 my ($self, $type, $code, @args) = @_;
691 local $ERROR_LEVEL = $ERROR_LEVEL + 1;
692 my $ppi = $self->ppi;
693 return unless $ppi->content eq $type;
695 $self->consume_token; # consume '[';
697 # Get from the '[' token the to Strucure::Constructor
700 $ppi->finish or $self->error($ppi,
701 "Runaway '" . $ppi->braces . "' in " . $self->_parsing_area(1), 1);
706 my $list = PPI::Structure::Constructor->new($ppi->start->clone);
707 $ret = $code->($self, $list, @args);
709 $self->error($self->ppi)
710 if $self->ppi != $ppi->finish;
712 # There is no public way to do this as of PPI 1.204_06. I'll add one to the
713 # next release, 1.205 (or so)
714 $list->{finish} = $self->consume_token->clone;
716 # Just clone the entire [] or {}
718 $self->_set_ppi($ppi->finish);
719 $self->consume_token;
725 # Work out what sort of production we are in for sane default error messages
728 my $height = shift || 0;
729 my (undef, undef, undef, $sub) = caller($height+$ERROR_LEVEL);
731 return "type constraint" if $sub =~ /(?:\b|_)tc(?:\b|_)/;
732 return "unpacked parameter"
733 if $sub =~ /(?:\b|_)unpacked(?:\b|_)/;
734 return "parameter" if $sub =~ /(?:\b|_)param(?:\b|_)/;
735 return "signature" if $sub =~ /(?:\b|_)signature(?:\b|_)/;
737 " unknown production ($sub)";
740 # error(PPI::Token $token, Str $msg?, Bool $no_in = 0)
742 my ($self, $token, $msg, $no_in) = @_;
744 $msg = "Error parsing " . $self->_parsing_area(2)
748 $msg = $msg . " near '$token'" .
750 : " in '" . $token->statement . "'"
761 my ($self, $need, $msg) = @_;
763 if ($self->ppi->content ne $need) {
764 $self->error($self->ppi, "'$need' expected whilst parsing " . $self->_parsing_area(2));
766 return $self->consume_token;
779 my $ppi = $self->ppi;
780 return $self->consume_token
781 if $ppi->class eq 'PPI::Token::Word';
785 sub _consume_if_isa {
786 my ($self, @classes) = @_;
789 return $self->consume_token
790 if $self->ppi->isa($_);
798 my $ppi = $self->ppi;
801 while (!$ppi->isa('PPI::Token::EOF') ) {
802 $ppi = $ppi->next_token;
803 last if $ppi->significant;
806 if ($ppi->class eq 'PPI::Token::Word' && exists $LEXTABLE{"$ppi"}) {
807 bless $ppi, "PPI::Token::LexSymbol";
808 $ppi->{lex} = $LEXTABLE{"$ppi"};
810 $self->_set_ppi( $ppi );
814 sub remaining_input {
815 my $tok = $_[0]->ppi;
818 while ( !$tok->isa('PPI::Token::EOF') ) {
819 $buff .= $tok->content;
820 $tok = $tok->next_token;
825 __PACKAGE__->meta->make_immutable;
828 # Extra PPI classes to represent what we want.
830 PPI::Statement::Expression::TCUnion;
831 use base 'PPI::Statement::Expression';
834 join('|', $_[0]->children );
839 PPI::Statement::Expression::TCParams;
841 use base 'PPI::Statement::Expression';
844 # $self->children stores everything so PPI cna track parents
845 # params just contains the keywords (not commas) inside the []
846 has type => ( is => 'ro');
849 default => sub { [] },
853 my ($class, $type) = @_;
855 return $class->meta->new_object(
856 __INSTANCE__ => $class->SUPER::new($type),
861 override add_element => sub {
862 my ($self, $ele) = @_;
864 push @{$self->params}, $ele;
868 $_[0]->type->content . '[' . join(',', @{$_[0]->params}) . ']'
875 PPI::Token::LexSymbol;
876 use base 'PPI::Token::Word';
884 # Used for LHS of fat comma
886 PPI::Token::StringifiedWord;
887 use base 'PPI::Token::Word';
890 override content => sub {
891 return '"' . super() . '"';
895 return $_[0]->PPI::Token::Word::content();
906 Parse::Method::Signatures - Perl6 like method signature parser
910 Inspired by L<Perl6::Signature> but streamlined to just support the subset
911 deemed useful for L<TryCatch> and L<MooseX::Method::Signatures>.
917 =item * Document the parameter return types.
919 =item * Probably lots of other things
925 There are only two public methods to this module, both of which should be
926 called as class methods. Both methods accept either a single (non-ref) scalar
927 as the value for the L</input> attribute, or normal new style arguments (hash
932 my $sig = Parse::Method::Signatures->signature( '(Str $foo)' )
934 Attempts to parse the (bracketed) method signature. Returns a value or croaks
939 my $param = Parse::Method::Signatures->param( 'Str $foo where { length($_) < 10 }')
941 Attempts to parse the specification for a single parameter. Returns value or
946 All the attributes on this class are read-only.
958 Offset into L</input> at which to start parsing. Useful for using with
959 Devel::Declare linestring
961 =head2 signature_class
963 B<Default:> Parse::Method::Signatures::Sig
965 B<Type:> Str (loaded on demand class name)
969 B<Default:> Parse::Method::Signatures::Param
971 B<Type:> Str (loaded on demand class name)
973 =head2 type_constraint_class
975 B<Default:> L<Parse::Method::Signatures::TypeConstraint>
977 B<Type:> Str (loaded on demand class name)
979 Class that is used to turn the parsed type constraint into an actual
980 L<Moose::Meta::TypeConstraint> object.
982 =head2 from_namespace
986 Let this module know which package it is parsing signatures form. This is
987 entirely optional, and the only effect is has is on parsing type constraints.
989 If this attribute is set it is passed to L</type_constraint_class> which can
990 use it to introspect the package (commmonly for L<MooseX::Types> exported
992 L<Parse::Method::Signature::TypeConstraints/find_registered_constraint> for
995 =head2 type_constraint_callback
999 Passed to the constructor of L</type_constraint_class>. Default implementation
1000 of this callback asks Moose for a type constrain matching the name passed in.
1001 If you have more complex requirements, such as parsing types created by
1002 L<MooseX::Types> then you will want a callback similar to this:
1004 # my $target_package defined elsewhere.
1006 my ($pms_tc, $name) = @_;
1007 my $code = $target_package->can($name);
1008 $code ? eval { $code->() }
1009 : $pms_tc->find_registered_constraint($name);
1012 Note that the above example is better provided by providing the
1013 L</from_namespace> attribute.
1017 Like Perl6::Signature, the parsing of certain constructs is currently only a
1018 'best effort' - specifically default values and where code blocks might not
1019 successfully for certain complex cases. Patches/Failing tests welcome.
1021 Additionally, default value specifications are not evaluated which means that
1022 no such lexical or similar errors will not be produced by this module.
1023 Constant folding will also not be performed.
1025 There are certain constructs that are simply too much hassle to avoid when the
1026 work around is simple. Currently the only cases that are known to parse wrong
1027 are when using anonymous variables (i.e. just sigils) in unpacked arrays. Take
1028 the following example:
1030 method foo (ArrayRef [$, $], $some_value_we_care_about) {
1032 In this case the C<$]> is treated as one of perl's magic variables
1033 (specifically, the patch level of the Perl interpreter) rather than a C<$>
1034 followed by a C<]> as was almost certainly intended. The work around for this
1035 is simple: introduce a space between the charcters:
1037 method foo (ArrayRef [ $, $ ], $some_value_we_care_about) {
1043 Ash Berlin <ash@cpan.org>.
1045 Thanks to Florian Ragwitz <rafl@debian.org>.
1047 Many thanks to Piers Crawley to showing me the way to refactor my spaghetti
1048 code into something more manageable.
1052 L<Devel::Declare> which is used by most modules that use this (currently by
1053 all modules known to the author.)
1055 L<http://github.com/ashb/trycatch/tree>.
1059 Licensed under the same terms as Perl itself.
1061 This distribution copyright 2008-2009, Ash Berlin <ash@cpan.org>