LCOV - code coverage report
Current view: top level - ballet/toml - fd_toml.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 520 1148 45.3 %
Date: 2024-11-13 11:58:15 Functions: 61 82 74.4 %

          Line data    Source code
       1             : #define _DEFAULT_SOURCE
       2             : #include "fd_toml.h"
       3             : #include "../../util/fd_util.h"
       4             : #include <ctype.h>
       5             : #include <time.h>
       6             : 
       7             : /* Implementation note:
       8             : 
       9             :    The lexer/parser of fd_toml.c is a simpler backtracking recursive
      10             :    descent parser.  A minimal amount of lookahead tuning is implemented;
      11             :    mostly just fast failure paths.  Obvious performance wins are
      12             :    possible by adding more speculative lookaheads that lead the CPU down
      13             :    "happy paths" such as long strings of ASCII.
      14             : 
      15             :    The indexer into fd_pod blindly inserts using fd_pod_insert, which
      16             :    may not be the most efficient allocation strategy. */
      17             : 
      18             : /* FD_TOML_PATH_MAX is the max supported pod path length. */
      19             : 
      20             : #define FD_TOML_PATH_MAX (512UL)
      21             : 
      22             : /* fd_toml_cur_t is a cursor object.  It is safe to copy this object via
      23             :    assignment to implement backtracking. */
      24             : 
      25             : struct fd_toml_cur {
      26             :   ulong        lineno;
      27             :   char const * data;
      28             : };
      29             : 
      30             : typedef struct fd_toml_cur fd_toml_cur_t;
      31             : 
      32             : /* fd_toml_parser_t is the internal parser state.  It implements the
      33             :    lexer/parser itself, logic to unescape and buffer, and logic to
      34             :    compose the data into an fd_pod_t. */
      35             : 
      36             : struct fd_toml_parser {
      37             :   fd_toml_cur_t c;
      38             :   char const *  data_end;     /* points one past EOF */
      39             :   long          error;        /* hint: fatal pod error occurred */
      40             :   uchar *       pod;          /* pod provided by user */
      41             : 
      42             :   /* The current buffered string (either for both keys and values) */
      43             : 
      44             :   uchar *       scratch;      /* base of scratch buf */
      45             :   uchar *       scratch_cur;  /* next free byte in scratch buf */
      46             :   uchar *       scratch_end;  /* points one past scratch buf */
      47             : 
      48             :   /* Buffered keys */
      49             : 
      50             :   uint          key_len;
      51             :   char          key[ FD_TOML_PATH_MAX ];  /* cstr */
      52             : };
      53             : 
      54             : typedef struct fd_toml_parser fd_toml_parser_t;
      55             : 
      56             : /* Accumulate and insert data into fd_pod *****************************/
      57             : 
      58             : static void
      59         561 : fd_toml_str_init( fd_toml_parser_t * parser ) {
      60         561 :   parser->scratch_cur = parser->scratch;
      61         561 : }
      62             : 
      63             : static int
      64             : fd_toml_str_append( fd_toml_parser_t * parser,
      65             :                     void const *       data,
      66           0 :                     ulong              sz ) {
      67             : 
      68           0 :   if( FD_UNLIKELY( parser->scratch_cur + sz >= parser->scratch_end ) ) {
      69           0 :     parser->error = FD_TOML_ERR_SCRATCH;
      70           0 :     return 0;
      71           0 :   }
      72             : 
      73           0 :   fd_memcpy( parser->scratch_cur, data, sz );
      74           0 :   parser->scratch_cur += sz;
      75           0 :   return 1;
      76           0 : }
      77             : 
      78             : static int
      79             : fd_toml_str_append_byte( fd_toml_parser_t * parser,
      80        7353 :                          int                c ) {
      81             : 
      82        7353 :   if( FD_UNLIKELY( parser->scratch_cur >= parser->scratch_end ) ) {
      83           0 :     parser->error = FD_TOML_ERR_SCRATCH;
      84           0 :     return 0;
      85           0 :   }
      86             : 
      87        7353 :   parser->scratch_cur[0] = (uchar)c;
      88        7353 :   parser->scratch_cur++;
      89        7353 :   return 1;
      90        7353 : }
      91             : 
      92             : /* fd_toml_str_append_utf8 appends the UTF-8 encoding of the given
      93             :    Unicode code point (<=UINT_MAX).  If rune is not a valid code point,
      94             :    writes the replacement code point instead. */
      95             : 
      96             : static int
      97             : fd_toml_str_append_utf8( fd_toml_parser_t * parser,
      98           0 :                          long               rune ) {
      99             : 
     100           0 :   if( FD_UNLIKELY( parser->scratch_cur + 4 >= parser->scratch_end ) ) {
     101           0 :     parser->error = FD_TOML_ERR_SCRATCH;
     102           0 :     return 0;
     103           0 :   }
     104             : 
     105           0 :   parser->scratch_cur = (uchar *)fd_cstr_append_utf8( (char *)parser->scratch_cur, (uint)rune );
     106           0 :   return 1;
     107           0 : }
     108             : 
     109             : /* Backtracking recursive-descent parser ******************************/
     110             : 
     111             : /* fd_toml_advance advances the parser cursor by 'n' chars.  Counts line
     112             :    numbers while advancing.  If you now for sure that the next 'n' chars
     113             :    don't contain any new lines, use fd_toml_advance_inline instead. */
     114             : 
     115             : static void /* consider aggressive inline */
     116             : fd_toml_advance( fd_toml_parser_t * parser,
     117        4545 :                  ulong              n ) {
     118             : 
     119        4545 :   char const * p    = parser->c.data;
     120        4545 :   char const * next = p + n;
     121        4545 :   if( FD_UNLIKELY( next > parser->data_end ) ) {
     122           0 :     FD_LOG_CRIT(( "fd_toml_advance out of bounds" ));
     123           0 :   }
     124             : 
     125             :   /* consider unroll */
     126        4545 :   ulong lines = 0UL;
     127        9090 :   for( ; p < next; p++ ) {
     128        4545 :     if( *p == '\n' ) lines++;
     129        4545 :   }
     130             : 
     131        4545 :   parser->c.lineno += lines;
     132        4545 :   parser->c.data    = next;
     133        4545 : }
     134             : 
     135             : static inline void
     136             : fd_toml_advance_inline( fd_toml_parser_t * parser,
     137      185058 :                         ulong              n ) {
     138      185058 :   parser->c.data += n;
     139      185058 : }
     140             : 
     141             : static int
     142          96 : fd_toml_upsert_empty_pod( fd_toml_parser_t * parser ) {
     143          96 :   if( !fd_pod_query_subpod( parser->pod, parser->key ) ) {
     144          96 :     uchar   subpod_mem[ FD_POD_FOOTPRINT_MIN ];
     145          96 :     uchar * subpod = fd_pod_join( fd_pod_new( subpod_mem, FD_POD_FOOTPRINT_MIN ) );
     146          96 :     if( FD_UNLIKELY( !fd_pod_insert( parser->pod, parser->key, FD_POD_VAL_TYPE_SUBPOD, FD_POD_FOOTPRINT_MIN, subpod ) ) ) {
     147           0 :       parser->error = FD_TOML_ERR_POD;
     148           0 :       return 0;
     149           0 :     }
     150          96 :     fd_pod_delete( fd_pod_leave( subpod ) );
     151          96 :   }
     152          96 :   return 1;
     153          96 : }
     154             : 
     155             : /* fd_toml_avail returns the number of bytes available for parsing. */
     156             : 
     157             : FD_FN_PURE static inline ulong
     158      222843 : fd_toml_avail( fd_toml_parser_t const * parser ) {
     159      222843 :   if( FD_UNLIKELY( parser->c.data > parser->data_end ) ) {
     160           0 :     FD_LOG_CRIT(( "Parse cursor is out of bounds" ));
     161           0 :   }
     162      222843 :   return (ulong)parser->data_end - (ulong)parser->c.data;
     163      222843 : }
     164             : 
     165             : #define SUB_PARSE( fn_call )                          \
     166       15357 :   __extension__ ({                                    \
     167       15357 :     fd_toml_cur_t const _macro_backtrack = parser->c; \
     168       15357 :     int ret = fn_call;                                \
     169       15357 :     if( !ret ) {                                      \
     170       10965 :       if( parser->error ) return 0;                   \
     171       10965 :       parser->c = _macro_backtrack;                   \
     172       10965 :     }                                                 \
     173       15357 :     ret;                                              \
     174       15357 :   })
     175             : 
     176             : #define EXPECT_CHAR(_c)                                      \
     177        4518 :   do {                                                       \
     178        4518 :     if( FD_UNLIKELY( !fd_toml_avail( parser )  ) ) return 0; \
     179        4518 :     if( FD_UNLIKELY( parser->c.data[0] != (_c) ) ) return 0; \
     180        4515 :     fd_toml_advance_inline( parser, 1UL );                   \
     181         231 :   } while(0);
     182             : 
     183             : /* Begin fd_toml_parse_{...} functions.  All these functions attempt
     184             :    take a single argument, the parser.  Each function attempts to match
     185             :    a token and returns 1 on success.  If the token was not matched,
     186             :    returns 0.  On success, the cursor is advanced to one past the read
     187             :    token.  On failure, the cursor may arbitrarily advance within bounds.
     188             :    Parsers can gracefully recover from failure (backtrack) by restoring
     189             :    the fd_toml_cur_t object to its original state. */
     190             : 
     191             : static int fd_toml_parse_keyval( fd_toml_parser_t * parser );
     192             : static int fd_toml_parse_val   ( fd_toml_parser_t * parser );
     193             : 
     194             : /* ws = *wschar
     195             :    wschar =  %x20  ; Space
     196             :    wschar =/ %x09  ; Horizontal tab */
     197             : 
     198             : static int
     199        5727 : fd_toml_parse_ws( fd_toml_parser_t * parser ) {
     200             : 
     201       22839 :   while( fd_toml_avail( parser ) ) {
     202       22836 :     char c = parser->c.data[0];
     203       22836 :     if( c != ' ' && c != '\t' ) break;
     204       17112 :     fd_toml_advance_inline( parser, 1UL );
     205       17112 :   }
     206             : 
     207        5727 :   return 1;
     208        5727 : }
     209             : 
     210             : /* comment-start-symbol = %x23
     211             :    non-ascii = %x80-D7FF / %xE000-10FFFF
     212             :    non-eol = %x09 / %x20-7F / non-ascii
     213             : 
     214             :    comment = comment-start-symbol *non-eol */
     215             : 
     216             : static int
     217        4017 : fd_toml_parse_comment( fd_toml_parser_t * parser ) {
     218        4017 :   if( FD_UNLIKELY( !fd_toml_avail( parser ) ) ) return 0;
     219        4014 :   if( FD_UNLIKELY( parser->c.data[0] != '#' ) ) return 0;
     220        3210 :   fd_toml_advance_inline( parser, 1UL );
     221             : 
     222      159279 :   while( fd_toml_avail( parser ) ) {
     223      159279 :     uint c = (uchar)parser->c.data[0];
     224      159279 :     if( FD_LIKELY( (c==0x09) |
     225      159279 :                    (c>=0x20 && c<0x7F) |
     226      159279 :                    (c>=0x80) ) ) {
     227      156069 :       fd_toml_advance_inline( parser, 1UL );
     228      156069 :     } else {
     229        3210 :       break;
     230        3210 :     }
     231      159279 :   }
     232             : 
     233        3210 :   return 1;
     234        4014 : }
     235             : 
     236             : /* quotation-mark = %x22 */
     237             : 
     238             : static int
     239        4554 : fd_toml_parse_quotation_mark( fd_toml_parser_t * parser ) {
     240        4554 :   if( FD_UNLIKELY( !fd_toml_avail( parser ) ) ) return 0;
     241        4551 :   if( FD_UNLIKELY( parser->c.data[0] != '"' ) ) return 0;
     242         204 :   fd_toml_advance_inline( parser, 1UL );
     243         204 :   return 1;
     244        4551 : }
     245             : 
     246             : /* basic-unescaped = wschar / %x21 / %x23-5B / %x5D-7E / non-ascii */
     247             : 
     248             : static int
     249         681 : fd_toml_parse_basic_unescaped( fd_toml_parser_t * parser ) {
     250             : 
     251         681 :   if( FD_UNLIKELY( !fd_toml_avail( parser ) ) ) return 0;
     252             : 
     253         681 :   int c = (uchar)parser->c.data[0];
     254         681 :   if( FD_LIKELY( (c==' ') | (c=='\t') |
     255         681 :                  (c==0x21)            |
     256         681 :                  (c>=0x23 && c<=0x5B)  |
     257         681 :                  (c>=0x5D && c<=0x7E)  |
     258         681 :                  (c>=0x80) ) ) { /* ok */ }
     259         102 :   else {
     260         102 :     return 0;
     261         102 :   }
     262             : 
     263         579 :   fd_toml_str_append_byte( parser, (uchar)c );
     264         579 :   fd_toml_advance( parser, 1UL );
     265         579 :   return 1;
     266         681 : }
     267             : 
     268             : /* fd_toml_xdigit converts a char to a hex digit.  Assumes that the
     269             :    char matches [0-9a-fA-F] */
     270             : 
     271             : FD_FN_CONST static inline uint
     272           0 : fd_toml_xdigit( int c ) {
     273           0 :   c = tolower( c );
     274           0 :   c = fd_int_if( c>'9', c-'a'+10, c-'0' );
     275           0 :   return (uint)c;
     276           0 : }
     277             : 
     278             : /* escaped = escape escape-seq-char
     279             :    escape = %x5C                   ; \
     280             :    escape-seq-char =  %x22         ; "    quotation mark  U+0022
     281             :    escape-seq-char =/ %x5C         ; \    reverse solidus U+005C
     282             :    escape-seq-char =/ %x62         ; b    backspace       U+0008
     283             :    escape-seq-char =/ %x66         ; f    form feed       U+000C
     284             :    escape-seq-char =/ %x6E         ; n    line feed       U+000A
     285             :    escape-seq-char =/ %x72         ; r    carriage return U+000D
     286             :    escape-seq-char =/ %x74         ; t    tab             U+0009
     287             :    escape-seq-char =/ %x75 4HEXDIG ; uXXXX                U+XXXX
     288             :    escape-seq-char =/ %x55 8HEXDIG ; UXXXXXXXX            U+XXXXXXXX */
     289             : 
     290             : static int
     291         102 : fd_toml_parse_escaped( fd_toml_parser_t * parser ) {
     292             : 
     293         102 :   if( FD_UNLIKELY( fd_toml_avail( parser ) < 2UL ) ) return 0;
     294         102 :   if( FD_UNLIKELY( parser->c.data[0] != '\\'     ) ) return 0;
     295           0 :   int kind = parser->c.data[1];
     296           0 :   fd_toml_advance_inline( parser, 2UL );
     297             : 
     298           0 :   int valid = 1;
     299           0 :   uint rune;
     300           0 :   switch( kind ) {
     301           0 :   case 'b':
     302           0 :     fd_toml_str_append_byte( parser, '\b' );
     303           0 :     return 1;
     304           0 :   case 'f':
     305           0 :     fd_toml_str_append_byte( parser, '\f' );
     306           0 :     return 1;
     307           0 :   case 'n':
     308           0 :     fd_toml_str_append_byte( parser, '\n' );
     309           0 :     return 1;
     310           0 :   case 'r':
     311           0 :     fd_toml_str_append_byte( parser, '\r' );
     312           0 :     return 1;
     313           0 :   case 't':
     314           0 :     fd_toml_str_append_byte( parser, '\t' );
     315           0 :     return 1;
     316           0 :   case '"':
     317           0 :   case '\\':
     318           0 :     fd_toml_str_append_byte( parser, kind );
     319           0 :     return 1;
     320           0 :   case 'u':
     321           0 :     if( FD_UNLIKELY( fd_toml_avail( parser ) < 4UL ) ) return 0;
     322           0 :     for( ulong j=0; j<4; j++ ) valid &= ( !!isxdigit( parser->c.data[j] ) );
     323           0 :     if( FD_UNLIKELY( !valid ) ) return 0;
     324           0 :     rune  = ( fd_toml_xdigit( parser->c.data[0] )<<12 );
     325           0 :     rune |= ( fd_toml_xdigit( parser->c.data[1] )<< 8 );
     326           0 :     rune |= ( fd_toml_xdigit( parser->c.data[2] )<< 4 );
     327           0 :     rune |= ( fd_toml_xdigit( parser->c.data[3] )     );
     328           0 :     if( FD_UNLIKELY( !fd_toml_str_append_utf8( parser, rune ) ) ) return 0;
     329           0 :     fd_toml_advance_inline( parser, 4UL );
     330           0 :     return 1;
     331           0 :   case 'U':
     332           0 :     if( FD_UNLIKELY( fd_toml_avail( parser ) < 8UL ) ) return 0;
     333           0 :     for( ulong j=0; j<8; j++ ) valid &= ( !!isxdigit( parser->c.data[j] ) );
     334           0 :     if( FD_UNLIKELY( !valid ) ) return 0;
     335           0 :     rune  = ( fd_toml_xdigit( parser->c.data[0] )<<28 );
     336           0 :     rune |= ( fd_toml_xdigit( parser->c.data[1] )<<24 );
     337           0 :     rune |= ( fd_toml_xdigit( parser->c.data[2] )<<20 );
     338           0 :     rune |= ( fd_toml_xdigit( parser->c.data[3] )<<16 );
     339           0 :     rune |= ( fd_toml_xdigit( parser->c.data[4] )<<12 );
     340           0 :     rune |= ( fd_toml_xdigit( parser->c.data[5] )<< 8 );
     341           0 :     rune |= ( fd_toml_xdigit( parser->c.data[6] )<< 4 );
     342           0 :     rune |= ( fd_toml_xdigit( parser->c.data[7] )     );
     343           0 :     if( FD_UNLIKELY( !fd_toml_str_append_utf8( parser, rune ) ) ) return 0;
     344           0 :     fd_toml_advance_inline( parser, 8UL );
     345           0 :     return 1;
     346           0 :   default:
     347           0 :     return 0;
     348           0 :   }
     349           0 : }
     350             : 
     351             : /* basic-char = basic-unescaped / escaped */
     352             : 
     353             : static int
     354         681 : fd_toml_parse_basic_char( fd_toml_parser_t * parser ) {
     355         681 :   if( FD_LIKELY( SUB_PARSE( fd_toml_parse_basic_unescaped( parser ) ) ) ) return 1;
     356         102 :   if( FD_LIKELY( SUB_PARSE( fd_toml_parse_escaped        ( parser ) ) ) ) return 1;
     357         102 :   return 0;
     358         102 : }
     359             : 
     360             : /* basic-string = quotation-mark *basic-char quotation-mark */
     361             : 
     362             : static int
     363        4452 : fd_toml_parse_basic_string( fd_toml_parser_t * parser ) {
     364        4452 :   if( FD_UNLIKELY( !SUB_PARSE( fd_toml_parse_quotation_mark( parser ) ) ) ) return 0;
     365         102 :   fd_toml_str_init( parser );
     366         681 :   while( SUB_PARSE( fd_toml_parse_basic_char( parser ) ) ) {}
     367         102 :   if( FD_UNLIKELY( !SUB_PARSE( fd_toml_parse_quotation_mark( parser ) ) ) ) return 0;
     368         102 :   return 1;
     369         102 : }
     370             : 
     371             : /* apostrophe = %x27 ; ' apostrophe */
     372             : 
     373             : static int
     374        4350 : fd_toml_parse_apostrophe( fd_toml_parser_t * parser ) {
     375        4350 :   if( FD_UNLIKELY( !fd_toml_avail( parser )  ) ) return 0;
     376        4347 :   if( FD_UNLIKELY( parser->c.data[0] != '\'' ) ) return 0;
     377           0 :   fd_toml_advance_inline( parser, 1UL );
     378           0 :   return 1;
     379        4347 : }
     380             : 
     381             : /* literal-char = %x09 / %x20-26 / %x28-7E / non-ascii */
     382             : 
     383             : static int
     384           0 : fd_toml_parse_literal_char( fd_toml_parser_t * parser ) {
     385             : 
     386           0 :   if( FD_UNLIKELY( !fd_toml_avail( parser ) ) ) return 0;
     387             : 
     388           0 :   int c = (uchar)parser->c.data[0];
     389           0 :   if( FD_LIKELY( (c==0x09) |
     390           0 :                  (c>=0x20 && c<=0x26) |
     391           0 :                  (c>=0x28 && c<=0x7E) |
     392           0 :                  (c>=0x80) ) ) { /* ok */ }
     393           0 :   else {
     394           0 :     return 0;
     395           0 :   }
     396             : 
     397           0 :   fd_toml_str_append_byte( parser, c );
     398           0 :   fd_toml_advance( parser, 1UL );
     399           0 :   return 1;
     400           0 : }
     401             : 
     402             : /* literal-string = apostrophe *literal-char apostrophe */
     403             : 
     404             : static int
     405        4350 : fd_toml_parse_literal_string( fd_toml_parser_t * parser ) {
     406        4350 :   if( FD_UNLIKELY( !SUB_PARSE( fd_toml_parse_apostrophe( parser ) ) ) ) return 0;
     407           0 :   fd_toml_str_init( parser );
     408           0 :   while( SUB_PARSE( fd_toml_parse_literal_char( parser ) ) ) {}
     409           0 :   if( FD_UNLIKELY( !SUB_PARSE( fd_toml_parse_apostrophe( parser ) ) ) ) return 0;
     410           0 :   return 1;
     411           0 : }
     412             : 
     413             : /* quoted-key = basic-string / literal-string */
     414             : 
     415             : static int
     416        4080 : fd_toml_parse_quoted_key( fd_toml_parser_t * parser ) {
     417        4080 :   if( FD_LIKELY( SUB_PARSE( fd_toml_parse_basic_string  ( parser ) ) ) ) return 1;
     418        4080 :   if( FD_LIKELY( SUB_PARSE( fd_toml_parse_literal_string( parser ) ) ) ) return 1;
     419        4080 :   return 0;
     420        4080 : }
     421             : 
     422             : /* unquoted-key = 1*( ALPHA / DIGIT / %x2D / %x5F ) ; A-Z / a-z / 0-9 / - / _ */
     423             : 
     424             : static int
     425       10749 : fd_toml_is_unquoted_key_char( int c ) {
     426       10749 :   return (c>='A' && c<='Z') |
     427       10749 :          (c>='a' && c<='z') |
     428       10749 :          (c>='0' && c<='9') |
     429       10749 :          (c=='-') |
     430       10749 :          (c=='_');
     431       10749 : }
     432             : 
     433             : static int
     434        4080 : fd_toml_parse_unquoted_key( fd_toml_parser_t * parser ) {
     435        4080 :   if( FD_UNLIKELY( !fd_toml_avail( parser )           ) ) return 0;
     436        4077 :   int c = (uchar)parser->c.data[0];
     437        4077 :   if( FD_UNLIKELY( !fd_toml_is_unquoted_key_char( c ) ) ) return 0;
     438         459 :   fd_toml_str_init( parser );
     439             : 
     440         459 :   fd_toml_str_append_byte( parser, c );
     441         459 :   fd_toml_advance_inline( parser, 1UL );
     442             : 
     443        6672 :   while( fd_toml_avail( parser ) ) {
     444        6672 :     c = (uchar)parser->c.data[0];
     445        6672 :     if( FD_LIKELY( fd_toml_is_unquoted_key_char( c ) ) ) {
     446        6213 :       fd_toml_str_append_byte( parser, c );
     447        6213 :       fd_toml_advance_inline( parser, 1UL );
     448        6213 :     } else {
     449         459 :       break;
     450         459 :     }
     451        6672 :   }
     452         459 :   return 1;
     453        4077 : }
     454             : 
     455             : /* simple-key = quoted-key / unquoted-key */
     456             : 
     457             : static int
     458        4080 : fd_toml_parse_simple_key( fd_toml_parser_t * parser ) {
     459        4080 :   if( FD_LIKELY( SUB_PARSE( fd_toml_parse_quoted_key  ( parser ) ) ) ) goto add;
     460        4080 :   if( FD_LIKELY( SUB_PARSE( fd_toml_parse_unquoted_key( parser ) ) ) ) goto add;
     461        3621 :   return 0;
     462             : 
     463         459 : add:
     464         459 :   do {
     465         459 :     uint  old_key_len = parser->key_len;
     466         459 :     ulong suffix_len  = (ulong)parser->scratch_cur - (ulong)parser->scratch;
     467         459 :     ulong key_len     = (ulong)old_key_len + suffix_len + 1;
     468         459 :     if( FD_UNLIKELY( key_len > sizeof(parser->key)  ) ) {
     469           0 :       FD_LOG_WARNING(( "oversz key: \"%.*s%.*s\"",
     470           0 :                       (int)old_key_len, parser->key,
     471           0 :                       (int)suffix_len,  (char *)parser->scratch ));
     472           0 :       parser->error = FD_TOML_ERR_KEY;
     473           0 :       return 0;
     474           0 :     }
     475             : 
     476         459 :     char * key_cur = fd_cstr_init( parser->key + old_key_len );
     477         459 :     key_cur = fd_cstr_append_text( key_cur, (char const *)parser->scratch, suffix_len );
     478         459 :     fd_cstr_fini( key_cur );
     479         459 :     parser->key_len = (uint)( key_cur - parser->key );
     480         459 :     return 1;
     481         459 :   } while(0);
     482         459 : }
     483             : 
     484             : /* dot-sep = ws %x2E ws  ; . Period */
     485             : 
     486             : static int
     487         459 : fd_toml_parse_dot_sep( fd_toml_parser_t * parser ) {
     488         459 :   fd_toml_parse_ws( parser );
     489         459 :   EXPECT_CHAR( '.' );
     490          39 :   fd_toml_parse_ws( parser );
     491          39 :   return 1;
     492         459 : }
     493             : 
     494             : /* dotted-key = simple-key 1*( dot-sep simple-key ) */
     495             : 
     496             : static int
     497        4041 : fd_toml_parse_dotted_key( fd_toml_parser_t * parser ) {
     498        4041 :   if( FD_UNLIKELY( !SUB_PARSE( fd_toml_parse_simple_key( parser ) ) ) ) return 0;
     499         459 :   while( fd_toml_avail( parser ) ) {
     500         459 :     if( FD_UNLIKELY( !SUB_PARSE( fd_toml_parse_dot_sep( parser ) ) ) ) break;
     501             : 
     502             :     /* Add trailing dot */
     503          39 :     if( parser->key_len + 2 > sizeof(parser->key) ) {
     504           0 :       parser->error = FD_TOML_ERR_KEY;
     505           0 :       return 0;
     506           0 :     }
     507          39 :     parser->key[ parser->key_len++ ] = '.';
     508          39 :     parser->key[ parser->key_len   ] = '\x00';
     509             : 
     510          39 :     if( FD_UNLIKELY( !SUB_PARSE( fd_toml_parse_simple_key( parser ) ) ) ) return 0;
     511          39 :   }
     512         420 :   return 1;
     513         420 : }
     514             : 
     515             : /* key = simple-key / dotted-key
     516             : 
     517             :    Doing simple-key *( dot-sep simple-key ) instead to simplify code */
     518             : 
     519             : static int
     520        4041 : fd_toml_parse_key( fd_toml_parser_t * parser ) {
     521        4041 :   return fd_toml_parse_dotted_key( parser );
     522        4041 : }
     523             : 
     524             : /* keyval-sep = ws %x3D ws */
     525             : 
     526             : static int
     527         348 : fd_toml_parse_keyval_sep( fd_toml_parser_t * parser ) {
     528         348 :   fd_toml_parse_ws( parser );
     529         348 :   if( FD_UNLIKELY( !fd_toml_avail( parser ) ) ) return 0;
     530         348 :   if( FD_UNLIKELY( parser->c.data[0] != '=' ) ) return 0;
     531         348 :   fd_toml_advance_inline( parser, 1UL );
     532         348 :   fd_toml_parse_ws( parser );
     533         348 :   return 1;
     534         348 : }
     535             : 
     536             : /* ml-basic-string-delim = 3quotation-mark */
     537             : 
     538             : static int
     539         372 : fd_toml_parse_ml_basic_string_delim( fd_toml_parser_t * parser ) {
     540         372 :   if( FD_UNLIKELY( parser->c.data + 3 > parser->data_end ) ) return 0;
     541         372 :   if( FD_UNLIKELY( ( parser->c.data[0] != '"' ) |
     542         372 :                    ( parser->c.data[1] != '"' ) |
     543         372 :                    ( parser->c.data[2] != '"' ) ) ) return 0;
     544           0 :   fd_toml_advance_inline( parser, 3UL );
     545           0 :   return 1;
     546         372 : }
     547             : 
     548             : /* mlb-unescaped = wschar / %x21 / %x23-5B / %x5D-7E / non-ascii */
     549             : 
     550             : static int
     551           0 : fd_toml_parse_mlb_unescaped( fd_toml_parser_t * parser ) {
     552           0 :   return fd_toml_parse_basic_unescaped( parser );
     553           0 : }
     554             : 
     555             : /* mlb-escaped-nl = escape ws newline *( wschar / newline ) */
     556             : 
     557             : static int
     558           0 : fd_toml_parse_mlb_escaped_nl( fd_toml_parser_t * parser ) {
     559           0 :   if( FD_UNLIKELY( fd_toml_avail( parser ) < 2UL ) ) return 0;
     560           0 :   if( FD_UNLIKELY( parser->c.data[0] != '\\'     ) ) return 0;
     561           0 :   fd_toml_advance_inline( parser, 1UL );
     562           0 :   SUB_PARSE( fd_toml_parse_ws( parser ) );
     563           0 :   if( FD_UNLIKELY( !fd_toml_avail( parser )      ) ) return 0;
     564           0 :   if( FD_UNLIKELY( parser->c.data[0] != '\n'     ) ) return 0;
     565           0 :   while( fd_toml_avail( parser ) ) {
     566           0 :     int c = (uchar)parser->c.data[0];
     567           0 :     if( (c==' ') | (c=='\t') | (c=='\n') ) {
     568           0 :       fd_toml_advance( parser, 1UL );
     569           0 :     } else {
     570           0 :       break;
     571           0 :     }
     572           0 :   }
     573           0 :   return 1;
     574           0 : }
     575             : 
     576             : /* mlb-content = mlb-char / newline / mlb-escaped-nl
     577             :    mlb-char = mlb-unescaped / escaped */
     578             : 
     579             : static int
     580           0 : fd_toml_parse_mlb_content( fd_toml_parser_t * parser ) {
     581           0 :   if( FD_UNLIKELY( !fd_toml_avail( parser ) ) ) return 0;
     582           0 :   if( FD_LIKELY( SUB_PARSE( fd_toml_parse_mlb_unescaped( parser ) ) ) ) return 1;
     583           0 :   if( FD_LIKELY( SUB_PARSE( fd_toml_parse_escaped      ( parser ) ) ) ) return 1;
     584           0 :   if( FD_LIKELY( parser->c.data[0] == '\n' ) ) {
     585           0 :     fd_toml_str_append_byte( parser, '\n' );
     586           0 :     fd_toml_advance( parser, 1UL );
     587           0 :     return 1;
     588           0 :   }
     589           0 :   if( FD_LIKELY( SUB_PARSE( fd_toml_parse_mlb_escaped_nl( parser ) ) ) ) return 1;
     590           0 :   return 0;
     591           0 : }
     592             : 
     593             : /* mlb-quotes = 1*2quotation-mark
     594             :    Note: This is used to allow normal quotes (", "") inside a multiline
     595             :          basic comment (""") */
     596             : 
     597             : static int
     598           0 : fd_toml_parse_mlb_quotes( fd_toml_parser_t * parser ) {
     599             : 
     600             :   /* Count number of quotes */
     601           0 :   char const * begin = parser->c.data;
     602           0 :   ulong quote_cnt = 0UL;
     603           0 :   while( fd_toml_avail( parser ) && parser->c.data[0] == '"' ) {
     604           0 :     fd_toml_advance_inline( parser, 1UL );
     605           0 :     quote_cnt++;
     606           0 :   }
     607             : 
     608           0 :   if( !quote_cnt || quote_cnt > 5 ) return 0;
     609           0 :   if( quote_cnt < 3 ) {
     610           0 :     fd_toml_str_append( parser, begin, quote_cnt );
     611           0 :     return 1;
     612           0 :   }
     613           0 :   if( quote_cnt==3 ) return 0;
     614             : 
     615             :   /* Backtrack by 3 quotes, as those might be the multiline */
     616           0 :   parser->c.data -= 3;
     617           0 :   quote_cnt      -= 3;
     618           0 :   fd_toml_str_append( parser, begin, quote_cnt );
     619           0 :   return 1;
     620           0 : }
     621             : 
     622             : /* ml-basic-body = *mlb-content *( mlb-quotes 1*mlb-content ) [ mlb-quotes ] */
     623             : 
     624             : static int
     625           0 : fd_toml_parse_ml_basic_body( fd_toml_parser_t * parser ) {
     626           0 :   while( SUB_PARSE( fd_toml_parse_mlb_content( parser ) ) ) {}
     627           0 :   for(;;) {
     628           0 :     if( FD_UNLIKELY( !SUB_PARSE( fd_toml_parse_mlb_quotes ( parser ) ) ) ) break;
     629           0 :     if( FD_UNLIKELY( !SUB_PARSE( fd_toml_parse_mlb_content( parser ) ) ) ) break;
     630           0 :     while( SUB_PARSE( fd_toml_parse_mlb_content( parser ) ) ) {}
     631           0 :   }
     632           0 :   SUB_PARSE( fd_toml_parse_mlb_quotes( parser ) );
     633           0 :   return 1;
     634           0 : }
     635             : 
     636             : /* ml-basic-string = ml-basic-string-delim [ newline ] ml-basic-body ml-basic-string-delim */
     637             : 
     638             : static int
     639         372 : fd_toml_parse_ml_basic_string( fd_toml_parser_t * parser ) {
     640         372 :   if( FD_UNLIKELY( !SUB_PARSE( fd_toml_parse_ml_basic_string_delim( parser ) ) ) ) return 0;
     641           0 :   if( FD_UNLIKELY( !fd_toml_avail( parser ) ) )                                    return 0;
     642           0 :   if( parser->c.data[0] == '\n' ) {
     643           0 :     fd_toml_advance( parser, 1UL );
     644           0 :   }
     645           0 :   fd_toml_str_init( parser );
     646           0 :   if( FD_UNLIKELY( !SUB_PARSE( fd_toml_parse_ml_basic_body        ( parser ) ) ) ) return 0;
     647           0 :   if( FD_UNLIKELY( !SUB_PARSE( fd_toml_parse_ml_basic_string_delim( parser ) ) ) ) return 0;
     648           0 :   return 1;
     649           0 : }
     650             : 
     651             : /* mll-quotes = 1*2apostrophe
     652             :    Note: This is used to allow normal quotes (', '') inside a multiline
     653             :          literal comment (''') */
     654             : 
     655             : static int
     656           0 : fd_toml_parse_mll_quotes( fd_toml_parser_t * parser ) {
     657             : 
     658             :   /* Count number of quotes */
     659           0 :   char const * begin = parser->c.data;
     660           0 :   ulong quote_cnt = 0UL;
     661           0 :   while( fd_toml_avail( parser ) && parser->c.data[0] == '\'' ) {
     662           0 :     fd_toml_advance_inline( parser, 1UL );
     663           0 :     quote_cnt++;
     664           0 :   }
     665             : 
     666           0 :   if( !quote_cnt || quote_cnt > 5 ) return 0;
     667           0 :   if( quote_cnt < 3 ) {
     668           0 :     fd_toml_str_append( parser, begin, quote_cnt );
     669           0 :     return 1;
     670           0 :   }
     671           0 :   if( quote_cnt==3 ) return 0;
     672             : 
     673             :   /* Backtrack by 3 quotes, as those might be the multiline */
     674           0 :   parser->c.data -= 3;
     675           0 :   quote_cnt      -= 3;
     676           0 :   fd_toml_str_append( parser, begin, quote_cnt );
     677           0 :   return 1;
     678           0 : }
     679             : 
     680             : /* mll-content = mll-char / newline
     681             :    mll-char = %x09 / %x20-26 / %x28-7E / non-ascii */
     682             : 
     683             : static int
     684           0 : fd_toml_parse_mll_content( fd_toml_parser_t * parser ) {
     685           0 :   if( FD_UNLIKELY( !fd_toml_avail( parser ) ) ) return 0;
     686             : 
     687           0 :   int c = (uchar)parser->c.data[0];
     688           0 :   if( FD_LIKELY( (c==0x09) |
     689           0 :                  (c>=0x20 && c<=0x26) |
     690           0 :                  (c>=0x28 && c<=0x7E) |
     691           0 :                  (c>=0x80) |
     692           0 :                  (c=='\n') ) ) {
     693             :     /* ok */
     694           0 :   } else {
     695           0 :     return 0;
     696           0 :   }
     697           0 :   if( FD_UNLIKELY( !fd_toml_str_append_byte( parser, c ) ) ) return 0;
     698             : 
     699           0 :   fd_toml_advance( parser, 1UL );
     700           0 :   return 1;
     701           0 : }
     702             : 
     703             : /* ml-literal-body = *mll-content *( mll-quotes 1*mll-content ) [ mll-quotes ] */
     704             : 
     705             : static int
     706           0 : fd_toml_parse_ml_literal_body( fd_toml_parser_t * parser ) {
     707           0 :   while( SUB_PARSE( fd_toml_parse_mll_content( parser ) ) ) {}
     708           0 :   for(;;) {
     709           0 :     if( FD_UNLIKELY( !SUB_PARSE( fd_toml_parse_mll_quotes ( parser ) ) ) ) break;
     710           0 :     if( FD_UNLIKELY( !SUB_PARSE( fd_toml_parse_mll_content( parser ) ) ) ) break;
     711           0 :     while( SUB_PARSE( fd_toml_parse_mll_content( parser ) ) ) {}
     712           0 :   }
     713           0 :   SUB_PARSE( fd_toml_parse_mll_quotes( parser ) );
     714           0 :   return 1;
     715           0 : }
     716             : 
     717             : /* ml-literal-string-delim = 3apostrophe */
     718             : 
     719             : static int
     720         270 : fd_toml_parse_ml_literal_string_delim( fd_toml_parser_t * parser ) {
     721         270 :   if( FD_UNLIKELY( parser->c.data + 3 > parser->data_end ) ) return 0;
     722         270 :   if( FD_UNLIKELY( ( parser->c.data[0] != '\'' ) |
     723         270 :                    ( parser->c.data[1] != '\'' ) |
     724         270 :                    ( parser->c.data[2] != '\'' ) ) ) return 0;
     725           0 :   fd_toml_advance_inline( parser, 3UL );
     726           0 :   return 1;
     727         270 : }
     728             : 
     729             : /* ml-literal-string = ml-literal-string-delim [ newline ] ml-literal-body
     730             :                        ml-literal-string-delim */
     731             : 
     732             : static int
     733         270 : fd_toml_parse_ml_literal_string( fd_toml_parser_t * parser ) {
     734         270 :   if( FD_UNLIKELY( !SUB_PARSE( fd_toml_parse_ml_literal_string_delim( parser ) ) ) ) return 0;
     735           0 :   if( FD_UNLIKELY( !fd_toml_avail( parser ) ) )                                      return 0;
     736           0 :     if( parser->c.data[0] == '\n' ) {
     737           0 :     fd_toml_advance( parser, 1UL );
     738           0 :   }
     739           0 :   fd_toml_str_init( parser );
     740           0 :   if( FD_UNLIKELY( !SUB_PARSE( fd_toml_parse_ml_literal_body        ( parser ) ) ) ) return 0;
     741           0 :   if( FD_UNLIKELY( !SUB_PARSE( fd_toml_parse_ml_literal_string_delim( parser ) ) ) ) return 0;
     742           0 :   return 1;
     743           0 : }
     744             : 
     745             : /* string = ml-basic-string / basic-string / ml-literal-string / literal-string */
     746             : 
     747             : static int
     748         372 : fd_toml_parse_string( fd_toml_parser_t * parser ) {
     749         372 :   if( FD_LIKELY( SUB_PARSE( fd_toml_parse_ml_basic_string  ( parser ) ) ) ) goto add;
     750         372 :   if( FD_LIKELY( SUB_PARSE( fd_toml_parse_basic_string     ( parser ) ) ) ) goto add;
     751         270 :   if( FD_LIKELY( SUB_PARSE( fd_toml_parse_ml_literal_string( parser ) ) ) ) goto add;
     752         270 :   if( FD_LIKELY( SUB_PARSE( fd_toml_parse_literal_string   ( parser ) ) ) ) goto add;
     753         270 :   return 0;
     754         102 : add:
     755         102 :   if( FD_UNLIKELY( !fd_toml_str_append_byte( parser, 0 ) ) ) return 0;
     756         102 :   if( FD_UNLIKELY( !fd_pod_insert(
     757         102 :       parser->pod, parser->key, FD_POD_VAL_TYPE_CSTR,
     758         102 :       (ulong)parser->scratch_cur - (ulong)parser->scratch,
     759         102 :       (char *)parser->scratch ) ) ) {
     760           0 :     parser->error = FD_TOML_ERR_POD;
     761           0 :     return 0;
     762           0 :   }
     763         102 :   return 1;
     764         102 : }
     765             : 
     766             : /* boolean = true / false */
     767             : 
     768             : static int
     769         270 : fd_toml_parse_boolean( fd_toml_parser_t * parser ) {
     770         270 :   int boolv = 0;
     771         270 :   if( parser->c.data + 4 > parser->data_end ) return 0;
     772         270 :   if( 0==memcmp( parser->c.data, "true", 4 ) ) {
     773          33 :     fd_toml_advance_inline( parser, 4 );
     774          33 :     boolv = 1;
     775          33 :     goto add;
     776          33 :   }
     777         237 :   if( parser->c.data + 5 > parser->data_end ) return 0;
     778         237 :   if( 0==memcmp( parser->c.data, "false", 5 ) ) {
     779          54 :     fd_toml_advance_inline( parser, 5 );
     780          54 :     boolv = 0;
     781          54 :     goto add;
     782          54 :   }
     783         183 :   return 0;
     784          87 : add:
     785          87 :   if( FD_UNLIKELY( !fd_pod_insert_int( parser->pod, parser->key, boolv ) ) ) {
     786           0 :     parser->error = FD_TOML_ERR_POD;
     787           0 :     return 0;
     788           0 :   }
     789          87 :   return 1;
     790          87 : }
     791             : 
     792             : /* ws-comment-newline = *( wschar / [ comment ] newline ) */
     793             : 
     794             : static int
     795          48 : fd_toml_parse_ws_comment_newline_inner( fd_toml_parser_t * parser ) {
     796          48 :   if( FD_UNLIKELY( !fd_toml_avail( parser ) ) ) return 0;
     797          48 :   int c = (uchar)parser->c.data[0];
     798          48 :   if( FD_UNLIKELY( c == ' ' || c == '\t' ) ) {
     799           0 :     fd_toml_advance_inline( parser, 1UL );
     800           0 :     return 1;
     801           0 :   }
     802          96 :   SUB_PARSE( fd_toml_parse_comment( parser ) );
     803          48 :   if( FD_UNLIKELY( !fd_toml_avail( parser )  ) ) return 0;
     804          48 :   if( FD_UNLIKELY( parser->c.data[0] != '\n' ) ) return 0;
     805           0 :   fd_toml_advance( parser, 1UL );
     806           0 :   return 1;
     807          48 : }
     808             : 
     809             : static int
     810          48 : fd_toml_parse_ws_comment_newline( fd_toml_parser_t * parser ) {
     811          48 :   while( SUB_PARSE( fd_toml_parse_ws_comment_newline_inner( parser ) ) ) {}
     812           0 :   return 1;
     813          48 : }
     814             : 
     815             : /* array-values =  ws-comment-newline val ws-comment-newline array-sep array-values
     816             :    array-values =/ ws-comment-newline val ws-comment-newline [ array-sep ] */
     817             : 
     818             : static int
     819          24 : fd_toml_parse_array_values( fd_toml_parser_t * parser ) {
     820             : 
     821          24 :   uint   old_len     = parser->key_len;
     822          24 :   char * suffix_cstr = parser->key + parser->key_len;
     823          24 :   if( FD_UNLIKELY( suffix_cstr + 22 > parser->key + sizeof(parser->key) ) ) {
     824             :     /* array index might be OOB (see python3 -c 'print(len(str(1<<64)))') */
     825           0 :     parser->error = FD_TOML_ERR_KEY;
     826           0 :     return 0;
     827           0 :   }
     828             : 
     829             :   /* Unrolled tail recursion with backtracking */
     830             : 
     831          24 :   fd_toml_cur_t backtrack = parser->c;
     832          24 :   for( ulong j=0;; j++ ) {
     833          24 :     char * child_key = fd_cstr_append_char( suffix_cstr, '.' );
     834          24 :            child_key = fd_cstr_append_ulong_as_text( child_key, 0, 0, j, fd_ulong_base10_dig_cnt( j ) );
     835          24 :     fd_cstr_fini( child_key );
     836          24 :     parser->key_len = (uint)( child_key - parser->key );
     837             : 
     838          24 :     fd_toml_parse_ws_comment_newline( parser );
     839          24 :     if( FD_UNLIKELY( !fd_toml_parse_val( parser ) ) ) {
     840          24 :       parser->c = backtrack;
     841          24 :       break;
     842          24 :     }
     843             : 
     844           0 :     FD_LOG_DEBUG(( "Added key %s", parser->key ));
     845             : 
     846           0 :     fd_toml_parse_ws_comment_newline( parser );
     847             : 
     848           0 :     backtrack = parser->c;
     849           0 :     if( fd_toml_avail( parser ) && parser->c.data[0] == ',' ) {
     850           0 :       fd_toml_advance_inline( parser, 1UL );
     851           0 :     } else {
     852           0 :       break;
     853           0 :     }
     854           0 :     backtrack = parser->c;
     855           0 :   }
     856             : 
     857             :   /* Undo array index */
     858             : 
     859          24 :   fd_cstr_fini( suffix_cstr );
     860          24 :   parser->key_len = old_len;
     861          24 :   return 1;
     862          24 : }
     863             : 
     864             : /* array = array-open [ array-values ] ws-comment-newline array-close
     865             : 
     866             :    array-open =  %x5B ; [
     867             :    array-close = %x5D ; ] */
     868             : 
     869             : static int
     870         183 : fd_toml_parse_array( fd_toml_parser_t * parser ) {
     871         183 :   uint key_len = parser->key_len;
     872             : 
     873         183 :   EXPECT_CHAR( '[' );
     874          24 :   fd_toml_upsert_empty_pod( parser );
     875          24 :   SUB_PARSE( fd_toml_parse_array_values      ( parser ) );
     876          24 :   SUB_PARSE( fd_toml_parse_ws_comment_newline( parser ) );
     877          24 :   EXPECT_CHAR( ']' );
     878             : 
     879          24 :   parser->key_len        = key_len;
     880          24 :   parser->key[ key_len ] = 0;
     881             : 
     882          24 :   return 1;
     883          24 : }
     884             : 
     885             : /* inline-table-sep   = ws %x2C ws  ; , Comma */
     886             : 
     887             : static int
     888           0 : fd_toml_parse_inline_table_sep( fd_toml_parser_t * parser ) {
     889           0 :   fd_toml_parse_ws( parser );
     890           0 :   if( FD_UNLIKELY( !fd_toml_avail( parser ) ) ) return 0;
     891           0 :   if( FD_UNLIKELY( parser->c.data[0] != ',' ) ) return 0;
     892           0 :   fd_toml_advance_inline( parser, 1UL );
     893           0 :   fd_toml_parse_ws( parser );
     894           0 :   return 1;
     895           0 : }
     896             : 
     897             : /* inline-table-keyvals = keyval [ inline-table-sep inline-table-keyvals ] */
     898             : 
     899             : static int
     900           0 : fd_toml_parse_inline_table_keyvals( fd_toml_parser_t * parser ) {
     901             : 
     902             :   /* Unrolled tail recursion with backtracking */
     903             : 
     904           0 :   if( !fd_toml_parse_keyval( parser ) ) return 0;
     905           0 :   fd_toml_cur_t backtrack = parser->c;
     906           0 :   for(;;) {
     907           0 :     if( !fd_toml_parse_inline_table_sep( parser ) ) {
     908           0 :       parser->c = backtrack;
     909           0 :       break;
     910           0 :     }
     911           0 :     if( !fd_toml_parse_keyval( parser ) ) return 0;
     912           0 :     backtrack = parser->c;
     913           0 :   }
     914             : 
     915           0 :   return 1;
     916           0 : }
     917             : 
     918             : /* inline-table = inline-table-open [ inline-table-keyvals ] inline-table-close
     919             : 
     920             :    inline-table-open  = %x7B ws ; {
     921             :    inline-table-close = ws %x7D ; } */
     922             : 
     923             : static int
     924         159 : fd_toml_parse_inline_table( fd_toml_parser_t * parser ) {
     925             : 
     926         159 :   EXPECT_CHAR( '{' );
     927           0 :   fd_toml_parse_ws( parser );
     928             : 
     929           0 :   uint old_key_len = parser->key_len;
     930           0 :   if( parser->key_len + 2 > sizeof(parser->key) ) {
     931           0 :     parser->error = FD_TOML_ERR_KEY;
     932           0 :     return 0;
     933           0 :   }
     934             : 
     935           0 :   parser->key[ parser->key_len   ] = '\x00';
     936           0 :   fd_toml_upsert_empty_pod( parser );
     937             : 
     938           0 :   parser->key[ parser->key_len++ ] = '.';
     939           0 :   parser->key[ parser->key_len   ] = '\x00';
     940             : 
     941           0 :   while( SUB_PARSE( fd_toml_parse_inline_table_keyvals( parser ) ) ) {}
     942             : 
     943           0 :   fd_toml_parse_ws( parser );
     944           0 :   EXPECT_CHAR( '}' );
     945             : 
     946           0 :   parser->key_len            = old_key_len;
     947           0 :   parser->key[ old_key_len ] = '\x00';
     948           0 :   return 1;
     949           0 : }
     950             : 
     951             : /* dec-int = [ minus / plus ] unsigned-dec-int
     952             :    unsigned-dec-int = DIGIT / digit1-9 1*( DIGIT / underscore DIGIT ) */
     953             : 
     954             : struct fd_toml_dec {
     955             :   ulong res;
     956             :   uint  len;
     957             :   uchar neg : 1;
     958             : };
     959             : 
     960             : typedef struct fd_toml_dec fd_toml_dec_t;
     961             : 
     962             : /* zero-prefixable-int = DIGIT *( DIGIT / underscore DIGIT )
     963             : 
     964             :    fd_toml_parse_zero_prefixable_int parses [0-9](_[0-9]|[0-9])*
     965             :    Assumes the first digit has been validated prior to call. */
     966             : 
     967             : static int
     968             : fd_toml_parse_zero_prefixable_int( fd_toml_parser_t * parser,
     969         246 :                                    fd_toml_dec_t *    dec ) {
     970             : 
     971         246 :   uint  len;
     972         246 :   ulong digits = 0UL;
     973         246 :   int allow_underscore = 0;
     974         942 :   for( len=0;; len++ ) {
     975         942 :     if( FD_UNLIKELY( allow_underscore && parser->c.data[0] == '_' ) ) {
     976          18 :       allow_underscore = 0;
     977          18 :       fd_toml_advance_inline( parser, 1UL );
     978          18 :       if( FD_UNLIKELY( !fd_toml_avail( parser )      ) ) return 0;
     979          18 :       if( FD_UNLIKELY( !isdigit( parser->c.data[0] ) ) ) return 0;
     980         924 :     } else {
     981         924 :       int digit = (uchar)parser->c.data[0];
     982         924 :       if( FD_UNLIKELY(
     983         924 :           __builtin_umull_overflow( digits, 10, &digits ) ||
     984         924 :           __builtin_uaddl_overflow( digits, (ulong)( digit - '0' ), &digits ) ) ) {
     985           0 :         parser->error = FD_TOML_ERR_RANGE;
     986           0 :         return 0;
     987           0 :       }
     988         924 :       fd_toml_advance_inline( parser, 1UL );
     989         924 :       if( !fd_toml_avail( parser ) ) break;
     990         924 :       if( !isdigit( parser->c.data[0] ) && parser->c.data[0] != '_' ) break;
     991         678 :       allow_underscore = 1;
     992         678 :     }
     993         942 :   }
     994             : 
     995         246 :   dec->res = digits;
     996         246 :   dec->len = len;
     997         246 :   return 1;
     998         246 : }
     999             : 
    1000             : static int
    1001             : fd_toml_parse_dec_int_( fd_toml_parser_t * parser,
    1002         318 :                         fd_toml_dec_t *    dec ) {
    1003         318 :   if( FD_UNLIKELY( !fd_toml_avail( parser ) ) ) return 0;
    1004         318 :   int c = (uchar)parser->c.data[0];
    1005             : 
    1006         318 :   int neg = 0;
    1007         318 :   switch( c ) {
    1008           0 :   case '-':
    1009           0 :     neg = 1;
    1010           0 :     __attribute__((fallthrough));
    1011           0 :   case '+':
    1012           0 :     fd_toml_advance_inline( parser, 1UL );
    1013           0 :     break;
    1014         318 :   }
    1015             : 
    1016         318 :   if( FD_UNLIKELY( !fd_toml_avail( parser ) ) ) return 0;
    1017         318 :   int first_digit = (uchar)parser->c.data[0];
    1018         318 :   if( first_digit == '0' ) {
    1019          24 :     dec->res = 0UL;
    1020          24 :     dec->neg = 0;
    1021          24 :     fd_toml_advance_inline( parser, 1UL );
    1022          24 :     return 1;
    1023          24 :   }
    1024             : 
    1025         294 :   if( FD_UNLIKELY( first_digit<='0' || first_digit>'9' ) ) return 0;
    1026             : 
    1027         246 :   dec->neg = !!neg;
    1028         246 :   return fd_toml_parse_zero_prefixable_int( parser, dec );
    1029         294 : }
    1030             : 
    1031             : static int
    1032         159 : fd_toml_parse_dec_int( fd_toml_parser_t * parser ) {
    1033         159 :   fd_toml_dec_t dec = {0};
    1034         159 :   if( FD_UNLIKELY( !fd_toml_parse_dec_int_( parser, &dec ) ) ) return 0;
    1035         135 :   long val = (long)dec.res;
    1036         135 :        val = fd_long_if( dec.neg, -val, val );
    1037         135 :   if( FD_UNLIKELY( !fd_pod_insert_long( parser->pod, parser->key, val ) ) ) {
    1038           0 :     parser->error = FD_TOML_ERR_POD;
    1039           0 :     return 0;
    1040           0 :   }
    1041         135 :   return 1;
    1042         135 : }
    1043             : 
    1044             : /* hex-int = hex-prefix HEXDIG *( HEXDIG / underscore HEXDIG ) */
    1045             : 
    1046             : static int
    1047         159 : fd_toml_parse_hex_int( fd_toml_parser_t * parser ) {
    1048         159 :   if( FD_UNLIKELY( fd_toml_avail( parser ) < 3    ) ) return 0;
    1049         159 :   if( FD_UNLIKELY( parser->c.data[0] != '0'       ) ) return 0;
    1050          12 :   if( FD_UNLIKELY( parser->c.data[1] != 'x'       ) ) return 0;
    1051           0 :   if( FD_UNLIKELY( !isxdigit( parser->c.data[2] ) ) ) return 0;  /* at least one digit */
    1052           0 :   fd_toml_advance_inline( parser, 2UL );
    1053             : 
    1054           0 :   ulong res = 0UL;
    1055           0 :   int allow_underscore = 0;
    1056           0 :   for(;;) {
    1057           0 :     int digit = (uchar)parser->c.data[0];
    1058           0 :     if( FD_UNLIKELY( allow_underscore && digit == '_' ) ) {
    1059           0 :       allow_underscore = 0;
    1060           0 :       fd_toml_advance_inline( parser, 1UL );
    1061           0 :       if( FD_UNLIKELY( !fd_toml_avail( parser )       ) ) return 0;
    1062           0 :       if( FD_UNLIKELY( !isxdigit( parser->c.data[0] ) ) ) return 0;
    1063           0 :     } else {
    1064           0 :       if( !isxdigit( digit ) ) break;
    1065           0 :       if( FD_UNLIKELY( res>>60 ) ) {
    1066           0 :         parser->error = FD_TOML_ERR_RANGE;
    1067           0 :         return 0;
    1068           0 :       }
    1069           0 :       res <<= 4;
    1070           0 :       res  |= fd_toml_xdigit( digit );
    1071           0 :       fd_toml_advance_inline( parser, 1UL );
    1072           0 :       if( !fd_toml_avail( parser ) ) break;
    1073           0 :       allow_underscore = 1;
    1074           0 :     }
    1075           0 :   }
    1076             : 
    1077           0 :   if( FD_UNLIKELY( !fd_pod_insert_long( parser->pod, parser->key, (long)res ) ) ) {
    1078           0 :     parser->error = FD_TOML_ERR_POD;
    1079           0 :     return 0;
    1080           0 :   }
    1081             : 
    1082           0 :   return 1;
    1083           0 : }
    1084             : 
    1085             : /* oct-int = oct-prefix digit0-7 *( digit0-7 / underscore digit0-7 ) */
    1086             : 
    1087             : static inline int
    1088           0 : fd_toml_is_odigit( int c ) {
    1089           0 :   return c>='0' && c<'8';
    1090           0 : }
    1091             : 
    1092             : static int
    1093         159 : fd_toml_parse_oct_int( fd_toml_parser_t * parser ) {
    1094         159 :   if( FD_UNLIKELY( fd_toml_avail( parser ) < 3             ) ) return 0;
    1095         159 :   if( FD_UNLIKELY( parser->c.data[0] != '0'                ) ) return 0;
    1096          12 :   if( FD_UNLIKELY( parser->c.data[1] != 'o'                ) ) return 0;
    1097           0 :   if( FD_UNLIKELY( !fd_toml_is_odigit( parser->c.data[2] ) ) ) return 0;  /* at least one digit */
    1098           0 :   fd_toml_advance_inline( parser, 2UL );
    1099             : 
    1100           0 :   ulong res = 0UL;
    1101           0 :   int allow_underscore = 0;
    1102           0 :   for(;;) {
    1103           0 :     int digit = (uchar)parser->c.data[0];
    1104           0 :     if( allow_underscore && digit == '_' ) {
    1105           0 :       allow_underscore = 0;
    1106           0 :       fd_toml_advance_inline( parser, 1UL );
    1107           0 :       if( FD_UNLIKELY( !fd_toml_avail( parser )                ) ) return 0;
    1108           0 :       if( FD_UNLIKELY( !fd_toml_is_odigit( parser->c.data[0] ) ) ) return 0;
    1109           0 :     } else {
    1110           0 :       if( !fd_toml_is_odigit( digit ) ) break;
    1111           0 :       if( FD_UNLIKELY( res>>61 ) ) {
    1112           0 :         parser->error = FD_TOML_ERR_RANGE;
    1113           0 :         return 0;
    1114           0 :       }
    1115           0 :       res <<= 3;
    1116           0 :       res  |= (ulong)( digit - '0' );
    1117           0 :       fd_toml_advance_inline( parser, 1UL );
    1118           0 :       if( !fd_toml_avail( parser ) ) break;
    1119           0 :       allow_underscore = 1;
    1120           0 :     }
    1121           0 :   }
    1122             : 
    1123           0 :   if( FD_UNLIKELY( !fd_pod_insert_long( parser->pod, parser->key, (long)res ) ) ) {
    1124           0 :     parser->error = FD_TOML_ERR_POD;
    1125           0 :     return 0;
    1126           0 :   }
    1127             : 
    1128           0 :   return 1;
    1129           0 : }
    1130             : 
    1131             : /* bin-int = bin-prefix digit0-1 *( digit0-1 / underscore digit0-1 ) */
    1132             : 
    1133             : static inline int
    1134           0 : fd_toml_is_bdigit( int c ) {
    1135           0 :   return c=='0' || c=='1';
    1136           0 : }
    1137             : 
    1138             : static int
    1139         159 : fd_toml_parse_bin_int( fd_toml_parser_t * parser ) {
    1140         159 :   if( FD_UNLIKELY( fd_toml_avail( parser ) < 3             ) ) return 0;
    1141         159 :   if( FD_UNLIKELY( parser->c.data[0] != '0'                ) ) return 0;
    1142          12 :   if( FD_UNLIKELY( parser->c.data[1] != 'b'                ) ) return 0;
    1143           0 :   if( FD_UNLIKELY( !fd_toml_is_bdigit( parser->c.data[2] ) ) ) return 0;  /* at least one digit */
    1144           0 :   fd_toml_advance_inline( parser, 2UL );
    1145             : 
    1146             :   /* TODO OVERFLOW DETECTION */
    1147             : 
    1148           0 :   ulong res = 0UL;
    1149           0 :   int allow_underscore = 0;
    1150           0 :   for(;;) {
    1151           0 :     int digit = (uchar)parser->c.data[0];
    1152           0 :     if( FD_UNLIKELY( allow_underscore && digit == '_' ) ) {
    1153           0 :       allow_underscore = 0;
    1154           0 :       fd_toml_advance_inline( parser, 1UL );
    1155           0 :       if( FD_UNLIKELY( !fd_toml_avail( parser )                ) ) return 0;
    1156           0 :       if( FD_UNLIKELY( !fd_toml_is_bdigit( parser->c.data[0] ) ) ) return 0;
    1157           0 :     } else {
    1158           0 :       if( !fd_toml_is_bdigit( digit ) ) break;
    1159           0 :       if( FD_UNLIKELY( res>>63 ) ) {
    1160           0 :         parser->error = FD_TOML_ERR_RANGE;
    1161           0 :         return 0;
    1162           0 :       }
    1163           0 :       res <<= 1;
    1164           0 :       res  |= (ulong)( digit - '0' );
    1165           0 :       fd_toml_advance_inline( parser, 1UL );
    1166           0 :       if( !fd_toml_avail( parser ) ) break;
    1167           0 :       allow_underscore = 1;
    1168           0 :     }
    1169           0 :   }
    1170             : 
    1171           0 :   if( FD_UNLIKELY( !fd_pod_insert_long( parser->pod, parser->key, (long)res ) ) ) {
    1172           0 :     parser->error = FD_TOML_ERR_POD;
    1173           0 :     return 0;
    1174           0 :   }
    1175             : 
    1176           0 :   return 1;
    1177           0 : }
    1178             : 
    1179             : /* integer = dec-int / hex-int / oct-int / bin-int */
    1180             : 
    1181             : static int
    1182         159 : fd_toml_parse_integer( fd_toml_parser_t * parser ) {
    1183         159 :   if( SUB_PARSE( fd_toml_parse_hex_int( parser ) ) ) return 1;
    1184         159 :   if( SUB_PARSE( fd_toml_parse_oct_int( parser ) ) ) return 1;
    1185         159 :   if( SUB_PARSE( fd_toml_parse_bin_int( parser ) ) ) return 1;
    1186         159 :   if( SUB_PARSE( fd_toml_parse_dec_int( parser ) ) ) return 1;
    1187          24 :   return 0;
    1188         159 : }
    1189             : 
    1190             : /* exp = "e" float-exp-part
    1191             :    float-exp-part = [ minus / plus ] zero-prefixable-int */
    1192             : 
    1193             : static int
    1194             : fd_toml_parse_exp( fd_toml_parser_t * parser,
    1195         135 :                    fd_toml_dec_t *    exp ) {
    1196         135 :   if( FD_UNLIKELY( fd_toml_avail( parser ) < 2 ) ) return 0;
    1197         135 :   switch( parser->c.data[0] ) {
    1198           0 :     case 'e': case 'E': break;
    1199         135 :     default:            return 0;
    1200         135 :   }
    1201           0 :   fd_toml_advance_inline( parser, 1UL );
    1202             : 
    1203           0 :   switch( parser->c.data[0] ) {
    1204           0 :   case '-':
    1205           0 :     exp->neg = 1;
    1206           0 :     __attribute__((fallthrough));
    1207           0 :   case '+':
    1208           0 :     fd_toml_advance_inline( parser, 1UL );
    1209           0 :     if( FD_UNLIKELY( !fd_toml_avail( parser ) ) ) return 0;
    1210           0 :     break;
    1211           0 :   }
    1212             : 
    1213           0 :   int first_digit = (uchar)parser->c.data[0];
    1214           0 :   if( FD_UNLIKELY( first_digit<'0' || first_digit>'9'                ) ) return 0;
    1215           0 :   if( FD_UNLIKELY( !fd_toml_parse_zero_prefixable_int( parser, exp ) ) ) return 0;
    1216           0 :   return 1;
    1217           0 : }
    1218             : 
    1219             : /* frac = decimal-point zero-prefixable-int
    1220             :    decimal-point = %x2E */
    1221             : 
    1222             : static int
    1223             : fd_toml_parse_frac( fd_toml_parser_t * parser,
    1224         135 :                     fd_toml_dec_t *    frac ) {
    1225         135 :   if( FD_UNLIKELY( fd_toml_avail( parser ) < 2                        ) ) return 0;
    1226         135 :   if( FD_UNLIKELY( parser->c.data[0] != '.'                           ) ) return 0;
    1227           0 :   fd_toml_advance_inline( parser, 1UL );
    1228             : 
    1229           0 :   int first_digit = (uchar)parser->c.data[0];
    1230           0 :   if( FD_UNLIKELY( first_digit<'0' || first_digit>'9'                 ) ) return 0;
    1231           0 :   if( FD_UNLIKELY( !fd_toml_parse_zero_prefixable_int( parser, frac ) ) ) return 0;
    1232           0 :   return 1;
    1233           0 : }
    1234             : 
    1235             : /* float = float-int-part ( exp / frac [ exp ] )
    1236             :    float-int-part = dec-int */
    1237             : 
    1238             : static int
    1239         159 : fd_toml_parse_float_normal( fd_toml_parser_t * parser ) {
    1240             : 
    1241         159 :   fd_toml_dec_t stem = {0};
    1242         159 :   if( FD_UNLIKELY( !fd_toml_parse_dec_int_( parser, &stem ) ) ) return 0;
    1243         135 :   if( FD_UNLIKELY( !fd_toml_avail( parser )                 ) ) return 0;
    1244         135 :   float res = (float)stem.res;
    1245             : 
    1246         135 :   int ok = 0;
    1247         135 :   fd_toml_dec_t frac_dec = {0};
    1248         135 :   if( SUB_PARSE( fd_toml_parse_frac( parser, &frac_dec ) ) ) {
    1249           0 :     float frac = (float)frac_dec.res;
    1250           0 :     while( frac_dec.len-- ) frac /= 10.0f;  /* use pow? */
    1251           0 :     res += frac;
    1252           0 :     ok   = 1;
    1253           0 :   }
    1254             : 
    1255           0 :   fd_toml_dec_t exp_dec = {0};
    1256         135 :   if( !SUB_PARSE( fd_toml_parse_exp( parser, &exp_dec ) ) ) {
    1257         135 :     if( FD_LIKELY( ok ) ) goto parsed;
    1258         135 :     return 0;
    1259         135 :   }
    1260             : 
    1261           0 :   float exp = powf( exp_dec.neg ? 0.1f : 10.0f, (float)exp_dec.res );
    1262           0 :   res *= exp;
    1263             : 
    1264           0 : parsed:
    1265           0 :   if( FD_UNLIKELY( !fd_pod_insert_float( parser->pod, parser->key, res ) ) ) {
    1266           0 :     parser->error = FD_TOML_ERR_POD;
    1267           0 :     return 0;
    1268           0 :   }
    1269           0 :   return 1;
    1270           0 : }
    1271             : 
    1272             : /* special-float = [ minus / plus ] ( inf / nan )
    1273             :    inf = %x69.6e.66  ; inf
    1274             :    nan = %x6e.61.6e  ; nan */
    1275             : 
    1276             : static int
    1277         159 : fd_float_parse_float_special( fd_toml_parser_t * parser ) {
    1278         159 :   if( FD_UNLIKELY( fd_toml_avail( parser ) < 3 ) ) return 0;
    1279         159 :   int c = (uchar)parser->c.data[0];
    1280             : 
    1281         159 :   switch( c ) {
    1282           0 :   case '-': case '+':
    1283           0 :     fd_toml_advance_inline( parser, 1UL );
    1284           0 :     if( FD_UNLIKELY( fd_toml_avail( parser ) < 3 ) ) return 0;
    1285           0 :     break;
    1286         159 :   }
    1287             : 
    1288         159 :   char const * str = parser->c.data;
    1289         159 :   fd_toml_advance_inline( parser, 3UL );
    1290             : 
    1291         159 :   if( 0==strncasecmp( str, "inf", 3 ) ) {
    1292           0 :     FD_LOG_WARNING(( "float infinity is unsupported" ));
    1293           0 :     parser->error = FD_TOML_ERR_RANGE;
    1294           0 :     return 0;
    1295           0 :   }
    1296             : 
    1297         159 :   if( 0==strncasecmp( str, "nan", 3 ) ) {
    1298           0 :     FD_LOG_WARNING(( "float NaN is unsupported" ));
    1299           0 :     parser->error = FD_TOML_ERR_RANGE;
    1300           0 :     return 1;
    1301           0 :   }
    1302             : 
    1303         159 :   return 0;
    1304         159 : }
    1305             : 
    1306             : /* float = float-int-part ( exp / frac [ exp ] )
    1307             :    float =/ special-float */
    1308             : 
    1309             : static int
    1310         159 : fd_toml_parse_float( fd_toml_parser_t * parser ) {
    1311         159 :   if( SUB_PARSE( fd_toml_parse_float_normal  ( parser ) ) ) return 1;
    1312         159 :   if( SUB_PARSE( fd_float_parse_float_special( parser ) ) ) return 1;
    1313         159 :   return 0;
    1314         159 : }
    1315             : 
    1316             : /* full-date      = date-fullyear "-" date-month "-" date-mday
    1317             :    date-fullyear  = 4DIGIT
    1318             :    date-month     = 2DIGIT  ; 01-12
    1319             :    date-mday      = 2DIGIT  ; 01-28, 01-29, 01-30, 01-31 based on month/year */
    1320             : 
    1321             : static int
    1322             : fd_toml_parse_full_date( fd_toml_parser_t * parser,
    1323         477 :                          struct tm *        time ) {
    1324         477 :   if( FD_UNLIKELY( fd_toml_avail( parser ) < 10 ) ) return 0;
    1325             : 
    1326         477 :   if( ( !isdigit( parser->c.data[0] ) )      |
    1327         477 :       ( !isdigit( parser->c.data[1] ) )      |
    1328         477 :       ( !isdigit( parser->c.data[2] ) )      |
    1329         477 :       ( !isdigit( parser->c.data[3] ) )      |
    1330         477 :                 ( parser->c.data[4] != '-' ) |
    1331         477 :       ( !isdigit( parser->c.data[5] ) )      |
    1332         477 :       ( !isdigit( parser->c.data[6] ) )      |
    1333         477 :                 ( parser->c.data[7] != '-' ) |
    1334         477 :       ( !isdigit( parser->c.data[8] ) ) |
    1335         477 :       ( !isdigit( parser->c.data[9] ) ) ) {
    1336         477 :     return 0;
    1337         477 :   }
    1338             : 
    1339           0 :   char cstr[ 11 ];
    1340           0 :   memcpy( cstr, parser->c.data, 10 );
    1341           0 :   cstr[10] = '\x00';
    1342           0 :   fd_toml_advance_inline( parser, 10UL );
    1343             : 
    1344           0 :   if( FD_UNLIKELY( !strptime( cstr, "%Y-%m-%d", time ) ) ) {
    1345           0 :     FD_LOG_WARNING(( "invalid date format" ));
    1346           0 :     return 0;
    1347           0 :   }
    1348           0 :   return 1;
    1349           0 : }
    1350             : 
    1351             : /* time-delim = "T" / %x20 ; T, t, or space */
    1352             : 
    1353             : static int
    1354           0 : fd_toml_parse_time_delim( fd_toml_parser_t * parser ) {
    1355           0 :   if( FD_UNLIKELY( !fd_toml_avail( parser ) ) ) return 0;
    1356           0 :   switch( parser->c.data[0] ) {
    1357           0 :   case 'T': case 't': case ' ': break;
    1358           0 :   default:                      return 0;
    1359           0 :   }
    1360           0 :   fd_toml_advance_inline( parser, 1UL );
    1361           0 :   return 1;
    1362           0 : }
    1363             : 
    1364             : /* time-secfrac = "." 1*DIGIT */
    1365             : 
    1366             : static int
    1367             : fd_toml_parse_time_secfrac( fd_toml_parser_t * parser,
    1368           0 :                             ulong *            pnanos ) {
    1369           0 :   if( fd_toml_avail( parser ) < 2                  ) return 0;
    1370           0 :   if( parser->c.data[0] != '.'                     ) return 0;
    1371           0 :   if( FD_UNLIKELY( !isdigit( parser->c.data[1] ) ) ) return 0;
    1372           0 :   fd_toml_advance_inline( parser, 1UL );
    1373             : 
    1374           0 :   ulong secfrac = 0UL;
    1375           0 :   ulong len     = 0UL;
    1376           0 :   do {
    1377           0 :     int digit = (uchar)parser->c.data[0];
    1378           0 :     secfrac = secfrac * 10UL + (ulong)( digit - '0' );
    1379           0 :     fd_toml_advance_inline( parser, 1UL );
    1380           0 :     len++;
    1381           0 :   } while( fd_toml_avail( parser ) && isdigit( parser->c.data[0] ) );
    1382           0 :   if( FD_UNLIKELY( len > 9 ) ) {
    1383           0 :     FD_LOG_WARNING(( "invalid time fraction format" ));
    1384           0 :     return 0;
    1385           0 :   }
    1386             : 
    1387           0 :   while( len++ < 9 ) secfrac *= 10UL;
    1388           0 :   *pnanos = secfrac;
    1389           0 :   return 1;
    1390           0 : }
    1391             : 
    1392             : /* partial-time = time-hour ":" time-minute ":" time-second [ time-secfrac ]
    1393             :    time-hour      = 2DIGIT  ; 00-23
    1394             :    time-minute    = 2DIGIT  ; 00-59
    1395             :    time-second    = 2DIGIT  ; 00-58, 00-59, 00-60 based on leap second rules */
    1396             : 
    1397             : static int
    1398             : fd_toml_parse_partial_time( fd_toml_parser_t * parser,
    1399         159 :                             ulong *            pnanos ) {
    1400             : 
    1401         159 :   if( FD_UNLIKELY( fd_toml_avail( parser ) < 8 ) ) return 0;
    1402         159 :   if( ( !isdigit( parser->c.data[0] ) )      |
    1403         159 :       ( !isdigit( parser->c.data[1] ) )      |
    1404         159 :                 ( parser->c.data[2] != ':' ) |
    1405         159 :       ( !isdigit( parser->c.data[3] ) )      |
    1406         159 :       ( !isdigit( parser->c.data[4] ) )      |
    1407         159 :                 ( parser->c.data[5] != ':' ) |
    1408         159 :       ( !isdigit( parser->c.data[6] ) )      |
    1409         159 :       ( !isdigit( parser->c.data[7] ) ) ) {
    1410         159 :     return 0;
    1411         159 :   }
    1412             : 
    1413           0 :   char cstr[ 9 ];
    1414           0 :   memcpy( cstr, parser->c.data, 8 );
    1415           0 :   cstr[8] = '\x00';
    1416           0 :   fd_toml_advance_inline( parser, 8UL );
    1417             : 
    1418           0 :   struct tm time[1];
    1419           0 :   if( FD_UNLIKELY( !strptime( cstr, "%H:%M:%S", time ) ) ) {
    1420           0 :     FD_LOG_WARNING(( "invalid time format" ));
    1421           0 :     return 0;
    1422           0 :   }
    1423             : 
    1424           0 :   ulong res = 0UL;
    1425           0 :         res += (ulong)time->tm_hour * 3600UL;
    1426           0 :         res += (ulong)time->tm_min  *   60UL;
    1427           0 :         res += (ulong)time->tm_sec;
    1428           0 :         res *= (ulong)1e9;
    1429           0 :   ulong ns_frac;
    1430           0 :   if( SUB_PARSE( fd_toml_parse_time_secfrac( parser, &ns_frac ) ) ) {
    1431           0 :     res += ns_frac;
    1432           0 :   }
    1433           0 :   *pnanos = res;
    1434           0 :   return 1;
    1435           0 : }
    1436             : 
    1437             : /* time-numoffset = ( "+" / "-" ) time-hour ":" time-minute */
    1438             : 
    1439             : static int
    1440             : fd_toml_parse_time_numoffset( fd_toml_parser_t * parser,
    1441           0 :                               long *             psec ) {
    1442           0 :   if( FD_UNLIKELY( !fd_toml_avail( parser ) ) ) return 0;
    1443             : 
    1444           0 :   int neg = 0;
    1445           0 :   switch( parser->c.data[0] ) {
    1446           0 :   case '+': neg = 0; break;
    1447           0 :   case '-': neg = 1; break;
    1448           0 :   default: return 0;
    1449           0 :   }
    1450           0 :   fd_toml_advance_inline( parser, 1UL );
    1451             : 
    1452           0 :   if( FD_UNLIKELY( fd_toml_avail( parser ) < 5   ) ) return 0;
    1453           0 :   if( ( !isdigit( parser->c.data[0] ) ) |
    1454           0 :       ( !isdigit( parser->c.data[1] ) ) |
    1455           0 :                 ( parser->c.data[2] != ':' ) |
    1456           0 :       ( !isdigit( parser->c.data[3] ) ) |
    1457           0 :       ( !isdigit( parser->c.data[4] ) ) ) {
    1458           0 :     FD_LOG_WARNING(( "invalid time offset format" ));
    1459           0 :     return 0;
    1460           0 :   }
    1461             : 
    1462           0 :   char cstr[ 6 ];
    1463           0 :   memcpy( cstr, parser->c.data, 5 );
    1464           0 :   cstr[5] = '\x00';
    1465           0 :   fd_toml_advance_inline( parser, 5UL );
    1466             : 
    1467           0 :   struct tm time;
    1468           0 :   if( FD_UNLIKELY( !strptime( cstr, "%H:%M", &time ) ) ) {
    1469           0 :     FD_LOG_WARNING(( "invalid time offset format" ));
    1470           0 :     return 0;
    1471           0 :   }
    1472           0 :   long abs_sec = (long)time.tm_hour * 3600L + (long)time.tm_min * 60L;
    1473           0 :   *psec = neg ? -abs_sec : abs_sec;
    1474           0 :   return 1;
    1475           0 : }
    1476             : 
    1477             : /* time-offset = "Z" / time-numoffset */
    1478             : 
    1479             : static int
    1480             : fd_toml_parse_time_offset( fd_toml_parser_t * parser,
    1481           0 :                            long *             psec ) {
    1482           0 :   if( FD_UNLIKELY( !fd_toml_avail( parser ) ) ) return 0;
    1483             : 
    1484           0 :   switch( parser->c.data[0] ) {
    1485           0 :   case 'Z': case 'z':
    1486           0 :     *psec = 0;
    1487           0 :     fd_toml_advance_inline( parser, 1UL );
    1488           0 :     return 1;
    1489           0 :   }
    1490             : 
    1491           0 :   return fd_toml_parse_time_numoffset( parser, psec );
    1492           0 : }
    1493             : 
    1494             : /* full-time = partial-time time-offset */
    1495             : 
    1496             : static int
    1497             : fd_toml_parse_full_time( fd_toml_parser_t * parser,
    1498           0 :                          ulong *            pnanos ) {
    1499           0 :   long off_sec = 0;
    1500           0 :   if( FD_UNLIKELY( !fd_toml_parse_partial_time( parser, pnanos   ) ) ) return 0;
    1501           0 :   if( FD_UNLIKELY( !fd_toml_parse_time_offset ( parser, &off_sec ) ) ) return 0;
    1502           0 :   *pnanos += (ulong)off_sec;
    1503           0 :   return 1;
    1504           0 : }
    1505             : 
    1506             : /* offset-date-time = full-date time-delim full-time */
    1507             : 
    1508             : static int
    1509             : fd_toml_parse_offset_date_time( fd_toml_parser_t * parser,
    1510         159 :                                 ulong *            pnanos ) {
    1511         159 :   struct tm date = {0};
    1512             : 
    1513         159 :   if( FD_UNLIKELY( !fd_toml_parse_full_date ( parser, &date  ) ) ) return 0;
    1514           0 :   if( FD_UNLIKELY( !fd_toml_parse_time_delim( parser         ) ) ) return 0;
    1515           0 :   if( FD_UNLIKELY( !fd_toml_parse_full_time ( parser, pnanos ) ) ) return 0;
    1516             : 
    1517           0 :   *pnanos += (ulong)timegm( &date ) * (ulong)1e9;
    1518           0 :   return 1;
    1519           0 : }
    1520             : 
    1521             : /* local-date-time = full-date time-delim partial-time */
    1522             : 
    1523             : static int
    1524             : fd_toml_parse_local_date_time( fd_toml_parser_t * parser,
    1525         159 :                                ulong *            pnanos ) {
    1526             : 
    1527         159 :   struct tm date = {0};
    1528         159 :   if( FD_UNLIKELY( !fd_toml_parse_full_date   ( parser, &date  ) ) ) return 0;
    1529           0 :   if( FD_UNLIKELY( !fd_toml_parse_time_delim  ( parser         ) ) ) return 0;
    1530           0 :   if( FD_UNLIKELY( !fd_toml_parse_partial_time( parser, pnanos ) ) ) return 0;
    1531             : 
    1532           0 :   *pnanos = (ulong)mktime( &date ) * (ulong)1e9;
    1533           0 :   return 1;
    1534           0 : }
    1535             : 
    1536             : /* local-date = full-date */
    1537             : 
    1538             : static int
    1539             : fd_toml_parse_local_date( fd_toml_parser_t * parser,
    1540         159 :                           ulong *            pnanos ) {
    1541         159 :   struct tm date = {0};
    1542         159 :   if( FD_UNLIKELY( !fd_toml_parse_full_date( parser, &date ) ) ) return 0;
    1543           0 :   *pnanos = (ulong)mktime( &date ) * (ulong)1e9;
    1544           0 :   return 1;
    1545         159 : }
    1546             : 
    1547             : /* local-time = partial-time */
    1548             : 
    1549             : static int
    1550             : fd_toml_parse_local_time( fd_toml_parser_t * parser,
    1551         159 :                           ulong *            pnanos ) {
    1552         159 :   return fd_toml_parse_partial_time( parser, pnanos );
    1553         159 : }
    1554             : 
    1555             : /* date-time = offset-date-time / local-date-time / local-date / local-time */
    1556             : 
    1557             : static int
    1558         159 : fd_toml_parse_date_time( fd_toml_parser_t * parser ) {
    1559         159 :   ulong unix_nanos;
    1560         159 :   if( SUB_PARSE( fd_toml_parse_offset_date_time( parser, &unix_nanos ) ) ) goto add;
    1561         159 :   if( SUB_PARSE( fd_toml_parse_local_date_time ( parser, &unix_nanos ) ) ) goto add;
    1562         159 :   if( SUB_PARSE( fd_toml_parse_local_date      ( parser, &unix_nanos ) ) ) goto add;
    1563         159 :   if( SUB_PARSE( fd_toml_parse_local_time      ( parser, &unix_nanos ) ) ) goto add;
    1564         159 :   return 0;
    1565           0 : add:
    1566           0 :   if( FD_UNLIKELY( !fd_pod_insert_ulong( parser->pod, parser->key, unix_nanos ) ) ) {
    1567           0 :     parser->error = FD_TOML_ERR_POD;
    1568           0 :     return 0;
    1569           0 :   }
    1570           0 :   return 1;
    1571           0 : }
    1572             : 
    1573             : /* val = string / boolean / array / inline-table / date-time / float / integer */
    1574             : 
    1575             : static int
    1576         372 : fd_toml_parse_val( fd_toml_parser_t * parser ) {
    1577             :   /* consider consider some lookahead for better performance */
    1578         372 :   if( SUB_PARSE( fd_toml_parse_string      ( parser ) ) ) return 1;
    1579         270 :   if( SUB_PARSE( fd_toml_parse_boolean     ( parser ) ) ) return 1;
    1580         183 :   if( SUB_PARSE( fd_toml_parse_array       ( parser ) ) ) return 1;
    1581         159 :   if( SUB_PARSE( fd_toml_parse_inline_table( parser ) ) ) return 1;
    1582         159 :   if( SUB_PARSE( fd_toml_parse_date_time   ( parser ) ) ) return 1;
    1583             :   /* NOTE: float and integer have a common dec-int prefix -- dedup for better performance */
    1584         159 :   if( SUB_PARSE( fd_toml_parse_float       ( parser ) ) ) return 1;
    1585         159 :   if( SUB_PARSE( fd_toml_parse_integer     ( parser ) ) ) return 1;
    1586          24 :   return 0;
    1587         159 : }
    1588             : 
    1589             : /* keyval = key keyval-sep val */
    1590             : 
    1591             : static int
    1592        3969 : fd_toml_parse_keyval( fd_toml_parser_t * parser ) {
    1593        3969 :   uint old_key_len = parser->key_len;
    1594        3969 :   if( FD_UNLIKELY( !SUB_PARSE( fd_toml_parse_key( parser ) ) ) ) return 0;
    1595             : 
    1596         348 :   if( FD_UNLIKELY( fd_pod_query( parser->pod, parser->key, NULL )==FD_POD_SUCCESS ) ) {
    1597           0 :     FD_LOG_WARNING(( "Duplicate key: \"%s\"", parser->key ));
    1598           0 :     parser->error = FD_TOML_ERR_DUP;
    1599           0 :     return 0;
    1600           0 :   }
    1601             : 
    1602         348 :   if( FD_UNLIKELY( !SUB_PARSE( fd_toml_parse_keyval_sep( parser ) ) ) ) return 0;
    1603         348 :   if( FD_UNLIKELY( !SUB_PARSE( fd_toml_parse_val       ( parser ) ) ) ) return 0;
    1604             : 
    1605         348 :   FD_LOG_DEBUG(( "Added key %s", parser->key ));
    1606         348 :   parser->key[ old_key_len ] = 0;
    1607         348 :   parser->key_len            = old_key_len;
    1608         348 :   return 1;
    1609         348 : }
    1610             : 
    1611             : /* std-table = std-table-open key std-table-close
    1612             : 
    1613             :    std-table-open  = %x5B ws     ; [ Left square bracket
    1614             :    std-table-close = ws %x5D     ; ] Right square bracket */
    1615             : 
    1616             : static int
    1617        3621 : fd_toml_parse_std_table( fd_toml_parser_t * parser ) {
    1618        3621 :   EXPECT_CHAR( '[' );
    1619          72 :   fd_toml_parse_ws( parser );
    1620             : 
    1621          72 :   parser->key[ parser->key_len = 0 ] = 0;
    1622          72 :   if( FD_UNLIKELY( !fd_toml_parse_key( parser ) ) ) return 0;
    1623             :   // FIXME: consider blocking duplicate tables?
    1624             :   //if( FD_UNLIKELY( fd_pod_query( parser->pod, parser->key, NULL )==FD_POD_SUCCESS ) ) {
    1625             :   //  FD_LOG_WARNING(( "Duplicate table: \"%s\"", parser->key ));
    1626             :   //  parser->error = FD_TOML_ERR_DUP;
    1627             :   //  return 0;
    1628             :   //}
    1629          72 :   FD_LOG_DEBUG(( "Added table %.*s", (int)parser->key_len, parser->key ));
    1630             : 
    1631          72 :   fd_toml_parse_ws( parser );
    1632          72 :   EXPECT_CHAR( ']' );
    1633          72 :   return 1;
    1634          72 : }
    1635             : 
    1636             : /* array-table = array-table = array-table-open key array-table-close
    1637             : 
    1638             :    array-table-open  = %x5B.5B ws  ; [[ Double left square bracket
    1639             :    array-table-close = ws %x5D.5D  ; ]] Double right square bracket */
    1640             : 
    1641             : static int
    1642        3621 : fd_toml_parse_array_table( fd_toml_parser_t * parser ) {
    1643        3621 :   if( fd_toml_avail( parser ) < 2UL ) return 0;
    1644        3618 :   if( ( parser->c.data[0] != '[' ) |
    1645        3618 :       ( parser->c.data[1] != '[' ) ) return 0;
    1646           0 :   fd_toml_advance_inline( parser, 2UL );
    1647             : 
    1648           0 :   fd_toml_parse_ws( parser );
    1649             : 
    1650             :   /* Set parser->key to path to array */
    1651             : 
    1652           0 :   parser->key[ parser->key_len = 0 ] = 0;
    1653           0 :   if( FD_UNLIKELY( !SUB_PARSE( fd_toml_parse_key( parser ) ) ) ) return 0;
    1654             : 
    1655             :   /* Count number of predecessors */
    1656             : 
    1657           0 :   ulong idx = 0UL;
    1658           0 :   uchar const * subpod = fd_pod_query_subpod( parser->pod, parser->key );
    1659           0 :   if( subpod ) {
    1660           0 :     idx = fd_pod_cnt( subpod );
    1661           0 :   }
    1662             : 
    1663             :   /* Append array index to path */
    1664             : 
    1665           0 :   char * key_c = parser->key + parser->key_len;
    1666           0 :   if( FD_UNLIKELY( key_c + 22 > parser->key + sizeof(parser->key) ) ) {
    1667             :     /* array index might be OOB (see python3 -c 'print(len(str(1<<64)))') */
    1668           0 :     parser->error = FD_TOML_ERR_KEY;
    1669           0 :     return 0;
    1670           0 :   }
    1671           0 :   key_c = fd_cstr_append_char( key_c, '.' );
    1672           0 :   key_c = fd_cstr_append_ulong_as_text( key_c, 0, 0, idx, fd_ulong_base10_dig_cnt( idx ) );
    1673           0 :   fd_cstr_fini( key_c );
    1674           0 :   parser->key_len = (uint)( key_c - parser->key );
    1675             : 
    1676           0 :   FD_LOG_DEBUG(( "Added array table %.*s", (int)parser->key_len, parser->key ));
    1677             : 
    1678             :   /* Continue parsing */
    1679             : 
    1680           0 :   fd_toml_parse_ws( parser );
    1681             : 
    1682           0 :   if( FD_UNLIKELY( fd_toml_avail( parser ) < 2UL ) ) return 0;
    1683           0 :   if( FD_UNLIKELY( ( parser->c.data[0] != ']' ) |
    1684           0 :                    ( parser->c.data[1] != ']' ) ) ) return 0;
    1685           0 :   fd_toml_advance_inline( parser, 2UL );
    1686           0 :   return 1;
    1687           0 : }
    1688             : 
    1689             : /* table = std-table / array-table */
    1690             : 
    1691             : static int
    1692        3621 : fd_toml_parse_table( fd_toml_parser_t * parser ) {
    1693        3621 :   if( SUB_PARSE( fd_toml_parse_array_table( parser ) ) ) goto add;
    1694        3621 :   if( SUB_PARSE( fd_toml_parse_std_table  ( parser ) ) ) goto add;
    1695        3549 :   return 0;
    1696          72 : add:
    1697          72 :   fd_toml_upsert_empty_pod( parser );
    1698             :   /* Add trailing dot */
    1699          72 :   if( parser->key_len + 2 > sizeof(parser->key) ) {
    1700           0 :     parser->error = FD_TOML_ERR_KEY;
    1701           0 :     return 0;
    1702           0 :   }
    1703          72 :   parser->key[ parser->key_len++ ] = '.';
    1704          72 :   parser->key[ parser->key_len   ] = '\x00';
    1705          72 :   return 1;
    1706          72 : }
    1707             : 
    1708             : /* expression =  ws [ comment ]
    1709             :    expression =/ ws keyval ws [ comment ]
    1710             :    expression =/ ws table ws [ comment ] */
    1711             : 
    1712             : static int
    1713        3969 : fd_toml_parse_expression( fd_toml_parser_t * parser ) {
    1714             : 
    1715        3969 :   fd_toml_parse_ws( parser );
    1716             : 
    1717        3969 :   if( FD_LIKELY( SUB_PARSE( fd_toml_parse_keyval( parser ) ) ) ) {
    1718         348 :     fd_toml_parse_ws( parser );
    1719         348 :   }
    1720        3621 :   else if( FD_LIKELY( SUB_PARSE( fd_toml_parse_table( parser ) ) ) ) {
    1721          72 :     fd_toml_parse_ws( parser );
    1722          72 :   }
    1723             : 
    1724        3969 :   SUB_PARSE( fd_toml_parse_comment( parser ) );
    1725           0 :   return 1;
    1726        3969 : }
    1727             : 
    1728             : /* toml = expression *( newline expression ) */
    1729             : 
    1730             : static int
    1731           3 : fd_toml_parse_toml( fd_toml_parser_t * parser ) {
    1732             : 
    1733           3 :   if( FD_UNLIKELY( !fd_toml_parse_expression( parser ) ) ) return 0;
    1734             : 
    1735        3969 :   for(;;) {
    1736        3969 :     if( FD_UNLIKELY( parser->error             ) ) break;
    1737        3969 :     if( FD_UNLIKELY( !fd_toml_avail( parser )  ) ) break;
    1738        3966 :     if( FD_UNLIKELY( parser->c.data[0] != '\n' ) ) break;
    1739        3966 :     fd_toml_advance( parser, 1UL );
    1740        3966 :     if( FD_UNLIKELY( !fd_toml_parse_expression( parser ) ) ) return 0;
    1741        3966 :   }
    1742             : 
    1743           3 :   return 1;
    1744           3 : }
    1745             : 
    1746             : long
    1747             : fd_toml_parse( void const * toml,
    1748             :                ulong        toml_sz,
    1749             :                uchar *      pod,
    1750             :                uchar *      scratch,
    1751           3 :                ulong        scratch_sz ) {
    1752             : 
    1753           3 :   if( FD_UNLIKELY( !toml_sz    ) ) return FD_TOML_SUCCESS;
    1754           3 :   if( FD_UNLIKELY( !scratch_sz ) ) {
    1755           0 :     FD_LOG_WARNING(( "zero scratch_sz" ));
    1756           0 :     return FD_TOML_ERR_SCRATCH;
    1757           0 :   }
    1758             : 
    1759           3 :   fd_toml_parser_t parser[1] = {{
    1760           3 :     .c = {
    1761           3 :       .data   = toml,
    1762           3 :       .lineno = 1UL,
    1763           3 :     },
    1764           3 :     .data_end    = (char const *)toml + toml_sz,
    1765           3 :     .pod         = pod,
    1766           3 :     .scratch     = scratch,
    1767           3 :     .scratch_cur = scratch,
    1768           3 :     .scratch_end = scratch + scratch_sz
    1769           3 :   }};
    1770             : 
    1771           3 :   int ok = fd_toml_parse_toml( parser );
    1772           3 :   if( FD_UNLIKELY( (!ok) | (fd_toml_avail( parser ) > 0) ) ) {
    1773             :     /* Parse failed */
    1774           0 :     return fd_long_if( !!parser->error, parser->error, fd_long_max( 1L, (long)parser->c.lineno ) );
    1775           0 :   }
    1776             : 
    1777           3 :   return FD_TOML_SUCCESS;
    1778           3 : }

Generated by: LCOV version 1.14