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             package TOML::Tiny::Parser;
2             # ABSTRACT: parser used by TOML::Tiny
3             $TOML::Tiny::Parser::VERSION = '0.14';
4 285     285   215429 use utf8;
  285         631  
  285         2027  
5 285     285   9214 use strict;
  285         592  
  285         5218  
6 285     285   1301 use warnings;
  285         522  
  285         7480  
7 285     285   1396 no warnings qw(experimental);
  285         562  
  285         8387  
8 285     285   3342 use v5.18;
  285         1165  
9              
10 285     285   1912 use Carp;
  285         731  
  285         20564  
11 285     285   130628 use Data::Dumper;
  285         1230673  
  285         19405  
12 285     285   2329 use Encode qw(decode FB_CROAK);
  285         665  
  285         16980  
13 285     285   131578 use TOML::Tiny::Util qw(is_strict_array);
  285         863  
  285         19677  
14 285     285   2258 use TOML::Tiny::Grammar;
  285         614  
  285         954263  
15              
16             require Math::BigFloat;
17             require Math::BigInt;
18             require TOML::Tiny::Tokenizer;
19              
20             our $TRUE = 1;
21             our $FALSE = 0;
22              
23             eval{
24             require Types::Serialiser;
25             $TRUE = Types::Serialiser::true();
26             $FALSE = Types::Serialiser::false();
27             };
28              
29             sub new {
30 430     430 0 496765 my ($class, %param) = @_;
31             bless{
32             inflate_integer => $param{inflate_integer},
33             inflate_float => $param{inflate_float},
34 53     53   276 inflate_datetime => $param{inflate_datetime} || sub{ shift },
35 35 100   35   208 inflate_boolean => $param{inflate_boolean} || sub{ shift eq 'true' ? $TRUE : $FALSE },
36             strict => $param{strict},
37 430   100     9314 }, $class;
      100        
38             }
39              
40             sub next_token {
41 6358     6358 0 10305 my $self = shift;
42 6358   66     22531 my $token = $self->{tokenizer} && $self->{tokenizer}->next_token;
43 6244         17780 return $token;
44             }
45              
46             sub parse {
47 430     430 0 1421 my ($self, $toml) = @_;
48              
49 430 100       2801 if ($self->{strict}) {
50 185         1274 $toml = decode('UTF-8', "$toml", FB_CROAK);
51             }
52              
53 424         16352 $self->{tokenizer} = TOML::Tiny::Tokenizer->new(source => $toml);
54 424         1888 $self->{keys} = [];
55 424         1644 $self->{root} = {};
56 424         1095 $self->{tables} = {}; # "seen" hash of explicitly defined table names (e.g. [foo])
57 424         1248 $self->{arrays} = {}; # "seen" hash of explicitly defined static arrays (e.g. foo=[])
58 424         992 $self->{array_tables} = {}; # "seen" hash of explicitly defined arrays of tables (e.g. [[foo]])
59              
60 424         1989 $self->parse_table;
61 245         675 my $result = $self->{root};
62              
63 245         1623 delete $self->{tokenizer};
64 245         718 delete $self->{keys};
65 245         480 delete $self->{root};
66 245         767 delete $self->{tables};
67 245         490 delete $self->{arrays};
68 245         492 delete $self->{array_tables};
69              
70 245         958 return $result;
71             }
72              
73             sub parse_error {
74 60     60 0 305 my ($self, $token, $msg) = @_;
75 60 100       267 my $line = $token ? $token->{line} : 'EOF';
76 60 50       269 if ($ENV{TOML_TINY_DEBUG}) {
77 0         0 my $root = Dumper($self->{root});
78 0         0 my $tok = Dumper($token);
79 0         0 my $src = substr $self->{tokenizer}{source}, $self->{tokenizer}{position}, 30;
80              
81 0         0 confess qq{
82             toml parse error at line $line:
83             $msg
84              
85             Current token:
86             $tok
87              
88             Parse state:
89             $root
90              
91             Source near location of error:
92             ...
93             $src
94             ...
95              
96             };
97             } else {
98 60         777 die "toml parse error at line $line: $msg\n";
99             }
100             }
101              
102             sub expect_type {
103 3551     3551 0 7781 my ($self, $token, $expected) = @_;
104 3551 100       7644 my $actual = $token ? $token->{type} : 'EOF';
105 3551 100       68662 $self->parse_error($token, "expected $expected, but found $actual")
106             unless $actual =~ /$expected/;
107             }
108              
109              
110             sub current_key {
111 891     891 0 1483 my $self = shift;
112 891         2029 my @keys = $self->get_keys;
113 891         2352 my $key = join '.', map{ qq{"$_"} } @keys;
  735         2540  
114 891         3582 return $key;
115             }
116              
117             sub push_keys {
118 1710     1710 0 3448 my ($self, $token) = @_;
119 1710         2561 push @{ $self->{keys} }, $token->{value};
  1710         4617  
120             }
121              
122             sub pop_keys {
123 1220     1220 0 1884 my $self = shift;
124 1220         1688 pop @{ $self->{keys} };
  1220         2741  
125             }
126              
127             sub get_keys {
128 2564     2564 0 3716 my $self = shift;
129 2564         3518 return map{ @$_ } @{ $self->{keys} };
  3664         10384  
  2564         5670  
130             }
131              
132             sub set_key {
133 936     936 0 2382 my ($self, $token) = @_;
134 936         2330 my @keys = $self->get_keys;
135 936         2083 my $key = pop @keys;
136 936         2642 my $node = $self->scan_to_key(\@keys);
137              
138 935 100 100     4333 if ($key && exists $node->{$key}) {
139 2         10 $self->parse_error($token, 'duplicate key: ' . $self->current_key);
140             }
141              
142 933         2674 $node->{$key} = $self->parse_value($token);
143             }
144              
145             sub declare_key {
146 889     889 0 2035 my ($self, $token) = @_;
147 889   100     2266 my $key = $self->current_key || return;
148              
149 467         1164 for ($token->{type}) {
150 467         1002 when ('inline_array') {
151             $self->parse_error($token, "duplicate key: $key")
152 162 50       451 if exists $self->{array_tables}{$key};
153              
154 162         670 $self->{arrays}{$key} = 1;
155             }
156              
157 305         578 when ('array_table') {
158 74 100       287 if (exists $self->{arrays}{$key}) {
159 1         6 $self->parse_error($token, "duplicate key: $key");
160             }
161              
162 73         290 $self->{array_tables}{$key} = 1;
163             }
164              
165 231         414 when ('table') {
166             $self->parse_error($token, "duplicate key: $key")
167             if exists $self->{arrays}{$key}
168 231 100 66     1256 || exists $self->{array_tables}{$key};
169              
170 229 100       646 if (exists $self->{tables}{$key}) {
171             # Tables cannot be redefined, *except* when doing so within a goddamn
172             # table array. Gawd I hate TOML.
173 9         17 my $in_a_stupid_table_array = 0;
174 9         31 my $node = $self->{root};
175              
176 9         23 for my $key ($self->get_keys) {
177 9 100 66     75 if (exists $node->{$key} && ref($node->{$key}) eq 'ARRAY') {
178 8         13 $in_a_stupid_table_array = 1;
179 8         17 last;
180             } else {
181 1         3 $node = $node->{$key};
182             }
183             }
184              
185 9 100       37 unless ($in_a_stupid_table_array) {
186 1         5 $self->parse_error($token, "duplicate key: $key");
187             }
188             }
189             else {
190 220         846 $self->{tables}{$key} = 1;
191             }
192             }
193             }
194             }
195              
196             sub scan_to_key {
197 1664     1664 0 2653 my $self = shift;
198 1664   100     4672 my $keys = shift // [ $self->get_keys ];
199 1664         3128 my $node = $self->{root};
200              
201 1664         3674 for my $key (@$keys) {
202 989 100       2221 if (exists $node->{$key}) {
203 714         1719 for (ref $node->{$key}) {
204 714         2127 $node = $node->{$key} when 'HASH';
205 145         520 $node = $node->{$key}[-1] when 'ARRAY';
206 3         6 default{
207 3         12 my $full_key = join '.', @$keys;
208 3         38 die "$full_key is already defined\n";
209             }
210             }
211             }
212             else {
213 275         831 $node = $node->{$key} = {};
214             }
215             }
216              
217 1661         3050 return $node;
218             }
219              
220             sub parse_table {
221 657     657 0 1367 my $self = shift;
222 657   100     2734 my $token = shift // $self->next_token // return; # may be undef on first token in empty document
      100        
223              
224 655         2651 $self->expect_type($token, 'table');
225 655         2691 $self->push_keys($token);
226 655         2355 $self->scan_to_key;
227              
228 653         2380 $self->declare_key($token);
229              
230 650         1864 TOKEN: while (my $token = $self->next_token) {
231 1483         3329 for ($token->{type}) {
232 1483         4430 next TOKEN when 'EOL';
233              
234 1175         2166 when ('key') {
235 902         2235 $self->expect_type($self->next_token, 'assign');
236 902         3508 $self->push_keys($token);
237 902         2400 $self->set_key($self->next_token);
238 834         129959 $self->pop_keys;
239              
240 834 100       1950 if (my $eol = $self->next_token) {
241 642         1618 $self->expect_type($eol, 'EOL');
242             } else {
243 135         544 return;
244             }
245             }
246              
247 273         570 when ('array_table') {
248 35         137 $self->pop_keys;
249 35         164 @_ = ($self, $token);
250 35         203 goto \&parse_array_table;
251             }
252              
253 238         461 when ('table') {
254 218         570 $self->pop_keys;
255 218         546 @_ = ($self, $token);
256 218         967 goto \&parse_table;
257             }
258              
259 20         42 default{
260 20         118 $self->parse_error($token, "expected key-value pair, table, or array of tables but got $_");
261             }
262             }
263             }
264             }
265              
266             sub parse_array_table {
267 74     74 0 142 my $self = shift;
268 74   33     181 my $token = shift // $self->next_token;
269 74         291 $self->expect_type($token, 'array_table');
270 74         348 $self->push_keys($token);
271              
272 74         204 $self->declare_key($token);
273              
274 73         181 my @keys = $self->get_keys;
275 73         136 my $key = pop @keys;
276 73         265 my $node = $self->scan_to_key(\@keys);
277 73   100     351 $node->{$key} //= [];
278 73         117 push @{ $node->{$key} }, {};
  73         218  
279              
280 71         189 TOKEN: while (my $token = $self->next_token) {
281 262         496 for ($token->{type}) {
282 262         725 next TOKEN when 'EOL';
283              
284 133         216 when ('key') {
285 79         172 $self->expect_type($self->next_token, 'assign');
286 79         267 $self->push_keys($token);
287 79         205 $self->set_key($self->next_token);
288 79         192 $self->pop_keys;
289             }
290              
291 54         118 when ('array_table') {
292 39         107 $self->pop_keys;
293 39         105 @_ = ($self, $token);
294 39         167 goto \&parse_array_table;
295             }
296              
297 15         36 when ('table') {
298 15         59 $self->pop_keys;
299 15         46 @_ = ($self, $token);
300 15         68 goto \&parse_table;
301             }
302              
303 0         0 default{
304 0         0 $self->parse_error($token, "expected key-value pair, table, or array of tables but got $_");
305             }
306             }
307             }
308             }
309              
310             sub parse_key {
311 0     0 0 0 my $self = shift;
312 0   0     0 my $token = shift // $self->next_token;
313 0         0 $self->expect_type($token, 'key');
314 0         0 return $token->{value};
315             }
316              
317             sub parse_value {
318 1314     1314 0 2123 my $self = shift;
319 1314         1920 my $token = shift;
320              
321 1314         2763 for ($token->{type}) {
322 1314         4098 return $token->{value} when 'string';
323 862         1598 return $self->inflate_float($token) when 'float';
324 764         1996 return $self->inflate_integer($token) when 'integer';
325 353         663 return $self->{inflate_boolean}->($token->{value}) when 'bool';
326 316         635 return $self->parse_datetime($token) when 'datetime';
327 262         656 return $self->parse_inline_table($token) when 'inline_table';
328 166         563 return $self->parse_array($token) when 'inline_array';
329              
330 4         7 default{
331 4         21 $self->parse_error($token, "value expected (bool, number, string, datetime, inline array, inline table), but found $_");
332             }
333             }
334             }
335              
336             #-------------------------------------------------------------------------------
337             # TOML permits a space instead of a T, which RFC3339 does not allow. TOML (at
338             # least, according to BurntSushi/toml-tests) allows z instead of Z, which
339             # RFC3339 also does not permit. We will be flexible and allow them both, but
340             # fix them up. TOML also specifies millisecond precision. If fractional seconds
341             # are specified. Whatever.
342             #-------------------------------------------------------------------------------
343             sub parse_datetime {
344 54     54 0 95 my $self = shift;
345 54         119 my $token = shift;
346 54         109 my $value = $token->{value};
347              
348             # Normalize
349 54         193 $value =~ tr/z/Z/;
350 54         145 $value =~ tr/ /T/;
351 54         172 $value =~ s/t/T/;
352 54         1716 $value =~ s/(\.\d+)($TimeOffset)$/sprintf(".%06d%s", $1 * 1000000, $2)/e;
  8         133  
353              
354 54         197 return $self->{inflate_datetime}->($value);
355             }
356              
357             sub parse_array {
358 162     162 0 273 my $self = shift;
359 162         285 my $token = shift;
360              
361 162         540 $self->declare_key($token);
362              
363 162         254 my @array;
364 162         265 my $expect = 'EOL|inline_array_close|string|float|integer|bool|datetime|inline_table|inline_array';
365              
366 162         261 TOKEN: while (1) {
367 939         2027 my $token = $self->next_token;
368 936         2380 $self->expect_type($token, $expect);
369              
370 933         3088 for ($token->{type}) {
371 933         2167 when ('comma') {
372 218         393 $expect = 'EOL|inline_array_close|string|float|integer|bool|datetime|inline_table|inline_array';
373 218         492 next TOKEN;
374             }
375              
376 715         1314 next TOKEN when 'EOL';
377 461         835 last TOKEN when 'inline_array_close';
378              
379 307         507 default{
380 307         1014 push @array, $self->parse_value($token);
381 305         877 $expect = 'comma|EOL|inline_array_close';
382             }
383             }
384             }
385              
386 154         687 return \@array;
387             }
388              
389             sub parse_inline_table {
390 96     96 0 201 my $self = shift;
391 96         165 my $token = shift;
392              
393 96         168 my $table = {};
394 96         193 my $expect = 'EOL|inline_table_close|key';
395              
396 96         188 TOKEN: while (1) {
397 189         457 my $token = $self->next_token;
398 189         590 $self->expect_type($token, $expect);
399              
400 181         678 for ($token->{type}) {
401 181         485 when ('comma') {
402             $expect = $self->{strict}
403 19 100       69 ? 'EOL|key'
404             : 'EOL|key|inline_table_close';
405              
406 19         55 next TOKEN;
407             }
408              
409 162         387 when ('key') {
410 74         207 $self->expect_type($self->next_token, 'assign');
411              
412 74         170 my $node = $table;
413 74         131 my @keys = @{ $token->{value} };
  74         210  
414 74         217 my $key = pop @keys;
415              
416 74         183 for (@keys) {
417 16   100     68 $node->{$_} ||= {};
418 16         62 $node = $node->{$_};
419             }
420              
421 74 50       210 if (exists $node->{$key}) {
422 0         0 $self->parse_error($token, 'duplicate key: ' . join('.', map{ qq{"$_"} } @{ $token->{value} }));
  0         0  
  0         0  
423             } else {
424 74         185 $node->{ $key } = $self->parse_value($self->next_token);
425             }
426              
427 74         173 $expect = 'comma|inline_table_close';
428 74         259 next TOKEN;
429             }
430              
431 88         262 last TOKEN when 'inline_table_close';
432              
433 2         5 default{
434 2         13 $self->parse_error($token, "inline table expected key-value pair, but found $_");
435             }
436             }
437             }
438              
439 86         314 return $table;
440             }
441              
442             sub inflate_float {
443 98     98 0 145 my $self = shift;
444 98         149 my $token = shift;
445 98         189 my $value = $token->{value};
446              
447             # Caller-defined inflation routine
448 98 100       231 if ($self->{inflate_float}) {
449 1         4 return $self->{inflate_float}->($value);
450             }
451              
452 97 100       478 return 'NaN' if $value =~ /^[-+]?nan$/i;
453 84 100       272 return 'inf' if $value =~ /^\+?inf$/i;
454 77 100       246 return '-inf' if $value =~ /^-inf$/i;
455              
456             # Not a bignum
457 73 100       614 if (0 + $value eq $value) {
458 38         174 return 0 + $value;
459             }
460              
461             #-----------------------------------------------------------------------------
462             # Scientific notation is a hairier situation. In order to determine whether a
463             # value will fit inside a perl svnv, we can't just coerce the value to a
464             # number and then test it against the string, because, for example, this will
465             # always be false:
466             #
467             # 9 eq "3e2"
468             #
469             # Instead, we are forced to test the coerced value against a BigFloat, which
470             # is capable of holding the number.
471             #-----------------------------------------------------------------------------
472 35 100       134 if ($value =~ /[eE]/) {
473 25 50       164 if (Math::BigFloat->new($value)->beq(0 + $value)) {
474 25         63967 return 0 + $value;
475             }
476             }
477              
478 10         71 return Math::BigFloat->new($value);
479             }
480              
481             sub inflate_integer {
482 411     411 0 671 my $self = shift;
483 411         619 my $token = shift;
484 411         855 my $value = $token->{value};
485              
486             # Caller-defined inflation routine
487 411 100       1112 if ($self->{inflate_integer}) {
488 1         4 return $self->{inflate_integer}->($value);
489             }
490              
491             # Hex
492 410 100       1257 if ($value =~ /^0x/) {
493 285     285   3533 no warnings 'portable';
  285         782  
  285         33823  
494 15         56 my $hex = hex $value;
495 15         81 my $big = Math::BigInt->new($value);
496 15 50       49484 return $big->beq($hex) ? $hex : $big;
497             }
498              
499             # Octal
500 395 100       1303 if ($value =~ /^0o/) {
501 285     285   2382 no warnings 'portable';
  285         734  
  285         36548  
502 11         59 $value =~ s/^0o/0/;
503 11         41 my $oct = oct $value;
504 11         69 my $big = Math::BigInt->from_oct($value);
505 11 50       47929 return $big->beq($oct) ? $oct : $big;
506             }
507              
508             # Binary
509 384 100       964 if ($value =~ /^0b/) {
510 285     285   2295 no warnings 'portable';
  285         749  
  285         37844  
511 10         49 my $bin = oct $value; # oct handles 0b as binary
512 10         66 my $big = Math::BigInt->new($value);
513 10 50       48339 return $big->beq($bin) ? $bin : $big;
514             }
515              
516             # Not a bignum
517 374 100       1281 if (0 + $value eq $value) {
518 368         1425 return 0 + $value;
519             }
520              
521 6         74 return Math::BigInt->new($value);
522             }
523              
524             1;
525              
526             __END__
527              
528             =pod
529              
530             =encoding UTF-8
531              
532             =head1 NAME
533              
534             TOML::Tiny::Parser - parser used by TOML::Tiny
535              
536             =head1 VERSION
537              
538             version 0.14
539              
540             =head1 AUTHOR
541              
542             Jeff Ober <sysread@fastmail.fm>
543              
544             =head1 COPYRIGHT AND LICENSE
545              
546             This software is copyright (c) 2021 by Jeff Ober.
547              
548             This is free software; you can redistribute it and/or modify it under
549             the same terms as the Perl 5 programming language system itself.
550              
551             =cut