File Coverage

blib/lib/Chess/Plisco/Macro.pm
Criterion Covered Total %
statement 150 182 82.4
branch 39 68 57.3
condition 9 15 60.0
subroutine 13 82 15.8
pod 68 69 98.5
total 279 416 67.0


line stmt bran cond sub pod time code
1             #! /bin/false
2              
3             # Copyright (C) 2021 Guido Flohr ,
4             # all rights reserved.
5              
6             # This program is free software. It comes without any warranty, to
7             # the extent permitted by applicable law. You can redistribute it
8             # and/or modify it under the terms of the Do What the Fuck You Want
9             # to Public License, Version 2, as published by Sam Hocevar. See
10             # http://www.wtfpl.net/ for more details.
11              
12             package Chess::Plisco::Macro;
13             $Chess::Plisco::Macro::VERSION = '0.4';
14 2     2   1106 use strict;
  2         4  
  2         59  
15              
16 2     2   938 use Filter::Util::Call;
  2         2437  
  2         120  
17 2     2   931 use PPI::Document;
  2         120339  
  2         6548  
18              
19             sub _define;
20             sub _define_from_file;
21             sub preprocess;
22             sub _extract_arguments;
23             sub _split_arguments;
24             sub _expand;
25             sub _expand_placeholders;
26             sub _expand_placeholder;
27              
28             my %defines;
29              
30             # The no-op empty subroutines only exist so that Pod::Coverage works
31             # correctly.
32             _define cp_pos_white_pieces => '$p', '$p->[CP_POS_WHITE_PIECES]';
33       0 1   sub cp_pos_white_pieces {}
34             _define cp_pos_black_pieces => '$p', '$p->[CP_POS_BLACK_PIECES]';
35       0 1   sub cp_pos_black_pieces {}
36             _define cp_pos_pawns => '$p', '$p->[CP_POS_PAWNS]';
37       0 1   sub cp_pos_pawns {}
38             _define cp_pos_knights => '$p', '$p->[CP_POS_KNIGHTS]';
39       0 1   sub cp_pos_knights {}
40             _define cp_pos_bishops => '$p', '$p->[CP_POS_BISHOPS]';
41       0 1   sub cp_pos_bishops {}
42             _define cp_pos_queens => '$p', '$p->[CP_POS_QUEENS]';
43       0 1   sub cp_pos_queens {}
44             _define cp_pos_rooks => '$p', '$p->[CP_POS_ROOKS]';
45       0 1   sub cp_pos_rooks {}
46             _define cp_pos_kings => '$p', '$p->[CP_POS_KINGS]';
47       0 1   sub cp_pos_kings {}
48             _define cp_pos_half_move_clock => '$p', '$p->[CP_POS_HALF_MOVE_CLOCK]';
49       0 1   sub cp_pos_half_move_clock {}
50             _define cp_pos_in_check => '$p', '$p->[CP_POS_IN_CHECK]';
51       0 1   sub cp_pos_in_check {}
52             _define cp_pos_half_moves => '$p', '$p->[CP_POS_HALF_MOVES]';
53       0 1   sub cp_pos_half_moves {}
54             _define cp_pos_signature => '$p', '$p->[CP_POS_SIGNATURE]';
55       0 1   sub cp_pos_signature {}
56             _define cp_pos_info => '$p', '$p->[CP_POS_INFO]';
57       0 1   sub cp_pos_info {}
58             _define cp_pos_reversible_clock => '$p', '$p->[CP_POS_REVERSIBLE_CLOCK]';
59       0 1   sub cp_pos_reversible_clock {}
60              
61             _define cp_pos_info_castling_rights => '$i', '$i & 0xf';
62       0 1   sub cp_pos_info_castling_rights {}
63             _define cp_pos_info_white_king_side_castling_right => '$i', '$i & (1 << 0)';
64       0 1   sub cp_pos_info_white_king_side_castling_right {}
65             _define cp_pos_info_white_queen_side_castling_right => '$i', '$i & (1 << 1)';
66       0 1   sub cp_pos_info_white_queen_side_castling_right {}
67             _define cp_pos_info_black_king_side_castling_right => '$i', '$i & (1 << 2)';
68       0 1   sub cp_pos_info_black_king_side_castling_right {}
69             _define cp_pos_info_black_queen_side_castling_right => '$i', '$i & (1 << 3)';
70       0 1   sub cp_pos_info_black_queen_side_castling_right {}
71             _define cp_pos_info_to_move => '$i', '(($i & (1 << 4)) >> 4)';
72       0 1   sub cp_pos_info_to_move {}
73             _define cp_pos_info_en_passant_shift => '$i', '(($i & (0x3f << 5)) >> 5)';
74       0 1   sub cp_pos_info_en_passant_shift {}
75             _define cp_pos_info_king_shift => '$i', '(($i & (0x3f << 11)) >> 11)';
76       0 1   sub cp_pos_info_king_shift {}
77             _define cp_pos_info_evasion => '$i', '(($i & (0x3 << 17)) >> 17)';
78       0 1   sub cp_pos_info_evasion {}
79             _define cp_pos_info_material => '$i', '($i >> 19)';
80       0 1   sub cp_pos_info_material {}
81              
82             _define _cp_pos_info_set_castling => '$i', '$c',
83             '($i = ($i & ~0xf) | $c)';
84             _define _cp_pos_info_set_white_king_side_castling_right => '$i', '$c',
85             '($i = ($i & ~(1 << 0)) | ($c << 0))';
86             _define _cp_pos_info_set_white_queen_side_castling_right => '$i', '$c',
87             '($i = ($i & ~(1 << 1)) | ($c << 1))';
88             _define _cp_pos_info_set_black_king_side_castling_right => '$i', '$c',
89             '($i = ($i & ~(1 << 2)) | ($c << 2))';
90             _define _cp_pos_info_set_black_queen_side_castling_right => '$i', '$c',
91             '($i = ($i & ~(1 << 3)) | ($c << 3))';
92             _define _cp_pos_info_set_to_move => '$i', '$c',
93             '($i = ($i & ~(1 << 4)) | ($c << 4))';
94             _define _cp_pos_info_set_en_passant_shift => '$i', '$s',
95             '($i = ($i & ~(0x3f << 5)) | ($s << 5))';
96             _define _cp_pos_info_set_king_shift => '$i', '$s',
97             '($i = ($i & ~(0x3f << 11)) | ($s << 11))';
98             _define _cp_pos_info_set_evasion => '$i', '$e',
99             '($i = ($i & ~(0x3 << 17)) | ($e << 17))';
100             _define _cp_pos_info_set_material => '$i', '$m',
101             '($i = (($i & 0x7fffffff) | ($m << 19)))';
102              
103             _define_from_file _cp_pos_info_update => '$p', '$i' => 'infoUpdate.pm';
104              
105             _define cp_pos_castling_rights => '$p',
106             '(cp_pos_info_castling_rights(cp_pos_info($p)))';
107       0 1   sub cp_pos_castling_rights {}
108             _define cp_pos_white_king_side_castling_right => '$p',
109             '(cp_pos_info_white_king_side_castling_right(cp_pos_info($p)))';
110       0 1   sub cp_pos_white_king_side_castling_right {}
111             _define cp_pos_white_queen_side_castling_right => '$p',
112             '(cp_pos_info_white_queen_side_castling_right(cp_pos_info($p)))';
113       0 1   sub cp_pos_white_queen_side_castling_right {}
114             _define cp_pos_black_king_side_castling_right => '$p',
115             '(cp_pos_info_black_king_side_castling_right(cp_pos_info($p)))';
116       0 1   sub cp_pos_black_king_side_castling_right {}
117             _define cp_pos_black_queen_side_castling_right => '$p',
118             '(cp_pos_info_black_queen_side_castling_right(cp_pos_info($p)))';
119       0 1   sub cp_pos_black_queen_side_castling_right {}
120             _define cp_pos_to_move => '$p', '(cp_pos_info_to_move(cp_pos_info($p)))';
121       0 1   sub cp_pos_to_move {}
122             _define cp_pos_en_passant_shift => '$p', '(cp_pos_info_en_passant_shift(cp_pos_info($p)))';
123       0 1   sub cp_pos_en_passant_shift {}
124             _define cp_pos_king_shift => '$p', '(cp_pos_info_king_shift(cp_pos_info($p)))';
125       0 1   sub cp_pos_king_shift {}
126             _define cp_pos_evasion => '$p', '(cp_pos_info_evasion(cp_pos_info($p)))';
127       0 1   sub cp_pos_evasion {}
128             _define cp_pos_material => '$p', '(cp_pos_info_material(cp_pos_info($p)))';
129       0 1   sub cp_pos_material {}
130              
131             _define _cp_pos_set_castling => '$p', '$c',
132             '(_cp_pos_info_set_castling(cp_pos_info($p), $c))';
133             _define _cp_pos_set_white_king_side_castling_right => '$p', '$c',
134             '(_cp_pos_info_set_white_king_side_castling_right(cp_pos_info($p), $c))';
135             _define _cp_pos_set_white_queen_side_castling_right => '$p', '$c',
136             '(_cp_pos_info_set_white_queen_side_castling_right(cp_pos_info($p), $c))';
137             _define _cp_pos_set_black_king_side_castling_right => '$p', '$c',
138             '(_cp_pos_info_set_black_king_side_castling_right(cp_pos_info($p), $c))';
139             _define _cp_pos_set_black_queen_side_castling_right => '$p', '$c',
140             '(_cp_pos_info_set_black_queen_side_castling_right(cp_pos_info($p), $c))';
141             _define _cp_pos_set_to_move => '$p', '$c',
142             '(_cp_pos_info_set_to_move(cp_pos_info($p), $c))';
143             _define _cp_pos_set_en_passant_shift => '$p', '$s',
144             '(_cp_pos_info_set_en_passant_shift(cp_pos_info($p), $s))';
145             _define _cp_pos_set_king_shift => '$p', '$s',
146             '(_cp_pos_info_set_king_shift(cp_pos_info($p), $s))';
147             _define _cp_pos_set_evasion => '$p', '$e',
148             '(_cp_pos_info_set_evasion(cp_pos_info($p), $e))';
149             _define _cp_pos_set_material => '$p', '$m',
150             '(_cp_pos_info_set_material(cp_pos_info($p), $m))';
151              
152             _define cp_pos_evasion_squares => '$p', '$p->[CP_POS_EVASION_SQUARES]';
153       0 1   sub cp_pos_evasion_squares {}
154              
155             _define cp_move_to => '$m', '(($m) & 0x3f)';
156       0 1   sub cp_move_to {}
157             _define cp_move_set_to => '$m', '$v', '(($m) = (($m) & ~0x3f) | (($v) & 0x3f))';
158       0 1   sub cp_move_set_to {}
159             _define cp_move_from => '$m', '(($m >> 6) & 0x3f)';
160       0 1   sub cp_move_from {}
161             _define cp_move_set_from => '$m', '$v',
162             '(($m) = (($m) & ~0xfc0) | (($v) & 0x3f) << 6)';
163       0 1   sub cp_move_set_from {}
164             _define cp_move_promote => '$m', '(($m >> 12) & 0x7)';
165       0 1   sub cp_move_promote {}
166             _define cp_move_set_promote => '$m', '$p',
167             '(($m) = (($m) & ~0x7000) | (($p) & 0x7) << 12)';
168       0 1   sub cp_move_set_promote {}
169             _define cp_move_piece => '$m', '(($m >> 15) & 0x7)';
170       0 1   sub cp_move_piece {}
171             _define cp_move_set_piece => '$m', '$a',
172             '(($m) = (($m) & ~0x38000) | (($a) & 0x7) << 15)';
173       0 1   sub cp_move_set_piece {}
174             _define cp_move_captured => '$m', '(($m >> 18) & 0x7)';
175       0 1   sub cp_move_captured {}
176             _define cp_move_set_captured => '$m', '$a',
177             '(($m) = (($m) & ~0x1c0000) | (($a) & 0x7) << 18)';
178             sub cp_move_set_captured {}
179             _define cp_move_color => '$m', '(($m >> 21) & 0x1)';
180       0 1   sub cp_move_color {}
181             _define cp_move_set_color => '$m', '$c',
182             '(($m) = (($m) & ~0x20_0000) | (($c) & 0x1) << 21)';
183       0 1   sub cp_move_set_captured {}
184             _define cp_move_coordinate_notation => '$m', 'cp_shift_to_square(cp_move_from $m) . cp_shift_to_square(cp_move_to $m) . CP_PIECE_CHARS->[CP_BLACK]->[cp_move_promote $m]';
185       0 1   sub cp_move_coordinate_notation {}
186             _define cp_move_significant => '$m', '($m & 0x7fff)';
187       0 1   sub cp_move_significant {}
188             _define cp_move_equivalent => '$m1', '$m2',
189             '(cp_move_significant($m1) == cp_move_significant($m2))';
190       0 1   sub cp_move_equivalent {}
191              
192             # Bitboard macros.
193             _define cp_bitboard_popcount => '$b', '$c',
194             '{ my $_b = $b; for ($c = 0; $_b; ++$c) { $_b &= $_b - 1; } }';
195       0 1   sub cp_bitboard_popcount {}
196             _define cp_bitboard_clear_but_least_set => '$b', '(($b) & -($b))';
197       0 1   sub cp_bitboard_clear_but_least_set {}
198             _define_from_file cp_bitboard_clear_but_most_set => '$bb', 'clearButMostSet.pm';
199       0 1   sub cp_bitboard_clear_but_most_set {}
200             _define_from_file cp_bitboard_count_isolated_trailing_zbits => '$bb',
201             'countIsolatedTrailingZbits.pm';
202       0 1   sub cp_bitboard_count_isolated_trailing_zbits {}
203             _define_from_file cp_bitboard_count_trailing_zbits => '$bb', 'countTrailingZbits.pm';
204       0 1   sub cp_bitboard_count_trailing_zbits {}
205             _define cp_bitboard_clear_least_set => '$bb', '(($bb) & (($bb) - 1))';
206       0 1   sub cp_bitboard_clear_least_set {}
207             _define cp_bitboard_more_than_one_set => '$bb', '($bb && ($bb & ($bb - 1)))';
208       0 1   sub cp_bitboard_more_than_one_set {}
209              
210             # Magic moves.
211             _define cp_mm_bmagic => '$s', '$o',
212             'CP_MAGICMOVESBDB->[$s][(((($o) & CP_MAGICMOVES_B_MASK->[$s]) * CP_MAGICMOVES_B_MAGICS->[$s]) >> 55) & ((1 << (64 - 55)) - 1)]';
213       0 1   sub cp_mm_bmagic {}
214             _define cp_mm_rmagic => '$s', '$o',
215             'CP_MAGICMOVESRDB->[$s][(((($o) & CP_MAGICMOVES_R_MASK->[$s]) * CP_MAGICMOVES_R_MAGICS->[$s]) >> 52) & ((1 << (64 - 52)) - 1)]';
216       0 1   sub cp_mm_rmagic {}
217              
218             # Conversion between different notions of a square.
219             _define cp_coordinates_to_shift => '$f', '$r', '(($r << 3) + $f)';
220       0 1   sub cp_coordinates_to_shift {}
221             _define cp_shift_to_coordinates => '$s', '($s & 0x7, $s >> 3)';
222       0 1   sub cp_shift_to_coordinates {}
223             _define cp_coordinates_to_square => '$f', '$r', 'chr(97 + $f) . (1 + $r)';
224       0 1   sub cp_coordinates_to_square {}
225             _define cp_square_to_coordinates => '$s', '(ord($s) - 97, -1 + substr $s, 1)';
226       0 1   sub cp_square_to_coordinates {}
227             _define cp_square_to_shift => '$s',
228             '(((substr $s, 1) - 1) << 3) + ord($s) - 97';
229       0 1   sub cp_square_to_shift {}
230             _define cp_shift_to_square => '$s', 'chr(97 + ($s & 0x7)) . (1 + ($s >> 3))';
231       0 1   sub cp_shift_to_square {}
232              
233             _define_from_file _cp_moves_from_mask => '$t', '@m', '$b',
234             'movesFromMask.pm';
235             _define_from_file _cp_promotion_moves_from_mask => '$t', '@m', '$b',
236             'promotionMovesFromMask.pm';
237             _define_from_file _cp_pos_move_pinned =>
238             '$p', '$from', '$to', '$ks', '$mp', '$hp', 'movePinned.pm';
239             _define_from_file _cp_pos_color_attacked => '$p', '$c', '$shift', 'attacked.pm';
240             _define_from_file _cp_pos_move_attacked => '$p', '$from', '$to', 'moveAttacked.pm';
241             _define _cp_pawn_double_step => '$f', '$t', '(!(($t - $f) & 0x9))';
242              
243             # Bit twiddling.
244             _define_from_file cp_abs => '$v', 'abs.pm';
245       0 1   sub cp_abs {}
246              
247             # At least as fast as the versions w/o branching for example
248             # a - ((a -b) & ((a - b) >> 63)), and there are no overflow issues.
249             _define cp_max => '$A', '$B', '((($A) > ($B)) ? ($A) : ($B))';
250       0 1   sub cp_max {}
251             _define cp_min => '$A', '$B', '((($A) < ($B)) ? ($A) : ($B))';
252       0 1   sub cp_min {}
253              
254             # Zobrist keys.
255             _define _cp_zk_lookup => '$p', '$c', '$s', '$zk_pieces[((($p) << 7) | (($c) << 6) | ($s)) - 128]';
256              
257             sub import {
258 0     0   0 my ($type) = @_;
259              
260 0         0 my $self = {
261             __source => '',
262             __eof => 0,
263             };
264              
265 0         0 filter_add(bless $self); ## no critic
266             }
267              
268             sub filter {
269 0     0 0 0 my ($self) = @_;
270              
271 0 0       0 return 0 if $self->{__eof};
272              
273 0         0 my $status = filter_read();
274              
275 0 0       0 if ($status > 0) {
    0          
276 0         0 $self->{__source} .= $_;
277 0         0 $_ = '';
278             } elsif ($status == 0) {
279 0         0 $_ = preprocess $self->{__source};
280 0         0 $self->{__eof} = 1;
281 0         0 return 1;
282             }
283              
284 0         0 return $status;
285             }
286              
287             sub _expand {
288 5     5   16 my ($parent, $invocation) = @_;
289              
290             # First find the invocation.
291 5         20 my @siblings = $parent->children;
292 5         31 my $count = -1;
293 5         9 my $idx;
294 5         10 foreach my $sibling (@siblings) {
295 5         10 ++$count;
296 5 50       17 if ($sibling == $invocation) {
297 5         34 $idx = $count;
298 5         10 last;
299             }
300             }
301              
302 5 50       19 return if !defined $idx;
303              
304             # First remove all elements following the invocation, and later re-add
305             # them.
306 5         13 my $name = $invocation->content;
307              
308 5         19 my $definition = $defines{$name};
309 5         21 my $code = $definition->{code}->content;
310 5         467 $code =~ s/\n//g;
311 5         19 my $cdoc = PPI::Document->new(\$code);
312 5         10085 my $cut = 0;
313 5 50       10 if (@{$definition->{args}} == 0) {
  5         25  
314             # Just a constant, no arguments.
315             # Check whether there is a list following, and discard it.
316 0         0 my $to;
317 0         0 foreach ($to = $idx + 1; $to < @siblings; ++$to) {
318 0 0       0 last if $siblings[$to]->significant;
319             }
320 0 0 0     0 if ($to < @siblings && $siblings[$to]->isa('PPI::Structure::List')) {
321 0         0 $cut = $to - $idx;
322             }
323             } else {
324 5         22 my @arguments = _extract_arguments $invocation;
325 5         18 my @placeholders = @{$definition->{args}};
  5         15  
326 5         12 my %placeholders;
327 5         18 for (my $i = 0; $i < @placeholders; ++$i) {
328 6         8 my $placeholder = $placeholders[$i];
329 6 50       14 if ($i > $#arguments) {
330 0         0 $placeholders{$placeholder} = [];
331             } else {
332 6         22 $placeholders{$placeholder} = $arguments[$i];
333             }
334             }
335 5         40 _expand_placeholders $cdoc, %placeholders;
336              
337 5         119 my ($to, $first_significant);
338 5         29 foreach ($to = $idx + 1; $to < @siblings; ++$to) {
339 8 100 100     39 if (!defined $first_significant && $siblings[$to]->significant) {
340 5         9 $first_significant = $siblings[$to];
341 5 100       19 if ($first_significant->isa('PPI::Structure::List')) {
342 3         27 --$to;
343 3         6 last;
344             }
345             }
346             }
347 5 100       18 $to = $idx if $to >= @siblings;
348 5         20 $cut = $to - $idx + 1;
349             }
350              
351 5         29 $parent->remove_child($invocation);
352              
353 5         101 my @tail;
354 5         16 for (my $i = $idx + 1; $i < @siblings; ++$i) {
355 10         108 push @tail, $parent->remove_child($siblings[$i]);
356             }
357              
358 5         91 splice @tail, 0, $cut;
359              
360 5         14 my @children = $cdoc->children;
361 5         24 foreach my $child (@children) {
362 5         13 $cdoc->remove_child($child);
363             }
364              
365              
366 5         89 foreach my $sibling (@children, @tail) {
367 10         62 $parent->add_element($sibling);
368             }
369              
370 5         64 return $invocation;
371             }
372              
373             sub _expand_placeholders {
374 6     6   3974 my ($doc, %placeholders) = @_;
375              
376             my $words = $doc->find(sub {
377             ($_[1]->isa('PPI::Token::Symbol') || $_[1]->isa('PPI::Token::Word'))
378 142 100 66 142   2171 && exists $placeholders{$_[1]->content}
379 6         45 });
380              
381 6         73 foreach my $word (@$words) {
382 10         72 _expand_placeholder $word, @{$placeholders{$word->content}};
  10         21  
383             }
384             }
385              
386             sub _expand_placeholder {
387 10     10   56 my ($word, @arglist) = @_;
388              
389             # Find the word in the parent.
390 10         33 my $parent = $word->parent;
391 10         38 my $idx;
392              
393 10         34 my @siblings = $parent->children;
394 10         47 my $word_idx;
395             my @tail;
396 10         31 for (my $i = 0; $i < @siblings; ++$i) {
397 10 50       33 if (defined $word_idx) {
    50          
398 0         0 my $sibling = $siblings[$i];
399 0         0 $parent->remove_child($sibling);
400 0         0 push @tail, $sibling;
401             } elsif ($siblings[$i] == $word) {
402 10         51 $word_idx = $i;
403 10         28 $parent->remove_child($word);
404             }
405             }
406              
407 10         227 foreach my $token (@arglist) {
408             # We have to clone the token, in case it had been used before.
409 10         46 $token = $token->clone;
410             }
411              
412 10         98 foreach my $token (@arglist, @tail) {
413 10         35 $parent->add_element($token);
414             }
415             }
416              
417             sub preprocess {
418 5     5 1 4869 my ($content) = @_;
419              
420 5         11 my ($head, $code, $tail);
421              
422 5 50       21 if ($content =~ /(.*\n)# *__BEGIN_MACROS__.*?\n(.*\n)# *__END_MACROS__.*?\n(.*)/s) {
423 0         0 ($head, $code, $tail) = ($1, $2, $3);
424 0         0 $head .= "\n";
425 0         0 $tail = "\n$tail";
426             } else {
427 5         11 $head = '';
428 5         12 $code = $content;
429 5         10 $tail = '';
430             }
431              
432 5         26 my $source = PPI::Document->new(\$code);
433              
434             # We always replace the last macro invocation only, and then re-scan the
435             # document. This should ensure that nested macro invocations will work.
436 5         6353 while (1) {
437             my $invocations = $source->find(sub {
438 158 100   158   1979 $_[1]->isa('PPI::Token::Word') && exists $defines{$_[1]->content}
439 10         273 });
440              
441 10 100       122 last if !$invocations;
442              
443 5         14 my $invocation = $invocations->[-1];
444 5         23 my $parent = $invocation->parent;
445              
446 5         68 _expand $parent, $invocation;
447             }
448              
449 5         19 return $head . $source->content . $tail;
450             }
451              
452             sub _define {
453 192     192   599 my ($name, @args) = @_;
454              
455 192         484 my $code = pop @args;
456 192 50       581 $code = '' if !defined $code;
457              
458 192 50       460 if (exists $defines{$name}) {
459 0         0 require Carp;
460 0         0 Carp::croak("duplicate macro definition '$name'");
461             }
462              
463 192         905 my $code_doc = PPI::Document->new(\$code);
464 192 50       860259 if (!$code_doc) {
465 0         0 require Carp;
466 0         0 my $msg = $@->message;
467 0         0 Carp::croak("cannot parse code for '$name': $msg\n");
468             }
469              
470 192         877 $code_doc->prune('PPI::Token::Comment');
471              
472 192         213458 $defines{$name} = {
473             args => [@args],
474             code => $code_doc,
475             };
476              
477 192         1037 return;
478             }
479              
480             sub _define_from_file {
481 20     20   86 my ($name, @args) = @_;
482              
483 20         48 my $relname = pop @args;
484 20         53 my $filename = __FILE__;
485 20         150 $filename =~ s{\.pm$}{/$relname};
486              
487 20 50       1371 open my $fh, '<', $filename
488             or die "cannot open '$filename' for reading: $!";
489            
490 20         964 my $code = join '', <$fh>;
491              
492 20         135 return _define $name, @args, $code;
493             }
494              
495             sub _extract_arguments {
496 5     5   11 my ($word) = @_;
497              
498 5         20 my $parent = $word->parent;
499 5         32 my @siblings = $parent->children;
500 5         32 my $pos;
501 5         22 for (my $i = 0; $i < @siblings; ++$i) {
502 5 50       16 if ($siblings[$i] == $word) {
503 5         53 $pos = $i;
504 5         10 last;
505             }
506             }
507              
508 5 50       27 return if !defined $pos;
509              
510             # No arguments?
511 5 50       21 return if $pos == $#siblings;
512              
513             # Skip insignicant tokens.
514 5         9 my $argidx;
515 5         18 for (my $i = $pos + 1; $i < @siblings; ++$i) {
516 7 100       29 if ($siblings[$i]->significant) {
517 5         8 $argidx = $i;
518 5         9 last;
519             }
520             }
521              
522 5 50       16 return if !defined $argidx;
523              
524 5         6 my @argnodes;
525 5         8 my $argnodes_parent = $parent;
526              
527 5 50       21 if ($siblings[$argidx]->isa('PPI::Token::Structure')) {
    100          
528             # No arguments.
529 0         0 return;
530             } elsif ($siblings[$argidx]->isa('PPI::Structure::List')) {
531             # Call with parentheses. The only child should be an expression.
532 3         40 my @expression = $siblings[$argidx]->children;
533 3 50       20 return if @expression != 1;
534 3         4 $argnodes_parent = $expression[0];
535 3 50       11 return if !$argnodes_parent->isa('PPI::Statement::Expression');
536 3         9 @argnodes = $argnodes_parent->children;
537             } else {
538 2         7 for (my $i = $argidx; $i < @siblings; ++$i) {
539             # Call without parentheses.
540 3 100 66     12 if ($siblings[$i]->isa('PPI::Token::Structure')
541             && ';' eq $siblings[$i]->content) {
542 1         6 last;
543             }
544              
545 2         7 push @argnodes, $siblings[$i];
546             }
547             }
548              
549 5         28 return _split_arguments $argnodes_parent, @argnodes;
550             }
551              
552             sub _split_arguments {
553 5     5   13 my ($parent, @argnodes) = @_;
554              
555 5         10 my @arguments;
556             my @argument;
557              
558 5         15 for (my $i = 0; $i < @argnodes; ++$i) {
559 8         14 my $argnode = $argnodes[$i];
560              
561 8         32 $parent->remove_child($argnode);
562              
563 8 100 66     247 if ($argnode->isa('PPI::Token::Operator')
564             && ',' eq $argnode->content) {
565 1         7 push @arguments, [@argument];
566 1         3 undef @argument;
567             } else {
568 7         19 push @argument, $argnode;
569             }
570             }
571 5 50       16 push @arguments, [@argument] if @argument;
572              
573 5         11 foreach my $argument (@arguments) {
574 6         21 while (!$argument->[0]->significant) {
575 1         3 shift @$argument;
576             }
577 6         19 while (!$argument->[-1]->significant) {
578 0         0 pop @$argument;
579             }
580             }
581              
582 5         16 return @arguments;
583             }
584              
585             1;