File Coverage

blib/lib/Text/MustacheTemplate/Lexer.pm
Criterion Covered Total %
statement 63 65 100.0
branch 22 24 100.0
condition 9 10 100.0
subroutine 9 9 100.0
pod 1 1 100.0
total 104 109 100.0


line stmt bran cond sub pod time code
1             package Text::MustacheTemplate::Lexer;
2 17     17   295223 use 5.022000;
  17         67  
3 17     17   89 use strict;
  17         53  
  17         460  
4 17     17   74 use warnings;
  17         35  
  17         993  
5              
6 17     17   89 use Exporter 5.57 'import';
  17         330  
  17         1054  
7              
8 17     17   118 use Carp qw/croak/;
  17         49  
  17         2787  
9              
10             our %EXPORT_TAGS = (
11             types => [qw/TOKEN_RAW_TEXT TOKEN_PADDING TOKEN_TAG TOKEN_DELIMITER/]
12             );
13             our @EXPORT_OK = map @$_, values %EXPORT_TAGS;
14              
15             our $OPEN_DELIMITER = '{{';
16             our $CLOSE_DELIMITER = '}}';
17              
18             my %CLOSE_DELIMITER_PREFIX = (
19             '{' => '}',
20             '=' => '=',
21             );
22              
23             use constant {
24             # enum
25 17         18975 TOKEN_RAW_TEXT => 0,
26             TOKEN_PADDING => 1,
27             TOKEN_TAG => 2,
28             TOKEN_DELIMITER => 3,
29 17     17   103 };
  17         30  
30              
31             our $_SOURCE;
32             our @_TOKENS;
33              
34             sub tokenize {
35 551     551 1 535153 my ($class, $source) = @_;
36              
37 551         1261 local $OPEN_DELIMITER = $OPEN_DELIMITER;
38 551         1097 local $CLOSE_DELIMITER = $CLOSE_DELIMITER;
39 551         1007 local $_SOURCE = $source;
40              
41 551         1848 my @tokens = ([TOKEN_DELIMITER, 0, undef, $OPEN_DELIMITER, $CLOSE_DELIMITER]);
42 551         2480 until ($_SOURCE =~ /\G\z/mgcano) {
43 2557   100     7755 my $pos = pos $_SOURCE || 0;
44 2557 100       28881 if ($_SOURCE =~ /\G\Q${OPEN_DELIMITER}\E([\{#\/&^!>\$<=])?/mgac) { # uncoverable branch false count:2
    50          
45 1302         2944 push @tokens => _tokenize_tag($1, $pos);
46             } elsif ($_SOURCE =~ /\G(?:(^[[:blank:]]+)|(.+?)(^[[:blank:]]+)?)(?=\Q${OPEN_DELIMITER}\E|\z)/msgac) {
47 1255 100       3450 if (defined $1) {
48 26         154 push @tokens => [TOKEN_PADDING, $pos, $1];
49             } else {
50 1229         3889 push @tokens => [TOKEN_RAW_TEXT, $pos, $2];
51 1229 100       5527 push @tokens => [TOKEN_PADDING, $pos+length($2), $3] if defined $3;
52             }
53             } else {
54 0         0 _error('Syntax Error: Unexpected Token', pos $_SOURCE); # uncoverable statement
55             }
56             }
57 544 50       1535 if (length $_SOURCE != pos $_SOURCE) { # uncoverable branch true
58 0         0 _error('Syntax Error: Unexpected Token', pos $_SOURCE); # uncoverable statement
59             }
60              
61 544         2873 return @tokens;
62             }
63              
64             sub _tokenize_tag {
65 1302     1302   3114 my ($type, $pos) = @_;
66              
67 1302 100 100     5479 my $prefix = defined $type ? ($CLOSE_DELIMITER_PREFIX{$type} || '') : '';
68 1302 100       10258 if ($_SOURCE =~ /\G(.+?)\Q${prefix}${CLOSE_DELIMITER}\E/msgac) {
69 1298         2750 my $body = $1;
70 1298 100 100     4261 if (defined $type && $type eq '=') {
71 40         86 my $delimiters = $body;
72 40         224 $delimiters =~ s/^\s+//ano;
73 40         200 $delimiters =~ s/\s+$//ano;
74 40 100       133 if ($delimiters =~ /=/ano) {
75 1         4 _error('Syntax Error: Invalid Delimiter', $pos);
76             }
77 39         146 my @delimiters = split /\s+/, $delimiters;
78 39 100       126 if (@delimiters != 2) {
79 2         5 _error('Syntax Error: Invalid Delimiter', $pos);
80             }
81 37         86 $OPEN_DELIMITER = $delimiters[0];
82 37         102 $CLOSE_DELIMITER = $delimiters[1];
83 37         270 return [TOKEN_DELIMITER, $pos, $body, $OPEN_DELIMITER, $CLOSE_DELIMITER];
84             } else {
85 1258 100       5593 return [TOKEN_TAG, $pos, $type, $body] if defined $type;
86 326         1554 return [TOKEN_TAG, $pos, $body];
87             }
88             } else {
89 4         15 _error('Syntax Error: Unexpected Token', $pos);
90             }
91             }
92              
93             sub _error {
94 7     7   19 my ($msg, $curr) = @_;
95              
96 7         15 my $src = $_SOURCE;
97 7         11 my $line = 1;
98 7         11 my $start = 0;
99 7   66     53 while ($src =~ /$/smgco && pos $src <= $curr) {# uncoverable condition left
100 3         78 $start = pos $src;
101 3         21 $line++;
102             }
103 7         14 my $end = pos $src;
104 7         13 my $len = $curr - $start;
105 7 100       18 $len-- if $len > 0;
106              
107 7         50 my $trace = join "\n",
108             "${msg}: line:$line",
109             substr($src, $start, $end - $start),
110             (' ' x $len) . '^';
111 7         1334 croak $trace, "\n";
112             }
113              
114             1;
115             __END__