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   1301625 use 5.010000;
  20         306  
3 20     20   135 use strict;
  20         42  
  20         534  
4 20     20   125 use warnings;
  20         43  
  20         674  
5 20     20   7672 use Encode;
  20         195555  
  20         2013  
6              
7             our $VERSION = "0.90_01";
8              
9 20     20   6871 use TOML::Parser::Tokenizer qw/:constant/;
  20         77  
  20         3859  
10 20     20   6812 use TOML::Parser::Tokenizer::Strict;
  20         62  
  20         1147  
11 20     20   5480 use TOML::Parser::Util qw/unescape_str/;
  20         53  
  20         1166  
12 20     20   6226 use Types::Serialiser;
  20         60630  
  20         30479  
13              
14             sub new {
15 56     56 1 46889 my $class = shift;
16 56 50 33     370 my $args = (@_ == 1 and ref $_[0] eq 'HASH') ? +shift : +{ @_ };
17             return bless +{
18 2     2   6 inflate_datetime => sub { $_[0] },
19 4 50   4   28 inflate_boolean => sub { $_[0] eq 'true' ? Types::Serialiser::true : Types::Serialiser::false },
20 56         652 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   121 my $self = shift;
39 56 100       539 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 351 my ($self, $src) = @_;
47              
48 56         130 local $ROOT = {};
49 56         102 local $CONTEXT = $ROOT;
50 56         165 local @TOKENS = $self->_tokenizer_class->tokenize($src);
51 36         168 while (my $token = shift @TOKENS) {
52 332         686 $self->_parse_token($token);
53             }
54 33         126 return $ROOT;
55             }
56              
57             sub _parse_token {
58 332     332   590 my ($self, $token) = @_;
59              
60 332         606 my ($type, $val) = @$token;
61 332 100       877 if ($type eq TOKEN_TABLE) {
    100          
    100          
    50          
62 40         114 $self->_parse_table($val);
63             }
64             elsif ($type eq TOKEN_ARRAY_OF_TABLE) {
65 17         32 $self->_parse_array_of_table($val);
66             }
67             elsif (my ($key, $value) = $self->_parse_key_and_value($token)) {
68 213 100       533 die "Duplicate key. key:$key" if exists $CONTEXT->{$key};
69 210         755 $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   557 my ($self, $token) = @_;
81              
82 319         507 my ($type, $val) = @$token;
83 319 100       573 if ($type eq TOKEN_KEY) {
84 257         392 my $token = shift @TOKENS;
85              
86 257         353 my $key = $val;
87 257         487 my $value = $self->_parse_value_token($token);
88 257         925 return ($key, $value);
89             }
90              
91 62         236 return;
92             }
93              
94             sub _parse_table {
95 40     40   74 my ($self, $keys) = @_;
96 40         98 my @keys = @$keys;
97              
98 40         71 $CONTEXT = $ROOT;
99 40         81 for my $k (@keys) {
100 67 100       165 if (exists $CONTEXT->{$k}) {
101             $CONTEXT = ref $CONTEXT->{$k} eq 'ARRAY' ? $CONTEXT->{$k}->[-1] :
102 9 50       40 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     429 $CONTEXT = $CONTEXT->{$k} ||= +{};
107             }
108             }
109             }
110              
111             sub _parse_array_of_table {
112 17     17   29 my ($self, $keys) = @_;
113 17         32 my @keys = @$keys;
114 17         28 my $last_key = pop @keys;
115              
116 17         25 $CONTEXT = $ROOT;
117 17         30 for my $k (@keys) {
118 6 50       14 if (exists $CONTEXT->{$k}) {
119             $CONTEXT = ref $CONTEXT->{$k} eq 'ARRAY' ? $CONTEXT->{$k}->[-1] :
120 6 0       15 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       49 $CONTEXT->{$last_key} = [] unless exists $CONTEXT->{$last_key};
129 17 50       43 die "invalid structure. @{[ join '.', @keys ]} cannot be `Array of table`" unless ref $CONTEXT->{$last_key} eq 'ARRAY';
  0         0  
130 17         24 push @{ $CONTEXT->{$last_key} } => $CONTEXT = {};
  17         67  
131             }
132              
133             sub _parse_value_token {
134 326     326   437 my $self = shift;
135 326         758 my $token = shift;
136              
137 326         647 my ($type, $val, @args) = @$token;
138 326 50 100     1434 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         172 $val =~ tr/_//d;
143 98         230 return 0+$val;
144             }
145             elsif ($type eq TOKEN_BOOLEAN) {
146 4         15 return $self->inflate_boolean($val);
147             }
148             elsif ($type eq TOKEN_DATETIME) {
149 2         8 return $self->inflate_datetime($val);
150             }
151             elsif ($type eq TOKEN_STRING) {
152 157         279 my ($is_raw) = @args;
153 157 100       455 return $is_raw ? $val : unescape_str($val);
154             }
155             elsif ($type eq TOKEN_MULTI_LINE_STRING_BEGIN) {
156 20         35 my ($is_raw) = @args;
157 20         49 my $value = $self->_parse_value_token(shift @TOKENS);
158 20         96 $value =~ s/\A(?:\r\n|[\r\n])//msg;
159 20         63 $value =~ s/\\\s+//msg;
160 20 50       76 if (my $token = shift @TOKENS) {
161 20         39 my ($type) = @$token;
162 20 50       69 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         20 my %data;
168 16         31 while (my $token = shift @TOKENS) {
169 62 100       95 last if $token->[0] eq TOKEN_INLINE_TABLE_END;
170 46 100       79 next if $token->[0] eq TOKEN_COMMENT;
171 44         70 my ($key, $value) = $self->_parse_key_and_value($token);
172 44 50       80 die "Duplicate key. key:$key" if exists $data{$key};
173 44         105 $data{$key} = $value;
174             }
175 16         33 return \%data;
176             }
177             elsif ($type eq TOKEN_ARRAY_BEGIN) {
178 29         54 my @data;
179              
180             my $last_token;
181 29         90 while (my $token = shift @TOKENS) {
182 85 100       175 last if $token->[0] eq TOKEN_ARRAY_END;
183 56 100       119 next if $token->[0] eq TOKEN_COMMENT;
184 49 100       102 if ($self->{strict_mode}) {
185 22 50 66     77 die "Unexpected token: $token->[0]" if defined $last_token && $token->[0] ne $last_token->[0];
186             }
187 49         120 push @data => $self->_parse_value_token($token);
188 49         130 $last_token = $token;
189             }
190 29         76 return \@data;
191             }
192              
193 0         0 die "Unexpected token: $type";
194             }
195              
196             sub inflate_datetime {
197 2     2 1 4 my $self = shift;
198 2         11 return $self->{inflate_datetime}->(@_);
199             }
200              
201             sub inflate_boolean {
202 4     4 1 9 my $self = shift;
203 4         19 return $self->{inflate_boolean}->(@_);
204             }
205              
206             1;
207             __END__