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_integer => $param{inflate_integer} || sub{ shift },
28 inflate_float => $param{inflate_float} || sub{ shift },
29 inflate_number => $param{inflate_number} || sub{ shift },
30 inflate_datetime => $param{inflate_datetime} || sub{ shift },
31 inflate_boolean => $param{inflate_boolean} || sub{ shift eq 'true' ? $TRUE : $FALSE },
32 strict_arrays => $param{strict_arrays},
37 $_[0]->{tokenizer} && $_[0]->{tokenizer}->next_token;
41 my ($self, $toml) = @_;
43 $self->{tokenizer} = TOML::Tiny::Tokenizer->new(source => $toml);
46 $self->{tables} = {}; # "seen" hash of explicitly defined table names
49 my $result = $self->{root};
51 delete $self->{tokenizer};
54 delete $self->{tables};
60 my ($self, $token, $msg) = @_;
61 my $line = $token ? $token->{line} : 'EOF';
62 if ($ENV{TOML_TINY_DEBUG}) {
63 my $root = Dumper($self->{root});
64 my $tok = Dumper($token);
65 my $src = substr $self->{tokenizer}{source}, $self->{tokenizer}{position}, 30;
68 toml parse error at line $line:
77 Source near location of error:
84 die "toml parse error at line $line: $msg\n";
89 my ($self, $token, $expected) = @_;
90 my $actual = $token->{type};
91 $self->parse_error($token, "expected $expected, but found $actual")
92 unless $actual eq $expected;
97 my ($self, $token) = @_;
98 push @{ $self->{keys} }, $token->{value};
103 pop @{ $self->{keys} };
108 return map{ @$_ } @{ $self->{keys} };
113 my $value = shift // $self->parse_value;
114 my @keys = $self->get_keys;
116 my $node = $self->scan_to_key(\@keys);
117 $self->parse_error(undef, 'duplicate key: '.join('.', @keys, $key))
118 if exists $node->{$key};
119 $node->{$key} = $value;
124 my $keys = shift // [ $self->get_keys ];
125 my $node = $self->{root};
127 for my $key (@$keys) {
128 if (exists $node->{$key}) {
129 for (ref $node->{$key}) {
130 $node = $node->{$key} when 'HASH';
131 $node = $node->{$key}[-1] when 'ARRAY';
133 my $full_key = join '.', @$keys;
134 die "$full_key is already defined\n";
139 $node = $node->{$key} = {};
149 my $token = shift // $self->next_token // return; # may be undef on first token in empty document
150 $self->expect_type($token, 'table');
151 $self->push_keys($token);
154 my @keys = $self->get_keys;
155 my $key = join '.', @keys;
156 if (exists $self->{tables}{$key}) {
157 # Tables cannot be redefined, *except* when doing so within a goddamn table
158 # array. Gawd I hate TOML.
159 my $in_a_stupid_table_array = 0;
160 my $node = $self->{root};
161 for my $key (@keys) {
162 if (exists $node->{$key} && ref($node->{$key}) eq 'ARRAY') {
163 $in_a_stupid_table_array = 1;
166 $node = $node->{$key};
170 $self->parse_error($token, "table $key is already defined")
171 unless $in_a_stupid_table_array;
173 $self->{tables}{$key} = 1;
176 TOKEN: while (my $token = $self->next_token) {
177 for ($token->{type}) {
178 next TOKEN when 'EOL';
181 $self->expect_type($self->next_token, 'assign');
182 $self->push_keys($token);
186 if (my $eol = $self->next_token) {
187 $self->expect_type($eol, 'EOL');
193 when ('array_table') {
195 @_ = ($self, $token);
196 goto \&parse_array_table;
201 @_ = ($self, $token);
206 $self->parse_error($token, "expected key-value pair, table, or array of tables but got $_");
212 sub parse_array_table {
214 my $token = shift // $self->next_token;
215 $self->expect_type($token, 'array_table');
216 $self->push_keys($token);
218 my @keys = $self->get_keys;
220 my $node = $self->scan_to_key(\@keys);
221 $node->{$key} //= [];
222 push @{ $node->{$key} }, {};
224 TOKEN: while (my $token = $self->next_token) {
225 for ($token->{type}) {
226 next TOKEN when 'EOL';
229 $self->expect_type($self->next_token, 'assign');
230 $self->push_keys($token);
235 when ('array_table') {
237 @_ = ($self, $token);
238 goto \&parse_array_table;
243 @_ = ($self, $token);
248 $self->parse_error($token, "expected key-value pair, table, or array of tables but got $_");
256 my $token = shift // $self->next_token;
257 $self->expect_type($token, 'key');
258 return $token->{value};
263 my $token = shift // $self->next_token;
265 for ($token->{type}) {
266 return $token->{value} when 'string';
267 return $self->{inflate_float}->($token->{value}) when 'float';
268 return $self->{inflate_integer}->($token->{value}) when 'integer';
269 return $self->{inflate_boolean}->($token->{value}) when 'bool';
270 return $self->{inflate_datetime}->($token->{value}) when 'datetime';
271 return $self->parse_inline_table when 'inline_table';
272 return $self->parse_inline_array when 'inline_array';
275 $self->parse_error($token, "value expected (bool, number, string, datetime, inline array, inline table), but found $_");
280 sub parse_inline_array {
284 TOKEN: while (my $token = $self->next_token) {
285 for ($token->{type}) {
286 next TOKEN when 'comma';
287 next TOKEN when 'EOL';
288 last TOKEN when 'inline_array_close';
291 push @array, $self->parse_value($token);
296 if (@array > 1 && $self->{strict_arrays}) {
297 my ($ok, $err) = is_strict_array(\@array);
298 $self->parse_error(undef, $err)
305 sub parse_inline_table {
309 TOKEN: while (my $token = $self->next_token) {
310 for ($token->{type}) {
311 next TOKEN when /comma/;
312 last TOKEN when /inline_table_close/;
315 $self->expect_type($self->next_token, 'assign');
316 my $key = $token->{value}[0];
317 $table->{ $key } = $self->parse_value;
321 $self->parse_error($token, "inline table expected key-value pair, but found $_");