File Coverage

blib/lib/TOML/Parser.pm
Criterion Covered Total %
statement 108 124 87.1
branch 55 72 76.3
condition 7 13 53.8
subroutine 20 22 90.9
pod 6 6 100.0
total 196 237 82.7


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__