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