File Coverage

blib/lib/Macro/Simple.pm
Criterion Covered Total %
statement 94 115 81.7
branch 21 36 58.3
condition 10 24 41.6
subroutine 17 20 85.0
pod 2 2 100.0
total 144 197 73.1


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