1 package TOML::Tiny::Parser;
2 # ABSTRACT: parser used by TOML::Tiny
6 no warnings qw(experimental);
11 use TOML::Tiny::Grammar;
12 use TOML::Tiny::Tokenizer;
13 use TOML::Tiny::Util qw(is_strict_array);
19 require Types::Serialiser;
20 $TRUE = Types::Serialiser::true();
21 $FALSE = Types::Serialiser::false();
25 my ($class, %param) = @_;
27 inflate_datetime => $param{inflate_datetime} || sub{ shift },
28 inflate_boolean => $param{inflate_boolean} || sub{ shift eq 'true' ? $TRUE : $FALSE },
29 strict_arrays => $param{strict_arrays},
34 $_[0]->{tokenizer} && $_[0]->{tokenizer}->next_token;
38 my ($self, $toml) = @_;
40 $self->{tokenizer} = TOML::Tiny::Tokenizer->new(source => $toml);
43 $self->{tables} = {}; # "seen" hash of explicitly defined table names
46 my $result = $self->{root};
48 delete $self->{tokenizer};
51 delete $self->{tables};
57 my ($self, $token, $msg) = @_;
58 my $line = $token ? $token->{line} : 'EOF';
59 if ($ENV{TOML_TINY_DEBUG}) {
60 my $root = Dumper($self->{root});
61 my $tok = Dumper($token);
62 my $src = substr $self->{tokenizer}{source}, $self->{tokenizer}{position}, 30;
65 toml parse error at line $line:
74 Source near location of error:
81 die "toml parse error at line $line: $msg\n";
86 my ($self, $token, $expected) = @_;
87 my $actual = $token->{type};
88 $self->parse_error($token, "expected $expected, but found $actual")
89 unless $actual eq $expected;
94 my ($self, $token) = @_;
95 push @{ $self->{keys} }, $token->{value};
100 pop @{ $self->{keys} };
105 return map{ @$_ } @{ $self->{keys} };
110 my $value = shift // $self->parse_value;
111 my @keys = $self->get_keys;
113 my $node = $self->scan_to_key(\@keys);
114 $self->parse_error(undef, 'duplicate key: '.join('.', @keys, $key))
115 if exists $node->{$key};
116 $node->{$key} = $value;
121 my $keys = shift // [ $self->get_keys ];
122 my $node = $self->{root};
124 for my $key (@$keys) {
125 if (exists $node->{$key}) {
126 for (ref $node->{$key}) {
127 $node = $node->{$key} when 'HASH';
128 $node = $node->{$key}[-1] when 'ARRAY';
130 my $full_key = join '.', @$keys;
131 die "$full_key is already defined\n";
136 $node = $node->{$key} = {};
146 my $token = shift // $self->next_token // return; # may be undef on first token in empty document
147 $self->expect_type($token, 'table');
148 $self->push_keys($token);
151 my @keys = $self->get_keys;
152 my $key = join '.', @keys;
153 if (exists $self->{tables}{$key}) {
154 # Tables cannot be redefined, *except* when doing so within a goddamn table
155 # array. Gawd I hate TOML.
156 my $in_a_stupid_table_array = 0;
157 my $node = $self->{root};
158 for my $key (@keys) {
159 if (exists $node->{$key} && ref($node->{$key}) eq 'ARRAY') {
160 $in_a_stupid_table_array = 1;
163 $node = $node->{$key};
167 $self->parse_error($token, "table $key is already defined")
168 unless $in_a_stupid_table_array;
170 $self->{tables}{$key} = 1;
173 TOKEN: while (my $token = $self->next_token) {
174 for ($token->{type}) {
175 next TOKEN when 'EOL';
178 $self->expect_type($self->next_token, 'assign');
179 $self->push_keys($token);
183 if (my $eol = $self->next_token) {
184 $self->expect_type($eol, 'EOL');
190 when ('array_table') {
192 @_ = ($self, $token);
193 goto \&parse_array_table;
198 @_ = ($self, $token);
203 $self->parse_error($token, "expected key-value pair, table, or array of tables but got $_");
209 sub parse_array_table {
211 my $token = shift // $self->next_token;
212 $self->expect_type($token, 'array_table');
213 $self->push_keys($token);
215 my @keys = $self->get_keys;
217 my $node = $self->scan_to_key(\@keys);
218 $node->{$key} //= [];
219 push @{ $node->{$key} }, {};
221 TOKEN: while (my $token = $self->next_token) {
222 for ($token->{type}) {
223 next TOKEN when /EOL/;
226 $self->expect_type($self->next_token, 'assign');
227 $self->push_keys($token);
232 when (/array_table/) {
234 @_ = ($self, $token);
235 goto \&parse_array_table;
240 @_ = ($self, $token);
245 $self->parse_error($token, "expected key-value pair, table, or array of tables but got $_");
253 my $token = shift // $self->next_token;
254 $self->expect_type($token, 'key');
255 return $token->{value};
260 my $token = shift // $self->next_token;
262 for ($token->{type}) {
265 return $token->{value} + 0;
269 for (my $n = $token->{value}) {
273 $n =~ s/^0o/0/; # convert to perl's octal format
291 return $token->{value} when /string/;
292 return $self->{inflate_boolean}->($token->{value}) when /bool/;
293 return $self->{inflate_datetime}->($token->{value}) when /datetime/;
294 return $self->parse_inline_table when /inline_table/;
295 return $self->parse_inline_array when /inline_array/;
298 $self->parse_error($token, "value expected (bool, number, string, datetime, inline array, inline table), but found $_");
303 sub parse_inline_array {
307 TOKEN: while (my $token = $self->next_token) {
308 for ($token->{type}) {
309 next TOKEN when /comma/;
310 next TOKEN when /EOL/;
311 last TOKEN when /inline_array_close/;
314 push @array, $self->parse_value($token);
319 if (@array > 1 && $self->{strict_arrays}) {
320 my ($ok, $err) = is_strict_array(\@array);
321 $self->parse_error(undef, $err)
328 sub parse_inline_table {
332 TOKEN: while (my $token = $self->next_token) {
333 for ($token->{type}) {
334 next TOKEN when /comma/;
335 last TOKEN when /inline_table_close/;
338 $self->expect_type($self->next_token, 'assign');
339 my $key = $token->{value}[0];
340 $table->{ $key } = $self->parse_value;
344 $self->parse_error($token, "inline table expected key-value pair, but found $_");