--- /dev/null
+skip_tags: true
+
+build: off
+
+cache:
+ - C:\strawberry
+
+platform:
+ - x86
+ - x64
+
+install:
+ - if not exist "C:\strawberry" cinst strawberryperl
+ - set PATH=C:\strawberry\perl\bin;C:\strawberry\perl\site\bin;C:\strawberry\c\bin;%PATH%
+ - cd C:\projects\%APPVEYOR_PROJECT_NAME%
+ - cpanm -n Dist::Zilla
+ - dzil authordeps --missing | cpanm -nq
+ - dzil listdeps --missing | cpanm -nq
+
+test_script:
+ - dzil test
--- /dev/null
+.build
+scratch.pl
--- /dev/null
+language: perl
+
+sudo: false
+
+notifications:
+ email: false
+
+cache:
+ directories:
+ - ~/perl5
+
+perl:
+ - "5.30"
+ - "5.28"
+ - "5.26"
+ - "5.24"
+ - "5.22"
+ - "5.20"
+ - "5.18"
+ - "5.16"
+ - "5.14"
+ - "5.12"
+ - "5.10"
+
+before_install:
+ - eval $(curl https://travis-perl.github.io/init) --auto --always-upgrade-modules
--- /dev/null
+{{$NEXT}}
+-Initial release
--- /dev/null
+=pod
+
+=encoding UTF-8
+
+=head1 NAME
+
+TOML::Tiny - a minimal TOML parser and serializer
+
+=head1 VERSION
+
+version 0.01
+
+=head1 AUTHOR
+
+Jeff Ober <sysread@fastmail.fm>
+
+=head1 COPYRIGHT AND LICENSE
+
+This software is copyright (c) 2020 by Jeff Ober.
+
+This is free software; you can redistribute it and/or modify it under
+the same terms as the Perl 5 programming language system itself.
+
+=cut
--- /dev/null
+* Serialization
+* Line numbers
+* Optimization
+* More complete unit tests
--- /dev/null
+requires 'perl' => '>= 5.014';
+requires 'JSON::PP' => '0';
+
+recommends 'Types::Serialiser' => 0;
+
+on test => sub{
+ requires 'Test::Pod' => '0';
+ requires 'Test2::V0' => '0';
+};
--- /dev/null
+name = TOML-Tiny
+author = Jeff Ober <sysread@fastmail.fm>
+license = Perl_5
+copyright_holder = Jeff Ober
+
+[@Author::BLUEFEET]
+[PodWeaver]
--- /dev/null
+package TOML::Tiny;
+# ABSTRACT: a minimal TOML parser and serializer
+
+use strict;
+use warnings;
+
+use TOML::Tiny::Grammar;
+
+our $GRAMMAR_V5 = $TOML::Tiny::Grammar::GRAMMAR_V5;
+
+1;
--- /dev/null
+package TOML::Tiny::Grammar;
+
+use strict;
+use warnings;
+
+our $GRAMMAR_V5 = qr{
+
+(?(DEFINE)
+ #-----------------------------------------------------------------------------
+ # Misc
+ #-----------------------------------------------------------------------------
+ (?<Value>
+ (?&Boolean)
+ | (?&DateTime)
+ | (?&Float)
+ | (?&Integer)
+ | (?&String)
+ | (?&Array)
+ | (?&InlineTable)
+ )
+
+ (?<NLSeq> (?: \x0A) | (?: \x0D \x0A))
+ (?<NL> (?&NLSeq) | (?&Comment))
+
+ (?<WSChar> [ \x20 \x09 ]) # (space, tab)
+ (?<WS> (?&WSChar)*)
+
+ (?<Comment> \x23 .* (?&NLSeq))
+
+ #-----------------------------------------------------------------------------
+ # Array of tables
+ #-----------------------------------------------------------------------------
+ (?<ArrayOfTables>
+ (?m)
+ (?s)
+
+ \[\[ (?&Key) \]\] \n
+ (?:
+ (?: (?&KeyValuePair) \n )
+ | (?&ArrayOfTables)
+ | (?&Table)
+ )*
+
+ (?-s)
+ (?-m)
+ )
+
+ #-----------------------------------------------------------------------------
+ # Table
+ #-----------------------------------------------------------------------------
+ (?<KeyValuePair> (?&Key) (?&WS) = (?&WS) (?&Value))
+ (?<KeyValuePairDecl> (?&Key) (?&WS) = (?&WS) (?&Value) (?&WS) (?&NL))
+
+ (?<InlineTableSep>
+ (?&WS)
+ [,]
+ (?&WS)
+ (?&NLSeq)?
+ (?&WS)
+ )
+
+ (?<KeyValuePairList>
+ (?&KeyValuePair) (?&InlineTableSep) (?&KeyValuePairList)?
+ | (?&KeyValuePair)
+ )
+
+ (?<InlineTable>
+ (?s)
+
+ {
+ (?&WS) (?&NLSeq)? (?&WS)
+
+ (?&KeyValuePairList)
+
+ (?&WS) (?&NLSeq)? (?&WS)
+ }
+
+ (?-s)
+ )
+
+ (?<TableDecl>
+ \[ (?&Key) \] \n
+ )
+
+ (?<Table>
+ (?&TableDecl)
+ (?:
+ (?&KeyValuePairDecl)
+ | (?&ArrayOfTables)
+ )*
+ )
+
+ #-----------------------------------------------------------------------------
+ # Array
+ #-----------------------------------------------------------------------------
+ (?<ListSep>
+ (?&WS)
+ [,]
+ (?&WS)
+ (?&NLSeq)?
+ (?&WS)
+ )
+
+ (?<List>
+ (?&Value) (?&ListSep) (?&List)?
+ | (?&Value)
+ )
+
+ (?<Array>
+ \[
+
+ (?&WS) (?&NLSeq)? (?&WS)
+
+ (?&List)
+
+ (?&WS) (?&NLSeq)? (?&WS)
+
+ \]
+ )
+
+ #-----------------------------------------------------------------------------
+ # Key
+ #-----------------------------------------------------------------------------
+ (?<BareKey> [-_a-zA-Z0-9]+)
+ (?<QuotedKey> (?&BasicString) | (?&StringLiteral))
+ (?<DottedKey>
+ (?: (?&BareKey) | (?&QuotedKey) )
+ (?: (?&WS) [.] (?&WS) (?: (?&BareKey) | (?&QuotedKey) ) )+
+ )
+ (?<Key> (?&DottedKey) | (?&BareKey) | (?&QuotedKey) )
+
+ #-----------------------------------------------------------------------------
+ # Boolean
+ #-----------------------------------------------------------------------------
+ (?<Boolean> \b(?:true)|(?:false))\b
+
+ #-----------------------------------------------------------------------------
+ # Integer
+ #-----------------------------------------------------------------------------
+ (?<Dec> [-+]? [_0-9]+)
+ (?<Hex> 0x[_0-9a-fA-F]+)
+ (?<Oct> 0o[_0-7]+)
+ (?<Bin> 0b[_01]+)
+ (?<Integer> (?&Hex) | (?&Oct) | (?&Bin) | (?&Dec))
+
+ #-----------------------------------------------------------------------------
+ # Float
+ #-----------------------------------------------------------------------------
+ (?<Exponent> [eE] (?&Dec))
+ (?<SpecialFloat> [-+]? (?:inf) | (?:nan))
+ (?<Fraction> [.] [_0-9]+)
+
+ (?<Float>
+ (?:
+ (?: (?&Dec) (?&Fraction) (?&Exponent) )
+ | (?: (?&Dec) (?&Exponent) )
+ | (?: (?&Dec) (?&Fraction) )
+ )
+ |
+ (?&SpecialFloat)
+ )
+
+ #-----------------------------------------------------------------------------
+ # String
+ #-----------------------------------------------------------------------------
+ (?<EscapeChar>
+ \\ # leading \
+ (?:
+ [\\/"btnfr] # escapes: \\ \/ \b \t \n \f \r
+ | (?: u \d{4} ) # unicode (4 bytes)
+ | (?: U \d{8} ) # unicode (8 bytes)
+ )
+ )
+
+ (?<StringLiteral>
+ (?: ' ([^']*) ') # single quoted string (no escaped chars allowed)
+ )
+
+ (?<MultiLineStringLiteral>
+ (?m)
+ (?s)
+ ''' # opening triple-quote
+ (.)*? # capture
+ ''' # closing triple-quote
+ (?-s)
+ (?-m)
+ )
+
+ (?<BasicString>
+ (?:
+ " # opening quote
+ (?: # capture escape sequences or any char except " or \
+ (?: (?&EscapeChar) )
+ | [^"\\]
+ )*
+ " # closing quote
+ )
+ )
+
+ (?<MultiLineString>
+ (?s)
+ """ # opening triple-quote
+ ( # capture:
+ (?: (?&EscapeChar) ) # escaped char
+ | .
+ )*?
+ """ # closing triple-quote
+ (?-s)
+ )
+
+ (?<String>
+ (?&MultiLineString)
+ | (?&BasicString)
+ | (?&MultiLineStringLiteral)
+ | (?&StringLiteral)
+ )
+
+ #-----------------------------------------------------------------------------
+ # Dates (RFC 3339)
+ # 1985-04-12T23:20:50.52Z
+ #-----------------------------------------------------------------------------
+ (?<Date> \d{4}-\d{2}-\d{2})
+
+ (?<Offset>
+ (?: [-+] \d{2}:\d{2} )
+ | [Z]
+ )
+
+ (?<SimpleTime>
+ \d{2}:\d{2}:\d{2}
+ (?: [.] \d+ )?
+ )
+
+ (?<Time>
+ (?&SimpleTime)
+ (?&Offset)?
+ )
+
+ (?<DateTime>
+ (?: (?&Date) [T ] (?&Time) )
+ | (?&Date)
+ | (?&Time)
+ )
+)
+
+}x;
+
+1;
--- /dev/null
+package TOML::Tiny::Parser;
+
+use strict;
+use warnings;
+use feature qw(switch);
+no warnings qw(experimental);
+
+use Carp;
+use DDP;
+use Data::Dumper;
+use TOML::Tiny::Tokenizer;
+
+our $TOML = $TOML::Tiny::Grammar::GRAMMAR_V5;
+our $TRUE = 1;
+our $FALSE = 0;
+
+BEGIN{
+ eval{
+ require Types::Serialiser;
+ $TRUE = Types::Serialiser::true();
+ $FALSE = Types::Serialiser::false();
+ };
+}
+
+sub new {
+ my ($class, %param) = @_;
+ return bless{
+ inflate_datetime => $param{inflate_datetime},
+ inflate_boolean => $param{inflate_boolean},
+ }, $class;
+}
+
+sub parse {
+ my $self = shift;
+ my $tokens = TOML::Tiny::Tokenizer::tokenize(@_);
+ my $root = {};
+ my $acc = { root => $root, node => $root };
+ $self->parse_token($_, $acc) for @$tokens;
+ return $root;
+}
+
+sub parse_token {
+ my $self = shift;
+ my $token = shift;
+ my $acc = shift;
+ my $type = shift @$token;
+
+ for ($type) {
+ # Table
+ when ('table') {
+ my $keys = $self->parse_key(shift @$token);
+ $acc->{node} = $self->mkpath($keys, $acc->{root});
+ }
+
+ # Array of tables
+ when ('array-of-tables') {
+ my $keys = $self->parse_key(shift @$token);
+ my $last = pop @$keys;
+ $acc->{node} = $self->mkpath($keys, $acc->{root});
+ $acc->{node}{$last} ||= [];
+ push @{ $acc->{node}{$last} } => $acc->{node} = {};
+ }
+
+ # Key-value pair
+ when ('assignment') {
+ my $keys = $self->parse_key(shift @$token);
+ my $last = pop @$keys;
+ my $value = $self->parse_value(shift @$token);
+ my $node = $self->mkpath($keys, $acc->{node});
+ $node->{$last} = $value;
+ }
+ }
+
+ return $acc;
+}
+
+sub mkpath {
+ my $self = shift;
+ my $keys = shift;
+ my $node = shift;
+
+ for my $key (@$keys) {
+ if (exists $node->{$key}) {
+ for (ref $node->{$key}) {
+ $node = $node->{$key}[-1] when 'ARRAY';
+ $node = $node->{$key} when 'HASH';
+ }
+ }
+ else {
+ $node = $node->{$key} ||= {};
+ }
+ }
+
+ return $node;
+}
+
+sub parse_key {
+ my ($self, $key) = @_;
+ my $type = shift @$key;
+ for ($type) {
+ return $self->dotted_key(shift @$key) when 'dotted-key';
+ return $self->quoted_key(shift @$key) when 'quoted-key';
+ return $self->bare_key(shift @$key) when 'bare-key';
+ }
+}
+
+sub parse_value {
+ my ($self, $token) = @_;
+ my $type = shift @$token;
+
+ for ($type) {
+ return $self->datetime(@$token) when 'datetime';
+ return $self->boolean(@$token) when 'boolean';
+
+ when ('array') {
+ my $contents = shift @$token;
+ return [ map{ $self->parse_value($_) } @$contents ];
+ }
+
+ when ('inline-table') {
+ my $tokens = shift @$token;
+ my $root = {};
+ my $acc = {root => $root, node => $root};
+ $self->parse_token($_, $acc) for @$tokens;
+ return $root;
+ }
+
+ default{
+ return shift @$token;
+ }
+ }
+}
+
+sub bare_key {
+ my ($self, $key) = @_;
+ return [$key];
+}
+
+sub quoted_key {
+ my ($self, $key) = @_;
+ $key =~ s/^"//;
+ $key =~ s/"$//;
+ return [$key];
+}
+
+sub dotted_key {
+ my ($self, $key) = @_;
+ my @parts = split /\./, $key;
+ return \@parts;
+}
+
+sub number {
+ my ($self, $n) = @_;
+ defined $n ? 0 + $n : $n;
+}
+
+sub datetime {
+ my ($self, $dt) = @_;
+
+ if ($self->{inflate_datetime}) {
+ my ($year, $month, $day, $hour, $minute, $second, $fractional, $offset) = $dt =~ qr{
+ (?:
+ (\d\d\d\d) - (\d\d) - (\d\d) # yyyy-mm-dd
+ )?
+
+ (?:
+ [T ]
+ (\d\d) : (\d\d) : (\d\d) # hh:mm:ss.fractional
+ (?:[.] (\d+) )?
+
+ ((?&Offset)?)
+ )?
+
+ $TOML::Tiny::Tokenizer::TOML
+ }x;
+
+ return {
+ original => $dt,
+ year => $self->number($year),
+ month => $self->number($month),
+ day => $self->number($day),
+ hour => $self->number($hour),
+ minute => $self->number($minute),
+ second => $self->number($second),
+ fractional => $self->number($fractional),
+ offset => $offset,
+ };
+ } else {
+ return $dt;
+ }
+}
+
+sub boolean {
+ my ($self, $bool) = @_;
+ if ($self->{inflate_boolean}) {
+ return $TRUE if $bool eq 'true';
+ return $FALSE;
+ } else {
+ return $bool;
+ }
+}
+
+1;
--- /dev/null
+package TOML::Tiny::Tokenizer;
+
+use strict;
+use warnings;
+use feature qw(switch);
+no warnings qw(experimental);
+
+use JSON::PP;
+use TOML::Tiny::Grammar;
+
+our $TOML = $TOML::Tiny::Grammar::GRAMMAR_V5;
+
+sub tokenize {
+ my $toml = shift;
+ my @tokens;
+
+ TOKEN: while ((pos($toml) // 0) < length($toml)) {
+ for ($toml) {
+ when (/\G ((?&Boolean)) $TOML/xgc) {
+ push @tokens, ['boolean', $1];
+ }
+
+ when (/\G ((?&DateTime)) $TOML/xgc) {
+ push @tokens, ['datetime', $1];
+ }
+
+ when (/\G ((?&Float)) $TOML/xgc) {
+ push @tokens, ['float', tokenize_float($1)];
+ }
+
+ when (/\G ((?&Integer)) $TOML/xgc) {
+ push @tokens, ['integer', tokenize_integer($1)];
+ }
+
+ when (/\G ((?&String)) $TOML/xgc) {
+ push @tokens, ['string', tokenize_string($1)];
+ }
+
+ when (/\G ((?&KeyValuePairDecl)) $TOML/xgc) {
+ push @tokens, tokenize_assignment($1);
+ }
+
+ when (/\G ((?&Array)) $TOML/xgc) {
+ push @tokens, tokenize_array($1);
+ }
+
+ when (/\G ((?&InlineTable)) $TOML/xgc) {
+ push @tokens, tokenize_inline_table($1);
+ }
+
+ when (/\G \[ (?&WS) ((?&Key)) (?&WS) \] (?&WS) (?&NL) $TOML/xgc) {
+ push @tokens, ['table', tokenize_key($1)];
+ }
+
+ when (/\G \[\[ (?&WS) ((?&Key)) (?&WS) \]\] (?&WS) (?&NL) $TOML/xgc) {
+ push @tokens, ['array-of-tables', tokenize_key($1)];
+ }
+
+ when (/\G (?: (?&WSChar) | (?&NLSeq) | (?&Comment) )+ $TOML/xgc) {
+ next TOKEN;
+ }
+
+ default{
+ my $prev = JSON::PP->new->pretty->encode($tokens[ scalar(@tokens) - 1 ]);
+ my $substr = substr($toml, pos($toml), 30) // 'undef';
+ die "syntax error at:\n-->$substr\n\nprevious token was: $prev\n";
+ }
+ }
+ }
+
+ return \@tokens;
+}
+
+sub tokenize_inline_table {
+ my $toml = shift;
+ my @items;
+
+ $toml =~ s/^\s*\{\s*//;
+ $toml =~ s/\s*\}\s*$//;
+
+ ITEM: while ((pos($toml) // 0) < length($toml)) {
+ for ($toml) {
+ next ITEM when /\G\s*/gc;
+ next ITEM when /\G\{/gc;
+ next ITEM when /\G\}/gc;
+
+ when (/\G ((?&KeyValuePair)) (?&WS) ,? $TOML/xgc) {
+ push @items, tokenize_assignment($1);
+ }
+
+ default{
+ die "invalid inline table syntax: $toml";
+ }
+ }
+ }
+
+ return ['inline-table', \@items];
+}
+
+sub tokenize_array {
+ my $toml = shift;
+ my @items;
+
+ $toml =~ s/^\s*\[\s*//;
+ $toml =~ s/\s*\]\s*$//;
+
+ ITEM: while ((pos($toml) // 0) < length($toml)) {
+ my $pos = pos($toml) // 0;
+ for ($toml) {
+ when (/\G ((?&Value)) (?&WS) [,]? $TOML/xgc) {
+ push @items, @{ tokenize($1) };
+ }
+
+ next ITEM when /\G\s*/gc;
+ next ITEM when /\G\[/gc;
+ next ITEM when /\G\]/gc;
+
+ default{
+ die "invalid array syntax: $toml";
+ }
+ }
+ }
+
+ return ['array', \@items];
+}
+
+sub tokenize_assignment {
+ my $toml = shift;
+
+ for ($toml) {
+ when (/\G(?&WS) ((?&Key)) (?&WS) = (?&WS) ((?&Value)) (?&NL)? $TOML/xgc) {
+ my $key = tokenize_key($1);
+ my $val = tokenize($2);
+ return ['assignment', $key, @$val];
+ }
+
+ default{
+ die "invalid assignment syntax: $toml";
+ }
+ }
+}
+
+sub tokenize_key {
+ my $toml = shift;
+
+ for ($toml) {
+ return ['dotted-key', $1] when /^ ((?&DottedKey)) $TOML/x;
+ return ['quoted-key', $1] when /^ ((?&QuotedKey)) $TOML/x;
+ return ['bare-key', $1] when /^ ((?&BareKey)) $TOML/x;
+
+ default{
+ die "invalid key: syntax $toml";
+ }
+ }
+}
+
+sub tokenize_string {
+ my $toml = shift;
+ my $str = '';
+
+ for ($toml) {
+ when (/^ ((?&MultiLineString)) $TOML/x) {
+ $str = substr $1, 3, length($1) - 6;
+ $str =~ s/^(?&WS) (?&NL) $TOML//x;
+ }
+
+ when (/^ ((?&BasicString)) $TOML/x) {
+ $str = substr($1, 1, length($1) - 2);
+ }
+
+ when (/^ ((?&MultiLineStringLiteral)) $TOML/x) {
+ $str = substr $1, 3, length($1) - 6;
+ $str =~ s/^(?&WS) (?&NL) $TOML//x;
+ }
+
+ when (/^ ((?&StringLiteral)) $TOML/x) {
+ $str = substr($1, 1, length($1) - 2);
+ }
+
+ default{
+ die "invalid string syntax: $toml";
+ }
+ }
+
+ return ''.$str;
+}
+
+sub tokenize_integer {
+ my $toml = shift;
+
+ for ($toml) {
+ when (/(?&Oct) $TOML/x) {
+ $toml =~ s/^0o/0/; # convert to perl's octal format
+ return oct $toml;
+ }
+
+ when (/(?&Bin) $TOML/x) {
+ return oct $toml;
+ }
+
+ when (/(?&Hex) $TOML/x) {
+ return hex $toml;
+ }
+
+ when (/(?&Dec) $TOML/x) {
+ }
+
+ default{
+ die "invalid datetime syntax: $toml";
+ }
+ }
+
+ return 0 + $toml;
+}
+
+sub tokenize_float {
+ my $toml = shift;
+ return 0 + $toml;
+}
+
+1;
--- /dev/null
+#-------------------------------------------------------------------------------
+# Tests the results of parsing the example TOML from
+# https://github.com/toml-lang/toml against the de facto standard TOML module
+# on CPAN. Includes the array-of-tables example as well since that is not
+# represented in the synopsis example
+#-------------------------------------------------------------------------------
+use Test2::V0;
+use TOML::Tiny::Parser;
+use TOML;
+
+my $toml = do{ local $/; <DATA> };
+
+my $parser = TOML::Tiny::Parser->new(
+ inflate_datetime => 0,
+ inflate_boolean => 0,
+);
+
+my $got = $parser->parse($toml);
+my $exp = from_toml($toml);
+
+is $got, $exp, 'parity with TOML module';
+done_testing;
+
+__DATA__
+# This is a TOML document.
+
+title = "TOML Example"
+
+[owner]
+name = "Tom Preston-Werner"
+dob = 1979-05-27T07:32:00-08:00 # First class dates
+
+[database]
+server = "192.168.1.1"
+ports = [ 8001, 8001, 8002 ]
+connection_max = 5000
+enabled = true
+options = {"quote-keys"=false}
+
+[servers]
+
+ # Indentation (tabs and/or spaces) is allowed but not required
+ [servers.alpha]
+ ip = "10.0.0.1"
+ dc = "eqdc10"
+
+ [servers.beta]
+ ip = "10.0.0.2"
+ dc = "eqdc10"
+
+[clients]
+data = [ ["gamma", "delta"], [1, 2] ]
+
+# Line breaks are OK when inside arrays
+hosts = [
+ "alpha",
+ "omega"
+]
+
+[[products]]
+name = "Hammer"
+sku = 738594937
+
+[[products]]
+
+[[products]]
+name = "Nail"
+sku = 284758393
+color = "gray"
--- /dev/null
+use Test2::V0;
+use TOML::Tiny;
+
+my $re = qr{ ((?&ArrayOfTables)) $TOML::Tiny::GRAMMAR_V5 }x;
+
+my @valid = (
+ qq{[[foo]]\n},
+
+ qq{[[foo]]
+bar = 1234},
+
+ qq{[[foo]]
+bar = 1234
+baz = 5678},
+
+ qq{[[foo]]
+bar = 1234
+[[baz]]
+bat = 5678},
+
+ qq{[[foo]]
+bar = 1234
+[baz]
+bat = 5678},
+);
+
+ok($_ =~ /$re/, $_) for @valid;
+
+done_testing;
--- /dev/null
+use Test2::V0;
+use TOML::Tiny;
+
+my $re = qr{ ((?&Array)) $TOML::Tiny::GRAMMAR_V5 }x;
+
+my @valid = (
+ q{[ 1, 2, 3 ]},
+ q{[ "red", "yellow", "green" ]},
+ q{[ [ 1, 2 ], [3, 4, 5] ]},
+ q{[ [ 1, 2 ], ["a", "b", "c"] ]},
+ q{[ "all", 'strings', """are the same""", '''type''' ]},
+ q{[ 0.1, 0.2, 0.5, 1, 2, 5 ]},
+ q{[ "Foo Bar <foo@example.com>", { name = "Baz Qux", email = "bazqux@example.com", url = "https://example.com/bazqux" } ]},
+ q{[ 1, 2, 3 ]},
+ q{[ 1, 2, ]},
+ q{[
+ 1,
+ 2,
+ ]},
+ q{[
+ 1,
+ 2
+ ]},
+);
+
+ok($_ =~ /$re/, $_) for @valid;
+
+done_testing;
--- /dev/null
+use Test2::V0;
+use TOML::Tiny;
+
+my $re = qr{ ((?&Boolean)) $TOML::Tiny::GRAMMAR_V5 }x;
+
+like 'true', $re, 'true';
+like 'false', $re, 'false';
+unlike 'invalid', $re, 'invalid';
+
+done_testing;
--- /dev/null
+use Test2::V0;
+use TOML::Tiny;
+
+my $re = qr{ ((?&DateTime)) $TOML::Tiny::GRAMMAR_V5 }x;
+
+my @valid = (
+ '1979-05-27T07:32:00Z',
+ '1979-05-27T00:32:00-07:00',
+ '1979-05-27T00:32:00.999999-07:00',
+ '1979-05-27 07:32:00Z',
+ '1979-05-27T07:32:00',
+ '1979-05-27T00:32:00.999999',
+ '1979-05-27',
+ '07:32:00',
+ '00:32:00.999999',
+);
+
+ok($_ =~ /$re/, $_) for @valid;
+
+done_testing;
--- /dev/null
+use Test2::V0;
+use TOML::Tiny;
+
+my $re = qr{ ((?&Float)) $TOML::Tiny::GRAMMAR_V5 }x;
+
+my @valid = qw(
+ +1.0
+ 3.1415
+ -0.01
+ 5e+22
+ 1e06
+ -2E-2
+ 6.626e-34
+ 224_617.445_991_228
+ inf
+ +inf
+ -inf
+ nan
+ +nan
+ -nan
+);
+
+ok($_ =~ /$re/, $_) for @valid;
+
+done_testing;
--- /dev/null
+use Test2::V0;
+use TOML::Tiny;
+
+my $re = qr{ ((?&InlineTable)) $TOML::Tiny::GRAMMAR_V5 }x;
+
+my @valid = (
+ q|{ first = "Tom", last = "Preston-Werner" }|,
+ q|{ x = 1, y = 2 }|,
+ q|{ type.name = "pug" }|,
+ q|{
+ x = 1
+ }|,
+ q|{
+ x = 1,
+ }|,
+ q|{
+ x = 1,
+ y = 2
+ }|,
+ q|{
+ x = 1,
+ y = 2,
+ }|
+);
+
+ok($_ =~ /$re/, $_) for @valid;
+
+done_testing;
--- /dev/null
+use Test2::V0;
+use TOML::Tiny;
+
+my $re = qr{ ((?&Integer)) $TOML::Tiny::GRAMMAR_V5 }x;
+
+my @valid = qw(
+ +99
+ 42
+ 0
+ -17
+ 1_000
+ 5_349_221
+ 1_2_3_4_5
+ 0xDEADBEEF
+ 0xdeadbeef
+ 0xdead_beef
+ 0o01234567
+ 0o755
+ 0b110101101
+);
+
+like($_, $re, $_) for @valid;
+
+done_testing;
--- /dev/null
+use Test2::V0;
+use TOML::Tiny;
+
+my $re = qr{ ((?&KeyValuePair)) $TOML::Tiny::GRAMMAR_V5 }x;
+
+my @valid = (
+ q{foo= "bar"},
+ q{foo ="bar"},
+ q{foo="bar"},
+ q{foo = "bar"},
+ q{foo = 1234},
+ q{foo = 12.34},
+ q{foo = true},
+ q{foo = [1,2,3]},
+ q{foo = [1, 2, 3]},
+ q{foo = [ 1, 2, 3 ]},
+);
+
+ok($_ =~ /$re/, $_) for @valid;
+
+done_testing;
--- /dev/null
+use Test2::V0;
+use TOML::Tiny;
+
+my $re = qr{ ((?&Key)) $TOML::Tiny::GRAMMAR_V5 }x;
+
+my @valid = qw(
+ key
+ bare_key
+ bare-key
+ 1234
+ "127.0.0.1"
+ "character encoding"
+ "ʎǝʞ"
+ 'key2'
+ 'quoted "value"'
+ physical.color
+ physical.shape
+ site."google.com"
+);
+
+ok($_ =~ /$re/, $_) for @valid;
+
+done_testing;
--- /dev/null
+use Test2::V0;
+use TOML::Tiny;
+
+sub test_simple_matches {
+ my ($re, @tests) = @_;
+ for (@tests) {
+ my ($toml, $expected, $label) = @$_;
+ my ($match) = $toml =~ $re;
+ is $match, $expected, $label;
+ }
+}
+
+subtest 'escaped characters' => sub{
+ my $re = qr{
+ ((?&EscapeChar))
+ $TOML::Tiny::GRAMMAR_V5
+ }x;
+
+ test_simple_matches($re,
+ ['\\\\', '\\\\', 'slash'],
+ ['\\b', '\\b', 'backspace'],
+ ['\\t', '\\t', 'tab'],
+ ['\\n', '\\n', 'line feed'],
+ ['\\f', '\\f', 'form feed'],
+ ['\\r', '\\r', 'carriage return'],
+ ['\\"', '\\"', 'quote'],
+ ['\\\\', '\\\\', 'backslash'],
+ ['\\u1234', '\\u1234', 'unicode (4 bytes)'],
+ ['\\U12345678', '\\U12345678', 'unicode (8 bytes)'],
+ ['\\x', undef, 'invalid'],
+ );
+};
+
+subtest 'string literals' => sub{
+ my $re = qr{
+ ((?&StringLiteral))
+ $TOML::Tiny::GRAMMAR_V5
+ }x;
+
+ test_simple_matches($re,
+ [q{'abc'}, q{'abc'}, 'single-quoted'],
+ [q{''}, q{''}, 'empty single-quoted'],
+ );
+};
+
+subtest 'basic strings' => sub{
+ my $re = qr{
+ ((?&BasicString))
+ $TOML::Tiny::GRAMMAR_V5
+ }x;
+
+ test_simple_matches($re,
+ ['""', '""', "empty string"],
+ ['"abc"', '"abc"', 'simple'],
+ ['"\\tfoo"', '"\\tfoo"', 'escaped chars'],
+ ['1234', undef, 'invalid'],
+ );
+};
+
+subtest 'multi-line strings' => sub{
+ my $re = qr{
+ ((?&MultiLineString))
+ $TOML::Tiny::GRAMMAR_V5
+ }x;
+
+ test_simple_matches($re,
+ [
+ qq{"""\nabc"""},
+ qq{"""\nabc"""},
+ 'simple',
+ ],
+
+ [
+ qq{"""a\n"b"\nc"""},
+ qq{"""a\n"b"\nc"""},
+ 'individual quotes within ml string',
+ ],
+
+ [
+ qq{"""foo"""bar"""},
+ qq{"""foo"""},
+ 'invalid: triple-quotes appear within ml string',
+ ],
+ );
+};
+
+subtest 'multi-line string literals' => sub{
+ my $re = qr{
+ ((?&MultiLineStringLiteral))
+ $TOML::Tiny::GRAMMAR_V5
+ }x;
+
+ test_simple_matches($re,
+ [
+ qq{'''\nabc'''},
+ qq{'''\nabc'''},
+ 'simple',
+ ],
+
+ [
+ qq{'''foo'''bar'''},
+ qq{'''foo'''},
+ 'invalid: triple-quotes appear within ml string',
+ ],
+ );
+};
+
+done_testing;
--- /dev/null
+use Test2::V0;
+use TOML::Tiny;
+
+my $re = qr{ (?&Table) $TOML::Tiny::GRAMMAR_V5 }x;
+
+my @valid = (
+ qq{[foo]\n},
+
+ qq{[foo]
+bar = 1234},
+
+ qq{[foo]
+bar = 1234
+baz = 5678},
+
+ qq{[foo]
+bar = 1234
+[baz]
+bat = 5678},
+);
+
+ok($_ =~ /$re/, $_) for @valid;
+
+done_testing;
--- /dev/null
+;; WARNING: This document is a work-in-progress and should not be considered
+;; authoritative until further notice.
+
+;; This is an attempt to define TOML in ABNF according to the grammar defined
+;; in RFC 5234 (http://www.ietf.org/rfc/rfc5234.txt).
+
+;; You can try out this grammar using http://instaparse.mojombo.com/
+;; To do so, in the lower right, click on Options and change `:input-format` to
+;; ':abnf'. Then paste this entire ABNF document into the grammar entry box
+;; (above the options). Then you can type or paste a sample TOML document into
+;; the beige box on the left. Tada!
+
+;; Overall Structure
+
+toml = expression *( newline expression )
+
+expression = ws [ comment ]
+expression =/ ws keyval ws [ comment ]
+expression =/ ws table ws [ comment ]
+
+;; Whitespace
+
+ws = *wschar
+wschar = %x20 ; Space
+wschar =/ %x09 ; Horizontal tab
+
+;; Newline
+
+newline = %x0A ; LF
+newline =/ %x0D.0A ; CRLF
+
+;; Comment
+
+comment-start-symbol = %x23 ; #
+non-ascii = %x80-D7FF / %xE000-10FFFF
+non-eol = %x09 / %x20-7F / non-ascii
+
+comment = comment-start-symbol *non-eol
+
+;; Key-Value pairs
+
+keyval = key keyval-sep val
+
+key = simple-key / dotted-key
+simple-key = quoted-key / unquoted-key
+
+unquoted-key = 1*( ALPHA / DIGIT / %x2D / %x5F ) ; A-Z / a-z / 0-9 / - / _
+quoted-key = basic-string / literal-string
+dotted-key = simple-key 1*( dot-sep simple-key )
+
+dot-sep = ws %x2E ws ; . Period
+keyval-sep = ws %x3D ws ; =
+
+val = string / boolean / array / inline-table / date-time / float / integer
+
+;; String
+
+string = ml-basic-string / basic-string / ml-literal-string / literal-string
+
+;; Basic String
+
+basic-string = quotation-mark *basic-char quotation-mark
+
+quotation-mark = %x22 ; "
+
+basic-char = basic-unescaped / escaped
+basic-unescaped = wschar / %x21 / %x23-5B / %x5D-7E / non-ascii
+escaped = escape escape-seq-char
+
+escape = %x5C ; \
+escape-seq-char = %x22 ; " quotation mark U+0022
+escape-seq-char =/ %x5C ; \ reverse solidus U+005C
+escape-seq-char =/ %x62 ; b backspace U+0008
+escape-seq-char =/ %x66 ; f form feed U+000C
+escape-seq-char =/ %x6E ; n line feed U+000A
+escape-seq-char =/ %x72 ; r carriage return U+000D
+escape-seq-char =/ %x74 ; t tab U+0009
+escape-seq-char =/ %x75 4HEXDIG ; uXXXX U+XXXX
+escape-seq-char =/ %x55 8HEXDIG ; UXXXXXXXX U+XXXXXXXX
+
+;; Multiline Basic String
+
+ml-basic-string = ml-basic-string-delim ml-basic-body ml-basic-string-delim
+ml-basic-string-delim = 3quotation-mark
+ml-basic-body = *mlb-content *( mlb-quotes 1*mlb-content ) [ mlb-quotes ]
+
+mlb-content = mlb-char / newline / mlb-escaped-nl
+mlb-char = mlb-unescaped / escaped
+mlb-quotes = 1*2quotation-mark
+mlb-unescaped = wschar / %x21 / %x23-5B / %x5D-7E / non-ascii
+mlb-escaped-nl = escape ws newline *( wschar / newline )
+
+;; Literal String
+
+literal-string = apostrophe *literal-char apostrophe
+
+apostrophe = %x27 ; ' apostrophe
+
+literal-char = %x09 / %x20-26 / %x28-7E / non-ascii
+
+;; Multiline Literal String
+
+ml-literal-string = ml-literal-string-delim ml-literal-body ml-literal-string-delim
+ml-literal-string-delim = 3apostrophe
+ml-literal-body = *mll-content *( mll-quotes 1*mll-content ) [ mll-quotes ]
+
+mll-content = mll-char / newline
+mll-char = %x09 / %x20-26 / %x28-7E / non-ascii
+mll-quotes = 1*2apostrophe
+
+;; Integer
+
+integer = dec-int / hex-int / oct-int / bin-int
+
+minus = %x2D ; -
+plus = %x2B ; +
+underscore = %x5F ; _
+digit1-9 = %x31-39 ; 1-9
+digit0-7 = %x30-37 ; 0-7
+digit0-1 = %x30-31 ; 0-1
+
+hex-prefix = %x30.78 ; 0x
+oct-prefix = %x30.6f ; 0o
+bin-prefix = %x30.62 ; 0b
+
+dec-int = [ minus / plus ] unsigned-dec-int
+unsigned-dec-int = DIGIT / digit1-9 1*( DIGIT / underscore DIGIT )
+
+hex-int = hex-prefix HEXDIG *( HEXDIG / underscore HEXDIG )
+oct-int = oct-prefix digit0-7 *( digit0-7 / underscore digit0-7 )
+bin-int = bin-prefix digit0-1 *( digit0-1 / underscore digit0-1 )
+
+;; Float
+
+float = float-int-part ( exp / frac [ exp ] )
+float =/ special-float
+
+float-int-part = dec-int
+frac = decimal-point zero-prefixable-int
+decimal-point = %x2E ; .
+zero-prefixable-int = DIGIT *( DIGIT / underscore DIGIT )
+
+exp = "e" float-exp-part
+float-exp-part = [ minus / plus ] zero-prefixable-int
+
+special-float = [ minus / plus ] ( inf / nan )
+inf = %x69.6e.66 ; inf
+nan = %x6e.61.6e ; nan
+
+;; Boolean
+
+boolean = true / false
+
+true = %x74.72.75.65 ; true
+false = %x66.61.6C.73.65 ; false
+
+;; Date and Time (as defined in RFC 3339)
+
+date-time = offset-date-time / local-date-time / local-date / local-time
+
+date-fullyear = 4DIGIT
+date-month = 2DIGIT ; 01-12
+date-mday = 2DIGIT ; 01-28, 01-29, 01-30, 01-31 based on month/year
+time-delim = "T" / %x20 ; T, t, or space
+time-hour = 2DIGIT ; 00-23
+time-minute = 2DIGIT ; 00-59
+time-second = 2DIGIT ; 00-58, 00-59, 00-60 based on leap second rules
+time-secfrac = "." 1*DIGIT
+time-numoffset = ( "+" / "-" ) time-hour ":" time-minute
+time-offset = "Z" / time-numoffset
+
+partial-time = time-hour ":" time-minute ":" time-second [ time-secfrac ]
+full-date = date-fullyear "-" date-month "-" date-mday
+full-time = partial-time time-offset
+
+;; Offset Date-Time
+
+offset-date-time = full-date time-delim full-time
+
+;; Local Date-Time
+
+local-date-time = full-date time-delim partial-time
+
+;; Local Date
+
+local-date = full-date
+
+;; Local Time
+
+local-time = partial-time
+
+;; Array
+
+array = array-open [ array-values ] ws-comment-newline array-close
+
+array-open = %x5B ; [
+array-close = %x5D ; ]
+
+array-values = ws-comment-newline val ws array-sep array-values
+array-values =/ ws-comment-newline val ws [ array-sep ]
+
+array-sep = %x2C ; , Comma
+
+ws-comment-newline = *( wschar / [ comment ] newline )
+
+;; Table
+
+table = std-table / array-table
+
+;; Standard Table
+
+std-table = std-table-open key std-table-close
+
+std-table-open = %x5B ws ; [ Left square bracket
+std-table-close = ws %x5D ; ] Right square bracket
+
+;; Inline Table
+
+inline-table = inline-table-open [ inline-table-keyvals ] inline-table-close
+
+inline-table-open = %x7B ws ; {
+inline-table-close = ws %x7D ; }
+inline-table-sep = ws %x2C ws ; , Comma
+
+inline-table-keyvals = keyval [ inline-table-sep inline-table-keyvals ]
+
+;; Array Table
+
+array-table = array-table-open key array-table-close
+
+array-table-open = %x5B.5B ws ; [[ Double left square bracket
+array-table-close = ws %x5D.5D ; ]] Double right square bracket
+
+;; Built-in ABNF terms, reproduced here for clarity
+
+ALPHA = %x41-5A / %x61-7A ; A-Z / a-z
+DIGIT = %x30-39 ; 0-9
+HEXDIG = DIGIT / "A" / "B" / "C" / "D" / "E" / "F"