| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package TOML::Parser; | 
| 2 | 20 |  |  | 20 |  | 1043592 | use 5.010000; | 
|  | 20 |  |  |  |  | 253 |  | 
| 3 | 20 |  |  | 20 |  | 100 | use strict; | 
|  | 20 |  |  |  |  | 32 |  | 
|  | 20 |  |  |  |  | 413 |  | 
| 4 | 20 |  |  | 20 |  | 133 | use warnings; | 
|  | 20 |  |  |  |  | 31 |  | 
|  | 20 |  |  |  |  | 573 |  | 
| 5 | 20 |  |  | 20 |  | 6731 | use Encode; | 
|  | 20 |  |  |  |  | 155461 |  | 
|  | 20 |  |  |  |  | 1600 |  | 
| 6 |  |  |  |  |  |  |  | 
| 7 |  |  |  |  |  |  | our $VERSION = "0.91"; | 
| 8 |  |  |  |  |  |  |  | 
| 9 | 20 |  |  | 20 |  | 5832 | use TOML::Parser::Tokenizer qw/:constant/; | 
|  | 20 |  |  |  |  | 56 |  | 
|  | 20 |  |  |  |  | 3203 |  | 
| 10 | 20 |  |  | 20 |  | 5553 | use TOML::Parser::Tokenizer::Strict; | 
|  | 20 |  |  |  |  | 40 |  | 
|  | 20 |  |  |  |  | 1128 |  | 
| 11 | 20 |  |  | 20 |  | 4749 | use TOML::Parser::Util qw/unescape_str/; | 
|  | 20 |  |  |  |  | 40 |  | 
|  | 20 |  |  |  |  | 930 |  | 
| 12 | 20 |  |  | 20 |  | 4994 | use Types::Serialiser; | 
|  | 20 |  |  |  |  | 49373 |  | 
|  | 20 |  |  |  |  | 24568 |  | 
| 13 |  |  |  |  |  |  |  | 
| 14 |  |  |  |  |  |  | sub new { | 
| 15 | 56 |  |  | 56 | 1 | 41331 | my $class = shift; | 
| 16 | 56 | 50 | 33 |  |  | 290 | my $args  = (@_ == 1 and ref $_[0] eq 'HASH') ? +shift : +{ @_ }; | 
| 17 |  |  |  |  |  |  | return bless +{ | 
| 18 | 2 |  |  | 2 |  | 6 | inflate_datetime => sub { $_[0] }, | 
| 19 | 4 | 50 |  | 4 |  | 30 | inflate_boolean  => sub { $_[0] eq 'true' ? Types::Serialiser::true : Types::Serialiser::false }, | 
| 20 | 56 |  |  |  |  | 552 | strict_mode      => 0, | 
| 21 |  |  |  |  |  |  | %$args, | 
| 22 |  |  |  |  |  |  | } => $class; | 
| 23 |  |  |  |  |  |  | } | 
| 24 |  |  |  |  |  |  |  | 
| 25 |  |  |  |  |  |  | sub parse_file { | 
| 26 | 0 |  |  | 0 | 1 | 0 | my ($self, $file) = @_; | 
| 27 | 0 | 0 |  |  |  | 0 | open my $fh, '<:encoding(utf-8)', $file or die $!; | 
| 28 | 0 |  |  |  |  | 0 | return $self->parse_fh($fh); | 
| 29 |  |  |  |  |  |  | } | 
| 30 |  |  |  |  |  |  |  | 
| 31 |  |  |  |  |  |  | sub parse_fh { | 
| 32 | 0 |  |  | 0 | 1 | 0 | my ($self, $fh) = @_; | 
| 33 | 0 |  |  |  |  | 0 | my $src = do { local $/; <$fh> }; | 
|  | 0 |  |  |  |  | 0 |  | 
|  | 0 |  |  |  |  | 0 |  | 
| 34 | 0 |  |  |  |  | 0 | return $self->parse($src); | 
| 35 |  |  |  |  |  |  | } | 
| 36 |  |  |  |  |  |  |  | 
| 37 |  |  |  |  |  |  | sub _tokenizer_class { | 
| 38 | 56 |  |  | 56 |  | 85 | my $self = shift; | 
| 39 | 56 | 100 |  |  |  | 452 | return $self->{strict_mode} ? 'TOML::Parser::Tokenizer::Strict' : 'TOML::Parser::Tokenizer'; | 
| 40 |  |  |  |  |  |  | } | 
| 41 |  |  |  |  |  |  |  | 
| 42 |  |  |  |  |  |  | our @TOKENS; | 
| 43 |  |  |  |  |  |  | our $ROOT; | 
| 44 |  |  |  |  |  |  | our $CONTEXT; | 
| 45 |  |  |  |  |  |  | sub parse { | 
| 46 | 56 |  |  | 56 | 1 | 222 | my ($self, $src) = @_; | 
| 47 |  |  |  |  |  |  |  | 
| 48 | 56 |  |  |  |  | 102 | local $ROOT    = {}; | 
| 49 | 56 |  |  |  |  | 90 | local $CONTEXT = $ROOT; | 
| 50 | 56 |  |  |  |  | 139 | local @TOKENS  = $self->_tokenizer_class->tokenize($src); | 
| 51 | 36 |  |  |  |  | 159 | while (my $token = shift @TOKENS) { | 
| 52 | 332 |  |  |  |  | 614 | $self->_parse_token($token); | 
| 53 |  |  |  |  |  |  | } | 
| 54 | 33 |  |  |  |  | 117 | return $ROOT; | 
| 55 |  |  |  |  |  |  | } | 
| 56 |  |  |  |  |  |  |  | 
| 57 |  |  |  |  |  |  | sub _parse_token { | 
| 58 | 332 |  |  | 332 |  | 530 | my ($self, $token) = @_; | 
| 59 |  |  |  |  |  |  |  | 
| 60 | 332 |  |  |  |  | 552 | my ($type, $val) = @$token; | 
| 61 | 332 | 100 |  |  |  | 853 | if ($type eq TOKEN_TABLE) { | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
| 62 | 40 |  |  |  |  | 79 | $self->_parse_table($val); | 
| 63 |  |  |  |  |  |  | } | 
| 64 |  |  |  |  |  |  | elsif ($type eq TOKEN_ARRAY_OF_TABLE) { | 
| 65 | 17 |  |  |  |  | 37 | $self->_parse_array_of_table($val); | 
| 66 |  |  |  |  |  |  | } | 
| 67 |  |  |  |  |  |  | elsif (my ($key, $value) = $self->_parse_key_and_value($token)) { | 
| 68 | 213 | 100 |  |  |  | 510 | die "Duplicate key. key:$key" if exists $CONTEXT->{$key}; | 
| 69 | 210 |  |  |  |  | 708 | $CONTEXT->{$key} = $value; | 
| 70 |  |  |  |  |  |  | } | 
| 71 |  |  |  |  |  |  | elsif ($type eq TOKEN_COMMENT) { | 
| 72 |  |  |  |  |  |  | # pass through | 
| 73 |  |  |  |  |  |  | } | 
| 74 |  |  |  |  |  |  | else { | 
| 75 | 0 |  |  |  |  | 0 | die "Unknown case. type:$type"; | 
| 76 |  |  |  |  |  |  | } | 
| 77 |  |  |  |  |  |  | } | 
| 78 |  |  |  |  |  |  |  | 
| 79 |  |  |  |  |  |  | sub _parse_key_and_value { | 
| 80 | 319 |  |  | 319 |  | 468 | my ($self, $token) = @_; | 
| 81 |  |  |  |  |  |  |  | 
| 82 | 319 |  |  |  |  | 478 | my ($type, $val) = @$token; | 
| 83 | 319 | 100 |  |  |  | 542 | if ($type eq TOKEN_KEY) { | 
| 84 | 257 |  |  |  |  | 362 | my $token = shift @TOKENS; | 
| 85 |  |  |  |  |  |  |  | 
| 86 | 257 |  |  |  |  | 333 | my $key = $val; | 
| 87 | 257 |  |  |  |  | 424 | my $value = $self->_parse_value_token($token); | 
| 88 | 257 |  |  |  |  | 916 | return ($key, $value); | 
| 89 |  |  |  |  |  |  | } | 
| 90 |  |  |  |  |  |  |  | 
| 91 | 62 |  |  |  |  | 267 | return; | 
| 92 |  |  |  |  |  |  | } | 
| 93 |  |  |  |  |  |  |  | 
| 94 |  |  |  |  |  |  | sub _parse_table { | 
| 95 | 40 |  |  | 40 |  | 68 | my ($self, $keys) = @_; | 
| 96 | 40 |  |  |  |  | 71 | my @keys = @$keys; | 
| 97 |  |  |  |  |  |  |  | 
| 98 | 40 |  |  |  |  | 53 | $CONTEXT = $ROOT; | 
| 99 | 40 |  |  |  |  | 82 | for my $k (@keys) { | 
| 100 | 67 | 100 |  |  |  | 140 | if (exists $CONTEXT->{$k}) { | 
| 101 |  |  |  |  |  |  | $CONTEXT = ref $CONTEXT->{$k} eq 'ARRAY' ? $CONTEXT->{$k}->[-1] : | 
| 102 | 9 | 50 |  |  |  | 38 | ref $CONTEXT->{$k} eq 'HASH'  ? $CONTEXT->{$k}       : | 
|  |  | 100 |  |  |  |  |  | 
| 103 | 0 |  |  |  |  | 0 | die "invalid structure. @{[ join '.', @keys ]} cannot be `Table`"; | 
| 104 |  |  |  |  |  |  | } | 
| 105 |  |  |  |  |  |  | else { | 
| 106 | 58 |  | 50 |  |  | 265 | $CONTEXT = $CONTEXT->{$k} ||= +{}; | 
| 107 |  |  |  |  |  |  | } | 
| 108 |  |  |  |  |  |  | } | 
| 109 |  |  |  |  |  |  | } | 
| 110 |  |  |  |  |  |  |  | 
| 111 |  |  |  |  |  |  | sub _parse_array_of_table { | 
| 112 | 17 |  |  | 17 |  | 28 | my ($self, $keys) = @_; | 
| 113 | 17 |  |  |  |  | 29 | my @keys     = @$keys; | 
| 114 | 17 |  |  |  |  | 31 | my $last_key = pop @keys; | 
| 115 |  |  |  |  |  |  |  | 
| 116 | 17 |  |  |  |  | 23 | $CONTEXT = $ROOT; | 
| 117 | 17 |  |  |  |  | 30 | for my $k (@keys) { | 
| 118 | 6 | 50 |  |  |  | 12 | if (exists $CONTEXT->{$k}) { | 
| 119 |  |  |  |  |  |  | $CONTEXT = ref $CONTEXT->{$k} eq 'ARRAY' ? $CONTEXT->{$k}->[-1] : | 
| 120 | 6 | 0 |  |  |  | 16 | ref $CONTEXT->{$k} eq 'HASH'  ? $CONTEXT->{$k}       : | 
|  |  | 50 |  |  |  |  |  | 
| 121 | 0 |  |  |  |  | 0 | die "invalid structure. @{[ join '.', @keys ]} cannot be `Array of table`."; | 
| 122 |  |  |  |  |  |  | } | 
| 123 |  |  |  |  |  |  | else { | 
| 124 | 0 |  | 0 |  |  | 0 | $CONTEXT = $CONTEXT->{$k} ||= +{}; | 
| 125 |  |  |  |  |  |  | } | 
| 126 |  |  |  |  |  |  | } | 
| 127 |  |  |  |  |  |  |  | 
| 128 | 17 | 100 |  |  |  | 52 | $CONTEXT->{$last_key} = [] unless exists $CONTEXT->{$last_key}; | 
| 129 | 17 | 50 |  |  |  | 48 | die "invalid structure. @{[ join '.', @keys ]} cannot be `Array of table`" unless ref $CONTEXT->{$last_key} eq 'ARRAY'; | 
|  | 0 |  |  |  |  | 0 |  | 
| 130 | 17 |  |  |  |  | 20 | push @{ $CONTEXT->{$last_key} } => $CONTEXT = {}; | 
|  | 17 |  |  |  |  | 74 |  | 
| 131 |  |  |  |  |  |  | } | 
| 132 |  |  |  |  |  |  |  | 
| 133 |  |  |  |  |  |  | sub _parse_value_token { | 
| 134 | 326 |  |  | 326 |  | 383 | my $self  = shift; | 
| 135 | 326 |  |  |  |  | 364 | my $token = shift; | 
| 136 |  |  |  |  |  |  |  | 
| 137 | 326 |  |  |  |  | 565 | my ($type, $val, @args) = @$token; | 
| 138 | 326 | 50 | 100 |  |  | 1274 | if ($type eq TOKEN_COMMENT) { | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
| 139 | 0 |  |  |  |  | 0 | return; # pass through | 
| 140 |  |  |  |  |  |  | } | 
| 141 |  |  |  |  |  |  | elsif ($type eq TOKEN_INTEGER || $type eq TOKEN_FLOAT) { | 
| 142 | 98 |  |  |  |  | 176 | $val =~ tr/_//d; | 
| 143 | 98 |  |  |  |  | 261 | return 0+$val; | 
| 144 |  |  |  |  |  |  | } | 
| 145 |  |  |  |  |  |  | elsif ($type eq TOKEN_BOOLEAN) { | 
| 146 | 4 |  |  |  |  | 16 | return $self->inflate_boolean($val); | 
| 147 |  |  |  |  |  |  | } | 
| 148 |  |  |  |  |  |  | elsif ($type eq TOKEN_DATETIME) { | 
| 149 | 2 |  |  |  |  | 6 | return $self->inflate_datetime($val); | 
| 150 |  |  |  |  |  |  | } | 
| 151 |  |  |  |  |  |  | elsif ($type eq TOKEN_STRING) { | 
| 152 | 157 |  |  |  |  | 217 | my ($is_raw) = @args; | 
| 153 | 157 | 100 |  |  |  | 371 | return $is_raw ? $val : unescape_str($val); | 
| 154 |  |  |  |  |  |  | } | 
| 155 |  |  |  |  |  |  | elsif ($type eq TOKEN_MULTI_LINE_STRING_BEGIN) { | 
| 156 | 20 |  |  |  |  | 25 | my ($is_raw) = @args; | 
| 157 | 20 |  |  |  |  | 37 | my $value = $self->_parse_value_token(shift @TOKENS); | 
| 158 | 20 |  |  |  |  | 69 | $value =~ s/\A(?:\r\n|[\r\n])//msg; | 
| 159 | 20 |  |  |  |  | 52 | $value =~ s/\\\s+//msg; | 
| 160 | 20 | 50 |  |  |  | 64 | if (my $token = shift @TOKENS) { | 
| 161 | 20 |  |  |  |  | 26 | my ($type) = @$token; | 
| 162 | 20 | 50 |  |  |  | 50 | return $value if $type eq TOKEN_MULTI_LINE_STRING_END; | 
| 163 | 0 |  |  |  |  | 0 | die "Unexpected token: $type"; | 
| 164 |  |  |  |  |  |  | } | 
| 165 |  |  |  |  |  |  | } | 
| 166 |  |  |  |  |  |  | elsif ($type eq TOKEN_INLINE_TABLE_BEGIN) { | 
| 167 | 16 |  |  |  |  | 18 | my %data; | 
| 168 | 16 |  |  |  |  | 30 | while (my $token = shift @TOKENS) { | 
| 169 | 62 | 100 |  |  |  | 89 | last if $token->[0] eq TOKEN_INLINE_TABLE_END; | 
| 170 | 46 | 100 |  |  |  | 77 | next if $token->[0] eq TOKEN_COMMENT; | 
| 171 | 44 |  |  |  |  | 63 | my ($key, $value) = $self->_parse_key_and_value($token); | 
| 172 | 44 | 50 |  |  |  | 72 | die "Duplicate key. key:$key" if exists $data{$key}; | 
| 173 | 44 |  |  |  |  | 99 | $data{$key} = $value; | 
| 174 |  |  |  |  |  |  | } | 
| 175 | 16 |  |  |  |  | 32 | return \%data; | 
| 176 |  |  |  |  |  |  | } | 
| 177 |  |  |  |  |  |  | elsif ($type eq TOKEN_ARRAY_BEGIN) { | 
| 178 | 29 |  |  |  |  | 48 | my @data; | 
| 179 |  |  |  |  |  |  |  | 
| 180 |  |  |  |  |  |  | my $last_token; | 
| 181 | 29 |  |  |  |  | 82 | while (my $token = shift @TOKENS) { | 
| 182 | 85 | 100 |  |  |  | 167 | last if $token->[0] eq TOKEN_ARRAY_END; | 
| 183 | 56 | 100 |  |  |  | 100 | next if $token->[0] eq TOKEN_COMMENT; | 
| 184 | 49 | 100 |  |  |  | 84 | if ($self->{strict_mode}) { | 
| 185 | 22 | 50 | 66 |  |  | 75 | die "Unexpected token: $token->[0]" if defined $last_token && $token->[0] ne $last_token->[0]; | 
| 186 |  |  |  |  |  |  | } | 
| 187 | 49 |  |  |  |  | 96 | push @data => $self->_parse_value_token($token); | 
| 188 | 49 |  |  |  |  | 116 | $last_token = $token; | 
| 189 |  |  |  |  |  |  | } | 
| 190 | 29 |  |  |  |  | 72 | return \@data; | 
| 191 |  |  |  |  |  |  | } | 
| 192 |  |  |  |  |  |  |  | 
| 193 | 0 |  |  |  |  | 0 | die "Unexpected token: $type"; | 
| 194 |  |  |  |  |  |  | } | 
| 195 |  |  |  |  |  |  |  | 
| 196 |  |  |  |  |  |  | sub inflate_datetime { | 
| 197 | 2 |  |  | 2 | 1 | 5 | my $self = shift; | 
| 198 | 2 |  |  |  |  | 8 | return $self->{inflate_datetime}->(@_); | 
| 199 |  |  |  |  |  |  | } | 
| 200 |  |  |  |  |  |  |  | 
| 201 |  |  |  |  |  |  | sub inflate_boolean { | 
| 202 | 4 |  |  | 4 | 1 | 9 | my $self = shift; | 
| 203 | 4 |  |  |  |  | 24 | return $self->{inflate_boolean}->(@_); | 
| 204 |  |  |  |  |  |  | } | 
| 205 |  |  |  |  |  |  |  | 
| 206 |  |  |  |  |  |  | 1; | 
| 207 |  |  |  |  |  |  | __END__ |