File Coverage

blib/lib/Macro/Simple.pm
Criterion Covered Total %
statement 93 114 81.5
branch 21 36 58.3
condition 10 24 41.6
subroutine 17 20 85.0
pod 2 2 100.0
total 143 196 72.9


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