File Coverage

blib/lib/TOML/Tiny/Parser.pm
Criterion Covered Total %
statement 284 297 95.6
branch 57 64 89.0
condition 27 35 77.1
subroutine 35 36 97.2
pod 0 21 0.0
total 403 453 88.9


line stmt bran cond sub pod time code
1             # ABSTRACT: parser used by TOML::Tiny
2             $TOML::Tiny::Parser::VERSION = '0.15';
3             use utf8;
4 286     286   167369 use strict;
  286         457  
  286         1478  
5 286     286   6702 use warnings;
  286         453  
  286         3913  
6 286     286   1017 no warnings qw(experimental);
  286         410  
  286         5652  
7 286     286   1050 use v5.18;
  286         431  
  286         5998  
8 286     286   2277  
  286         857  
9             use Carp;
10 286     286   1450 use Data::Dumper;
  286         526  
  286         15105  
11 286     286   91856 use Encode qw(decode FB_CROAK);
  286         911018  
  286         14812  
12 286     286   1872 use TOML::Tiny::Util qw(is_strict_array);
  286         534  
  286         12475  
13 286     286   96840 use TOML::Tiny::Grammar;
  286         607  
  286         14228  
14 286     286   1637  
  286         467  
  286         710830  
15             require Math::BigFloat;
16             require Math::BigInt;
17             require TOML::Tiny::Tokenizer;
18              
19             our $TRUE = 1;
20             our $FALSE = 0;
21              
22             eval{
23             require Types::Serialiser;
24             $TRUE = Types::Serialiser::true();
25             $FALSE = Types::Serialiser::false();
26             };
27              
28             my ($class, %param) = @_;
29             bless{
30 430     430 0 407478 inflate_integer => $param{inflate_integer},
31             inflate_float => $param{inflate_float},
32             inflate_datetime => $param{inflate_datetime} || sub{ shift },
33             inflate_boolean => $param{inflate_boolean} || sub{ shift eq 'true' ? $TRUE : $FALSE },
34 53     53   201 strict => $param{strict},
35 35 100   35   173 }, $class;
36             }
37 430   100     7158  
      100        
38             my $self = shift;
39             my $token = $self->{tokenizer} && $self->{tokenizer}->next_token;
40             return $token;
41 6358     6358 0 7528 }
42 6358   66     17364  
43 6244         13876 my ($self, $toml) = @_;
44              
45             if ($self->{strict}) {
46             $toml = decode('UTF-8', "$toml", FB_CROAK);
47 430     430 0 1015 }
48              
49 430 100       2035 $self->{tokenizer} = TOML::Tiny::Tokenizer->new(source => $toml);
50 185         893 $self->{keys} = [];
51             $self->{root} = {};
52             $self->{tables} = {}; # "seen" hash of explicitly defined table names (e.g. [foo])
53 424         12175 $self->{arrays} = {}; # "seen" hash of explicitly defined static arrays (e.g. foo=[])
54 424         1098 $self->{array_tables} = {}; # "seen" hash of explicitly defined arrays of tables (e.g. [[foo]])
55 424         1218  
56 424         867 $self->parse_table;
57 424         921 my $result = $self->{root};
58 424         813  
59             delete $self->{tokenizer};
60 424         1485 delete $self->{keys};
61 245         523 delete $self->{root};
62             delete $self->{tables};
63 245         1285 delete $self->{arrays};
64 245         521 delete $self->{array_tables};
65 245         378  
66 245         582 return $result;
67 245         398 }
68 245         371  
69             my ($self, $token, $msg) = @_;
70 245         671 my $line = $token ? $token->{line} : 'EOF';
71             if ($ENV{TOML_TINY_DEBUG}) {
72             my $root = Dumper($self->{root});
73             my $tok = Dumper($token);
74 60     60 0 186 my $src = substr $self->{tokenizer}{source}, $self->{tokenizer}{position}, 30;
75 60 100       212  
76 60 50       230 confess qq{
77 0         0 toml parse error at line $line:
78 0         0 $msg
79 0         0  
80             Current token:
81 0         0 $tok
82              
83             Parse state:
84             $root
85              
86             Source near location of error:
87             ...
88             $src
89             ...
90              
91             };
92             } else {
93             die "toml parse error at line $line: $msg\n";
94             }
95             }
96              
97             my ($self, $token, $expected) = @_;
98 60         607 my $actual = $token ? $token->{type} : 'EOF';
99             $self->parse_error($token, "expected $expected, but found $actual")
100             unless $actual =~ /$expected/;
101             }
102              
103 3551     3551 0 5783  
104 3551 100       5826 my $self = shift;
105 3551 100       50976 my @keys = $self->get_keys;
106             my $key = join '.', map{ qq{"$_"} } @keys;
107             return $key;
108             }
109              
110             my ($self, $token) = @_;
111 891     891 0 1195 push @{ $self->{keys} }, $token->{value};
112 891         1519 }
113 891         1813  
  735         1917  
