| blib/lib/Devel/Declare/Lexer.pm | |||
|---|---|---|---|
| Criterion | Covered | Total | % |
| statement | 312 | 324 | 96.3 |
| branch | 126 | 194 | 64.9 |
| condition | 6 | 9 | 66.6 |
| subroutine | 27 | 27 | 100.0 |
| pod | 0 | 4 | 0.0 |
| total | 471 | 558 | 84.4 |
| line | stmt | bran | cond | sub | pod | time | code |
|---|---|---|---|---|---|---|---|
| 1 | package Devel::Declare::Lexer; | ||||||
| 2 | |||||||
| 3 | 10 | 10 | 124013 | use strict; | |||
| 10 | 22 | ||||||
| 10 | 397 | ||||||
| 4 | 10 | 10 | 98 | use warnings; | |||
| 10 | 18 | ||||||
| 10 | 253 | ||||||
| 5 | 10 | 10 | 119 | use v5; | |||
| 10 | 34 | ||||||
| 10 | 612 | ||||||
| 6 | |||||||
| 7 | our $VERSION = '0.014'; | ||||||
| 8 | |||||||
| 9 | 10 | 10 | 12113 | use Data::Dumper; | |||
| 10 | 129780 | ||||||
| 10 | 805 | ||||||
| 10 | 10 | 10 | 12380 | use Devel::Declare; | |||
| 10 | 156328 | ||||||
| 10 | 123 | ||||||
| 11 | 10 | 10 | 8613 | use Devel::Declare::Lexer::Stream; | |||
| 10 | 37 | ||||||
| 10 | 312 | ||||||
| 12 | 10 | 10 | 6887 | use Devel::Declare::Lexer::Token; | |||
| 10 | 26 | ||||||
| 10 | 350 | ||||||
| 13 | 10 | 10 | 6680 | use Devel::Declare::Lexer::Token::Bareword; | |||
| 10 | 29 | ||||||
| 10 | 271 | ||||||
| 14 | 10 | 10 | 12885 | use Devel::Declare::Lexer::Token::Declarator; | |||
| 10 | 29 | ||||||
| 10 | 280 | ||||||
| 15 | 10 | 10 | 6420 | use Devel::Declare::Lexer::Token::EndOfStatement; | |||
| 10 | 27 | ||||||
| 10 | 261 | ||||||
| 16 | 10 | 10 | 17796 | use Devel::Declare::Lexer::Token::Heredoc; | |||
| 10 | 30 | ||||||
| 10 | 279 | ||||||
| 17 | 10 | 10 | 6811 | use Devel::Declare::Lexer::Token::LeftBracket; | |||
| 10 | 29 | ||||||
| 10 | 355 | ||||||
| 18 | 10 | 10 | 6868 | use Devel::Declare::Lexer::Token::Newline; | |||
| 10 | 183 | ||||||
| 10 | 322 | ||||||
| 19 | 10 | 10 | 7274 | use Devel::Declare::Lexer::Token::Operator; | |||
| 10 | 29 | ||||||
| 10 | 277 | ||||||
| 20 | 10 | 10 | 6888 | use Devel::Declare::Lexer::Token::RightBracket; | |||
| 10 | 29 | ||||||
| 10 | 377 | ||||||
| 21 | 10 | 10 | 6733 | use Devel::Declare::Lexer::Token::String; | |||
| 10 | 33 | ||||||
| 10 | 344 | ||||||
| 22 | 10 | 10 | 13670 | use Devel::Declare::Lexer::Token::Variable; | |||
| 10 | 35 | ||||||
| 10 | 278 | ||||||
| 23 | 10 | 10 | 6667 | use Devel::Declare::Lexer::Token::Whitespace; | |||
| 10 | 33 | ||||||
| 10 | 344 | ||||||
| 24 | |||||||
| 25 | 10 | 10 | 142 | use vars qw/ @ISA $DEBUG $SHOWTRANSLATE /; | |||
| 10 | 919 | ||||||
| 10 | 2190 | ||||||
| 26 | @ISA = (); | ||||||
| 27 | $DEBUG = 0; | ||||||
| 28 | $SHOWTRANSLATE = 0; | ||||||
| 29 | |||||||
| 30 | sub import | ||||||
| 31 | { | ||||||
| 32 | 10 | 10 | 124 | my $class = shift; | |||
| 33 | 10 | 51 | my $caller = caller; | ||||
| 34 | |||||||
| 35 | 10 | 129 | import_for($caller, @_); | ||||
| 36 | } | ||||||
| 37 | |||||||
| 38 | sub import_for | ||||||
| 39 | { | ||||||
| 40 | 10 | 10 | 0 | 78 | my ($caller, @args) = @_; | ||
| 41 | 10 | 28 | my $class = shift; | ||||
| 42 | |||||||
| 43 | 10 | 10 | 63 | no strict 'refs'; | |||
| 10 | 20 | ||||||
| 10 | 58635 | ||||||
| 44 | |||||||
| 45 | 10 | 28 | my %subinject = (); | ||||
| 46 | 10 | 100 | 78 | if(ref($args[0]) =~ /HASH/) { | |||
| 47 | 1 | 50 | 3 | $DEBUG and print STDERR "Using hash for import\n"; | |||
| 48 | 1 | 2 | %subinject = %{$args[0]}; | ||||
| 1 | 6 | ||||||
| 49 | 1 | 4 | @args = keys %subinject; | ||||
| 50 | } | ||||||
| 51 | |||||||
| 52 | 10 | 21 | my @consts; | ||||
| 53 | |||||||
| 54 | 10 | 35 | my %tags = map { $_ => 1 } @args; | ||||
| 14 | 167 | ||||||
| 55 | 10 | 50 | 59 | if($tags{":debug"}) { | |||
| 56 | 0 | 0 | $DEBUG = 1; | ||||
| 57 | } | ||||||
| 58 | 10 | 100 | 56 | if($tags{":lexer_test"}) { | |||
| 59 | 2 | 50 | 7 | $DEBUG and print STDERR "Adding 'lexer_test' to keyword list\n"; | |||
| 60 | |||||||
| 61 | 2 | 6 | push @consts, "lexer_test"; | ||||
| 62 | } | ||||||
| 63 | |||||||
| 64 | 10 | 29 | my @names = @args; | ||||
| 65 | 10 | 28 | for my $name (@names) { | ||||
| 66 | 14 | 100 | 67 | next if $name =~ /:/; | |||
| 67 | 12 | 50 | 40 | $DEBUG and print STDERR "Adding '$name' to keyword list\n"; | |||
| 68 | |||||||
| 69 | 12 | 39 | push @consts, $name; | ||||
| 70 | } | ||||||
| 71 | |||||||
| 72 | 10 | 28 | for my $word (@consts) { | ||||
| 73 | 14 | 50 | 92 | $DEBUG and print STDERR "Injecting '$word' into '$caller'\n"; | |||
| 74 | 14 | 201 | Devel::Declare->setup_for( | ||||
| 75 | $caller, | ||||||
| 76 | { | ||||||
| 77 | $word => { const => \&lexer } | ||||||
| 78 | } | ||||||
| 79 | ); | ||||||
| 80 | 14 | 100 | 527 | if($subinject{$word}) { | |||
| 81 | 1 | 50 | 4 | $DEBUG and print STDERR "- Using sub provided in import\n"; | |||
| 82 | 1 | 2 | *{$caller.'::'.$word} = $subinject{$word}; | ||||
| 1 | 40 | ||||||
| 83 | } else { | ||||||
| 84 | 13 | 50 | 49 | $DEBUG and print STDERR "- Using default sub\n"; | |||
| 85 | 13 | 31 | *{$caller.'::'.$word} = sub () { 1; }; | ||||
| 13 | 580 | ||||||
| 86 | } | ||||||
| 87 | } | ||||||
| 88 | } | ||||||
| 89 | |||||||
| 90 | my %named_lexed_stack = (); | ||||||
| 91 | sub lexed | ||||||
| 92 | { | ||||||
| 93 | 12 | 12 | 0 | 209860 | my ($key, $callback) = @_; | ||
| 94 | 12 | 50 | 76 | $DEBUG and print STDERR "Registered callback for keyword '$key'\n"; | |||
| 95 | 12 | 312 | $named_lexed_stack{$key} = $callback; | ||||
| 96 | } | ||||||
| 97 | |||||||
| 98 | sub call_lexed | ||||||
| 99 | { | ||||||
| 100 | 69 | 69 | 0 | 119 | my ($name, $stream) = @_; | ||
| 101 | |||||||
| 102 | 69 | 50 | 148 | $DEBUG and print STDERR "Checking for callbacks for keyword '$name'\n"; | |||
| 103 | 69 | 50 | 134 | $DEBUG and print STDERR Dumper($stream) . "\n"; | |||
| 104 | |||||||
| 105 | 69 | 125 | my $callback = $named_lexed_stack{$name}; | ||||
| 106 | 69 | 100 | 163 | if($callback) { | |||
| 107 | 50 | 50 | 109 | $DEBUG and print STDERR "Found callback '$callback' for keyword '$name'\n"; | |||
| 108 | 50 | 148 | $stream = &$callback($stream); | ||||
| 109 | } | ||||||
| 110 | |||||||
| 111 | 69 | 50 | 856 | $DEBUG and print STDERR Dumper($stream) . "\n"; | |||
| 112 | |||||||
| 113 | 69 | 359 | return $stream; | ||||
| 114 | } | ||||||
| 115 | |||||||
| 116 | sub lexer | ||||||
| 117 | { | ||||||
| 118 | 69 | 69 | 0 | 32273 | my ($symbol, $offset) = @_; | ||
| 119 | |||||||
| 120 | 69 | 50 | 331 | $DEBUG and print "=" x 80, "\n"; | |||
| 121 | |||||||
| 122 | 69 | 207 | my $linestr = Devel::Declare::get_linestr; | ||||
| 123 | 69 | 100 | my $original_linestr = $linestr; | ||||
| 124 | 69 | 133 | my $original_offset = $offset; | ||||
| 125 | 69 | 50 | 170 | $DEBUG and print STDERR "Starting with linestr '$linestr'\n"; | |||
| 126 | |||||||
| 127 | 69 | 116 | my @tokens = (); | ||||
| 128 | 69 | 424 | tie @tokens, "Devel::Declare::Lexer::Stream"; | ||||
| 129 | 69 | 498 | my ($len, $tok); | ||||
| 130 | 69 | 89 | my $eoleos = 0; | ||||
| 131 | 69 | 85 | my $line = 1; | ||||
| 132 | |||||||
| 133 | # Skip the declarator | ||||||
| 134 | 69 | 190 | $offset += Devel::Declare::toke_move_past_token($offset); | ||||
| 135 | 69 | 333 | push @tokens, new Devel::Declare::Lexer::Token::Declarator( value => $symbol ); | ||||
| 136 | 69 | 50 | 511 | $DEBUG and print STDERR "Skipped declarator '$symbol'\n"; | |||
| 137 | |||||||
| 138 | 69 | 420 | my %lineoffsets = ( 1 => $offset ); | ||||
| 139 | |||||||
| 140 | # We call this from a few places inside the loop | ||||||
| 141 | my $skipspace = sub { | ||||||
| 142 | # Move past any whitespace | ||||||
| 143 | 425 | 425 | 878 | $len = Devel::Declare::toke_skipspace($offset); | |||
| 144 | 425 | 100 | 1019 | if($len > 0) { | |||
| 50 | |||||||
| 50 | |||||||
| 145 | 278 | 437 | $tok = substr($linestr, $offset, $len); | ||||
| 146 | 278 | 50 | 521 | $DEBUG and print STDERR "Skipped whitespace '$tok', length [$len]\n"; | |||
| 147 | 278 | 1095 | push @tokens, new Devel::Declare::Lexer::Token::Whitespace( value => $tok ); | ||||
| 148 | 278 | 1675 | $offset += $len; | ||||
| 149 | |||||||
| 150 | 278 | 100 | 885 | if($tok =~ /\n/) { | |||
| 151 | # its odd that this works without handling any line numbering | ||||||
| 152 | # I think we end up here when an end of line is found after a bareword (e.g. print\n"something") | ||||||
| 153 | # It probably still needs some work on line numbering, but everything just seems to work! | ||||||
| 154 | 1 | 50 | 4 | $DEBUG and print STDERR "Got end of line in skipspace, probable bareword preceeding EOL\n"; | |||
| 155 | 1 | 3 | Devel::Declare::clear_lex_stuff; | ||||
| 156 | |||||||
| 157 | # We've got a new line so we need to refresh our linestr | ||||||
| 158 | 1 | 4 | $linestr = Devel::Declare::get_linestr; | ||||
| 159 | 1 | 2 | $original_linestr = $linestr; | ||||
| 160 | |||||||
| 161 | 1 | 50 | 2 | $DEBUG and print STDERR "Refreshed linestr [$linestr]\n"; | |||
| 162 | } | ||||||
| 163 | } elsif ($len < 0) { | ||||||
| 164 | # Again, its odd that we don't handle any line numbering here, and a $len of < 0 is a definite EOL | ||||||
| 165 | 0 | 0 | 0 | $DEBUG and print STDERR "Got end of line in skipspace\n"; | |||
| 166 | } elsif ($len == 0) { | ||||||
| 167 | 147 | 50 | 400 | $DEBUG and print STDERR "No whitespace skipped\n"; | |||
| 168 | } | ||||||
| 169 | 425 | 995 | return $len; | ||||
| 170 | 69 | 360 | }; | ||||
| 171 | |||||||
| 172 | # Capture the tokens | ||||||
| 173 | 69 | 50 | 164 | $DEBUG and print STDERR "Linestr length [", length $linestr, "]\n"; | |||
| 174 | 69 | 188 | my $heredoc = undef; | ||||
| 175 | 69 | 84 | my $heredoc_end_re = undef; | ||||
| 176 | 69 | 87 | my $heredoc_end_re2 = undef; | ||||
| 177 | 69 | 87 | my $nest = 0; # nested bracket tracking, just in case we get ; inside a block | ||||
| 178 | 69 | 179 | while($offset < length $linestr) { | ||||
| 179 | 563 | 50 | 991 | $DEBUG and print STDERR Dumper(\%lineoffsets) . "\n"; | |||
| 180 | 563 | 100 | 100 | 1675 | if($heredoc && !(substr($linestr, $offset, 2) eq "\n")) { | ||
| 181 | 22 | 28 | my $c = substr($linestr, $offset, 1); | ||||
| 182 | 22 | 50 | 37 | $DEBUG and print STDERR "Consuming char from heredoc: '$c'\n"; | |||
| 183 | 22 | 22 | $offset += 1; | ||||
| 184 | 22 | 100 | 42 | if($c =~ /\n/) { | |||
| 185 | 2 | 50 | 4 | $DEBUG and print STDERR "Newline found in heredoc (current line $line)\n"; | |||
| 186 | #$line++; | ||||||
| 187 | #$lineoffsets{$line} = $offset; | ||||||
| 188 | } else { | ||||||
| 189 | 20 | 33 | $heredoc->{value} .= $c; | ||||
| 190 | } | ||||||
| 191 | 22 | 50 | 34 | $DEBUG and print STDERR "New heredoc value: " . $heredoc->{value} . "\n"; | |||
| 192 | 22 | 30 | my $heredoc_name = $heredoc->{name}; | ||||
| 193 | 22 | 100 | 72 | if($heredoc->{value} =~ /$heredoc_end_re/) { | |||
| 194 | 1 | 6 | $heredoc->{value} =~ s/$heredoc_end_re2//; | ||||
| 195 | 1 | 50 | 4 | $DEBUG and print STDERR "Consumed heredoc, name [$heredoc_name]:\n" . $heredoc->{value} . "\n"; | |||
| 196 | 1 | 4 | push @tokens, $heredoc; | ||||
| 197 | 1 | 5 | $heredoc = undef; | ||||
| 198 | 1 | 2 | $heredoc_end_re = undef; | ||||
| 199 | 1 | 2 | $heredoc_end_re2 = undef; | ||||
| 200 | } | ||||||
| 201 | 22 | 50 | next; | ||||
| 202 | } | ||||||
| 203 | |||||||
| 204 | 541 | 50 | 972 | $DEBUG and print STDERR "Offset[$offset], nest [$nest], Remaining[", substr($linestr, $offset), "]\n"; | |||
| 205 | |||||||
| 206 | 541 | 100 | 1325 | if(substr($linestr, $offset, 1) eq ';') { | |||
| 207 | 80 | 50 | 168 | $DEBUG and print STDERR "Got end of statement\n"; | |||
| 208 | 80 | 352 | push @tokens, new Devel::Declare::Lexer::Token::EndOfStatement; | ||||
| 209 | 80 | 482 | $offset += 1; | ||||
| 210 | 80 | 106 | $eoleos = 1; | ||||
| 211 | 80 | 100 | 220 | last unless $nest; | |||
| 212 | 11 | 29 | next; | ||||
| 213 | } | ||||||
| 214 | |||||||
| 215 | 461 | 100 | 924 | if(substr($linestr, $offset, 2) eq "\n") { | |||
| 216 | 36 | 100 | 69 | if($heredoc) { | |||
| 217 | 2 | 50 | 4 | $DEBUG and print STDERR "Got end of line in heredoc\n"; | |||
| 218 | 2 | 5 | $heredoc->{value} .= "\n"; | ||||
| 219 | } | ||||||
| 220 | |||||||
| 221 | 36 | 100 | 82 | if(!$heredoc) { | |||
| 222 | 34 | 50 | 98 | $DEBUG and print STDERR "Got end of line in loop (current line $line)\n"; | |||
| 223 | 34 | 156 | push @tokens, new Devel::Declare::Lexer::Token::Newline; | ||||
| 224 | 34 | 153 | $offset += 1; | ||||
| 225 | } | ||||||
| 226 | |||||||
| 227 | # this lets us capture a newline directly after a semicolon | ||||||
| 228 | # and immediately exit the loop - otherwise we might start | ||||||
| 229 | # consuming code that doesn't belong to us | ||||||
| 230 | 36 | 50 | 66 | 196 | last if $eoleos && !$nest; | ||
| 231 | 36 | 84 | $eoleos = 0; | ||||
| 232 | |||||||
| 233 | # If we're here, it's just a new line inside the statement that | ||||||
| 234 | # we do want to consume | ||||||
| 235 | |||||||
| 236 | # We don't use skipspace here - it does too much! | ||||||
| 237 | #&$skipspace; | ||||||
| 238 | 36 | 110 | $len = Devel::Declare::toke_skipspace($offset); | ||||
| 239 | 36 | 100 | 86 | if($len != 0) { | |||
| 240 | # TODO it seems odd that we don't add $len to the | ||||||
| 241 | # offset... this might come back to bite us later! | ||||||
| 242 | #$offset += $len - 6; | ||||||
| 243 | 21 | 50 | 54 | $DEBUG and print STDERR "Skipped $len whitespace following EOL, not added to \$offset\n"; | |||
| 244 | } | ||||||
| 245 | |||||||
| 246 | 36 | 67 | Devel::Declare::clear_lex_stuff; | ||||
| 247 | |||||||
| 248 | # Got a new line, so we need to refresh linestr | ||||||
| 249 | 36 | 90 | $linestr = Devel::Declare::get_linestr; | ||||
| 250 | # It's not the next line, its everything upto and including the next line | ||||||
| 251 | # so really our original_linestr is wrong! | ||||||
| 252 | 36 | 118 | $original_linestr = $linestr; | ||||
| 253 | |||||||
| 254 | # Record some offsets for later - we start on line 1 and the first $line++ is 2 | ||||||
| 255 | # so we make a special case for recording line 1's offset | ||||||
| 256 | 36 | 100 | 75 | if($line == 1) { | |||
| 257 | 11 | 30 | $lineoffsets{1} = (length $symbol) + 1; | ||||
| 258 | }; | ||||||
| 259 | 36 | 44 | $line++; | ||||
| 260 | 36 | 100 | 98 | $lineoffsets{$line} = $heredoc ? $offset + 1 : $offset; | |||
| 261 | |||||||
| 262 | 36 | 50 | 87 | $DEBUG and print STDERR "Refreshed linestr [$linestr], added lineoffset for line $line, offset $offset\n"; | |||
| 263 | 36 | 81 | next; | ||||
| 264 | } | ||||||
| 265 | |||||||
| 266 | # FIXME Does this ever happen? | ||||||
| 267 | 425 | 50 | 672 | if(&$skipspace < 0) { | |||
| 268 | 0 | 0 | 0 | $DEBUG and print STDERR "Got skipspace < 0\n"; | |||
| 269 | 0 | 0 | last; | ||||
| 270 | } | ||||||
| 271 | |||||||
| 272 | # Check if its a opening bracket | ||||||
| 273 | 425 | 100 | 1685 | if(substr($linestr, $offset, 1) =~ /(\{|\[|\()/) { | |||
| 274 | 32 | 71 | my $b = substr($linestr, $offset, 1); | ||||
| 275 | 32 | 166 | push @tokens, new Devel::Declare::Lexer::Token::LeftBracket( value => $b ); | ||||
| 276 | 32 | 422 | $nest++; | ||||
| 277 | 32 | 50 | 83 | $DEBUG and print STDERR "Got left bracket '$b', nest[$nest]\n"; | |||
| 278 | 32 | 42 | $offset += 1; | ||||
| 279 | 32 | 77 | next; | ||||
| 280 | } | ||||||
| 281 | # Check if its a closing bracket | ||||||
| 282 | 393 | 100 | 1224 | if(substr($linestr, $offset, 1) =~ /(\}|\]|\))/) { | |||
| 283 | 32 | 57 | my $b = substr($linestr, $offset, 1); | ||||
| 284 | 32 | 156 | push @tokens, new Devel::Declare::Lexer::Token::RightBracket( value => $b ); | ||||
| 285 | 32 | 159 | $nest--; | ||||
| 286 | 32 | 50 | 93 | $DEBUG and print STDERR "Got right bracket '$b', nest[$nest]\n"; | |||
| 287 | 32 | 57 | $offset += 1; | ||||
| 288 | 32 | 78 | next; | ||||
| 289 | } | ||||||
| 290 | # Check for a reference | ||||||
| 291 | 361 | 100 | 813 | if(substr($linestr, $offset, 1) =~ /\\/) { | |||
| 292 | 1 | 3 | $tok = substr($linestr, $offset, 1); | ||||
| 293 | 1 | 50 | 3 | $DEBUG and print STDERR "Got reference operator '$tok'\n"; | |||
| 294 | 1 | 5 | push @tokens, new Devel::Declare::Lexer::Token::Operator( value => $tok); | ||||
| 295 | 1 | 5 | $offset += 1; | ||||
| 296 | 1 | 3 | next; | ||||
| 297 | } | ||||||
| 298 | # Check for variable | ||||||
| 299 | 360 | 100 | 1221 | if(substr($linestr, $offset, 1) =~ /(\$|\%|\@|\*)/) { | |||
| 300 | # get the sign | ||||||
| 301 | # TODO the variable name is captured later - it should probably be done here | ||||||
| 302 | 54 | 83 | $tok = substr($linestr, $offset, 1); | ||||
| 303 | 54 | 50 | 121 | $DEBUG and print STDERR "Got variable '$tok'\n"; | |||
| 304 | 54 | 227 | push @tokens, new Devel::Declare::Lexer::Token::Variable( value => $tok ); | ||||
| 305 | 54 | 276 | $offset += 1; | ||||
| 306 | 54 | 2201 | next; | ||||
| 307 | } | ||||||
| 308 | # Check for string | ||||||
| 309 | 306 | 100 | 1002 | if(substr($linestr, $offset, 1) =~ /^(q|\"|\')/) { | |||
| 310 | # FIXME need to determine string type properly | ||||||
| 311 | 74 | 127 | my $strstype = substr($linestr, $offset, 1); | ||||
| 312 | |||||||
| 313 | 74 | 98 | my $allow_string = 1; | ||||
| 314 | |||||||
| 315 | 74 | 100 | 1773 | if($strstype eq 'q') { | |||
| 316 | 13 | 50 | 42 | if(substr($linestr, $offset + 1, 1) !~ /\|\{\[\(\#/) { | |||
| 317 | 13 | 50 | 44 | $DEBUG and print STDERR "This 'q' isnt a string type\n"; | |||
| 318 | 13 | 19 | $allow_string = 0; | ||||
| 319 | } | ||||||
| 320 | } | ||||||
| 321 | |||||||
| 322 | 74 | 100 | 153 | if($allow_string) { | |||
| 323 | 61 | 96 | my $stretype = $strstype; | ||||
| 324 | 61 | 50 | 137 | if($strstype =~ /q/) { | |||
| 325 | 0 | 0 | 0 | if(substr($linestr, $offset, 2) =~ /qq/) { | |||
| 326 | 0 | 0 | $strstype = substr($linestr, $offset, 3); | ||||
| 327 | 0 | 0 | $offset += 2; | ||||
| 328 | } else { | ||||||
| 329 | 0 | 0 | $strstype = substr($linestr, $offset, 2); | ||||
| 330 | 0 | 0 | $offset += 1; | ||||
| 331 | } | ||||||
| 332 | 0 | 0 | $stretype = substr($linestr, $offset, 1); | ||||
| 333 | 0 | 0 | $stretype =~ tr/\(/)/; | ||||
| 334 | 0 | 0 | $len = Devel::Declare::toke_scan_str($offset); | ||||
| 335 | } else { | ||||||
| 336 | 61 | 377 | $len = Devel::Declare::toke_scan_str($offset); | ||||
| 337 | } | ||||||
| 338 | 61 | 50 | 150 | $DEBUG and print STDERR "Got string type '$strstype', end type '$stretype'\n"; | |||
| 339 | 61 | 434 | $tok = Devel::Declare::get_lex_stuff; | ||||
| 340 | 61 | 117 | Devel::Declare::clear_lex_stuff; | ||||
| 341 | 61 | 50 | 142 | $DEBUG and print STDERR "Got string '$tok'\n"; | |||
| 342 | 61 | 487 | push @tokens, new Devel::Declare::Lexer::Token::String( start => $strstype, end => $stretype, value => $tok ); | ||||
| 343 | # get a new linestr - we might have captured multiple lines | ||||||
| 344 | 61 | 377 | $linestr = Devel::Declare::get_linestr; | ||||
| 345 | 61 | 78 | $offset += $len; | ||||
| 346 | |||||||
| 347 | # If we do have multiple lines, we'll fix line numbering at the end | ||||||
| 348 | |||||||
| 349 | 61 | 177 | next; | ||||
| 350 | } | ||||||
| 351 | } | ||||||
| 352 | # Check for heredoc | ||||||
| 353 | 245 | 100 | 602 | if(substr($linestr, $offset)=~ /^(<<\s*([\w\d]+)\s*\n)/) { | |||
| 354 | # Heredocs are weird - we'll just remember we're in a heredoc until we get the end token | ||||||
| 355 | 1 | 50 | 4 | $DEBUG and print STDERR "Got a heredoc with name '$2'\n"; | |||
| 356 | 1 | 1322 | $heredoc = new Devel::Declare::Lexer::Token::Heredoc( name => $2, value => '' ); | ||||
| 357 | 1 | 30 | $heredoc_end_re = qr/\n$2\n$/; | ||||
| 358 | 1 | 10 | $heredoc_end_re2 = qr/$2\n$/; | ||||
| 359 | 1 | 50 | 5 | $DEBUG and print STDERR "Created regex $heredoc_end_re and $heredoc_end_re2\n"; | |||
| 360 | |||||||
| 361 | # get a new linestr - we might have captured multiple lines | ||||||
| 362 | 1 | 3 | $offset += 2 + (length $1); | ||||
| 363 | |||||||
| 364 | 1 | 5 | $len = Devel::Declare::toke_skipspace($offset); | ||||
| 365 | 1 | 3 | $linestr = Devel::Declare::get_linestr; | ||||
| 366 | 1 | 3 | $offset += $len; | ||||
| 367 | 1 | 50 | 4 | $DEBUG and print STDERR "Skipped $len whitespace at start of heredoc, got new linestr[$linestr]\n"; | |||
| 368 | |||||||
| 369 | 1 | 1 | $line++; | ||||
| 370 | 1 | 3 | $lineoffsets{$line} = $offset; | ||||
| 371 | |||||||
| 372 | # If we do have multiple lines, we'll fix line numbering at the end | ||||||
| 373 | |||||||
| 374 | 1 | 4 | next; | ||||
| 375 | } | ||||||
| 376 | # Check for operator after strings (so heredocs < | ||||||
| 377 | 244 | 100 | 681 | if(substr($linestr, $offset, 1) =~ /[!\+\-\*\/\.><=,|&\?:]/) { | |||
| 378 | 88 | 134 | $tok = substr($linestr, $offset, 1); | ||||
| 379 | 88 | 50 | 173 | $DEBUG and print STDERR "Got operator '$tok'\n"; | |||
| 380 | 88 | 401 | push @tokens, new Devel::Declare::Lexer::Token::Operator( value => $tok ); | ||||
| 381 | 88 | 460 | $offset += 1; | ||||
| 382 | 88 | 277 | next; | ||||
| 383 | } | ||||||
| 384 | # Check for bareword | ||||||
| 385 | 156 | 370 | $len = Devel::Declare::toke_scan_word($offset, 1); | ||||
| 386 | 156 | 100 | 309 | if($len) { | |||
| 387 | 155 | 275 | $tok = substr($linestr, $offset, $len); | ||||
| 388 | 155 | 50 | 294 | $DEBUG and print STDERR "Got bareword '$tok'\n"; | |||
| 389 | 155 | 586 | push @tokens, new Devel::Declare::Lexer::Token::Bareword( value => $tok ); | ||||
| 390 | 155 | 693 | $offset += $len; | ||||
| 391 | 155 | 398 | next; | ||||
| 392 | } | ||||||
| 393 | |||||||
| 394 | } | ||||||
| 395 | |||||||
| 396 | # Callback (AT COMPILE TIME) to allow manipulation of the token stream before injection | ||||||
| 397 | 69 | 50 | 149 | $DEBUG and print STDERR Dumper(\@tokens) . "\n"; | |||
| 398 | 69 | 90 | @tokens = @{call_lexed($symbol, \@tokens)}; | ||||
| 69 | 250 | ||||||
| 399 | |||||||
| 400 | 69 | 6248 | my $stmt = ""; | ||||
| 401 | 69 | 221 | for my $token (@tokens) { | ||||
| 402 | 982 | 6705 | $stmt .= $token->get; | ||||
| 403 | } | ||||||
| 404 | |||||||
| 405 | 69 | 50 | 726 | $DEBUG and print "=" x 80, "\n"; | |||
| 406 | |||||||
| 407 | 69 | 100 | 248 | if($symbol =~ /^lexer_test$/) { | |||
| 408 | 19 | 50 | 41 | $DEBUG and print STDERR "Escaping statement for variable assignment\n"; | |||
| 409 | 19 | 33 | $stmt =~ s/\\/\\\\/g; | ||||
| 410 | 19 | 53 | $stmt =~ s/\"/\\"/g; | ||||
| 411 | 19 | 38 | $stmt =~ s/\$/\\\$/g; | ||||
| 412 | 19 | 42 | $stmt =~ s/\n/\\n/g; | ||||
| 413 | 19 | 31 | chomp $stmt; | ||||
| 414 | 19 | 37 | $stmt = substr($stmt, 0, (length $stmt)); # strip the final \\n | ||||
| 415 | } else { | ||||||
| 416 | 50 | 124 | $stmt =~ s/\n//g; # remove multiline on final statement | ||||
| 417 | 50 | 96 | chomp $stmt; | ||||
| 418 | } | ||||||
| 419 | 69 | 50 | 156 | $DEBUG and print STDERR "Final statement: [$stmt]\n"; | |||
| 420 | |||||||
| 421 | # FIXME line numbering is broken if a \n appears inside a block, e.g. keyword { print "\n"; } | ||||||
| 422 | #my @lcnt = split /[^\\]\\n/, $stmt; | ||||||
| 423 | 69 | 275 | my @lcnt = split /\\n/, $stmt; | ||||
| 424 | 69 | 148 | my $lc = scalar @lcnt; | ||||
| 425 | 69 | 50 | 158 | $DEBUG and print STDERR "Lines:\n", Dumper(\@lcnt) . "\n"; | |||
| 426 | 69 | 113 | my $lineadjust = $lc - $line; | ||||
| 427 | 69 | 50 | 149 | $DEBUG and print STDERR "Linecount[$lc] lines[$line] - missing $lineadjust lines\n"; | |||
| 428 | |||||||
| 429 | # we've got a new linestr, we need to re-fix all our offsets | ||||||
| 430 | 69 | 50 | 173 | $DEBUG and print STDERR "\n\nStarted with linestr [$linestr]\n"; | |||
| 431 | 10 | 10 | 104 | use Data::Dumper; | |||
| 10 | 40 | ||||||
| 10 | 8753 | ||||||
| 432 | 69 | 50 | 153 | $DEBUG and print STDERR Dumper(\%lineoffsets) . "\n"; | |||
| 433 | |||||||
| 434 | 69 | 264 | for my $l (sort keys %lineoffsets) { | ||||
| 435 | 106 | 165 | my $sol = $lineoffsets{$l}; | ||||
| 436 | 106 | 100 | 556 | last if !defined $lineoffsets{$l+1}; # don't mess with the current line, yet! | |||
| 437 | 37 | 67 | my $eol = $lineoffsets{$l + 1} - 1; | ||||
| 438 | 37 | 109 | my $diff = $eol - $sol; | ||||
| 439 | 37 | 70 | my $substr = substr($linestr, $sol, $diff); | ||||
| 440 | 37 | 50 | 81 | $DEBUG and print STDERR "\nLine $l, sol[$sol], eol[$eol], diff[$diff], linestr[$linestr], substr[$substr]\n"; | |||
| 441 | 37 | 105 | substr($linestr, $sol, $diff) = " " x $diff; | ||||
| 442 | } | ||||||
| 443 | |||||||
| 444 | # now clear up the last line | ||||||
| 445 | 69 | 50 | 172 | $DEBUG and print STDERR "Still got linestr[$linestr]\n"; | |||
| 446 | 69 | 100 | 223 | my $sol = $line == 1 ? (length $symbol) + 1 + $original_offset : $lineoffsets{$line}; | |||
| 447 | 69 | 104 | my $eol = (length $linestr) - 1; | ||||
| 448 | 69 | 89 | my $diff = $eol - $sol; | ||||
| 449 | 69 | 141 | my $substr = substr($linestr, $sol, $diff); | ||||
| 450 | 69 | 50 | 390 | $DEBUG and print STDERR "Got substr[$substr] sol[$sol] eol[$eol] diff[$diff]\n"; | |||
| 451 | |||||||
| 452 | 69 | 143 | my $newline = "\n" x $lineadjust; | ||||
| 453 | 69 | 100 | 175 | if($symbol =~ /^lexer_test$/) { | |||
| 454 | 19 | 40 | $newline .= "and \$lexed = \"$stmt\";"; | ||||
| 455 | } else { | ||||||
| 456 | 50 | 142 | $newline .= " and " . substr($stmt, length $symbol); | ||||
| 457 | } | ||||||
| 458 | |||||||
| 459 | 69 | 158 | substr($linestr, $sol, (length $linestr) - $sol - 1) = $newline; # put the rest of the statement in | ||||
| 460 | |||||||
| 461 | 69 | 50 | 33 | 1884 | ($DEBUG || $SHOWTRANSLATE) and print STDERR "Got new linestr[$linestr] from original_linestr[$original_linestr]\n"; | ||
| 462 | |||||||
| 463 | 69 | 50 | 150 | $DEBUG and print "=" x 80, "\n"; | |||
| 464 | 69 | 2899 | Devel::Declare::set_linestr($linestr); | ||||
| 465 | } | ||||||
| 466 | |||||||
| 467 | 1; | ||||||
| 468 | |||||||
| 469 | =encoding utf8 | ||||||
| 470 | |||||||
| 471 | =head1 NAME | ||||||
| 472 | |||||||
| 473 | Devel::Declare::Lexer - Easier than Devel::Declare | ||||||
| 474 | |||||||
| 475 | =head1 SYNOPSIS | ||||||
| 476 | |||||||
| 477 | # Add :debug tag to enable debugging | ||||||
| 478 | # Add :lexer_test to enable variable assignment | ||||||
| 479 | # Anything not starting with : becomes a keyword | ||||||
| 480 | use Devel::Declare::Lexer qw/ keyword /; | ||||||
| 481 | |||||||
| 482 | BEGIN { | ||||||
| 483 | # Create a callback for the keyword (inside a BEGIN block!) | ||||||
| 484 | Devel::Declare::Lexer::lexed(keyword => sub { | ||||||
| 485 | # Get the stream out (given as an arrayref) | ||||||
| 486 | my ($stream_r) = @_; | ||||||
| 487 | my @stream = @$stream_r; | ||||||
| 488 | |||||||
| 489 | my $str = $stream[2]; # in the example below, the string is the 3rd token | ||||||
| 490 | |||||||
| 491 | # Create a new stream (we could manipulate the existing one though) | ||||||
| 492 | my @ns = (); | ||||||
| 493 | tie @ns, "Devel::Declare::Lexer::Stream"; | ||||||
| 494 | |||||||
| 495 | # Add a few tokens to print the string | ||||||
| 496 | push @ns, ( | ||||||
| 497 | # You need this (for now) | ||||||
| 498 | new Devel::Declare::Lexer::Token::Declarator( value => 'keyword' ), | ||||||
| 499 | new Devel::Declare::Lexer::Token::Whitespace( value => ' ' ), | ||||||
| 500 | |||||||
| 501 | # Everything else is your own custom code | ||||||
| 502 | new Devel::Declare::Lexer::Token( value => 'print' ), | ||||||
| 503 | new Devel::Declare::Lexer::Token::Whitespace( value => ' ' ), | ||||||
| 504 | $string, | ||||||
| 505 | new Devel::Declare::Lexer::Token::EndOfStatement, | ||||||
| 506 | new Devel::Declare::Lexer::Token::Newline, | ||||||
| 507 | ); | ||||||
| 508 | |||||||
| 509 | # Stream now contains: | ||||||
| 510 | # keyword and print "This is a string"; | ||||||
| 511 | # keyword evaluates to 1, everything after the and gets executed | ||||||
| 512 | |||||||
| 513 | # Return an arrayref | ||||||
| 514 | return \@ns; | ||||||
| 515 | }); | ||||||
| 516 | } | ||||||
| 517 | |||||||
| 518 | # Use the keyword anywhere in this package | ||||||
| 519 | keyword "This is a string"; | ||||||
| 520 | |||||||
| 521 | =head1 DESCRIPTION | ||||||
| 522 | |||||||
| 523 | L |
||||||
| 524 | by generating a token stream from the statement and providing a callback for | ||||||
| 525 | you to manipulate it before its parsed by Perl. | ||||||
| 526 | |||||||
| 527 | The example in the synopsis creates a keyword named 'keyword', which accepts | ||||||
| 528 | a string and prints it. | ||||||
| 529 | |||||||
| 530 | Although this simple example could be done using print, say or any other simple | ||||||
| 531 | subroutine, L |
||||||
| 532 | |||||||
| 533 | For example, it could be used to auto-expand subroutine declarations, e.g. | ||||||
| 534 | method MethodName ( $a, @b ) { | ||||||
| 535 | ... | ||||||
| 536 | } | ||||||
| 537 | into | ||||||
| 538 | sub MethodName ($@) { | ||||||
| 539 | my ($self, $a, @b) = @_; | ||||||
| 540 | ... | ||||||
| 541 | } | ||||||
| 542 | |||||||
| 543 | Unlike L |
||||||
| 544 | taking care of multiline strings or code blocks - it's all done for you. | ||||||
| 545 | |||||||
| 546 | =head1 ADVANCED USAGE | ||||||
| 547 | |||||||
| 548 | L |
||||||
| 549 | calling package which returns a 1. Because your statement typically gets | ||||||
| 550 | transformed into something like | ||||||
| 551 | keyword and [your statement here]; | ||||||
| 552 | the fact keyword evaluates to 1 means everything following the and will always | ||||||
| 553 | be executed. | ||||||
| 554 | |||||||
| 555 | You can extend this by using a different import syntax when loading L |
||||||
| 556 | use Devel::Declare::Lexer { keyword => sub { $Some::Package::variable } }; | ||||||
| 557 | which will cause the provided sub to be injected instead of the default sub. | ||||||
| 558 | |||||||
| 559 | =head1 SEE ALSO | ||||||
| 560 | |||||||
| 561 | Some examples can be found in the source download. | ||||||
| 562 | |||||||
| 563 | For more information about how L |
||||||
| 564 | documentation for L |
||||||
| 565 | |||||||
| 566 | =head1 AUTHORS | ||||||
| 567 | |||||||
| 568 | Ian Kent - L |
||||||
| 569 | |||||||
| 570 | http://www.iankent.co.uk/ | ||||||
| 571 | |||||||
| 572 | =head1 COPYRIGHT AND LICENSE | ||||||
| 573 | |||||||
| 574 | This library is free software under the same terms as perl itself | ||||||
| 575 | |||||||
| 576 | Copyright (c) 2013 Ian Kent | ||||||
| 577 | |||||||
| 578 | Devel::Declare::Lexer is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; | ||||||
| 579 | without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the license for more details. | ||||||
| 580 | |||||||
| 581 | =cut |