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