File Coverage

blib/lib/Macro/Simple.pm
Criterion Covered Total %
statement 89 109 81.6
branch 20 36 55.5
condition 10 24 41.6
subroutine 16 19 84.2
pod 1 1 100.0
total 136 189 71.9


line stmt bran cond sub pod time code
1 1     1   68598 use 5.008003;
  1         4  
2 1     1   5 use strict;
  1         2  
  1         23  
3 1     1   4 use warnings;
  1         2  
  1         64  
4              
5             package Macro::Simple;
6              
7             our $AUTHORITY = 'cpan:TOBYINK';
8             our $VERSION = '0.001';
9              
10 1     1   7 use Carp;
  1         2  
  1         99  
11 1   33     502 use constant DO_MACRO =>
12 1     1   17 ( $] ge 5.014000 and require Parse::Keyword and require PPI );
  1         2  
13              
14             sub import {
15 1     1   23 my ( $class, $macros ) = ( shift, @_ );
16 1         3 my $caller = caller;
17 1         5 $class->setup_for( $caller, $macros );
18             }
19              
20             sub setup_for {
21 1     1 1 2 my ( $class, $caller, $macros ) = ( shift, @_ );
22            
23 1         2 my $installer = DO_MACRO ? '_setup_using_parse_keyword' : '_setup_fallback';
24            
25 1         8 for my $key ( sort keys %$macros ) {
26 2         14 my ( $subname, $prototype ) = ( $key =~ m{\A(\w+)(.+)\z} );
27            
28 2         5 my $generator = $macros->{$key};
29 2 0 33     7 if ( 'HASH' eq ref $generator and $generator->{is} ) {
30 0         0 my $code = $generator->{is}->inline_check( '$x' );
31 0     0   0 $generator = sub { sprintf 'my $x = %s; %s', $_[0], $code };
  0         0  
32             }
33 2 50 33     13 if ( 'HASH' eq ref $generator and $generator->{assert} ) {
    100          
34 0         0 my $code = $generator->{assert}->inline_assert( '$x' );
35 0     0   0 $generator = sub { sprintf 'my $x = %s; %s', $_[0], $code };
  0         0  
36             }
37             elsif ( not ref $generator ) {
38 1         2 my $format = $generator;
39 1     1   4 $generator = sub { sprintf $format, @_ };
  1         8  
40             }
41            
42 2         13 $class->$installer( {
43             caller => $caller,
44             subname => $subname,
45             prototype => $prototype,
46             generator => $generator,
47             } );
48             }
49             }
50              
51             sub _setup_using_parse_keyword {
52 2     2   4 my ( $class, $opt ) = ( shift, @_ );
53 2         3 my ( $caller, $subname ) = @{$opt}{qw/ caller subname /};
  2         7  
54 1     1   138082 no strict qw( refs );
  1         3  
  1         244  
55 2     1   193 *{ "$caller\::$subname" } = eval 'sub { 1 }';
  2         19  
  1         4  
  1         3  
56             Parse::Keyword::install_keyword_handler(
57 2         73 \&{ "$caller\::$subname" },
58 2     2   269 sub { $class->_parse( $opt ) },
59 2         4 );
60             }
61              
62             sub _setup_fallback {
63 0     0   0 my ( $class, $opt ) = ( shift, @_ );
64             my ( $caller, $subname, $prototype, $generator ) =
65 0         0 @{$opt}{qw/ caller subname prototype generator /};
  0         0  
66 0         0 my $code = $generator->( map "\$_[$_]", 0 .. 100 );
67 1     1   9 no strict qw( refs );
  1         14  
  1         379  
68 0         0 *{ "$caller\::$subname" } = eval "sub $prototype { $code }";
  0         0  
69             }
70              
71             sub _parse {
72 2     2   7 my ( $class, $opt ) = ( shift, @_ );
73             my ( $caller, $subname, $prototype, $generator ) =
74 2         4 @{$opt}{qw/ caller subname prototype generator /};
  2         7  
75            
76 2         15 require Parse::Keyword;
77 2         8 require PPI;
78 2         10 my $str = Parse::Keyword::lex_peek( 1000 );
79 2         12 my $ppi = 'PPI::Document'->new( \$str );
80 2         4168 my $list = $ppi->find_first( 'Structure::List' );
81 2         563 my @tokens = $list->find_first( 'Statement::Expression' )->children;
82 2         382 my $length = 2;
83            
84 2         4 my @args = undef;
85 2         9 while ( my $t = shift @tokens ) {
86 8         27 $length += length( "$t" );
87            
88 8 100 66     82 if ( $t->isa( 'PPI::Token::Operator' ) and $t =~ m{\A(,|\=\>)\z} ) {
    100 66        
89 2         19 push @args, undef;
90             }
91             elsif ( defined $args[-1] or not $t->isa( 'PPI::Token::Whitespace' ) ) {
92 1     1   9 no warnings qw(uninitialized);
  1         2  
  1         713  
93 4         9 $args[-1] .= "$t";
94             }
95             }
96 2 50       13 pop @args unless defined $args[-1];
97            
98 2 50       9 if ( $prototype =~ /\A\((.+)\)\z/ ) {
99 2         3 my $i = 0;
100 2         5 local $_ = $1;
101 2         3 my $saw_semicolon = 0;
102 2         4 my $saw_slurpy = 0;
103 2         6 while ( length ) {
104 5         8 my $backslashed = 0;
105 5         5 my $chars = '';
106            
107 5 100       16 if ( /\A;/ ) {
108 1         1 $saw_semicolon++;
109 1         4 s/\A.//;
110 1         2 redo;
111             }
112            
113 4 50       9 if ( /\A\\/ ) {
114 0         0 $backslashed++;
115 0         0 s/\A.//;
116             }
117            
118 4 50       8 if ( /\A\[(.+?)\]/ ) {
119 0         0 $chars = $1;
120 0         0 s/\A\[(.+?)\]//;
121             }
122             else {
123 4         8 $chars = substr $_, 0, 1;
124 4         11 s/\A.//;
125             }
126            
127 4 100       9 if (!$saw_semicolon) {
128 3 50       13 $#args >= $i
129             or croak "Not enough arguments for macro $subname$prototype";
130             }
131            
132 4         7 my $arg = $args[$i];
133 4 50 33     33 if ( $backslashed and $chars eq '@' ) {
    50 33        
    50          
134 0 0       0 $arg =~ /\A\s*\@/
135             or croak "Expected array for argument $i to macro $subname$prototype; got: $arg";
136             }
137             elsif ( $backslashed and $chars eq '%' ) {
138 0 0       0 $arg =~ /\A\s*\%/
139             or croak "Expected hash for argument $i to macro $subname$prototype; got: $arg";
140             }
141             elsif ( $chars =~ /[@%]/ ) {
142 0         0 $saw_slurpy++;
143             }
144            
145 4         10 $i++;
146             }
147            
148 2 50 33     6 if ( $#args >= $i and !$saw_slurpy ) {
149 0         0 croak "Too many arguments for macro $subname$prototype";
150             }
151             }
152            
153 2         22 Parse::Keyword::lex_read( $length );
154 2         7 Parse::Keyword::lex_stuff( sprintf ' && do { %s }', $generator->(@args) );
155 2     2   43 return sub { }; # will never be called. sigh.
156             }
157              
158             1;
159              
160             __END__