114 891         2312 my $self = shift;
115             pop @{ $self->{keys} };
116             }
117              
118 1710     1710 0 2658 my $self = shift;
119 1710         2000 return map{ @$_ } @{ $self->{keys} };
  1710         3494  
120             }
121              
122             my ($self, $token) = @_;
123 1220     1220 0 1458 my @keys = $self->get_keys;
124 1220         1332 my $key = pop @keys;
  1220         2074  
125             my $node = $self->scan_to_key(\@keys);
126              
127             if ($key && exists $node->{$key}) {
128 2564     2564 0 2951 $self->parse_error($token, 'duplicate key: ' . $self->current_key);
129 2564         2665 }
  3664         7988  
  2564         4188  
130              
131             $node->{$key} = $self->parse_value($token);
132             }
133 936     936 0 1767  
134 936         1736 my ($self, $token) = @_;
135 936         1536 my $key = $self->current_key || return;
136 936         2061  
137             for ($token->{type}) {
138 935 100 100     3650 when ('inline_array') {
139 2         5 $self->parse_error($token, "duplicate key: $key")
140             if exists $self->{array_tables}{$key};
141              
142 933         2122 $self->{arrays}{$key} = 1;
143             }
144              
145             when ('array_table') {
146 889     889 0 1452 if (exists $self->{arrays}{$key}) {
147 889   100     1801 $self->parse_error($token, "duplicate key: $key");
148             }
149 467         884  
150 467         838 $self->{array_tables}{$key} = 1;
151             }
152 162 50       330  
153             when ('table') {
154 162         518 $self->parse_error($token, "duplicate key: $key")
155             if exists $self->{arrays}{$key}
156             || exists $self->{array_tables}{$key};
157 305         432  
158 74 100       231 if (exists $self->{tables}{$key}) {
159 1         4 # Tables cannot be redefined, *except* when doing so within a goddamn
160             # table array. Gawd I hate TOML.
161             my $in_a_stupid_table_array = 0;
162 73         220 my $node = $self->{root};
163              
164             for my $key ($self->get_keys) {
165 231         327 if (exists $node->{$key} && ref($node->{$key}) eq 'ARRAY') {
166             $in_a_stupid_table_array = 1;
167             last;
168 231 100 66     962 } else {
169             $node = $node->{$key};
170 229 100       488 }
171             }
172              
173 9         22 unless ($in_a_stupid_table_array) {
174 9         15 $self->parse_error($token, "duplicate key: $key");
175             }
176 9         18 }
177 9 100 66     55 else {
178 8         15 $self->{tables}{$key} = 1;
179 8         12 }
180             }
181 1         3 }
182             }
183              
184             my $self = shift;
185 9 100       38 my $keys = shift // [ $self->get_keys ];
186 1         4 my $node = $self->{root};
187              
188             for my $key (@$keys) {
189             if (exists $node->{$key}) {
190 220         692 for (ref $node->{$key}) {
191             $node = $node->{$key} when 'HASH';
192             $node = $node->{$key}[-1] when 'ARRAY';
193             default{
194             my $full_key = join '.', @$keys;
195             die "$full_key is already defined\n";
196             }
197 1664     1664 0 2067 }
198 1664   100     3660 }
199 1664         2456 else {
200             $node = $node->{$key} = {};
201 1664         2958 }
202 989 100       1686 }
203 714         1348  
204 714         1674 return $node;
205 145         382 }
206 3         6  
207 3         8 my $self = shift;
208 3         22 my $token = shift // $self->next_token // return; # may be undef on first token in empty document
209              
210             $self->expect_type($token, 'table');
211             $self->push_keys($token);
212             $self->scan_to_key;
213 275         627  
214             $self->declare_key($token);
215              
216             TOKEN: while (my $token = $self->next_token) {
217 1661         2349 for ($token->{type}) {
218             next TOKEN when 'EOL';
219              
220             when ('key') {
221 657     657 0 1094 $self->expect_type($self->next_token, 'assign');
222 657   100     2126 $self->push_keys($token);
      100        
223             $self->set_key($self->next_token);
224 655         2022 $self->pop_keys;
225 655         2081  
226 655         1761 if (my $eol = $self->next_token) {
227             $self->expect_type($eol, 'EOL');
228 653         1751 } else {
229             return;
230 650         1434 }
231 1483         2522 }
232 1483         3476  
233             when ('array_table') {
234 1175         1703 $self->pop_keys;
235 902         1708 @_ = ($self, $token);
236 902         2645 goto \&parse_array_table;
237 902         1737 }
238 834         95634  
239             when ('table') {
240 834 100       1552 $self->pop_keys;
241 642         1292 @_ = ($self, $token);
242             goto \&parse_table;
243 135         417 }
244              
245             default{
246             $self->parse_error($token, "expected key-value pair, table, or array of tables but got $_");
247 273         451 }
248 35         101 }
249 35         120 }
250 35         149 }
251              
252             my $self = shift;
253 238         352 my $token = shift // $self->next_token;
254 218         459 $self->expect_type($token, 'array_table');
255 218         769 $self->push_keys($token);
256 218         724  
257             $self->declare_key($token);
258              
259 20         25 my @keys = $self->get_keys;
260 20         86 my $key = pop @keys;
261             my $node = $self->scan_to_key(\@keys);
262             $node->{$key} //= [];
263             push @{ $node->{$key} }, {};
264              
265             TOKEN: while (my $token = $self->next_token) {
266             for ($token->{type}) {
267 74     74 0 109 next TOKEN when 'EOL';
268 74   33     163  
269 74         249 when ('key') {
270 74         208 $self->expect_type($self->next_token, 'assign');
271             $self->push_keys($token);
272 74         156 $self->set_key($self->next_token);
273             $self->pop_keys;
274 73         130 }
275 73         105  
276 73         204 when ('array_table') {
277 73   100     302 $self->pop_keys;
278 73         89 @_ = ($self, $token);
  73         175  
279             goto \&parse_array_table;
280 71         155 }
281 262         379  
282 262         550 when ('table') {
283             $self->pop_keys;
284 133         180 @_ = ($self, $token);
285 79         137 goto \&parse_table;
286 79         213 }
287 79         142  
288 79         159 default{
289             $self->parse_error($token, "expected key-value pair, table, or array of tables but got $_");
290             }
291 54         80 }
292 39         80 }
293 39         76 }
294 39         129  
295             my $self = shift;
296             my $token = shift // $self->next_token;
297 15         45 $self->expect_type($token, 'key');
298 15         42 return $token->{value};
299 15         42 }
300 15         55  
301             my $self = shift;
302             my $token = shift;
303 0         0  
304 0         0 for ($token->{type}) {
305             return $token->{value} when 'string';
306             return $self->inflate_float($token) when 'float';
307             return $self->inflate_integer($token) when 'integer';
308             return $self->{inflate_boolean}->($token->{value}) when 'bool';
309             return $self->parse_datetime($token) when 'datetime';
310             return $self->parse_inline_table($token) when 'inline_table';
311 0     0 0 0 return $self->parse_array($token) when 'inline_array';
312 0   0     0  
313 0         0 default{
314 0         0 $self->parse_error($token, "value expected (bool, number, string, datetime, inline array, inline table), but found $_");
315             }
316             }
317             }
318 1314     1314 0 1675  
319 1314         1538 #-------------------------------------------------------------------------------
320             # TOML permits a space instead of a T, which RFC3339 does not allow. TOML (at
321 1314         2075 # least, according to BurntSushi/toml-tests) allows z instead of Z, which
322 1314         3135 # RFC3339 also does not permit. We will be flexible and allow them both, but
323 862         1252 # fix them up. TOML also specifies millisecond precision. If fractional seconds
324 764         1564 # are specified. Whatever.
325 353         518 #-------------------------------------------------------------------------------
326 316         455 my $self = shift;
327 262         484 my $token = shift;
328 166         407 my $value = $token->{value};
329              
330 4         6 # Normalize
331 4         18 $value =~ tr/z/Z/;
332             $value =~ tr/ /T/;
333             $value =~ s/t/T/;
334             $value =~ s/(\.\d+)($TimeOffset)$/sprintf(".%06d%s", $1 * 1000000, $2)/e;
335              
336             return $self->{inflate_datetime}->($value);
337             }
338              
339             my $self = shift;
340             my $token = shift;
341              
342             $self->declare_key($token);
343              
344 54     54 0 88 my @array;
345 54         89 my $expect = 'EOL|inline_array_close|string|float|integer|bool|datetime|inline_table|inline_array';
346 54         80  
347             TOKEN: while (1) {
348             my $token = $self->next_token;
349 54         146 $self->expect_type($token, $expect);
350 54         108  
351 54         136 for ($token->{type}) {
352 54         1412 when ('comma') {
  8         59  
353             $expect = 'EOL|inline_array_close|string|float|integer|bool|datetime|inline_table|inline_array';
354 54         168 next TOKEN;
355             }
356              
357             next TOKEN when 'EOL';
358 162     162 0 207 last TOKEN when 'inline_array_close';
359 162         180  
360             default{
361 162         355 push @array, $self->parse_value($token);
362             $expect = 'comma|EOL|inline_array_close';
363 162         193 }
364 162         218 }
365             }
366 162         185  
367 939         1471 return \@array;
368 936         1717 }
369              
370 933         2214 my $self = shift;
371 933         1651 my $token = shift;
372 218         306  
373 218         351 my $table = {};
374             my $expect = 'EOL|inline_table_close|key';
375              
376 715         948 TOKEN: while (1) {
377 461         600 my $token = $self->next_token;
378             $self->expect_type($token, $expect);
379 307         404  
380 307         666 for ($token->{type}) {
381 305         649 when ('comma') {
382             $expect = $self->{strict}
383             ? 'EOL|key'
384             : 'EOL|key|inline_table_close';
385              
386 154         534 next TOKEN;
387             }
388              
389             when ('key') {
390 96     96 0 146 $self->expect_type($self->next_token, 'assign');
391 96         123  
392             my $node = $table;
393 96         143 my @keys = @{ $token->{value} };
394 96         152 my $key = pop @keys;
395              
396 96         116 for (@keys) {
397 189         346 $node->{$_} ||= {};
398 189         495 $node = $node->{$_};
399             }
400 181         557  
401 181         392 if (exists $node->{$key}) {
402             $self->parse_error($token, 'duplicate key: ' . join('.', map{ qq{"$_"} } @{ $token->{value} }));
403 19 100       54 } else {
404             $node->{ $key } = $self->parse_value($self->next_token);
405             }
406 19         35  
407             $expect = 'comma|inline_table_close';
408             next TOKEN;
409 162         326 }
410 74         156  
411             last TOKEN when 'inline_table_close';
412 74         154  
413 74         107 default{
  74         189  
414 74         124 $self->parse_error($token, "inline table expected key-value pair, but found $_");
415             }
416 74         166 }
417 16   100     60 }
418 16         27  
419             return $table;
420             }
421 74 50       167  
422 0         0 my $self = shift;
  0         0  
  0         0  
