Allow the name of the keyword to be changed.
[p5sagit/Function-Parameters.git] / lib / Function / Parameters.pm
CommitLineData
7a63380c 1package Function::Parameters;
2
3use strict;
4use warnings;
5
6our $VERSION = '0.03';
7
8use Devel::Declare;
9use B::Hooks::EndOfScope;
10use B::Compiling;
11
12sub guess_caller {
13 my ($start) = @_;
14 $start ||= 1;
15
16 my $defcaller = (caller $start)[0];
17 my $caller = $defcaller;
18
19 for (my $level = $start; ; ++$level) {
20 my ($pkg, $function) = (caller $level)[0, 3] or last;
21 #warn "? $pkg, $function";
22 $function =~ /::import\z/ or return $caller;
23 $caller = $pkg;
24 }
25 $defcaller
26}
27
28sub _fun ($) { $_[0] }
29
30sub import {
31 my $class = shift;
b4fcf7d0 32 my $keyword = shift;
7a63380c 33 my $caller = guess_caller;
34 #warn "caller = $caller";
35
36 Devel::Declare->setup_for(
37 $caller,
b4fcf7d0 38 { $keyword => { const => \&parser } }
7a63380c 39 );
40
41 no strict 'refs';
b4fcf7d0 42 *{$caller . '::' . $keyword} = \&_fun;
7a63380c 43}
44
45sub report_pos {
46 my ($offset, $name) = @_;
47 $name ||= '';
48 my $line = Devel::Declare::get_linestr();
49 substr $line, $offset + 1, 0, "\x{20de}\e[m";
50 substr $line, $offset, 0, "\e[31;1m";
51 print STDERR "$name($offset)>> $line\n";
52}
53
54sub parser {
55 my ($declarator, $start) = @_;
56 my $offset = $start;
57 my $line = Devel::Declare::get_linestr();
58
59 my $fail = do {
60 my $_file = PL_compiling->file;
61 my $_line = PL_compiling->line;
62 sub {
63 my $n = $_line + substr($line, $start, $offset - $start) =~ tr[\n][];
64 die join('', @_) . " at $_file line $n\n";
65 }
66 };
67
68 my $atomically = sub {
69 my ($pars) = @_;
70 sub {
71 my $tmp = $offset;
72 my @ret = eval { $pars->(@_) };
73 if ($@) {
74 $offset = $tmp;
75 die $@;
76 }
77 wantarray ? @ret : $ret[0]
78 }
79 };
80
81 my $try = sub {
82 my ($pars) = @_;
83 my @ret = eval { $pars->() };
84 if ($@) {
85 return;
86 }
87 wantarray ? @ret : $ret[0]
88 };
89
90 my $skipws = sub {
91 #warn ">> $line";
92 my $skip = Devel::Declare::toke_skipspace($offset);
93 if ($skip < 0) {
94 $skip == -$offset or die "Internal error: offset=$offset, skip=$skip";
95 Devel::Declare::set_linestr($line);
96 return;
97 }
98 $line = Devel::Declare::get_linestr();
99 #warn "toke_skipspace($offset) = $skip\n== $line";
100 $offset += $skip;
101 };
102
103 $offset += Devel::Declare::toke_move_past_token($offset);
104 $skipws->();
105 my $manip_start = $offset;
106
107 my $name;
108 if (my $len = Devel::Declare::toke_scan_word($offset, 1)) {
109 $name = substr $line, $offset, $len;
110 $offset += $len;
111 $skipws->();
112 }
113
114 my $scan_token = sub {
115 my ($str) = @_;
116 my $len = length $str;
117 substr($line, $offset, $len) eq $str or $fail->(qq{Missing "$str"});
118 $offset += $len;
119 $skipws->();
120 };
121
122 my $scan_id = sub {
123 my $len = Devel::Declare::toke_scan_word($offset, 0) or $fail->('Missing identifier');
124 my $name = substr $line, $offset, $len;
125 $offset += $len;
126 $skipws->();
127 $name
128 };
129
130 my $scan_var = $atomically->(sub {
131 (my $sigil = substr($line, $offset, 1)) =~ /^[\$\@%]\z/ or $fail->('Missing [$@%]');
132 $offset += 1;
133 $skipws->();
134 my $name = $scan_id->();
135 $sigil . $name
136 });
137
138 my $separated_by = $atomically->(sub {
139 my ($sep, $pars) = @_;
140 my $len = length $sep;
141 defined(my $x = $try->($pars)) or return;
142 my @res = $x;
143 while () {
144 substr($line, $offset, $len) eq $sep or return @res;
145 $offset += $len;
146 $skipws->();
147 push @res, $pars->();
148 }
149 });
150
151 my $many_till = $atomically->(sub {
152 my ($end, $pars) = @_;
153 my $len = length $end;
154 my @res;
155 until (substr($line, $offset, $len) eq $end) {
156 push @res, $pars->();
157 }
158 @res
159 });
160
161 my $scan_params = $atomically->(sub {
162 if ($try->(sub { $scan_token->('('); 1 })) {
163 my @param = $separated_by->(',', $scan_var);
164 $scan_token->(')');
165 return @param;
166 }
167 $try->($scan_var)
168 });
169
170 my @param = $scan_params->();
171
172 my $scan_pargroup_opt = sub {
173 substr($line, $offset, 1) eq '(' or return '';
174 my $len = Devel::Declare::toke_scan_str($offset);
175 my $res = Devel::Declare::get_lex_stuff();
176 Devel::Declare::clear_lex_stuff();
177 $res eq '' and $fail->(qq{Can't find ")" anywhere before EOF});
178 $offset += $len;
179 $skipws->();
180 "($res)"
181 };
182
183 my $scan_attr = sub {
184 my $name = $scan_id->();
185 my $param = $scan_pargroup_opt->() || '';
186 $name . $param
187 };
188
189 my $scan_attributes = $atomically->(sub {
190 $try->(sub { $scan_token->(':'); 1 }) or return '', [];
191 my $proto = $scan_pargroup_opt->();
192 my @attrs = $many_till->('{', $scan_attr);
193 ' ' . $proto, \@attrs
194 });
195
196 my ($proto, $attributes) = $scan_attributes->();
197 my $attr = @$attributes ? ' : ' . join(' ', @$attributes) : '';
198
199 $scan_token->('{');
200
201 my $manip_end = $offset;
202 my $manip_len = $manip_end - $manip_start;
203 #print STDERR "($manip_start:$manip_len:$manip_end)\n";
204
205 my $params = @param ? 'my (' . join(', ', @param) . ') = @_;' : '';
206 #report_pos $offset;
207 $proto =~ tr[\n][ ];
208
209 if (defined $name) {
210 my $pkg = __PACKAGE__;
211 #print STDERR "($manip_start:$manip_len) [$line]\n";
212 substr $line, $manip_start, $manip_len, " do { sub $name$proto; sub $name$proto$attr { BEGIN { ${pkg}::terminate_me(q[$name]); } $params ";
213 } else {
214 substr $line, $manip_start, $manip_len, " sub$proto$attr { $params ";
215 }
216 #print STDERR ".> $line\n";
217 Devel::Declare::set_linestr($line);
218}
219
220sub terminate_me {
221 my ($name) = @_;
222 on_scope_end {
223 my $line = Devel::Declare::get_linestr();
224 #print STDERR "~~> $line\n";
225 my $offset = Devel::Declare::get_linestr_offset();
226 substr $line, $offset, 0, " \\&$name };";
227 Devel::Declare::set_linestr($line);
228 #print STDERR "??> $line\n";
229 };
230}
231
2321
233
234__END__
235
236=head1 NAME
237
238Function::Parameters - subroutine definitions with parameter lists
239
240=head1 SYNOPSIS
241
242 use Function::Parameters;
243
244 fun foo($bar, $baz) {
245 return $bar + $baz;
246 }
247
248 fun mymap($fun, @args) :(&@) {
249 my @res;
250 for (@args) {
251 push @res, $fun->($_);
252 }
253 @res
254 }
255
256 print "$_\n" for mymap { $_ * 2 } 1 .. 4;
257
258=head1 DESCRIPTION
259
260This module lets you use parameter lists in your subroutines. Thanks to
261L<Devel::Declare> it works without source filters.
262
263WARNING: This is my first attempt at using L<Devel::Declare> and I have
264almost no experience with perl's internals. So while this module might
265appear to work, it could also conceivably make your programs segfault.
266Consider this module alpha quality.
267
268=head2 Basic stuff
269
270To use this new functionality, you have to use C<fun> instead of C<sub> -
271C<sub> continues to work as before. The syntax is almost the same as for
272C<sub>, but after the subroutine name (or directly after C<fun> if you're
273writing an anonymous sub) you can write a parameter list in parens. This
274list consists of comma-separated variables.
275
276The effect of C<fun foo($bar, $baz) {> is as if you'd written
277C<sub foo { my ($bar, $baz) = @_; >, i.e. the parameter list is simply
278copied into C<my> and initialized from L<@_|perlvar/"@_">.
279
280=head2 Advanced stuff
281
282If you need L<subroutine attributes|perlsub/"Subroutine Attributes">, you can
283put them after the parameter list with their usual syntax. There's one
284exception, though: you can only use one colon (to start the attribute list);
285multiple attributes have to be separated by spaces.
286
287Syntactically, these new parameter lists live in the spot normally occupied
288by L<prototypes|perlsub/"Prototypes">. However, you can include a prototype by
289specifying it as the first attribute (this is syntactically unambiguous
290because normal attributes have to start with a letter).
291
292Normally, Perl subroutines are not in scope in their own body, meaning the
293parser doesn't know the name C<foo> or its prototype when processing
294C<sub foo ($) { foo $bar[1], $bar[0]; }>, parsing it as
295C<$bar-E<gt>foo([1], $bar[0])>. Yes. You can add parens to change the
296interpretation of this code, but C<foo($bar[1], $bar[0])> will only trigger
297a I<foo() called too early to check prototype> warning. This module attempts
298to fix all of this by adding a subroutine declaration before the definition,
299so the parser knows the name (and possibly prototype) while it processes the
300body. Thus C<fun foo($x) :($) { $x }> really turns into
301C<sub foo ($); sub foo ($) { my ($x) = @_; $x }>.
302
303=head1 AUTHOR
304
305Lukas Mai, C<< <l.mai at web.de> >>
306
307=head1 COPYRIGHT & LICENSE
308
309Copyright 2009 Lukas Mai.
310
311This program is free software; you can redistribute it and/or modify it
312under the terms of either: the GNU General Public License as published
313by the Free Software Foundation; or the Artistic License.
314
315See http://dev.perl.org/licenses/ for more information.
316
317=cut