| 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__ |