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