LCOV - code coverage report
Current view: top level - ballet/toml - fd_toml.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 531 1164 45.6 %
Date: 2025-03-20 12:08:36 Functions: 61 83 73.5 %

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

Generated by: LCOV version 1.14