423             my $token = shift;
424 74         152 my $value = $token->{value};
425              
426             # Caller-defined inflation routine
427 74         134 if ($self->{inflate_float}) {
428 74         207 return $self->{inflate_float}->($value);
429             }
430              
431 88         224 return 'NaN' if $value =~ /^[-+]?nan$/i;
432             return 'inf' if $value =~ /^\+?inf$/i;
433 2         4 return '-inf' if $value =~ /^-inf$/i;
434 2         12  
435             # Not a bignum
436             if (0 + $value eq $value) {
437             return 0 + $value;
438             }
439 86         266  
440             #-----------------------------------------------------------------------------
441             # Scientific notation is a hairier situation. In order to determine whether a
442             # value will fit inside a perl svnv, we can't just coerce the value to a
443 98     98 0 120 # number and then test it against the string, because, for example, this will
444 98         118 # always be false:
445 98         148 #
446             # 9 eq "3e2"
447             #
448 98 100       192 # Instead, we are forced to test the coerced value against a BigFloat, which
449 1         3 # is capable of holding the number.
450             #-----------------------------------------------------------------------------
451             if ($value =~ /[eE]/) {
452 97 100       347 if (Math::BigFloat->new($value)->beq(0 + $value)) {
453 84 100       219 return 0 + $value;
454 77 100       205 }
455             }
456              
457 73 100       437 return Math::BigFloat->new($value);
458 38         134 }
459              
460             my $self = shift;
461             my $token = shift;
462             my $value = $token->{value};
463              
464             # Caller-defined inflation routine
465             if ($self->{inflate_integer}) {
466             return $self->{inflate_integer}->($value);
467             }
468              
469             # Hex
470             if ($value =~ /^0x/) {
471             no warnings 'portable';
472 35 100       108 my $hex = hex $value;
473 25 50       104 my $big = Math::BigInt->new($value);
474 25         49021 return $big->beq($hex) ? $hex : $big;
475             }
476              
477             # Octal
478 10         52 if ($value =~ /^0o/) {
479             no warnings 'portable';
480             $value =~ s/^0o/0/;
481             my $oct = oct $value;
482 411     411 0 578 my $big = Math::BigInt->from_oct($value);
483 411         492 return $big->beq($oct) ? $oct : $big;
484 411         659 }
485              
486             # Binary
487 411 100       798 if ($value =~ /^0b/) {
488 1         4 no warnings 'portable';
489             my $bin = oct $value; # oct handles 0b as binary
490             my $big = Math::BigInt->new($value);
491             return $big->beq($bin) ? $bin : $big;
492 410 100       933 }
493 286     286   2692  
  286         578  
  286         25114  
494 15         40 # Not a bignum
495 15         64 if (0 + $value eq $value) {
496 15 50       37933 return 0 + $value;
497             }
498              
499             return Math::BigInt->new($value);
500 395 100       862 }
501 286     286   1890  
  286         563  
  286         26871  
502 11         41 1;
503 11         31  
504 11         51  
505 11 50       35655 =pod
506              
507             =encoding UTF-8
508              
509 384 100       735 =head1 NAME
510 286     286   1775  
  286         603  
  286         27002  
511 10         27 TOML::Tiny::Parser - parser used by TOML::Tiny
512 10         48  
513 10 50       39942 =head1 VERSION
514              
515             version 0.15
516              
517 374 100       984 =head1 AUTHOR
518 368         1115  
519             Jeff Ober <sysread@fastmail.fm>
520              
521 6         56 =head1 COPYRIGHT AND LICENSE
522              
523             This software is copyright (c) 2021 by Jeff Ober.
524              
525             This is free software; you can redistribute it and/or modify it under
526             the same terms as the Perl 5 programming language system itself.
527              
528             =cut