controller-legacy/vendor/confini.c
Tobias Reisinger 09a379ef9f fix: cleanup vendor
fix: only use piface when need by relay config
2020-08-20 14:09:55 +02:00

5017 lines
121 KiB
C

/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
/* Please make sure that the TAB width in your editor is set to 4 spaces */
/**
@file confini.c
@brief libconfini functions
@author Stefano Gioffré
@copyright GNU General Public License, version 3 or any later version
@version 1.14.0
@date 2016-2020
@see https://madmurphy.github.io/libconfini
**/
/*/|
(_|_) _ _ _ __ _ _
| (_) |__ ___ ___ _ __ / _(_)_ __ (_)
| | | '_ \ / __/ _ \| '_ \| |_| | '_ \| |
| | | |_) | (_| (_) | | | | _| | | | | |
|_|_|_.__/ \___\___/|_| |_|_| |_|_| |_|_| _ _
( | )
|/*/
/**
@def INIFORMAT_TABLE_AS(_____)
Content of the table:
- Bits 1-19: INI syntax
- Bits 20-22: INI semantics
- Bits 23-24: Human syntax (disabled entries)
@typedef int (* IniStatsHandler) (
IniStatistics * statistics,
void * user_data
)
@param statistics
A pointer to the #IniStatistics to handle
@param user_data
The custom argument previously passed to the caller function
@typedef int (* IniDispHandler) (
IniDispatch *dispatch,
void * user_data
)
@param dispatch
A pointer to the #IniDispatch to handle
@param user_data
The custom argument previously passed to the caller function
@typedef int (* IniStrHandler) (
char * ini_string,
size_t string_length,
size_t string_num,
IniFormat format,
void * user_data
)
@param ini_string
The INI string to handle
@param string_length
The length of the INI string in bytes
@param string_num
The unique number that identifies @p ini_string within a
sequence of INI strings; it equals zero if @p ini_string is the
first or the only member of the sequence
@param format
The format of the INI file from which @p ini_string has been extracted
@param user_data
The custom argument previously passed to the caller function
@typedef int (* IniSubstrHandler) (
const char * ini_string,
size_t fragm_offset,
size_t fragm_length,
size_t fragm_num,
IniFormat format,
void * user_data
)
@param ini_string
The INI string containing the fragment to handle
@param fragm_offset
The offset of the selected fragment in bytes
@param fragm_length
The length of the selected fragment in bytes
@param fragm_num
The unique number that identifies the selected fragment within a
sequence of fragments of @p ini_string; it equals zero if the
fragment is the first or the only member of the sequence
@param format
The format of the INI file from which @p ini_string has been
extracted
@param user_data
The custom argument previously passed to the caller function
@struct IniFormat
@property IniFormat::delimiter_symbol
The key-value delimiter character (ASCII only allowed); if set
to `\0`, any space is delimiter
(`/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`); if, within the format
given, `IniFormat::delimiter_symbol` matches a metacharacter
(`'\\'`, `'\''`, `'\"'`), its role as metacharacter will have
higher priority than its role as delimiter symbol (i.e., the
format will have no key-value delimiter); you may use the
#IniDelimiters `enum` for this.
@property IniFormat::case_sensitive
If set to `true`, string comparisons will be always
case-sensitive.
@property IniFormat::semicolon_marker
The rule of the semicolon character (use `enum`
#IniCommentMarker for this).
@property IniFormat::hash_marker
The rule of the hash character (use `enum` #IniCommentMarker for
this).
@property IniFormat::section_paths
Defines whether and how the format supports sections (use `enum`
#IniSectionPaths for this).
@property IniFormat::multiline_nodes
Defines which class of entries are allowed to be multi-line (use
`enum` #IniMultiline for this).
@property IniFormat::no_spaces_in_names
If set to `true`, key and section names containing spaces (even
within quotes) will be rendered as #INI_UNKNOWN. Note that
setting #IniFormat::delimiter_symbol to #INI_ANY_SPACE will not
automatically set this option to `true` (spaces will still be
allowed in section names).
@property IniFormat::no_single_quotes
If set to `true`, the single-quote character (`'`) will be
considered as a normal character.
@property IniFormat::no_double_quotes
If set to `true`, the double-quote character (`"`) will be
considered as a normal character.
@property IniFormat::implicit_is_not_empty
If set to `true`, implicit keys (see @ref libconfini) will
be always dispatched using the values given by the global
variables #INI_GLOBAL_IMPLICIT_VALUE and
#INI_GLOBAL_IMPLICIT_V_LEN for the fields #IniDispatch::value
and to #IniDispatch::v_len respectively; if set to `false`,
implicit keys will be considered to be empty keys.
@property IniFormat::do_not_collapse_values
If set to `true`, sequences of one or more spaces in values
(`/\s+/`) will be dispatched verbatim.
@property IniFormat::preserve_empty_quotes
If set to `true`, and if single/double quotes are
metacharacters, ensures that, within values, empty strings
enclosed between quotes (`""` or `''`) will not be collapsed
together with the spaces that surround them. This option is
useful for values containing space-delimited arrays, in order to
preserve their empty members -- as in, for instance:
`coordinates = "" ""`. Note that, in section and key names,
empty strings enclosed between quotes are _always_ collapsed
together with their surrounding spaces.
@property IniFormat::disabled_after_space
If set to `true`, what follows `/[#;]\s/` is allowed to be
parsed as a disabled entry.
@property IniFormat::disabled_can_be_implicit
If set to `false`, comments that do not contain a key-value
delimiter will never be parsed as disabled keys, but always as
simple comments (even if the format supports implicit keys).
@struct IniStatistics
@property IniStatistics::format
The format of the INI file (see #IniFormat)
@property IniStatistics::bytes
The size in bytes of the parsed file
@property IniStatistics::members
The size in number of members (nodes) of the parsed file -- this
number always equals the number of dispatches that will be sent
by #load_ini_file(), #load_ini_path() or #strip_ini_cache()
@struct IniDispatch
@property IniDispatch::format
The format of the INI file (see #IniFormat)
@property IniDispatch::type
The dispatch type (see `enum` #IniNodeType)
@property IniDispatch::data
#IniDispatch::data can contain a comment, a section path or a
key name depending, on #IniDispatch::type; cannot be `NULL`
@property IniDispatch::value
It can be the value of a key element, an empty string or it can
point to the address pointed by the global variable
#INI_GLOBAL_IMPLICIT_VALUE (_the latter is the only case in
which `IniDispatch::value` can be `NULL`_)
@property IniDispatch::append_to
The current section path; cannot be `NULL`
@property IniDispatch::d_len
The length of the string #IniDispatch::data
@property IniDispatch::v_len
The length of the string #IniDispatch::value
@property IniDispatch::at_len
The length of the string #IniDispatch::append_to
@property IniDispatch::dispatch_id
The dispatch number (the first dispatch is number zero)
**/
/*\
|*|
|*| LOCAL ENVIRONMENT
|*| ________________________________
\*/
/* PREPROCESSOR PREAMBLE */
/* String concatenation facilities */
#define __PP_CAT__(STR1, STR2) STR1##STR2
#define __PP_UCAT__(STR1, STR2) STR1##_##STR2
#define __PP_EVALUCAT__(STR1, STR2) __PP_UCAT__(STR1, STR2)
/* PREPROCESSOR ENVIRONMENT */
#ifndef CONFINI_IO_FLAVOR
/**
@brief The I/O API to use (possibly overridden via
`-DCONFINI_IO_FLAVOR=[FLAVOR]`)
Possible values are `CONFINI_STANDARD` and `CONFINI_POSIX`
**/
#define CONFINI_IO_FLAVOR CONFINI_STANDARD
#endif
/* PREPROCESSOR GLUE */
#define _LIBCONFINI_PRIVATE_ITEM_(GROUP, ITEM) \
__PP_EVALUCAT__(__PP_CAT__(_LIB, GROUP), __PP_CAT__(ITEM, _))
#define _LIBCONFINI_CURRENT_FLAVOR_GET_(NAME) \
_LIBCONFINI_PRIVATE_ITEM_(CONFINI_IO_FLAVOR, NAME)
#define _LIBCONFINI_IS_FLAVOR_(NAME) \
_LIBCONFINI_PRIVATE_ITEM_(CONFINI_IO_FLAVOR, FLAVOR) == \
_LIBCONFINI_PRIVATE_ITEM_(NAME, FLAVOR)
/* AVAILABLE I/O FLAVORS */
/* `-DCONFINI_IO_FLAVOR=CONFINI_STANDARD` (C99 Standard, default, REQUIRED) */
#define _LIBCONFINI_STANDARD_SEOF_FN_(FILEPTR) fseek(FILEPTR, 0, SEEK_END)
#define _LIBCONFINI_STANDARD_FT_FN_(FILEPTR) ftell(FILEPTR)
#define _LIBCONFINI_STANDARD_FT_T_ long signed int
/* Any unique non-zero integer to identify this I/O API */
#define _LIBCONFINI_STANDARD_FLAVOR_ 1
/* `-DCONFINI_IO_FLAVOR=CONFINI_POSIX` */
#define _LIBCONFINI_POSIX_SEOF_FN_(FILEPTR) fseeko(FILEPTR, 0, SEEK_END)
#define _LIBCONFINI_POSIX_FT_FN_(FILEPTR) ftello(FILEPTR)
#define _LIBCONFINI_POSIX_FT_T_ off_t
/* Any unique non-zero integer to identify this I/O API */
#define _LIBCONFINI_POSIX_FLAVOR_ 2
/* Define `_POSIX_C_SOURCE` macro when `CONFINI_IO_FLAVOR == CONFINI_POSIX` */
#if _LIBCONFINI_IS_FLAVOR_(CONFINI_POSIX)
#ifndef _POSIX_C_SOURCE
#define _POSIX_C_SOURCE 200809L
#endif
#endif
/* It is possible to add other I/O APIs here. Feel free to contribute! */
/* CHECKS */
#if _LIBCONFINI_PRIVATE_ITEM_(CONFINI_IO_FLAVOR, FLAVOR) == 0
#error Unsupported I/O API defined in macro CONFINI_IO_FLAVOR
#endif
/* HEADERS */
#include <stdlib.h>
#include "confini.h"
/* ALIASES */
#define _LIBCONFINI_FALSE_ 0
#define _LIBCONFINI_TRUE_ 1
#define _LIBCONFINI_CHARBOOL_ unsigned char
#define _LIBCONFINI_SIMPLE_SPACE_ ' '
#define _LIBCONFINI_HT_ '\t'
#define _LIBCONFINI_FF_ '\f'
#define _LIBCONFINI_VT_ '\v'
#define _LIBCONFINI_CR_ '\r'
#define _LIBCONFINI_LF_ '\n'
#define _LIBCONFINI_BACKSLASH_ '\\'
#define _LIBCONFINI_OPEN_SECTION_ '['
#define _LIBCONFINI_CLOSE_SECTION_ ']'
#define _LIBCONFINI_SUBSECTION_ '.'
#define _LIBCONFINI_SEMICOLON_ ';'
#define _LIBCONFINI_HASH_ '#'
#define _LIBCONFINI_DOUBLE_QUOTES_ '"'
#define _LIBCONFINI_SINGLE_QUOTES_ '\''
#define _LIBCONFINI_SEEK_EOF_(FILEPTR) \
_LIBCONFINI_CURRENT_FLAVOR_GET_(SEOF_FN)(FILEPTR)
#define _LIBCONFINI_FTELL_(FILEPTR) \
_LIBCONFINI_CURRENT_FLAVOR_GET_(FT_FN)(FILEPTR)
#define _LIBCONFINI_OFF_T_ \
_LIBCONFINI_CURRENT_FLAVOR_GET_(FT_T)
/* FUNCTIONAL MACROS AND CONSTANTS */
/* The character that will replace sequences of one or more spaces (`/\s+/`) */
#define _LIBCONFINI_COLLAPSED_ _LIBCONFINI_SIMPLE_SPACE_
/*
These may be any character in theory... But after the left-trim of each node
leading spaces work pretty well as metacharacters...
*/
/* Internal marker of standard comments */
#define _LIBCONFINI_SC_INT_MARKER_ _LIBCONFINI_SIMPLE_SPACE_
/* Internal marker of inline comments */
#define _LIBCONFINI_IC_INT_MARKER_ _LIBCONFINI_HT_
/*
Checks whether a character can be escaped within a given format
*/
#define _LIBCONFINI_IS_ESC_CHAR_(CHR, FMT) ( \
CHR == _LIBCONFINI_BACKSLASH_ ? \
!INIFORMAT_HAS_NO_ESC(FMT) \
: CHR == _LIBCONFINI_DOUBLE_QUOTES_ ? \
!FMT.no_double_quotes \
: \
CHR == _LIBCONFINI_SINGLE_QUOTES_ && !FMT.no_single_quotes \
)
/*
Checks whether a character represents any marker within a given format
*/
#define _LIBCONFINI_IS_ANY_MARKER_(CHR, FMT) ( \
CHR == _LIBCONFINI_HASH_ ? \
FMT.hash_marker != INI_IS_NOT_A_MARKER \
: \
CHR == _LIBCONFINI_SEMICOLON_ && FMT.semicolon_marker != INI_IS_NOT_A_MARKER \
)
/*
Checks whether a character represents any marker except `INI_IGNORE` within a
given format
*/
#define _LIBCONFINI_IS_COM_MARKER_(CHR, FMT) ( \
CHR == _LIBCONFINI_HASH_ ? \
FMT.hash_marker != INI_IS_NOT_A_MARKER && FMT.hash_marker != INI_IGNORE \
: \
CHR == _LIBCONFINI_SEMICOLON_ && FMT.semicolon_marker != INI_IS_NOT_A_MARKER && FMT.semicolon_marker != INI_IGNORE \
)
/*
Checks whether a character represents a marker of type `INI_DISABLED_OR_COMMENT`
within a given format
*/
#define _LIBCONFINI_IS_DIS_MARKER_(CHR, FMT) ( \
CHR == _LIBCONFINI_HASH_ ? \
FMT.hash_marker == INI_DISABLED_OR_COMMENT \
: \
CHR == _LIBCONFINI_SEMICOLON_ && FMT.semicolon_marker == INI_DISABLED_OR_COMMENT \
)
/*
Checks whether a character represents a marker of type `INI_IGNORE` within a
given format
*/
#define _LIBCONFINI_IS_IGN_MARKER_(CHR, FMT) ( \
CHR == _LIBCONFINI_HASH_ ? \
FMT.hash_marker == INI_IGNORE \
: \
CHR == _LIBCONFINI_SEMICOLON_ && FMT.semicolon_marker == INI_IGNORE \
)
/*
Checks whether a pointer is within the range
`INI_GLOBAL_IMPLICIT_VALUE >= PTR <= INI_GLOBAL_IMPLICIT_VALUE + INI_GLOBAL_IMPLICIT_V_LEN`
*/
#define _LIBCONFINI_IMPLICIT_RANGE_(PTR) \
(PTR >= INI_GLOBAL_IMPLICIT_VALUE && PTR <= INI_GLOBAL_IMPLICIT_VALUE + INI_GLOBAL_IMPLICIT_V_LEN)
/*
Maybe in the future there will be support for UTF-8 casefold (although probably
not -- see "Code considerations" in the Manual), but for now only ASCII...
*/
#define _LIBCONFINI_CHR_CASEFOLD_(CHR) (CHR > 0x40 && CHR < 0x5b ? CHR | 0x60 : CHR)
/*
Possible depths of `_LIBCONFINI_SPACES_` (see function #is_some_space()).
Please, consider the following three constants as belonging together to a
virtual opaque `enum`.
*/
#define _LIBCONFINI_WITH_EOL_ -1
#define _LIBCONFINI_NO_EOL_ 1
#define _LIBCONFINI_JUST_S_T_ 3
/*
Other constants related to `_LIBCONFINI_SPACES_`
*/
#define _LIBCONFINI_EOL_IDX_ 0
#define _LIBCONFINI_SPALEN_ 6
/*
The list of space characters -- do not change its order or its content!
*/
static const char _LIBCONFINI_SPACES_[_LIBCONFINI_SPALEN_] = {
_LIBCONFINI_LF_,
_LIBCONFINI_CR_,
_LIBCONFINI_VT_,
_LIBCONFINI_FF_,
_LIBCONFINI_HT_,
_LIBCONFINI_SIMPLE_SPACE_
};
/**
@brief A list of possible string representations of boolean pairs
There may be infinite pairs here. Each pair must be organized according to the
following order:
1. Signifier of `false`
2. Signifier of `true`
@note Everything **must** be lowercase in this list.
**/
static const char * const INI_BOOLEANS[][2] = {
{ "no", "yes" },
{ "false", "true" },
{ "off", "on" }
};
/* ABSTRACT UTILITIES */
/**
@brief Checks whether a character is a space
@param chr The target character
@param depth What is actually considered a space (possible
values: `_LIBCONFINI_WITH_EOL_`,
`_LIBCONFINI_NO_EOL_`, `_LIBCONFINI_JUST_S_T_`)
@return A boolean: `true` if the character matches, `false` otherwise
**/
static inline _LIBCONFINI_CHARBOOL_ is_some_space (const char chr, const int8_t depth) {
register int8_t idx = depth;
while (++idx < _LIBCONFINI_SPALEN_ && chr != _LIBCONFINI_SPACES_[idx]);
return idx < _LIBCONFINI_SPALEN_;
}
/**
@brief Soft left trim -- does not change the buffer
@param str The target string
@param offs The offset where to start the left trim
@param depth What is actually considered a space (possible
values: `_LIBCONFINI_WITH_EOL_`,
`_LIBCONFINI_NO_EOL_`, `_LIBCONFINI_JUST_S_T_`)
@return The offset of the first non-space character
**/
static inline size_t ltrim_s (const char * const str, const size_t offs, const int8_t depth) {
register size_t idx = offs;
while (is_some_space(str[idx++], depth));
return idx - 1;
}
/**
@brief Hard left trim -- **does** change the buffer
@param str The target string
@param offs The offset where to start the left trim
@param depth What is actually considered a space (possible
values: `_LIBCONFINI_WITH_EOL_`,
`_LIBCONFINI_NO_EOL_`, `_LIBCONFINI_JUST_S_T_`)
@return The offset of the first non-space character
**/
static inline size_t ltrim_h (char * const str, const size_t offs, const int8_t depth) {
register size_t idx = offs;
while (is_some_space(str[idx], depth)) { str[idx++] = '\0'; }
return idx;
}
/**
@brief Shifting left trim -- **does** change the buffer
@param str The target string
@param offs The offset where to start the left trim
@param depth What is actually considered a space (possible
values: `_LIBCONFINI_WITH_EOL_`,
`_LIBCONFINI_NO_EOL_`, `_LIBCONFINI_JUST_S_T_`)
@return The new length of the string
**/
static inline size_t ltrim_hh (char * const str, const size_t offs, const int8_t depth) {
register size_t idx_d = offs, idx_s = offs;
while (is_some_space(str[idx_s++], depth));
if (--idx_s - idx_d) {
while ((str[idx_d++] = str[idx_s++]));
for (idx_s = idx_d; str[idx_s]; str[idx_s++] = '\0');
return idx_d - 1;
}
while (str[idx_s++]);
return idx_s - 1;
}
/**
@brief Soft right trim -- does not change the buffer
@param str The target string
@param len The length of the string
@param depth What is actually considered a space (possible
values: `_LIBCONFINI_WITH_EOL_`,
`_LIBCONFINI_NO_EOL_`, `_LIBCONFINI_JUST_S_T_`)
@return The length of the string until the last non-space character
**/
static inline size_t rtrim_s (const char * const str, const size_t len, const int8_t depth) {
register size_t idx = len + 1;
while (--idx > 0 && is_some_space(str[idx - 1], depth));
return idx;
}
/**
@brief Hard right trim -- **does** change the buffer
@param str The target string
@param len The length of the string
@param depth What is actually considered a space (possible
values: `_LIBCONFINI_WITH_EOL_`,
`_LIBCONFINI_NO_EOL_`, `_LIBCONFINI_JUST_S_T_`)
@return The new length of the string
**/
static inline size_t rtrim_h (char * const str, const size_t len, const int8_t depth) {
register size_t idx = len;
while (idx > 0 && is_some_space(str[idx - 1], depth)) { str[--idx] = '\0'; }
return idx;
}
/**
@brief Unescaped soft right trim (right trim of `/(?:\s|\\[\n\r])+$/`)
-- does not change the buffer
@param str The target string
@param len The length of the string
@return The length of the string until the last non-space character
**/
static inline size_t urtrim_s (const char * const str, const size_t len) {
register uint8_t abcd = 1;
register size_t idx = len;
/* \ /\
\ */ continue_urtrim: /* \
\/ ______________________ \ */
if (idx < 1) {
return idx;
}
switch (str[--idx]) {
case _LIBCONFINI_VT_:
case _LIBCONFINI_FF_:
case _LIBCONFINI_HT_:
case _LIBCONFINI_SIMPLE_SPACE_:
abcd = 1;
goto continue_urtrim;
case _LIBCONFINI_LF_:
case _LIBCONFINI_CR_:
abcd = 3;
goto continue_urtrim;
case _LIBCONFINI_BACKSLASH_:
if (abcd >>= 1) {
goto continue_urtrim;
}
}
return idx + 1;
}
/**
@brief Converts an ASCII string to lower case
@param str The target string
@return Nothing
**/
static inline void string_tolower (char * const str) {
for (register char * chrptr = str; *chrptr; chrptr++) {
*chrptr = _LIBCONFINI_CHR_CASEFOLD_(*chrptr);
}
}
/* CONCRETE UTILITIES */
/**
@brief Unparsed hard left trim (left trim of
`/^(?:\s+|\\[\n\r]|''|"")+/`) -- **does** change the buffer
@param srcstr The target string (it may contain multi-line
escape sequences)
@param offs The offset where to start the left trim
@param format The format of the INI file
@return The offset of the first non-trivial character
**/
static inline size_t qultrim_h (char * const srcstr, const size_t offs, const IniFormat format) {
/*
Mask `abcd` (8 bits used):
FLAG_1 Single quotes are not metacharacters (const)
FLAG_2 Double quotes are not metacharacters (const)
FLAG_4 Unescaped single quotes are odd right now
FLAG_8 Unescaped double quotes are odd right now
FLAG_16 We are in an odd sequence of backslashes
FLAG_32 Erase the previous character
FLAG_64 Erase this character
FLAG_128 Continue the loop
*/
register uint8_t abcd = (format.no_double_quotes ? 130 : 128) | format.no_single_quotes;
size_t idx = offs;
do {
abcd = !(abcd & 28) && is_some_space(srcstr[idx], _LIBCONFINI_NO_EOL_) ?
(abcd & 207) | 64
: !(abcd & 12) && (srcstr[idx] == _LIBCONFINI_LF_ || srcstr[idx] == _LIBCONFINI_CR_) ?
(
abcd & 16 ?
(abcd & 239) | 96
:
abcd | 64
)
: !(abcd & 25) && srcstr[idx] == _LIBCONFINI_SINGLE_QUOTES_ ?
(
abcd & 4 ?
(abcd & 235) | 96
:
(abcd & 143) | 4
)
: !(abcd & 22) && srcstr[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ?
(
abcd & 8 ?
(abcd & 231) | 96
:
(abcd & 159) | 8
)
: srcstr[idx] == _LIBCONFINI_BACKSLASH_ ?
(abcd & 159) ^ 16
:
abcd & 31;
if (abcd & 32) {
srcstr[idx - 1] = '\0';
}
if (abcd & 64) {
srcstr[idx] = '\0';
}
idx++;
} while (abcd & 128);
return abcd & 28 ? idx - 2 : idx - 1;
}
/**
@brief Soft left trim within an unparsed disabled entry (left trim of
`/(?:(?:^|\\?[\n\r])[ \t\v\f]*(?:#(?:[ \t\v\f]|''|"")*)?)+/`)
-- does not change the buffer
@param srcstr The target string (it may contain multi-line
escape sequences)
@param offs The offset where to start the left trim
@param format The format of the INI file
@return The offset of the first non-trivial character
**/
static inline size_t dqultrim_s (const char * const srcstr, const size_t offs, const IniFormat format) {
/*
Mask `abcd` (7 bits used):
FLAG_1 Single quotes are not metacharacters (const)
FLAG_2 Double quotes are not metacharacters (const)
FLAG_4 Unescaped single quotes are odd right now
FLAG_8 Unescaped double quotes are odd right now
FLAG_16 We are in an odd sequence of backslashes
FLAG_32 A new line has just begun
FLAG_64 Continue the left trim
*/
register uint16_t abcd = format.no_single_quotes |
(format.no_double_quotes << 1) |
96;
register size_t idx = offs;
do {
abcd = is_some_space(srcstr[idx], _LIBCONFINI_NO_EOL_) ?
(
abcd & 28 ?
abcd & 63
:
abcd
)
: srcstr[idx] == _LIBCONFINI_LF_ || srcstr[idx] == _LIBCONFINI_CR_ ?
(
abcd & 12 ?
(abcd & 47) | 32
:
(abcd & 111) | 32
)
: srcstr[idx] == _LIBCONFINI_BACKSLASH_ ?
(
abcd & 28 ?
(abcd & 31) | 16
:
(abcd & 95) | 16
)
: (abcd & 32) && _LIBCONFINI_IS_ANY_MARKER_(srcstr[idx], format) ?
abcd & 79
: !(abcd & 54) && srcstr[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ?
(abcd & 95) ^ 8
: !(abcd & 57) && srcstr[idx] == _LIBCONFINI_SINGLE_QUOTES_ ?
(abcd & 95) ^ 4
: srcstr[idx] ?
abcd & 31
:
abcd & 19;
idx++;
} while (abcd & 64);
return abcd & 28 ? idx - 2 : idx - 1;
}
/**
@brief Gets the position of the first occurence out of quotes of a
given character, stopping after a given number of charcters
@param str The string where to search
@param chr The character to to search
@param len The maximum number of characters to read
@param format The format of the INI file
@return The offset of the first occurence of @p chr, or @p len if
@p chr has not been not found
**/
static inline size_t getn_metachar_pos (const char * const str, const char chr, const size_t len, const IniFormat format) {
size_t idx = 0;
/*
Mask `abcd` (5 bits used):
FLAG_1 Single quotes are not metacharacters (const)
FLAG_2 Double quotes are not metacharacters (const)
FLAG_4 Unescaped single quotes are odd right now
FLAG_8 Unescaped double quotes are odd right now
FLAG_16 We are in an odd sequence of backslashes
*/
for (
register uint8_t abcd = (format.no_double_quotes << 1) | format.no_single_quotes;
idx < len && ((abcd & 12) || (chr ? str[idx] != chr : !is_some_space(str[idx], _LIBCONFINI_WITH_EOL_)));
abcd = str[idx] == _LIBCONFINI_BACKSLASH_ ? abcd ^ 16
: !(abcd & 22) && str[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ? abcd ^ 8
: !(abcd & 25) && str[idx] == _LIBCONFINI_SINGLE_QUOTES_ ? abcd ^ 4
: abcd & 15,
idx++
);
return idx;
}
/**
@brief Gets the position of the first occurence out of quotes of a
given character
@param str The string where to search
@param chr The character to to search
@param format The format of the INI file
@return The offset of the first occurence of @p chr or the length of
@p str if @p chr has not been not found
**/
static inline size_t get_metachar_pos (const char * const str, const char chr, const IniFormat format) {
size_t idx = 0;
/*
Mask `abcd` (5 bits used):
--> As in #getn_metachar_pos()
*/
for (
register uint8_t abcd = (format.no_double_quotes << 1) | format.no_single_quotes;
str[idx] && ((abcd & 12) || (chr ? str[idx] != chr : !is_some_space(str[idx], _LIBCONFINI_NO_EOL_)));
abcd = str[idx] == _LIBCONFINI_BACKSLASH_ ? abcd ^ 16
: !(abcd & 22) && str[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ? abcd ^ 8
: !(abcd & 25) && str[idx] == _LIBCONFINI_SINGLE_QUOTES_ ? abcd ^ 4
: abcd & 15,
idx++
);
return idx;
}
/**
@brief Replaces `/\\(\n\r?|\r\n?)[\t \v\f]*[#;]/` or `/\\(\n\r?|\r\n?)/`
with `"$1"`
@param srcstr The target string (it may contain multi-line
escape sequences)
@param len Length of the string
@param is_disabled The string represents a disabled entry
@param format The format of the INI file
@return The new length of the string
**/
static size_t unescape_cr_lf (char * const srcstr, const size_t len, const _LIBCONFINI_CHARBOOL_ is_disabled, const IniFormat format) {
register size_t idx_s = 0, idx_d = 0;
register uint8_t eol_i = _LIBCONFINI_EOL_IDX_;
register _LIBCONFINI_CHARBOOL_ is_escaped = _LIBCONFINI_FALSE_;
size_t probe;
while (idx_s < len) {
if (
is_escaped && (
srcstr[idx_s] == _LIBCONFINI_SPACES_[eol_i] || srcstr[idx_s] == _LIBCONFINI_SPACES_[eol_i ^= 1]
)
) {
srcstr[idx_d - 1] = srcstr[idx_s++];
if (srcstr[idx_s] == _LIBCONFINI_SPACES_[eol_i ^ 1]) {
srcstr[idx_d++] = srcstr[idx_s++];
}
if (is_disabled) {
probe = ltrim_s(srcstr, idx_s, _LIBCONFINI_NO_EOL_);
if (_LIBCONFINI_IS_DIS_MARKER_(srcstr[probe], format)) {
idx_s = probe + 1;
}
}
is_escaped = _LIBCONFINI_FALSE_;
} else {
is_escaped = srcstr[idx_s] == _LIBCONFINI_BACKSLASH_ ?
!is_escaped
:
_LIBCONFINI_FALSE_;
srcstr[idx_d++] = srcstr[idx_s++];
}
}
for (idx_s = idx_d; idx_s < len; srcstr[idx_s++] = '\0');
return idx_d;
}
/**
@brief Sanitizes a section path
@param secpath The section path
@param format The format of the INI file
@return The new length of the string
Out of quotes, similar to ECMAScript
`secpath.replace(/\.*\s*$|(?:\s*(\.))+\s*|^\s+/g, "$1").replace(/\s+/g, " ")`
A section path can start with a dot (append), but cannot end with a dot. Spaces
surrounding dots will be removed. Fragments surrounded by single or double
quotes (if these are enabled) are prevented from changes.
**/
static size_t sanitize_section_path (char * const secpath, const IniFormat format) {
/*
Mask `abcd` (12 bits used):
FLAG_1 Single quotes are not metacharacters (const)
FLAG_2 Double quotes are not metacharacters (const)
FLAG_4 Unescaped single quotes are odd right now
FLAG_8 Unescaped double quotes are odd right now
FLAG_16 We are in an odd sequence of backslashes
FLAG_32 These are initial spaces
FLAG_64 This is a space out of quotes
FLAG_128 This is a dot out of quotes
FLAG_256 This is anything *but* an opening single/double quote
FLAG_512 Don't ignore the last two characters
FLAG_1024 Don't overwrite the previous character
FLAG_2048 Path contains at least one name
*/
register uint16_t abcd = (format.no_double_quotes ? 1826 : 1824) | format.no_single_quotes;
register size_t idx_s = 0, idx_d = 0;
for (; secpath[idx_s]; idx_s++) {
/* Revision #2 */
abcd = !(abcd & 12) && is_some_space(secpath[idx_s], _LIBCONFINI_WITH_EOL_) ?
(
abcd & 224 ?
(abcd & 3055) | 832
:
(abcd & 4079) | 1856
)
: !(abcd & 12) && secpath[idx_s] == _LIBCONFINI_SUBSECTION_ ?
(
abcd & (abcd & 32 ? 128 : 192) ?
(abcd & 2959) | 896
:
(abcd & 3983) | 1920
)
: !(abcd & 25) && secpath[idx_s] == _LIBCONFINI_SINGLE_QUOTES_ ?
(
~abcd & 4 ?
(abcd & 3839) | 1540
: abcd & 256 ?
(abcd & 3867) | 3840
:
(abcd & 3579) | 1280
)
: !(abcd & 22) && secpath[idx_s] == _LIBCONFINI_DOUBLE_QUOTES_ ?
(
~abcd & 8 ?
(abcd & 3839) | 1544
: abcd & 256 ?
(abcd & 3863) | 3840
:
(abcd & 3575) | 1280
)
: secpath[idx_s] == _LIBCONFINI_BACKSLASH_ ?
((abcd & 3871) | 3840) ^ 16
:
(abcd & 3855) | 3840;
if (abcd & 512) {
secpath[
abcd & 1024 ?
idx_d++
: idx_d ?
idx_d - 1
:
idx_d
] = !(~abcd & 384) ?
_LIBCONFINI_SUBSECTION_
: !(~abcd & 320) ?
_LIBCONFINI_COLLAPSED_
:
secpath[idx_s];
} else if (idx_d) {
idx_d--;
}
}
for (
idx_s = idx_d && (abcd & 2048) && (abcd & 192) ?
--idx_d
:
idx_d;
secpath[idx_s];
secpath[idx_s++] = '\0'
);
return idx_d;
}
/**
@brief Out of quotes similar to ECMAScript
`ini_string.replace(/''|""/g, "").replace(/^[\n\r]\s*|\s+/g, " ")`
@param ini_string The string to collapse -- multi-line escape
sequences must be already unescaped at
this stage
@param format The format of the INI file
@return The new length of the string
**/
static size_t collapse_everything (char * const ini_string, const IniFormat format) {
/*
Mask `abcd` (9 bits used):
FLAG_1 Single quotes are not metacharacters (const)
FLAG_2 Double quotes are not metacharacters (const)
FLAG_4 Unescaped single quotes are odd right now
FLAG_8 Unescaped double quotes are odd right now
FLAG_16 We are in an odd sequence of backslashes
FLAG_32 This is *not* a space out of quotes
FLAG_64 This is an opening single/double quote
FLAG_128 Don't ignore this character
FLAG_256 Jump this character and the one before this
*/
register size_t idx_s = 0, idx_d = 0;
register uint16_t abcd = (is_some_space(*ini_string, _LIBCONFINI_WITH_EOL_) ? 128 : 160) |
(format.no_double_quotes << 1) |
format.no_single_quotes;
for (; ini_string[idx_s]; idx_s++) {
/* Revision #2 */
abcd = !(abcd & 12) && is_some_space(ini_string[idx_s], _LIBCONFINI_WITH_EOL_) ?
(
abcd & 32 ?
(abcd & 143) | 128
:
abcd & 47
)
: !(abcd & 25) && ini_string[idx_s] == _LIBCONFINI_SINGLE_QUOTES_ ?
(
~abcd & 4 ?
(abcd & 239) | 196
: abcd & 64 ?
(abcd & 299) | 256
:
(abcd & 171) | 160
)
: !(abcd & 22) && ini_string[idx_s] == _LIBCONFINI_DOUBLE_QUOTES_ ?
(
~abcd & 8 ?
(abcd & 239) | 200
: abcd & 64 ?
(abcd & 295) | 256
:
(abcd & 167) | 160
)
: ini_string[idx_s] == _LIBCONFINI_BACKSLASH_ ?
((abcd & 191) | 160) ^ 16
:
(abcd & 175) | 160;
if (abcd & 256) {
idx_d--;
} else if (abcd & 128) {
ini_string[idx_d++] = abcd & 44 ? ini_string[idx_s] : _LIBCONFINI_COLLAPSED_;
}
}
for (
idx_s = !(abcd & 32) && idx_d ?
--idx_d
:
idx_d;
ini_string[idx_s];
ini_string[idx_s++] = '\0'
);
return idx_d;
}
/**
@brief Out of quotes similar to ECMAScript
`ini_string.replace(/\s+/g, " ")`
@param ini_string The string to collapse -- multi-line escape
sequences must be already unescaped at this
stage
@param format The format of the INI file
@return The new length of the string
**/
static size_t collapse_spaces (char * const ini_string, const IniFormat format) {
/*
Mask `abcd` (7 bits used):
FLAG_1 Single quotes are not metacharacters (const)
FLAG_2 Double quotes are not metacharacters (const)
FLAG_4 Unescaped single quotes are odd right now
FLAG_8 Unescaped double quotes are odd right now
FLAG_16 We are in an odd sequence of backslashes
FLAG_32 This is a space out of quotes
FLAG_64 Jump this character
*/
register uint8_t abcd = (format.no_double_quotes ? 34 : 32) | format.no_single_quotes;
register size_t idx_s = 0;
size_t idx_d = 0;
for (; ini_string[idx_s]; idx_s++) {
/* Revision #1 */
abcd = !(abcd & 12) && is_some_space(ini_string[idx_s], _LIBCONFINI_WITH_EOL_) ?
(
abcd & 32 ?
(abcd & 111) | 64
:
(abcd & 47) | 32
)
: !(abcd & 25) && ini_string[idx_s] == _LIBCONFINI_SINGLE_QUOTES_ ?
(abcd & 15) ^ 4
: !(abcd & 22) && ini_string[idx_s] == _LIBCONFINI_DOUBLE_QUOTES_ ?
(abcd & 15) ^ 8
: ini_string[idx_s] == _LIBCONFINI_BACKSLASH_ ?
(abcd & 31) ^ 16
:
abcd & 15;
if (~abcd & 64) {
ini_string[idx_d++] = abcd & 32 ? _LIBCONFINI_COLLAPSED_ : ini_string[idx_s];
}
}
for (
idx_s = (abcd & 32) && idx_d ?
--idx_d
:
idx_d;
ini_string[idx_s];
ini_string[idx_s++] = '\0'
);
return idx_d;
}
/**
@brief Similar to ECMAScript `str.replace(/''|""/g, "")`
@param str The string to collapse
@param format The format of the INI file
@return The new length of the string
**/
static size_t collapse_empty_quotes (char * const str, const IniFormat format) {
/*
Mask `abcd` (7 bits used):
FLAG_1 Single quotes are not metacharacters (const)
FLAG_2 Double quotes are not metacharacters (const)
FLAG_4 Unescaped single quotes are odd right now
FLAG_8 Unescaped double quotes are odd right now
FLAG_16 We are in an odd sequence of backslashes
FLAG_32 This is an opening single/double quote
FLAG_64 These are empty quotes
*/
register uint8_t abcd = (format.no_double_quotes << 1) | format.no_single_quotes;
register size_t lshift = ltrim_s(str, 0, _LIBCONFINI_WITH_EOL_), idx = lshift;
for (; str[idx]; idx++) {
/* Revision #1 */
abcd = str[idx] == _LIBCONFINI_BACKSLASH_ ?
(abcd & 31) ^ 16
: !(abcd & 22) && str[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ?
(
~abcd & 40 ?
((abcd & 47) | 32) ^ 8
:
(abcd & 71) | 64
)
: !(abcd & 25) && str[idx] == _LIBCONFINI_SINGLE_QUOTES_ ?
(
~abcd & 36 ?
((abcd & 47) | 32) ^ 4
:
(abcd & 75) | 64
)
:
abcd & 15;
str[idx - lshift] = str[idx];
if (abcd & 64) {
lshift += 2;
}
}
for (idx -= lshift; str[idx]; str[idx++] = '\0');
return rtrim_h(str, idx - lshift, _LIBCONFINI_WITH_EOL_);
}
/**
@brief Removes all comment initializers (`#` and/or `;`) from the
beginning of each line of a comment
@param srcstr The comment to parse (it may contain multi-line
escape sequences)
@param len The length of @p srcstr
@param format The format of the INI file
@return The new length of the string
- In multi-line comments: `srcstr.replace(/^[#;]+|(\n\r?|\r\n?)[\t \v\f]*[#;]+/g, "$1")`
- In single-line comments: `srcstr.replace(/^[#;]+/, "")`
The argument @p srcstr may begin with a comment initializer (`#` or `;`
depending on the format), or with the character that immediately follows it.
**/
static size_t uncomment (char * const srcstr, size_t len, const IniFormat format) {
register size_t idx_s = 0, idx_d = 0;
if (format.multiline_nodes == INI_MULTILINE_EVERYWHERE) {
/*
The comment can be multi-line
*/
/*
Mask `abcd` (6 bits used):
FLAG_1 Don't erase any character
FLAG_2 We are in an odd sequence of backslashes
FLAG_4 This new line character is escaped
FLAG_8 This character is a comment character and follows
`/(\n\s*|\r\s*)/`
FLAG_16 This character is a part of a group of spaces that follow
a new line (`/(\n|\r)[\t \v\f]+/`)
FLAG_32 This character is *not* a new line character (`/[\r\n]/`)
*/
for (register uint8_t abcd = 8; idx_s < len; idx_s++) {
abcd = srcstr[idx_s] == _LIBCONFINI_BACKSLASH_ ?
((abcd & 35) | 32) ^ 2
: srcstr[idx_s] == _LIBCONFINI_LF_ || srcstr[idx_s] == _LIBCONFINI_CR_ ?
(abcd << 1) & 4
: !(abcd & 32) && _LIBCONFINI_IS_ANY_MARKER_(srcstr[idx_s], format) ?
(abcd & 40) | 8
: !(abcd & 40) && is_some_space(srcstr[idx_s], _LIBCONFINI_NO_EOL_) ?
(abcd & 57) | 16
:
(abcd & 33) | 32;
if (!(abcd & 25)) {
srcstr[abcd & 4 ? idx_d - 1 : idx_d++] = srcstr[idx_s];
} else if (!(abcd & 28)) {
idx_d++;
}
}
} else {
/*
The comment cannot be multi-line
*/
for (; idx_s < len && _LIBCONFINI_IS_ANY_MARKER_(srcstr[idx_s], format); idx_s++);
if (!idx_s) {
return len;
}
for (; idx_s < len; srcstr[idx_d++] = srcstr[idx_s++]);
}
for (idx_s = idx_d; idx_s < len; srcstr[idx_s++] = '\0');
return idx_d;
}
/**
@brief Tries to determine the type of a member "as if it was active"
@param srcstr String containing an individual node (it may
contain multi-line escape sequences)
@param len Length of the node
@param allow_implicit A boolean: `true` if keys without a key-value
delimiter are allowed, `false` otherwise
@param format The format of the INI file
@return The node type (see header)
**/
static uint8_t get_type_as_active (
const char * const srcstr,
const size_t len,
const _LIBCONFINI_CHARBOOL_ allow_implicit,
const IniFormat format
) {
const _LIBCONFINI_CHARBOOL_ invalid_delimiter = _LIBCONFINI_IS_ESC_CHAR_(format.delimiter_symbol, format);
if (
!len || _LIBCONFINI_IS_ANY_MARKER_(*srcstr, format) || (
*((unsigned char *) srcstr) == format.delimiter_symbol && !invalid_delimiter
)
) {
return INI_UNKNOWN;
}
register uint16_t abcd;
register size_t idx;
if (format.section_paths != INI_NO_SECTIONS && *srcstr == _LIBCONFINI_OPEN_SECTION_) {
if (format.no_spaces_in_names) {
/*
Search for the CLOSE SECTION character and possible spaces in names
-- i.e., ECMAScript `/[^\.\s]\s+[^\.\s]/g.test(srcstr)`. The
algorithm is made more complex by the fact that LF and CR characters
are still escaped at this stage.
*/
/*
Mask `abcd` (10 bits used):
FLAG_1 Single quotes are not metacharacters (const)
FLAG_2 Double quotes are not metacharacters (const)
FLAG_4 Only one level of nesting is allowed (const)
FLAG_8 Unescaped single quotes are odd right now
FLAG_16 Unescaped double quotes are odd right now
FLAG_32 We are in an odd sequence of backslashes
FLAG_64 This is a space
FLAG_128 What follows cannot contain spaces
FLAG_256 Continue the loop
FLAG_512 Section path is *not* valid
*/
idx = 1;
abcd = (format.section_paths == INI_ONE_LEVEL_ONLY ? 772: 768) |
(format.no_double_quotes << 1) |
format.no_single_quotes;
do {
/* Revision #2 */
abcd = idx >= len ?
abcd & 767
: !(abcd & 42) && srcstr[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ?
(abcd & 991) ^ 16
: !(abcd & 49) && srcstr[idx] == _LIBCONFINI_SINGLE_QUOTES_ ?
(abcd & 991) ^ 8
: srcstr[idx] == _LIBCONFINI_LF_ || srcstr[idx] == _LIBCONFINI_CR_ ?
(abcd & 991) | 64
: is_some_space(srcstr[idx], _LIBCONFINI_NO_EOL_) ?
(
~abcd & 32 ?
abcd | 64
: ~abcd & 192 ?
(abcd & 991) | 192
:
(abcd & 767) | 128
)
: !(abcd & 28) && srcstr[idx] == _LIBCONFINI_SUBSECTION_ ?
(
~abcd & 224 ?
abcd & 799
:
abcd & 767
)
: !(abcd & 24) && srcstr[idx] == _LIBCONFINI_CLOSE_SECTION_ ?
(
~abcd & 224 ?
abcd & 159
:
abcd & 767
)
: srcstr[idx] == _LIBCONFINI_BACKSLASH_ ?
(
~abcd & 32 ?
abcd | 32
: ~abcd & 192 ?
(abcd & 991) | 128
:
(abcd & 735)
)
: ~abcd & 192 ?
(abcd & 927) | 128
:
(abcd & 671) | 128;
idx++;
} while (abcd & 256);
if (abcd & 512) {
return INI_UNKNOWN;
}
} else if ((idx = getn_metachar_pos(srcstr, _LIBCONFINI_CLOSE_SECTION_, len, format) + 1) > len) {
return INI_UNKNOWN;
}
/*
Scan for possible non-space characters following the CLOSE SECTION
character: if found the node cannot represent a section path (but it can
possibly represent a key). Empty quotes surrounded by spaces will be
tolerated.
*/
/*
Recycling variable `abcd` (6 bits used)...:
FLAG_1 Single quotes are not metacharacters (const)
FLAG_2 Double quotes are not metacharacters (const)
FLAG_4 Unescaped single quotes are odd right now
FLAG_8 Unescaped double quotes are odd right now
FLAG_16 We are in an odd sequence of backslashes
FLAG_32 Continue the loop
*/
abcd = 32 | (format.no_double_quotes << 1) | format.no_single_quotes;
/* \ /\
\ */ nonspace_check: /* \
\/ ______________________ \ */
if (abcd) {
if (idx >= len) {
return INI_SECTION;
}
switch (srcstr[idx++]) {
case _LIBCONFINI_VT_:
case _LIBCONFINI_FF_:
case _LIBCONFINI_HT_:
case _LIBCONFINI_SIMPLE_SPACE_:
abcd = abcd & 28 ? 0 : abcd & 47;
goto nonspace_check;
case _LIBCONFINI_LF_:
case _LIBCONFINI_CR_:
abcd = abcd & 12 ? 0 : abcd & 47;
goto nonspace_check;
case _LIBCONFINI_BACKSLASH_:
abcd = abcd & 28 ? 0 : abcd | 16;
goto nonspace_check;
case _LIBCONFINI_DOUBLE_QUOTES_:
abcd = abcd & 22 ? 0 : (abcd & 47) ^ 8;
goto nonspace_check;
case _LIBCONFINI_SINGLE_QUOTES_:
abcd = abcd & 25 ? 0 : (abcd & 47) ^ 4;
goto nonspace_check;
}
}
}
/*
It can be just a key...
*/
if (invalid_delimiter && !allow_implicit) {
return INI_UNKNOWN;
}
/*
Recycling variable `abcd` (2 bits used)...:
FLAG_1 The delimiter **must** be present
FLAG_2 Search for spaces in names
*/
abcd = (format.no_spaces_in_names << 1) | (allow_implicit ? 0 : 1);
if (abcd) {
idx = getn_metachar_pos(srcstr, (char) format.delimiter_symbol, len, format);
if ((abcd & 1) && idx == len) {
return INI_UNKNOWN;
}
if (abcd & 2) {
idx = urtrim_s(srcstr, idx);
do {
if (is_some_space(srcstr[--idx], _LIBCONFINI_WITH_EOL_)) {
return INI_UNKNOWN;
}
} while (idx);
}
}
return INI_KEY;
}
/**
@brief Examines a (single-/multi-line) segment and checks whether
it contains more than just one node
@param srcstr Segment to examine (it may contain multi-line
escape sequences)
@param format The format of the INI file
@return Number of entries found
**/
static size_t further_cuts (char * const srcstr, const IniFormat format) {
/*
Shared flags of mask `abcd` (8 bits used):
FLAG_1 Single quotes are not metacharacters (const)
FLAG_2 Double quotes are not metacharacters (const)
FLAG_4 Do not allow disabled entries after space (const)
FLAG_8 Formats supports multi-line entries everywhere (const)
FLAG_16 Formats supports multi-line entries everywhere except in
comments (const)
FLAG_32 Unescaped single quotes are odd right now
FLAG_64 Unescaped double quotes are odd right now
FLAG_128 We are in an odd sequence of backslashes
*/
register uint16_t abcd = ((format.disabled_after_space << 2) ^ 4) |
(format.no_double_quotes << 1) |
format.no_single_quotes | (
format.multiline_nodes == INI_MULTILINE_EVERYWHERE ?
8
: format.multiline_nodes == INI_BUT_COMMENTS ?
16
:
0
);
register size_t idx;
size_t focus_at, unparsable_at, search_at = 0, num_entries = 0;
/* \ /\
\ */ search_for_cuts: /* \
\/ ______________________ \ */
if (!srcstr[search_at]) {
return num_entries;
}
unparsable_at = 0;
abcd = _LIBCONFINI_IS_DIS_MARKER_(srcstr[search_at], format) && (
!(abcd & 4) || !is_some_space(srcstr[search_at + 1], _LIBCONFINI_NO_EOL_)
) ?
(abcd & 31) | 2560
: _LIBCONFINI_IS_IGN_MARKER_(srcstr[search_at], format) ?
(abcd & 8 ? (abcd & 31) | 1024 : abcd & 31)
: (abcd & 8) && (
srcstr[search_at] == _LIBCONFINI_IC_INT_MARKER_ || _LIBCONFINI_IS_ANY_MARKER_(srcstr[search_at], format)
) ?
(abcd & 31) | 3072
:
(abcd & 31) | 2048;
if (abcd & 2048) {
num_entries++;
}
if (abcd & 1536) {
/*
Node starts with `/[;#]/` and can be a disabled entry in any format, or
a simple comment or a block that must be ignored in multi-line formats
*/
/*
Mask `abcd` (14 bits used):
FLAG_256 This or the previous character was not a space
FLAG_512 We are in a disabled entry or a comment (semi-const)
FLAG_1024 We are in a simple comment or in a block that must be ignored
and format supports multi-line entries (semi-const)
FLAG_2048 We are *not* in a block that must be ignored (semi-const)
FLAG_4096 We have *not* just found an inline comment nested within a
disabled entry
FLAG_8192 We had previously found an inline comment nested in this
segment, but the entry that preceded it had been checked and
did not seem to represent a valid disabled entry
NOTE: For FLAG_1-FLAG_16 I will keep the values already assigned at
the beginning of the function; all other flags are already set
to zero. For the meaning of flags FLAG_1-FLAG_128 see the
beginning of the function.
*/
idx = ltrim_s(srcstr, search_at + 1, _LIBCONFINI_NO_EOL_) - 1;
/* \ /\
\ */ inactive_cut: /* \
\/ ______________________ \ */
switch (srcstr[++idx]) {
case '\0':
/* End of string */
if (~abcd & 8) {
/*
Check if this is a valid disabled entry. If it is not,
search for line breaks.
If the code has reached this point it means that according
to the format disabled entries can be multi-line but
comments cannot, and #get_type_as_active() has never been
invoked on this entry.
*/
focus_at = dqultrim_s(srcstr, search_at, format);
if (
srcstr[focus_at] && !get_type_as_active(
srcstr + focus_at,
idx - focus_at,
format.disabled_can_be_implicit,
format
)
) {
srcstr[search_at] = _LIBCONFINI_SC_INT_MARKER_;
unparsable_at = search_at + 1;
}
}
break;
case _LIBCONFINI_LF_:
case _LIBCONFINI_CR_:
/*
Line break has been found in a multi-line disabled entry or
a comment. Search for `/\\(?:\n\r?|\r\n?)\s*[^;#]/`.
*/
focus_at = dqultrim_s(srcstr, search_at, format);
idx = ltrim_s(srcstr, idx + 1, _LIBCONFINI_WITH_EOL_);
if (
abcd & 2048 ?
!(
_LIBCONFINI_IS_DIS_MARKER_(srcstr[idx], format) && (abcd & 24) && (
(~abcd & 516) || !is_some_space(srcstr[idx + 1], _LIBCONFINI_NO_EOL_)
)
) && !(
_LIBCONFINI_IS_COM_MARKER_(srcstr[idx], format) && (abcd & 8) && (
((abcd ^ 512) & 8704) || !get_type_as_active(
srcstr + focus_at,
idx - focus_at,
format.disabled_can_be_implicit,
format
)
)
)
:
!_LIBCONFINI_IS_IGN_MARKER_(srcstr[idx], format)
) {
rtrim_h(srcstr, idx, _LIBCONFINI_WITH_EOL_);
search_at = qultrim_h(srcstr, idx, format);
goto search_for_cuts;
}
/*
No case break here, keep it like this! `case /[ \t\v\f]/` must
follow (switch case fallthrough).
*/
__attribute__((fallthrough));
case _LIBCONFINI_VT_:
case _LIBCONFINI_FF_:
case _LIBCONFINI_HT_:
case _LIBCONFINI_SIMPLE_SPACE_:
abcd = (abcd & 15999) | 4096;
goto inactive_cut;
case _LIBCONFINI_BACKSLASH_:
abcd = (abcd | 4352) ^ 128;
goto inactive_cut;
default:
abcd = !(abcd & 1376) && (~abcd & 8200) && _LIBCONFINI_IS_ANY_MARKER_(srcstr[idx], format) ?
(abcd & 12159) | 256
: !(abcd & 162) && srcstr[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ?
((abcd & 16255) | 4352) ^ 64
: !(abcd & 193) && srcstr[idx] == _LIBCONFINI_SINGLE_QUOTES_ ?
((abcd & 16255) | 4352) ^ 32
:
(abcd & 16255) | 4352;
if (abcd & 4096) {
goto inactive_cut;
}
if (~abcd & 8192) {
/*
Inline comment has been found in a (supposedly) disabled entry.
*/
focus_at = dqultrim_s(srcstr, search_at, format);
if (get_type_as_active(
srcstr + focus_at,
idx - focus_at,
format.disabled_can_be_implicit,
format
)) {
if (!_LIBCONFINI_IS_IGN_MARKER_(srcstr[idx], format)) {
srcstr[idx] = _LIBCONFINI_IC_INT_MARKER_;
num_entries++;
}
srcstr[idx - 1] = '\0';
if (abcd & 8) {
goto inactive_cut;
}
unparsable_at = idx + 1;
} else {
abcd |= 8192;
srcstr[search_at] = _LIBCONFINI_SC_INT_MARKER_;
if (abcd & 8) {
goto inactive_cut;
}
unparsable_at = search_at + 1;
}
}
/* No case break here (last case) */
}
} else if (_LIBCONFINI_IS_ANY_MARKER_(srcstr[search_at], format)) {
/*
Node starts with `/[;#]/` but cannot be multi-line or represent a
disabled entry
*/
unparsable_at = search_at + 1;
} else {
/*
Node is active: search for inline comments
*/
/*
Recycling variable `abcd` (11 bits used)...:
FLAG_256 Comment marker follows an escaped new line made of only one
character (i.e., `"\\\n"` or `"\\\r"` but neither `"\\\r\n"`
or `"\\\n\r"`)
FLAG_512 This was neither a hash nor a semicolon character
FLAG_1024 This was not a space
NOTE: For FLAG_1-FLAG_16 I will keep the values already assigned at the
beginning of the function; all other flags are already set to zero
(see previous usage of `abcd` within this function), with the only
exception of FLAG_2048, which I am going to overwrite immediately.
For the meaning of flags FLAG_1-FLAG_128 see the beginning of the
function.
*/
abcd = (abcd & 2047) | 1536;
idx = search_at;
/* \ /\
\ */ active_cut: /* \
\/ ______________________ \ */
switch (srcstr[++idx]) {
case '\0':
/* End of string */
break;
case _LIBCONFINI_VT_:
case _LIBCONFINI_FF_:
case _LIBCONFINI_HT_:
case _LIBCONFINI_SIMPLE_SPACE_:
abcd = (abcd & 639) | 512;
goto active_cut;
case _LIBCONFINI_LF_:
case _LIBCONFINI_CR_:
abcd = (abcd & 639) | ((abcd << 1) & 256) | 512;
goto active_cut;
case _LIBCONFINI_BACKSLASH_:
abcd = ((abcd & 1791) | 1536) ^ 128;
goto active_cut;
default:
abcd = _LIBCONFINI_IS_ANY_MARKER_(srcstr[idx], format) ?
abcd & 1407
: !(abcd & 162) && srcstr[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ?
((abcd & 1791) | 1536) ^ 64
: !(abcd & 193) && srcstr[idx] == _LIBCONFINI_SINGLE_QUOTES_ ?
((abcd & 1791) | 1536) ^ 32
:
(abcd & 1791) | 1536;
if (abcd & 1760) {
goto active_cut;
}
/*
Inline comment has been found in an active entry.
*/
if (abcd & 256) {
/*
Remove the backslash if the comment immediately follows an
escaped new line expressed by one chararacter
(`/\\[\r\n]/`). In case of CR + LF or LF + CR
(`/\\\n\r|\\\r\n/`) the backslash will be removed later by
#strip_ini_cache().
*/
srcstr[idx - 2] = '\0';
}
srcstr[idx - 1] = '\0';
if (!_LIBCONFINI_IS_IGN_MARKER_(srcstr[idx], format)) {
srcstr[idx] = _LIBCONFINI_IC_INT_MARKER_;
if (abcd & 8) {
search_at = idx;
goto search_for_cuts;
}
num_entries++;
} else if (abcd & 8) {
search_at = idx;
goto search_for_cuts;
}
unparsable_at = idx + 1;
/* No case break here (last case) */
}
}
if (unparsable_at) {
/*
Cut unparsable multi-line comments
*/
for (idx = unparsable_at; srcstr[idx]; idx++) {
if (srcstr[idx] == _LIBCONFINI_LF_ || srcstr[idx] == _LIBCONFINI_CR_) {
search_at = qultrim_h(srcstr, idx, format);
goto search_for_cuts;
}
}
}
return num_entries;
}
/** @startfnlist **/
/*\
|*|
|*| GLOBAL ENVIRONMENT
|*| ________________________________
\*/
/* LIBRARY'S MAIN FUNCTIONS */
/** @utility{strip_ini_cache} **/
/**
@brief Parses and tokenizes a buffer containing an INI file, then
dispatches its content to a custom callback
@param ini_source The buffer containing the INI file to tokenize
@param ini_length The length of @p ini_source without counting the
NUL terminator (if any -- se below)
@param format The format of the INI file
@param f_init The function that will be invoked before the
first dispatch, or `NULL`
@param f_foreach The function that will be invoked for each
dispatch, or `NULL`
@param user_data A custom argument, or `NULL`
@return Zero for success, otherwise an error code (see `enum`
#ConfiniInterruptNo)
The @p ini_source parameter must be a valid pointer to a buffer of size
@p ini_length + 1 and cannot be `NULL`. The @p ini_source string does not need
to be NUL-terminated, but _it does need one extra byte where to append a NUL
terminator_ -- in fact, as soon as this function is invoked,
`ini_source[ini_length]` will be immediately set to `\0`.
In most cases, as when using `strlen()` for computing @p ini_length, this is not
a concern, since `ini_source[ini_length]` will always be `\0` by the very
definition of `strlen()`, and will only get overwritten with the same value.
However, if you are passing a substring of a string, for example the fragment
`foo=bar` of the string `foo=barracuda`, you must expect the string to be
immediately truncated into `foo=bar\0acuda`.
In other words, @p ini_source must point to a memory location where at least
`ini_length + 1` bytes are freely usable.
The user given function @p f_init (see #IniStatsHandler data type) will be
invoked with two arguments: `statistics` (a pointer to an #IniStatistics
structure containing some properties about the file read) and `user_data` (the
custom argument @p user_data previously passed). If @p f_init returns a non-zero
value the caller function will be interrupted.
The user given function @p f_foreach (see #IniDispHandler data type) will be
invoked with two arguments: `dispatch` (a pointer to an #IniDispatch structure
containing the parsed member of the INI file) and `user_data` (the custom
argument @p user_data previously passed). If @p f_foreach returns a non-zero
value the caller function will be interrupted.
After invoking `strip_ini_cache()`, the buffer pointed by the @p ini_source
parameter must be considered as a _corrupted buffer_ and should be freed or
overwritten. For more information about this function, please refer to the
@ref libconfini.
The parsing algorithms used by **libconfini** are able to parse any type of file
encoded in 8-bit code units, as long as the characters that match the regular
expression `/[\s\[\]\.\\;#"']/` refer to the same code points they refer to in
ASCII (as they do, for example, in UTF-8 and ISO-8859-1), independently of
platform-specific conventions.
@note In order to be null-byte-injection-safe, before dispatching the parsed
content this function will strip all `NUL` characters possibly present
in the buffer (with the exception of the last one).
Possible return values are: #CONFINI_SUCCESS, #CONFINI_IINTR, #CONFINI_FEINTR,
#CONFINI_EOOR.
@include topics/strip_ini_cache.c
**/
int strip_ini_cache (
register char * const ini_source,
const size_t ini_length,
const IniFormat format,
const IniStatsHandler f_init,
const IniDispHandler f_foreach,
void * const user_data
) {
const _LIBCONFINI_CHARBOOL_ valid_delimiter = !_LIBCONFINI_IS_ESC_CHAR_(format.delimiter_symbol, format);
_LIBCONFINI_CHARBOOL_ tmp_bool;
register size_t idx, tmp_fast_size_t_1, tmp_fast_size_t_2;
size_t tmp_size_t_1, tmp_size_t_2;
ini_source[ini_length] = '\0';
/*
PART ONE: Examine and isolate each segment
*/
#define __ISNT_ESCAPED__ tmp_bool
#define __LSHIFT__ tmp_fast_size_t_1
#define __EOL_N__ tmp_fast_size_t_2
#define __N_MEMBERS__ tmp_size_t_1
#define __NODE_AT__ tmp_size_t_2
/* UTF-8 BOM */
__LSHIFT__ = *((unsigned char *) ini_source) == 0xEF &&
*((unsigned char *) ini_source + 1) == 0xBB &&
*((unsigned char *) ini_source + 2) == 0xBF
? 3 : 0;
for (
__N_MEMBERS__ = 0,
__EOL_N__ = _LIBCONFINI_EOL_IDX_,
__ISNT_ESCAPED__ = _LIBCONFINI_TRUE_,
__NODE_AT__ = 0,
idx = __LSHIFT__;
idx < ini_length;
idx++
) {
ini_source[idx - __LSHIFT__] = ini_source[idx];
if (ini_source[idx] == _LIBCONFINI_SPACES_[__EOL_N__] || ini_source[idx] == _LIBCONFINI_SPACES_[__EOL_N__ ^= 1]) {
if (format.multiline_nodes == INI_NO_MULTILINE || __ISNT_ESCAPED__) {
ini_source[idx - __LSHIFT__] = '\0';
__N_MEMBERS__ += further_cuts(ini_source + qultrim_h(ini_source, __NODE_AT__, format), format);
__NODE_AT__ = idx - __LSHIFT__ + 1;
} else if (ini_source[idx + 1] == _LIBCONFINI_SPACES_[__EOL_N__ ^ 1]) {
idx++;
ini_source[idx - __LSHIFT__] = ini_source[idx];
}
__ISNT_ESCAPED__ = _LIBCONFINI_TRUE_;
} else if (ini_source[idx] == _LIBCONFINI_BACKSLASH_) {
__ISNT_ESCAPED__ = !__ISNT_ESCAPED__;
} else if (ini_source[idx]) {
__ISNT_ESCAPED__ = _LIBCONFINI_TRUE_;
} else {
/* Remove `NUL` characters from the buffer (if any) */
__LSHIFT__++;
}
}
const size_t real_length = idx - __LSHIFT__;
while (idx > real_length) {
ini_source[--idx] = '\0';
}
__N_MEMBERS__ += further_cuts(ini_source + qultrim_h(ini_source, __NODE_AT__, format), format);
/* Debug */
/*
for (size_t tmp = 0; tmp < ini_length + 1; tmp++) {
putchar(ini_source[tmp] == 0 ? '$' : ini_source[tmp]);
}
putchar('\n');
*/
IniStatistics this_doc = {
.format = format,
.bytes = ini_length,
.members = __N_MEMBERS__
};
if (f_init && f_init(&this_doc, user_data)) {
return CONFINI_IINTR;
}
#undef __NODE_AT__
#undef __N_MEMBERS__
#undef __EOL_N__
#undef __LSHIFT__
#undef __ISNT_ESCAPED__
/*
PART TWO: Dispatch the parsed input
*/
if (!f_foreach) {
return CONFINI_SUCCESS;
}
#define __ITER__ tmp_fast_size_t_1
#define __NODE_AT__ tmp_fast_size_t_2
#define __PARENT_IS_DISABLED__ tmp_bool
#define __REAL_PARENT_LEN__ tmp_size_t_1
#define __CURR_PARENT_LEN__ tmp_size_t_2
__REAL_PARENT_LEN__ = 0, __CURR_PARENT_LEN__ = 0;
char
* curr_parent_str = ini_source + real_length,
* subparent_str = curr_parent_str,
* real_parent_str = curr_parent_str;
IniDispatch dsp = {
.format = format,
.dispatch_id = 0
};
__PARENT_IS_DISABLED__ = _LIBCONFINI_FALSE_;
for (__NODE_AT__ = 0, idx = 0; idx <= real_length; idx++) {
if (ini_source[idx]) {
continue;
}
if (!ini_source[__NODE_AT__] || _LIBCONFINI_IS_IGN_MARKER_(ini_source[__NODE_AT__], format)) {
__NODE_AT__ = idx + 1;
continue;
}
if (dsp.dispatch_id >= this_doc.members) {
return CONFINI_EOOR;
}
dsp.data = ini_source + __NODE_AT__;
dsp.d_len = idx - __NODE_AT__;
/* Set `dsp.value` to an empty string */
dsp.value = ini_source + idx;
if (
_LIBCONFINI_IS_DIS_MARKER_(*dsp.data, format) && (
format.disabled_after_space || !is_some_space(dsp.data[1], _LIBCONFINI_NO_EOL_)
)
) {
__ITER__ = dqultrim_s(dsp.data, 0, format);
dsp.type = get_type_as_active(
dsp.data + __ITER__,
dsp.d_len - __ITER__,
format.disabled_can_be_implicit,
format
);
if (dsp.type) {
dsp.data += __ITER__;
dsp.d_len -= __ITER__;
/*
// Not strictly needed...
for (; __ITER__ > 0; dsp.data[--__ITER__] = '\0');
*/
}
dsp.type |= 4;
} else {
switch (*dsp.data) {
default:
if (!_LIBCONFINI_IS_ANY_MARKER_(*dsp.data, format)) {
dsp.type = get_type_as_active(dsp.data, dsp.d_len, _LIBCONFINI_TRUE_, format);
break;
}
/*
No case break here, keep it like this!
`case _LIBCONFINI_SC_INT_MARKER_` must follow
(switch case fallthrough).
*/
__attribute__((fallthrough));
case _LIBCONFINI_SC_INT_MARKER_:
/*
// Not strictly needed...
*dsp.data = '\0';
*/
dsp.type = INI_COMMENT;
break;
case _LIBCONFINI_IC_INT_MARKER_:
/*
// Not strictly needed...
*dsp.data = '\0';
*/
dsp.type = INI_INLINE_COMMENT;
/* No case break here (last case) */
}
}
if (__CURR_PARENT_LEN__ && *subparent_str) {
__ITER__ = 0;
do {
curr_parent_str[__ITER__ + __CURR_PARENT_LEN__] = subparent_str[__ITER__];
} while (subparent_str[__ITER__++]);
__CURR_PARENT_LEN__ += __ITER__ - 1;
subparent_str = curr_parent_str + __CURR_PARENT_LEN__;
}
if (__PARENT_IS_DISABLED__ && !(dsp.type & 4)) {
real_parent_str[__REAL_PARENT_LEN__] = '\0';
__CURR_PARENT_LEN__ = __REAL_PARENT_LEN__;
curr_parent_str = real_parent_str;
__PARENT_IS_DISABLED__ = _LIBCONFINI_FALSE_;
} else if (!__PARENT_IS_DISABLED__ && dsp.type == INI_DISABLED_SECTION) {
__REAL_PARENT_LEN__ = __CURR_PARENT_LEN__;
real_parent_str = curr_parent_str;
__PARENT_IS_DISABLED__ = _LIBCONFINI_TRUE_;
}
dsp.append_to = curr_parent_str;
dsp.at_len = __CURR_PARENT_LEN__;
if (dsp.type == INI_COMMENT || dsp.type == INI_INLINE_COMMENT) {
dsp.d_len = uncomment(++dsp.data, dsp.d_len - 1, format);
} else if (format.multiline_nodes != INI_NO_MULTILINE) {
dsp.d_len = unescape_cr_lf(dsp.data, dsp.d_len, dsp.type & 4, format);
}
switch (dsp.type) {
/*
case INI_UNKNOWN:
// Do nothing
break;
*/
case INI_SECTION:
case INI_DISABLED_SECTION:
*dsp.data++ = '\0';
__ITER__ = getn_metachar_pos(dsp.data, _LIBCONFINI_CLOSE_SECTION_, dsp.d_len, format);
while (dsp.data[__ITER__]) {
dsp.data[__ITER__++] = '\0';
}
dsp.d_len = format.section_paths == INI_ONE_LEVEL_ONLY ?
collapse_everything(dsp.data, format)
:
sanitize_section_path(dsp.data, format);
if (format.section_paths == INI_ONE_LEVEL_ONLY || *dsp.data != _LIBCONFINI_SUBSECTION_) {
/*
Append to root (this is an absolute path)
*/
curr_parent_str = dsp.data;
__CURR_PARENT_LEN__ = dsp.d_len;
subparent_str = ini_source + idx;
dsp.append_to = subparent_str;
dsp.at_len = 0;
} else if (format.section_paths == INI_ABSOLUTE_ONLY || !__CURR_PARENT_LEN__) {
/*
Append to root and remove the leading dot (parent is root or
relative paths are not allowed)
*/
curr_parent_str = ++dsp.data;
__CURR_PARENT_LEN__ = --dsp.d_len;
subparent_str = ini_source + idx;
dsp.append_to = subparent_str;
dsp.at_len = 0;
} else if (dsp.d_len != 1) {
/*
Append to the current parent (this is a relative path
and parent is not root)
*/
subparent_str = dsp.data;
}
if (INI_GLOBAL_LOWERCASE_MODE && !format.case_sensitive) {
string_tolower(dsp.data);
}
break;
case INI_KEY:
case INI_DISABLED_KEY:
if (
valid_delimiter && (
__ITER__ = getn_metachar_pos(dsp.data, (char) dsp.format.delimiter_symbol, dsp.d_len, format)
) < dsp.d_len
) {
dsp.data[__ITER__] = '\0';
dsp.value = dsp.data + __ITER__ + 1;
switch ((format.preserve_empty_quotes << 1) | format.do_not_collapse_values) {
case 0: dsp.v_len = collapse_everything(dsp.value, format); break;
case 1: dsp.v_len = collapse_empty_quotes(dsp.value, format); break;
case 2: dsp.v_len = collapse_spaces(dsp.value, format); break;
case 4:
dsp.value += ltrim_h(dsp.value, 0, _LIBCONFINI_WITH_EOL_);
dsp.v_len = rtrim_h(dsp.value, dsp.d_len + dsp.data - dsp.value, _LIBCONFINI_WITH_EOL_);
/* No case break here (last case) */
}
} else if (format.implicit_is_not_empty) {
dsp.value = INI_GLOBAL_IMPLICIT_VALUE;
dsp.v_len = INI_GLOBAL_IMPLICIT_V_LEN;
}
dsp.d_len = collapse_everything(dsp.data, format);
if (INI_GLOBAL_LOWERCASE_MODE && !format.case_sensitive) {
string_tolower(dsp.data);
}
break;
case INI_COMMENT:
case INI_INLINE_COMMENT:
dsp.append_to = ini_source + idx;
dsp.at_len = 0;
/* No case break here (last case) */
}
if (f_foreach(&dsp, user_data)) {
return CONFINI_FEINTR;
}
dsp.dispatch_id++;
__NODE_AT__ = idx + 1;
}
#undef __CURR_PARENT_LEN__
#undef __REAL_PARENT_LEN__
#undef __PARENT_IS_DISABLED__
#undef __NODE_AT__
#undef __ITER__
return CONFINI_SUCCESS;
}
/** @utility{load_ini_file} **/
/**
@brief Parses an INI file and dispatches its content to a custom
callback using a `FILE` structure as argument
@param ini_file The `FILE` handle pointing to the INI file to
parse
@param format The format of the INI file
@param f_init The function that will be invoked before the
first dispatch, or `NULL`
@param f_foreach The function that will be invoked for each
dispatch, or `NULL`
@param user_data A custom argument, or `NULL`
@return Zero for success, otherwise an error code (see `enum`
#ConfiniInterruptNo)
@note This function is absent if the `--without-io-api` option was passed to
the `configure` script when the library was compiled
The @p ini_file parameter must be a `FILE` handle with read privileges. On some
platforms, such as Microsoft Windows, it might be needed to add the binary
specifier to the mode string (`"b"`) in order to prevent discrepancies between
the physical size of the file and its computed size:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c}
FILE * my_file = fopen("example.conf", "rb");
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
For the two parameters @p f_init and @p f_foreach see function
#strip_ini_cache().
The parsing algorithms used by **libconfini** are able to parse any type of file
encoded in 8-bit code units, as long as the characters that match the regular
expression `/[\s\[\]\.\\;#"']/` refer to the same code points they refer to in
ASCII (as they do, for example, in UTF-8 and ISO-8859-1), independently of
platform-specific conventions.
@note In order to be null-byte-injection safe, `NUL` characters, if present in
the file, will be removed from the dispatched strings.
Possible return values are: #CONFINI_SUCCESS, #CONFINI_IINTR, #CONFINI_FEINTR,
#CONFINI_ENOMEM, #CONFINI_EIO, #CONFINI_EOOR, #CONFINI_EBADF, #CONFINI_EFBIG.
@include topics/load_ini_file.c
**/
int load_ini_file (
FILE * const ini_file,
const IniFormat format,
const IniStatsHandler f_init,
const IniDispHandler f_foreach,
void * const user_data
) {
_LIBCONFINI_OFF_T_ file_size;
if (_LIBCONFINI_SEEK_EOF_(ini_file) || (file_size = _LIBCONFINI_FTELL_(ini_file)) < 0) {
return CONFINI_EBADF;
}
if ((size_t) file_size > SIZE_MAX) {
return CONFINI_EFBIG;
}
char * const cache = (char *) malloc((size_t) file_size + 1);
if (!cache) {
return CONFINI_ENOMEM;
}
rewind(ini_file);
if (fread(cache, 1, (size_t) file_size, ini_file) < (size_t) file_size) {
free(cache);
return CONFINI_EIO;
}
const int return_value = strip_ini_cache(cache, (size_t) file_size, format, f_init, f_foreach, user_data);
free(cache);
return return_value;
}
/** @utility{load_ini_path} **/
/**
@brief Parses an INI file and dispatches its content to a custom
callback using a path as argument
@param path The path of the INI file
@param format The format of the INI file
@param f_init The function that will be invoked before the
first dispatch, or `NULL`
@param f_foreach The function that will be invoked for each
dispatch, or `NULL`
@param user_data A custom argument, or `NULL`
@return Zero for success, otherwise an error code (see `enum`
#ConfiniInterruptNo)
@note This function is absent if the `--without-io-api` option was passed to
the `configure` script when the library was compiled
For the two parameters @p f_init and @p f_foreach see function
#strip_ini_cache().
The parsing algorithms used by **libconfini** are able to parse any type of file
encoded in 8-bit code units, as long as the characters that match the regular
expression `/[\s\[\]\.\\;#"']/` refer to the same code points they refer to in
ASCII (as they do, for example, in UTF-8 and ISO-8859-1), independently of
platform-specific conventions.
@note In order to be null-byte-injection safe, `NUL` characters, if present in
the file, will be removed from the dispatched strings.
Possible return values are: #CONFINI_SUCCESS, #CONFINI_IINTR, #CONFINI_FEINTR,
#CONFINI_ENOENT, #CONFINI_ENOMEM, #CONFINI_EIO, #CONFINI_EOOR, #CONFINI_EBADF,
#CONFINI_EFBIG.
@include topics/load_ini_path.c
**/
int load_ini_path (
const char * const path,
const IniFormat format,
const IniStatsHandler f_init,
const IniDispHandler f_foreach,
void * const user_data
) {
FILE * const ini_file = fopen(path, "rb");
if (!ini_file) {
return CONFINI_ENOENT;
}
_LIBCONFINI_OFF_T_ file_size;
if (_LIBCONFINI_SEEK_EOF_(ini_file) || (file_size = _LIBCONFINI_FTELL_(ini_file)) < 0) {
return CONFINI_EBADF;
}
if ((size_t) file_size > SIZE_MAX) {
return CONFINI_EFBIG;
}
char * const cache = (char *) malloc((size_t) file_size + 1);
if (!cache) {
return CONFINI_ENOMEM;
}
rewind(ini_file);
if (fread(cache, 1, (size_t) file_size, ini_file) < (size_t) file_size) {
free(cache);
return CONFINI_EIO;
}
/* No checks here, as there is nothing we can do about it... */
fclose(ini_file);
const int return_value = strip_ini_cache(cache, (size_t) file_size, format, f_init, f_foreach, user_data);
free(cache);
return return_value;
}
/* OTHER UTILITIES (NOT REQUIRED BY LIBCONFINI'S MAIN FUNCTIONS) */
/** @utility{ini_string_match_ss} **/
/**
@brief Compares two simple strings and checks whether they match
@param simple_string_a The first simple string
@param simple_string_b The second simple string
@param format The format of the INI file
@return A boolean: `true` if the two strings match, `false` otherwise
Simple strings are user-given strings or the result of #ini_string_parse(). The
@p format argument is used for the following fields:
- `format.case_sensitive`
**/
bool ini_string_match_ss (
const char * const simple_string_a,
const char * const simple_string_b,
const IniFormat format
) {
register size_t idx = 0;
if (format.case_sensitive) {
do {
if (simple_string_a[idx] != simple_string_b[idx]) {
return _LIBCONFINI_FALSE_;
}
} while (simple_string_a[idx++]);
return _LIBCONFINI_TRUE_;
}
do {
if (_LIBCONFINI_CHR_CASEFOLD_(simple_string_a[idx]) != _LIBCONFINI_CHR_CASEFOLD_(simple_string_b[idx])) {
return _LIBCONFINI_FALSE_;
}
} while (simple_string_a[idx++]);
return _LIBCONFINI_TRUE_;
}
/** @utility{ini_string_match_si} **/
/**
@brief Compares a simple string and an INI string and and checks
whether they match
@param ini_string The INI string escaped according to
@p format
@param simple_string The simple string
@param format The format of the INI file
@return A boolean: `true` if the two strings match, `false` otherwise
This function grants that the result of the comparison between a simple string
and an INI string
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c}
printf(
"%s\n",
ini_string_match_si(my_simple_string, my_ini_string, format) ?
"They match"
:
"They don't match"
);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
will always match the result of the _literal_ comparison between the simple
string and the INI string after the latter has been parsed by
#ini_string_parse() when `format.do_not_collapse_values` is set to `false`.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c}
ini_string_parse(my_ini_string, format);
printf(
"%s\n",
ini_string_match_ss(my_simple_string, my_ini_string, format) ?
"They match"
:
"They don't match"
);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
INI strings are the strings typically dispatched by #load_ini_file(),
#load_ini_path() or #strip_ini_cache(), which may contain quotes and the three
escape sequences `\\`, `\'` and `\"`. Simple strings are user-given strings or
the result of #ini_string_parse().
In order to be suitable for both names and values, **this function always
considers sequences of one or more spaces out of quotes in the INI string as
collapsed**, even when `format.do_not_collapse_values` is set to `true`.
The @p format argument is used for the following fields:
- `format.case_sensitive`
- `format.no_double_quotes`
- `format.no_single_quotes`
- `format.multiline_nodes`
@include topics/ini_string_match_si.c
**/
bool ini_string_match_si (
const char * const simple_string,
const char * const ini_string,
const IniFormat format
) {
/*
Mask `abcd` (8 bits used):
FLAG_1 Single quotes are not metacharacters (const)
FLAG_2 Double quotes are not metacharacters (const)
FLAG_4 Format supports escape sequences (const)
FLAG_8 Unescaped single quotes are odd right now
FLAG_16 Unescaped double quotes are odd right now
FLAG_32 This is an escaped single/double quote in a format that supports
single/double quotes
FLAG_64 This is a space
FLAG_128 Skip this character
*/
register uint8_t abcd = INIFORMAT_HAS_NO_ESC(format) ?
67
:
68 | (format.no_double_quotes << 1) | format.no_single_quotes;
register size_t idx_i = 0;
size_t idx_s = 0, nbacksl = 0;
/* \ /\
\ */ si_match: /* \
\/ ______________________ \ */
if ((abcd & 4) && ini_string[idx_i] == _LIBCONFINI_BACKSLASH_) {
for (abcd &= 63, nbacksl++; ini_string[++idx_i] == _LIBCONFINI_BACKSLASH_; nbacksl++);
}
abcd = !(abcd & 10) && ini_string[idx_i] == _LIBCONFINI_DOUBLE_QUOTES_ ?
(
nbacksl & 1 ?
(abcd & 63) | 32
:
((abcd & 223) | 128) ^ 16
)
: !(abcd & 17) && ini_string[idx_i] == _LIBCONFINI_SINGLE_QUOTES_ ?
(
nbacksl & 1 ?
(abcd & 63) | 32
:
((abcd & 223) | 128) ^ 8
)
: (abcd & 24) || !is_some_space(ini_string[idx_i], _LIBCONFINI_WITH_EOL_) ?
abcd & 31
: abcd & 64 ?
(abcd & 223) | 128
:
(abcd & 95) | 64;
if (nbacksl) {
nbacksl = ((abcd & 32 ? nbacksl : nbacksl + 1) >> 1) + 1;
while (--nbacksl) {
if (simple_string[idx_s++] != _LIBCONFINI_BACKSLASH_) {
return _LIBCONFINI_FALSE_;
}
}
}
if ((abcd & 128) || ((abcd & 64) && !simple_string[idx_s])) {
idx_i++;
goto si_match;
}
if (
abcd & 64 ?
simple_string[idx_s] != _LIBCONFINI_COLLAPSED_ || !simple_string[idx_s + 1]
: format.case_sensitive ?
ini_string[idx_i] != simple_string[idx_s]
:
_LIBCONFINI_CHR_CASEFOLD_(ini_string[idx_i]) != _LIBCONFINI_CHR_CASEFOLD_(simple_string[idx_s])
) {
return _LIBCONFINI_FALSE_;
}
idx_s++;
if (ini_string[idx_i++]) {
goto si_match;
}
return _LIBCONFINI_TRUE_;
}
/** @utility{ini_string_match_ii} **/
/**
@brief Compares two INI strings and checks whether they match
@param ini_string_a The first INI string unescaped according to
@p format
@param ini_string_b The second INI string unescaped according to
@p format
@param format The format of the INI file
@return A boolean: `true` if the two strings match, `false` otherwise
This function grants that the result of the comparison between two INI strings
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c}
printf(
"%s\n",
ini_string_match_ii(my_ini_string_1, my_ini_string_2, format) ?
"They match"
:
"They don't match"
);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
will always match the result of the _literal_ comparison between the same two
INI strings after these have been parsed by #ini_string_parse() when
`format.do_not_collapse_values` is set to `false`.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c}
ini_string_parse(my_ini_string_1, format);
ini_string_parse(my_ini_string_2, format);
printf("%s\n",
ini_string_match_ss(my_ini_string_1, my_ini_string_2, format) ?
"They match"
:
"They don't match"
);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
INI strings are the strings typically dispatched by #load_ini_file(),
#load_ini_path() or #strip_ini_cache(), which may contain quotes and the three
escape sequences `\\`, `\'` and `\"`.
In order to be suitable for both names and values, **this function always
considers sequences of one or more spaces out of quotes in both strings as
collapsed**, even when `format.do_not_collapse_values` is set to `true`.
The @p format argument is used for the following fields:
- `format.case_sensitive`
- `format.no_double_quotes`
- `format.no_single_quotes`
- `format.multiline_nodes`
**/
bool ini_string_match_ii (
const char * const ini_string_a,
const char * const ini_string_b,
const IniFormat format
) {
const _LIBCONFINI_CHARBOOL_ has_escape = !INIFORMAT_HAS_NO_ESC(format);
register uint8_t side = 1;
register _LIBCONFINI_CHARBOOL_ turn_allowed = _LIBCONFINI_TRUE_;
uint8_t abcd_pair[2];
const char * chrptr_pair[2] = { ini_string_a, ini_string_b };
size_t nbacksl_pair[2];
/*
Masks `abcd_pair[0]` and `abcd_pair[1]` (7 bits used):
FLAG_1 Single quotes are not metacharacters (const)
FLAG_2 Double quotes are not metacharacters (const)
FLAG_4 Unescaped single quotes are odd right now
FLAG_8 Unescaped double quotes are odd right now
FLAG_16 We are in an odd sequence of backslashes and format supports
escape sequences
FLAG_32 This is a space
FLAG_64 Skip this character
*/
abcd_pair[1] = *abcd_pair = 32 | (format.no_double_quotes << 1) | format.no_single_quotes;
/* \ /\
\ */ ii_match: /* \
\/ ______________________ \ */
nbacksl_pair[side] = 0;
if (has_escape && *chrptr_pair[side] == _LIBCONFINI_BACKSLASH_) {
for (nbacksl_pair[side]++; *(++chrptr_pair[side]) == _LIBCONFINI_BACKSLASH_; nbacksl_pair[side]++);
abcd_pair[side] = nbacksl_pair[side] & 1 ? (abcd_pair[side] & 31) | 16 : abcd_pair[side] & 15;
if (
(
(abcd_pair[side] & 9) || *chrptr_pair[side] != _LIBCONFINI_SINGLE_QUOTES_
) && (
(abcd_pair[side] & 6) || *chrptr_pair[side] != _LIBCONFINI_DOUBLE_QUOTES_
)
) {
nbacksl_pair[side]++;
}
} else {
abcd_pair[side] = !(abcd_pair[side] & 25) && *chrptr_pair[side] == _LIBCONFINI_SINGLE_QUOTES_ ?
((abcd_pair[side] & 111) | 64) ^ 4
: !(abcd_pair[side] & 22) && *chrptr_pair[side] == _LIBCONFINI_DOUBLE_QUOTES_ ?
((abcd_pair[side] & 111) | 64) ^ 8
: !(abcd_pair[side] & 12) && is_some_space(*chrptr_pair[side], _LIBCONFINI_WITH_EOL_) ?
(abcd_pair[side] & 111) | 96
: *chrptr_pair[side] ?
abcd_pair[side] & 47
:
abcd_pair[side] & 15;
if (abcd_pair[side] & 64) {
chrptr_pair[side]++;
goto ii_match;
}
}
if (side && turn_allowed) {
side ^= 1;
goto ii_match;
}
turn_allowed = _LIBCONFINI_TRUE_;
if (*nbacksl_pair || nbacksl_pair[1]) {
if (*nbacksl_pair >> 1 != nbacksl_pair[1] >> 1) {
return _LIBCONFINI_FALSE_;
}
side = 1;
goto ii_match;
}
if (
(
abcd_pair[side ^ 1] & 32 ?
!(abcd_pair[side] & 32)
:
abcd_pair[(side ^= 1) ^ 1] & 32
) && *chrptr_pair[side]
) {
if (*chrptr_pair[side]++ != _LIBCONFINI_COLLAPSED_) {
return _LIBCONFINI_FALSE_;
}
abcd_pair[side ^ 1] &= 95;
turn_allowed = _LIBCONFINI_FALSE_;
goto ii_match;
}
if (
format.case_sensitive ?
**chrptr_pair != *chrptr_pair[1]
:
_LIBCONFINI_CHR_CASEFOLD_(**chrptr_pair) != _LIBCONFINI_CHR_CASEFOLD_(*chrptr_pair[1])
) {
return _LIBCONFINI_FALSE_;
}
if (**chrptr_pair) {
(*chrptr_pair)++;
}
if (*chrptr_pair[1]) {
chrptr_pair[1]++;
}
if (**chrptr_pair || *chrptr_pair[1]) {
*abcd_pair &= 95;
abcd_pair[1] &= 95;
side = 1;
goto ii_match;
}
return _LIBCONFINI_TRUE_;
}
/** @utility{ini_array_match} **/
/**
@brief Compares two INI arrays and checks whether they match
@param ini_string_a The first INI array
@param ini_string_b The second INI array
@param delimiter The delimiter between the array members -- if
zero (see #INI_ANY_SPACE), any space is
delimiter (`/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`)
@param format The format of the INI file
@return A boolean: `true` if the two arrays match, `false` otherwise
This function grants that the result of the comparison between two INI arrays
will always match the the _literal_ comparison between the individual members
of both arrays after these have been parsed, one by one, by #ini_string_parse()
(with `format.do_not_collapse_values` set to `false`).
This function can be used, with `'.'` as delimiter, to compare section paths.
INI strings are the strings typically dispatched by #load_ini_file(),
#load_ini_path() or #strip_ini_cache(), which may contain quotes and the three
escape sequences `\\`, `\'` and `\"`.
In order to be suitable for both names and values, **this function always
considers sequences of one or more spaces out of quotes in both strings as
collapsed**, even when `format.do_not_collapse_values` is set to `true`.
The @p format argument is used for the following fields:
- `format.case_sensitive`
- `format.no_double_quotes`
- `format.no_single_quotes`
- `format.multiline_nodes`
**/
bool ini_array_match (
const char * const ini_string_a,
const char * const ini_string_b,
const char delimiter,
const IniFormat format
) {
if (_LIBCONFINI_IS_ESC_CHAR_(delimiter, format)) {
/*
We have no delimiters (array has only one member)
*/
return ini_string_match_ii(ini_string_a, ini_string_b, format);
}
const _LIBCONFINI_CHARBOOL_ has_escape = !INIFORMAT_HAS_NO_ESC(format);
register uint8_t side = 1;
register _LIBCONFINI_CHARBOOL_ turn_allowed = _LIBCONFINI_TRUE_;
uint8_t abcd_pair[2];
size_t nbacksl_pair[2];
const char * chrptr_pair[2] = {
ini_string_a + ltrim_s(ini_string_a, 0, _LIBCONFINI_WITH_EOL_),
ini_string_b + ltrim_s(ini_string_b, 0, _LIBCONFINI_WITH_EOL_)
};
/*
Masks `abcd_pair[0]` and `abcd_pair[1]` (8 bits used):
FLAG_1 Single quotes are not metacharacters (const)
FLAG_2 Double quotes are not metacharacters (const)
FLAG_4 Unescaped single quotes are odd right now
FLAG_8 Unescaped double quotes are odd right now
FLAG_16 We are in an odd sequence of backslashes and format supports
escape sequences
FLAG_32 This is a space
FLAG_64 This is a delimiter
FLAG_128 Skip this character
*/
abcd_pair[1] = *abcd_pair = 32 | (format.no_double_quotes << 1) | format.no_single_quotes;
/* \ /\
\ */ delimited_match: /* \
\/ ______________________ \ */
nbacksl_pair[side] = 0;
if (has_escape && *chrptr_pair[side] == _LIBCONFINI_BACKSLASH_) {
for (nbacksl_pair[side]++; *(++chrptr_pair[side]) == _LIBCONFINI_BACKSLASH_; nbacksl_pair[side]++);
abcd_pair[side] = nbacksl_pair[side] & 1 ? (abcd_pair[side] & 31) | 16 : abcd_pair[side] & 15;
if (
(
(abcd_pair[side] & 9) || *chrptr_pair[side] != _LIBCONFINI_SINGLE_QUOTES_
) && (
(abcd_pair[side] & 6) || *chrptr_pair[side] != _LIBCONFINI_DOUBLE_QUOTES_
)
) {
nbacksl_pair[side]++;
}
} else {
abcd_pair[side] = !(abcd_pair[side] & 12) && is_some_space(*chrptr_pair[side], _LIBCONFINI_WITH_EOL_) ?
(
delimiter || (abcd_pair[side] & 64) ?
(abcd_pair[side] & 239) | 160
:
(abcd_pair[side] & 111) | 96
)
: delimiter && !(abcd_pair[side] & 12) && *chrptr_pair[side] == delimiter ?
(abcd_pair[side] & 111) | 96
: !(abcd_pair[side] & 25) && *chrptr_pair[side] == _LIBCONFINI_SINGLE_QUOTES_ ?
((abcd_pair[side] & 175) | 128) ^ 4
: !(abcd_pair[side] & 22) && *chrptr_pair[side] == _LIBCONFINI_DOUBLE_QUOTES_ ?
((abcd_pair[side] & 175) | 128) ^ 8
: *chrptr_pair[side] ?
abcd_pair[side] & 47
: delimiter ?
abcd_pair[side] & 15
:
(abcd_pair[side] & 79) ^ 64;
if (abcd_pair[side] & 128) {
chrptr_pair[side]++;
goto delimited_match;
}
}
if (side && turn_allowed) {
side ^= 1;
goto delimited_match;
}
turn_allowed = _LIBCONFINI_TRUE_;
if (*nbacksl_pair || nbacksl_pair[1]) {
if (*nbacksl_pair >> 1 != nbacksl_pair[1] >> 1) {
return _LIBCONFINI_FALSE_;
}
side = 1;
goto delimited_match;
}
if ((*abcd_pair ^ abcd_pair[1]) & 64) {
return _LIBCONFINI_FALSE_;
}
if (
!(
abcd_pair[side ^ 1] & 32 ?
abcd_pair[side] & 96
:
(abcd_pair[(side ^= 1) ^ 1] & 96) ^ 32
) && *chrptr_pair[side]
) {
if (*chrptr_pair[side]++ != _LIBCONFINI_COLLAPSED_) {
return _LIBCONFINI_FALSE_;
}
abcd_pair[side ^ 1] &= 223;
turn_allowed = _LIBCONFINI_FALSE_;
goto delimited_match;
}
if (~*abcd_pair & 64) {
if (
format.case_sensitive ?
**chrptr_pair != *chrptr_pair[1]
:
_LIBCONFINI_CHR_CASEFOLD_(**chrptr_pair) != _LIBCONFINI_CHR_CASEFOLD_(*chrptr_pair[1])
) {
return _LIBCONFINI_FALSE_;
}
*abcd_pair &= 223;
abcd_pair[1] &= 223;
}
if (**chrptr_pair) {
(*chrptr_pair)++;
}
if (*chrptr_pair[1]) {
chrptr_pair[1]++;
}
if (**chrptr_pair || *chrptr_pair[1]) {
side = 1;
goto delimited_match;
}
return _LIBCONFINI_TRUE_;
}
/** @utility{ini_unquote} **/
/**
@brief Unescapes `\'`, `\"`, and `\\` and removes all unescaped quotes
(if single/double quotes are considered metacharacters in
respect to the format given)
@param ini_string The string to be unescaped
@param format The format of the INI file
@return The new length of the string
This function is very similar to #ini_string_parse(), except that does not
bother collapsing the sequences of more than one space that might result from
removing empty quotes. Its purpose is to be used to parse key and section names,
since these are always dispatched as already collapsed. In order to parse
values, or array parts listed in values, please use #ini_string_parse().
If you only need to compare @p ini_string with another string, consider to use
#ini_string_match_si() and #ini_string_match_ii() instead of parsing the former
and perform a simple comparison afterwards. These two functions are in fact able
to check directly for equality between unparsed INI strings without actually
modifiyng them.
Usually @p ini_string comes from an #IniDispatch (but any other string may be
used as well). If the string does not contain quotes, or if quotes are
considered to be normal characters, no changes will be made.
@note If @p ini_string comes from #INI_GLOBAL_IMPLICIT_VALUE this function is
no-op and will only return the value of #INI_GLOBAL_IMPLICIT_V_LEN minus
the offset of @p ini_string within #INI_GLOBAL_IMPLICIT_VALUE.
The @p format argument is used for the following fields:
- `format.no_single_quotes`
- `format.no_double_quotes`
- `format.multiline_nodes`
@include topics/ini_string_parse.c
**/
size_t ini_unquote (char * const ini_string, const IniFormat format) {
if (_LIBCONFINI_IMPLICIT_RANGE_(ini_string)) {
return INI_GLOBAL_IMPLICIT_V_LEN + INI_GLOBAL_IMPLICIT_VALUE - ini_string;
}
register size_t idx = 0;
if (INIFORMAT_HAS_NO_ESC(format)) {
/*
There are no escape sequences... I will just return the length of the
string...
*/
while (ini_string[idx++]);
return idx - 1;
}
size_t lshift = 0, nbacksl = 0;
/*
Mask `abcd` (6 bits used):
FLAG_1 Single quotes are not metacharacters (const)
FLAG_2 Double quotes are not metacharacters (const)
FLAG_4 Unescaped single quotes are odd right now
FLAG_8 Unescaped double quotes are odd right now
FLAG_16 This is an unescaped single quote and format supports single
quotes
FLAG_32 This is an unescaped double quote and format supports double
quotes
*/
for (register uint8_t abcd = (format.no_double_quotes << 1) | format.no_single_quotes; ini_string[idx]; idx++) {
abcd = !(abcd & 6) && ini_string[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ?
(abcd & 47) | 32
: !(abcd & 9) && ini_string[idx] == _LIBCONFINI_SINGLE_QUOTES_ ?
(abcd & 31) | 16
:
abcd & 15;
if (!(nbacksl & 1) && (abcd & 48)) {
abcd ^= (abcd >> 2) & 12;
lshift++;
continue;
}
if (ini_string[idx] == _LIBCONFINI_BACKSLASH_) {
nbacksl++;
} else {
if ((nbacksl & 1) && (abcd & 48)) {
lshift++;
}
lshift += nbacksl >> 1;
nbacksl = 0;
}
if (lshift) {
ini_string[idx - lshift] = ini_string[idx];
}
}
lshift += nbacksl >> 1;
for (idx -= lshift; ini_string[idx]; ini_string[idx++] = '\0');
return idx - lshift;
}
/** @utility{ini_string_parse} **/
/**
@brief Unescapes `\'`, `\"`, and `\\` and removes all unescaped quotes
(if single/double quotes are considered metacharacters in
respect to the format given); if the format allows it, sequences
of one or more spaces out of quotes will be collapsed
@param ini_string The string to be unescaped
@param format The format of the INI file
@return The new length of the string
This function is meant to be used to parse values. In order to parse key and
section names please use #ini_unquote().
If you only need to compare @p ini_string with another string, consider to use
#ini_string_match_si() and #ini_string_match_ii() instead of parsing the former
and perform a simple comparison afterwards. These two functions are in fact able
to check directly for equality between unparsed INI strings without actually
modifying them.
Usually @p ini_string comes from an #IniDispatch (but any other string may be
used as well). If `format.do_not_collapse_values` is set to non-zero, spaces
surrounding empty quotes will be collapsed together with the latter.
@note If @p ini_string comes from #INI_GLOBAL_IMPLICIT_VALUE this function is
no-op and will only return the value of #INI_GLOBAL_IMPLICIT_V_LEN minus
the offset of @p ini_string within #INI_GLOBAL_IMPLICIT_VALUE.
The @p format argument is used for the following fields:
- `format.no_single_quotes`
- `format.no_double_quotes`
- `format.multiline_nodes`
- `format.do_not_collapse_values`
@note `format.multiline_nodes` is used only to figure out whether there are
escape sequences or not. For all other purposes new line characters will
be considered to be equal to any other space character, even if the
format is not multi-line -- new line characters should never appear in
non-multi-line formats.
@include topics/ini_string_parse.c
**/
size_t ini_string_parse (char * const ini_string, const IniFormat format) {
if (_LIBCONFINI_IMPLICIT_RANGE_(ini_string)) {
return INI_GLOBAL_IMPLICIT_V_LEN + INI_GLOBAL_IMPLICIT_VALUE - ini_string;
}
/*
Mask `abcd` (8 bits used):
FLAG_1 Single quotes are not metacharacters (const)
FLAG_2 Double quotes are not metacharacters (const)
FLAG_4 Do not collapse spaces within members (const)
FLAG_8 Unescaped single quotes are odd right now
FLAG_16 Unescaped double quotes are odd right now
FLAG_32 This is an *escaped* single/double quote and format supports
single/double quotes
FLAG_64 This is a space
FLAG_128 Skip this character
*/
register uint8_t abcd = (format.do_not_collapse_values ? 68 : 64) |
(format.no_double_quotes << 1) |
format.no_single_quotes;
size_t idx, lshift;
if (format.multiline_nodes == INI_NO_MULTILINE) {
switch (abcd) {
case 64 | 2 | 1 :
/*
There are no escape sequences, but spaces might still need to
be collapsed.
*/
for (idx = 0, lshift = 0; ini_string[idx]; idx++) {
abcd = !is_some_space(ini_string[idx], _LIBCONFINI_WITH_EOL_) ? 3 : abcd & 64 ? 195 : 67;
if (abcd & 128) {
lshift++;
} else {
ini_string[idx - lshift] = abcd & 64 ? _LIBCONFINI_COLLAPSED_ : ini_string[idx];
}
}
for (
idx -= (abcd & 64) && lshift < idx ?
++lshift
:
lshift;
ini_string[idx];
ini_string[idx++] = '\0'
);
return idx - lshift;
case 64 | 4 | 2 | 1 :
/*
There are no escape sequences and spaces don't need to be
collapsed, but left and right trim might still be necessary.
*/
return rtrim_h(ini_string, ltrim_hh(ini_string, 0, _LIBCONFINI_WITH_EOL_), _LIBCONFINI_WITH_EOL_);
}
}
/*
There might be escape sequences...
*/
size_t nbacksl = 0;
for (idx = lshift = 0; ini_string[idx]; idx++) {
abcd = !(abcd & 10) && ini_string[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ?
(
nbacksl & 1 ?
(abcd & 63) | 32
:
((abcd & 223) | 128) ^ 16
)
: !(abcd & 17) && ini_string[idx] == _LIBCONFINI_SINGLE_QUOTES_ ?
(
nbacksl & 1 ?
(abcd & 63) | 32
:
((abcd & 223) | 128) ^ 8
)
: (abcd & 28) || !is_some_space(ini_string[idx], _LIBCONFINI_WITH_EOL_) ?
abcd & 31
: abcd & 64 ?
(abcd & 223) | 128
:
(abcd & 95) | 64;
if (abcd & 128) {
lshift++;
continue;
}
if (ini_string[idx] == _LIBCONFINI_BACKSLASH_) {
nbacksl++;
} else {
if (abcd & 32) {
lshift++;
}
lshift += nbacksl >> 1;
nbacksl = 0;
}
ini_string[idx - lshift] = abcd & 64 ? _LIBCONFINI_COLLAPSED_ : ini_string[idx];
}
lshift += nbacksl >> 1;
for (
idx -= (abcd & 64) && lshift < idx ?
++lshift
:
lshift;
ini_string[idx];
ini_string[idx++] = '\0'
);
return (abcd & 28) ^ 4 ?
idx - lshift
:
rtrim_h(ini_string, idx - lshift, _LIBCONFINI_WITH_EOL_);
}
/** @utility{ini_array_get_length} **/
/**
@brief Gets the length of a stringified INI array in number of members
@param ini_string The stringified array (it can be `NULL`)
@param delimiter The delimiter between the array members -- if
zero (see #INI_ANY_SPACE), any space is
delimiter (`/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`)
@param format The format of the INI file
@return The length of the INI array
Usually @p ini_string comes from an #IniDispatch (but any other string may be
used as well).
@note If @p delimiter matches a metacharacter within the format given (`'\\'`,
`'\''` or `'\"'`), its role as metacharacter will have higher priority
than its role as delimiter (i.e., the array will have no delimiters and
will contain only one member).
**/
size_t ini_array_get_length (
const char * const ini_string,
const char delimiter,
const IniFormat format
) {
if (!ini_string) {
return 0;
}
if (_LIBCONFINI_IS_ESC_CHAR_(delimiter, format)) {
/*
We have no delimiters (array has only one member)
*/
return 1;
}
/*
Mask `abcd` (8 bits used):
FLAG_1 Single quotes are not metacharacters (const)
FLAG_2 Double quotes are not metacharacters (const)
FLAG_4 Delimiter is not any space (const)
FLAG_8 Unescaped single quotes are odd right now
FLAG_16 Unescaped double quotes are odd right now
FLAG_32 We are in an odd sequence of backslashes
FLAG_64 This is a space
FLAG_128 This is a delimiter
*/
register uint8_t abcd = (delimiter ? 64 : 68) |
(format.no_double_quotes << 1) |
format.no_single_quotes;
size_t counter = 0;
for (size_t idx = 0; ini_string[idx]; idx++) {
/* Revision #1 */
abcd = !(abcd & 28) && ini_string[idx] == delimiter ?
(abcd & 159) | 128
: !(abcd & 24) && is_some_space(ini_string[idx], _LIBCONFINI_WITH_EOL_) ?
(
(abcd & 68) ^ 4 ?
(abcd & 95) | 64
:
(abcd & 223) | 192
)
: ini_string[idx] == _LIBCONFINI_BACKSLASH_ ?
(abcd & 63) ^ 32
: !(abcd & 42) && ini_string[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ?
(abcd & 31) ^ 16
: !(abcd & 49) && ini_string[idx] == _LIBCONFINI_SINGLE_QUOTES_ ?
(abcd & 31) ^ 8
:
abcd & 31;
if (abcd & 128) {
counter++;
}
}
return !counter || (~abcd & 68) ?
counter + 1
:
counter;
}
/** @utility{ini_array_foreach} **/
/**
@brief Calls a custom function for each member of a stringified INI
array, without modifying the content of the buffer -- useful for
read-only (`const`) stringified arrays
@param ini_string The stringified array (it can be `NULL`)
@param delimiter The delimiter between the array members -- if
zero (see #INI_ANY_SPACE), any space is
delimiter (`/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`)
@param format The format of the INI file
@param f_foreach The function that will be invoked for each array
member
@param user_data A custom argument, or `NULL`
@return Zero for success, otherwise an error code (see `enum`
#ConfiniInterruptNo)
Usually @p ini_string comes from an #IniDispatch (but any other string may be
used as well).
The user given function @p f_foreach (see #IniSubstrHandler data type) will be
invoked with six arguments: `ini_string`, `memb_offset` (the offset of the
member in bytes), `memb_length` (the length of the member in bytes), `memb_num`
(the offset of the member in number of members), `format` (the format of the INI
file), `user_data` (the custom argument @p user_data previously passed). If
@p f_foreach returns a non-zero value the function #ini_array_foreach() will be
interrupted.
@note If @p delimiter matches a metacharacter within the format given (`'\\'`,
`'\''` or `'\"'`), its role as metacharacter will have higher priority
than its role as delimiter (i.e., the array will have no delimiters and
will contain only one member).
Possible return values are: #CONFINI_SUCCESS, #CONFINI_FEINTR.
@include topics/ini_array_foreach.c.
**/
int ini_array_foreach (
const char * const ini_string,
const char delimiter,
const IniFormat format,
const IniSubstrHandler f_foreach,
void * const user_data
) {
if (!ini_string) {
return CONFINI_SUCCESS;
}
/*
Mask `abcd` (8 bits used):
FLAG_1 Single quotes are not metacharacters (const)
FLAG_2 Double quotes are not metacharacters (const)
FLAG_4 Delimiter is not any space (const)
FLAG_8 Unescaped single quotes are odd until now
FLAG_16 Unescaped double quotes are odd until now
FLAG_32 We are in an odd sequence of backslashes
FLAG_64 This is not a delimiter
FLAG_128 Stop the loop
*/
register size_t idx;
size_t offs = ltrim_s(ini_string, 0, _LIBCONFINI_WITH_EOL_);
if (_LIBCONFINI_IS_ESC_CHAR_(delimiter, format)) {
/*
We have no delimiters (array has only one member)
*/
idx = 0;
while (ini_string[idx++]);
return f_foreach(ini_string, offs, rtrim_s(ini_string + offs, idx - offs - 1, _LIBCONFINI_WITH_EOL_), 0, format, user_data) ?
CONFINI_FEINTR
:
CONFINI_SUCCESS;
}
register uint8_t abcd = (delimiter ? 4 : 0) |
(format.no_double_quotes << 1) |
format.no_single_quotes;
size_t memb_num = 0;
idx = offs;
do {
abcd = (delimiter ? ini_string[idx] == delimiter : is_some_space(ini_string[idx], _LIBCONFINI_WITH_EOL_)) ?
abcd & 159
: ini_string[idx] == _LIBCONFINI_BACKSLASH_ ?
(abcd | 64) ^ 32
: !(abcd & 42) && ini_string[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ?
((abcd & 223) | 64) ^ 16
: !(abcd & 49) && ini_string[idx] == _LIBCONFINI_SINGLE_QUOTES_ ?
((abcd & 223) | 64) ^ 8
: ini_string[idx] ?
(abcd & 223) | 64
:
128;
if (!(abcd & 88)) {
if (
f_foreach(
ini_string,
offs,
rtrim_s(ini_string + offs, idx - offs, _LIBCONFINI_WITH_EOL_),
memb_num++,
format,
user_data
)
) {
return CONFINI_FEINTR;
}
offs = abcd & 128 ? idx + 1 : ltrim_s(ini_string, idx + 1, _LIBCONFINI_WITH_EOL_);
}
idx = abcd & 216 ? idx + 1 : offs;
} while (!(abcd & 128) && ((abcd & 92) || ini_string[idx]));
return CONFINI_SUCCESS;
}
/** @utility{ini_array_shift} **/
/**
@brief Shifts the location pointed by @p ini_strptr to the next member
of the INI array (without modifying the content of the buffer),
or to `NULL` if the INI array has no more members -- useful for
read-only (`const`) stringified arrays
@param ini_strptr The memory location of the stringified array --
it cannot be `NULL`, but it can point to `NULL`
@param delimiter The delimiter between the array members -- if
zero (see #INI_ANY_SPACE), any space is
delimiter (`/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`)
@param format The format of the INI file
@return The length of the array member that has been left behind
Usually @p ini_strptr comes from an #IniDispatch (but any other string may be
used as well).
@note If @p delimiter matches a metacharacter within the format given (`'\\'`,
`'\''` or `'\"'`), its role as metacharacter will have higher priority
than its role as delimiter (i.e., the array will have no delimiters and
will contain only one member).
@include topics/ini_array_shift.c
**/
size_t ini_array_shift (const char ** const ini_strptr, const char delimiter, const IniFormat format) {
size_t toklen = 0;
if (*ini_strptr && !_LIBCONFINI_IS_ESC_CHAR_(delimiter, format)) {
if (!delimiter) {
toklen = ltrim_s(*ini_strptr, 0, _LIBCONFINI_WITH_EOL_);
}
toklen += get_metachar_pos(*ini_strptr + toklen, delimiter, format);
*ini_strptr += toklen;
toklen = rtrim_s(*ini_strptr - toklen, toklen, _LIBCONFINI_WITH_EOL_);
if (**ini_strptr) {
*ini_strptr += ltrim_s(*ini_strptr, 1, _LIBCONFINI_WITH_EOL_);
if (delimiter || **ini_strptr) {
return toklen;
}
}
}
*ini_strptr = (char *) 0;
return toklen;
}
/** @utility{ini_array_collapse} **/
/**
@brief Compresses the distribution of the data in a stringified INI
array by removing all the white spaces that surround its
delimiters, empty quotes, collapsable spaces, etc
@param ini_string The stringified array
@param delimiter The delimiter between the array members --
if zero (`INI_ANY_SPACE`) any space is
delimiter (`/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`)
@param format The format of the INI file
@return The new length of the stringified array
Out of quotes similar to ECMAScript
`ini_string.replace(new RegExp("^\\s+|\\s*(?:(" + delimiter + ")\\s*|($))", "g"), "$1$2")`.
If #INI_ANY_SPACE (`0`) is used as delimiter, one or more different spaces
(`/[\t \v\f\n\r]+/`) will be always collapsed to one space, independently of
what the format says.
Usually @p ini_string comes from an #IniDispatch (but any other string may be
used as well).
This function can be useful before invoking `memcpy()` using @p ini_string as
source, when saving memory is a priority.
The @p format argument is used for the following fields:
- `format.no_single_quotes`
- `format.no_double_quotes`
- `format.do_not_collapse_values`
- `format.preserve_empty_quotes`
Examples:
1. Using comma as delimiter:
- Before: `&nbsp;first&nbsp;&nbsp; ,&nbsp;&nbsp;&nbsp; second&nbsp;&nbsp;
,&nbsp;&nbsp; third&nbsp;&nbsp; ,&nbsp; etc.&nbsp;&nbsp;`
- After: `first,second,third,etc.`
2. Using `INI_ANY_SPACE` as delimiter:
- Before: `&nbsp;&nbsp;first&nbsp;&nbsp;&nbsp; second&nbsp;&nbsp;&nbsp;
third&nbsp;&nbsp;&nbsp;&nbsp; etc.&nbsp;&nbsp;&nbsp;`
- After: `first second third etc.`
@note If @p ini_string comes from #INI_GLOBAL_IMPLICIT_VALUE this function is
no-op and will only return the value of #INI_GLOBAL_IMPLICIT_V_LEN minus
the offset of @p ini_string within #INI_GLOBAL_IMPLICIT_VALUE.
@note If @p delimiter matches a metacharacter within the format given (`'\\'`,
`'\''` or `'\"'`), its role as metacharacter will have higher priority
than its role as delimiter (i.e., the array will have no delimiters and
will contain only one member).
@include topics/ini_array_collapse.c
@note The actual space occupied by the array might get reduced further after
each member is parsed by #ini_string_parse().
**/
size_t ini_array_collapse (char * const ini_string, const char delimiter, const IniFormat format) {
if (_LIBCONFINI_IMPLICIT_RANGE_(ini_string)) {
return INI_GLOBAL_IMPLICIT_V_LEN + INI_GLOBAL_IMPLICIT_VALUE - ini_string;
}
if (_LIBCONFINI_IS_ESC_CHAR_(delimiter, format)) {
/*
We have no delimiters (array has only one member)
*/
switch ((format.preserve_empty_quotes << 1) | format.do_not_collapse_values) {
case 0: return collapse_everything(ini_string, format);
case 1: return collapse_empty_quotes(ini_string, format);
case 2: return collapse_spaces(ini_string, format);
case 3: return rtrim_h(ini_string, ltrim_hh(ini_string, 0, _LIBCONFINI_WITH_EOL_), _LIBCONFINI_WITH_EOL_);
}
}
/*
Mask `abcd` (16 bits used):
FLAG_1 Single quotes are not metacharacters (const)
FLAG_2 Double quotes are not metacharacters (const)
FLAG_4 Do not collapse spaces within members (const)
FLAG_8 Preserve empty quotes (const)
FLAG_16 Any space is delimiter (const)
FLAG_32 Unescaped single quotes are odd right now
FLAG_64 Unescaped double quotes are odd right now
FLAG_128 We are in an odd sequence of backslashes
FLAG_256 This is *not* a delimiter out of quotes
FLAG_512 This is *not* a space out of quotes
FLAG_1024 These are some quotes
FLAG_2048 These are some quotes or among the last spaces are some empty
quotes
FLAG_4096 Save current `idx_d` in `fallback`
FLAG_8192 Restore `idx_d` from `fallback` before writing
FLAG_16384 Decrease `idx_d` before writing
FLAG_32768 Keep increasing `idx_d` after writing
*/
size_t idx_s = 0, idx_d = 0, fallback = 0;
register uint16_t abcd = (delimiter ? 0 : 16) |
(format.preserve_empty_quotes << 3) |
(format.do_not_collapse_values << 2) |
(format.no_double_quotes << 1) |
format.no_single_quotes;
for (; ini_string[idx_s]; idx_s++) {
/* Revision #1 */
abcd = !(abcd & 112) && ini_string[idx_s] == delimiter ?
(
(abcd & 536) && ((abcd & 1560) ^ 8) && ((abcd & 1560) ^ 1544) && ((abcd & 1304) ^ 1032) ?
(abcd & 33407) | 33280
:
(abcd & 41599) | 41472
)
: !(abcd & 96) && is_some_space(ini_string[idx_s], _LIBCONFINI_WITH_EOL_) ?
(
!((abcd & 1816) ^ 1800) ?
(abcd & 43391) | 40960
: !(~abcd & 1560) ?
(abcd & 41087) | 40960
: !((abcd & 536) ^ 528) || !((abcd & 1560) ^ 536) || !((abcd & 1560) ^ 1048) ?
(abcd & 32895) | 32768
: !(abcd & 540) || !((abcd & 1564) ^ 8) || !((abcd & 536) ^ 16) || !((abcd & 1560) ^ 24) ?
abcd & 2431
: ((abcd & 540) ^ 4) && ((abcd & 796) ^ 12) && ((abcd & 1564) ^ 12) && ((abcd & 1308) ^ 1032) ?
(abcd & 39295) | 36864
:
(abcd & 35199) | 32768
)
: !(abcd & 193) && ini_string[idx_s] == _LIBCONFINI_SINGLE_QUOTES_ ?
(
!((abcd & 3896) ^ 8) ?
(abcd & 44927) | 44064
: !((abcd & 3896) ^ 2056) ?
(abcd & 36735) | 36128
: !((abcd & 1056) ^ 32) ?
(abcd & 33631) | 33536
: !(abcd & 40) || !(~abcd & 1064) ?
((abcd & 36735) | 35840) ^ 32
: ((abcd & 1064) ^ 1032) && ((abcd & 1064) ^ 1056) ?
(abcd & 40831) | 39968
:
((abcd & 20351) | 19456) ^ 32
)
: !(abcd & 162) && ini_string[idx_s] == _LIBCONFINI_DOUBLE_QUOTES_ ?
(
!((abcd & 3928) ^ 8) ?
(abcd & 44927) | 44096
: !((abcd & 3928) ^ 2056) ?
(abcd & 36735) | 36160
: !((abcd & 1088) ^ 64) ?
(abcd & 33599) | 33536
: !(abcd & 72) || !(~abcd & 1096) ?
((abcd & 36735) | 35840) ^ 64
: ((abcd & 1096) ^ 1088) && ((abcd & 1096) ^ 1032) ?
(abcd & 40831) | 40000
:
((abcd & 20351) | 19456) ^ 64
)
: ini_string[idx_s] == _LIBCONFINI_BACKSLASH_ ?
(
(abcd & 888) && ((abcd & 1144) ^ 1032) && ((abcd & 1144) ^ 1048) && ((abcd & 2936) ^ 8) ?
((abcd & 33791) | 33536) ^ 128
:
((abcd & 41983) | 41728) ^ 128
)
: (abcd & 888) && ((abcd & 1144) ^ 1032) && ((abcd & 1144) ^ 1048) && ((abcd & 2936) ^ 8) ?
(abcd & 33663) | 33536
:
(abcd & 41855) | 41728;
ini_string[
abcd & 16384 ?
--idx_d
: abcd & 8192 ?
(idx_d = fallback)
: abcd & 4096 ?
(fallback = idx_d)
:
idx_d
] = (abcd & 1636) && ((abcd & 1392) ^ 16) ?
ini_string[idx_s]
:
_LIBCONFINI_COLLAPSED_;
if (abcd & 32768) {
idx_d++;
}
}
for (
idx_s = ((abcd & 16) && !idx_d) || (!(~abcd & 1040) && idx_d < 4) ?
(idx_d = 0)
: !(abcd & 536) || !(~abcd & 1544) || !((abcd & 1560) ^ 8) || !((abcd & 1304) ^ 1032) ?
(idx_d = fallback)
: !((abcd & 1624) ^ 1104) || !((abcd & 1592) ^ 1072) ?
(idx_d -= 2)
: ((abcd & 1552) ^ 16) && ((abcd & 632) ^ 16) && ((abcd & 1624) ^ 1616) && ((abcd & 1592) ^ 1584) ?
idx_d
:
--idx_d;
ini_string[idx_s];
ini_string[idx_s++] = '\0'
);
return idx_d;
}
/** @utility{ini_array_break} **/
/**
@brief Replaces the first delimiter found (together with the spaces
that surround it) with `\0`
@param ini_string The stringified array (it can be `NULL`)
@param delimiter The delimiter between the array members -- if
zero (see #INI_ANY_SPACE), any space is
delimiter (`/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`)
@param format The format of the INI file
@return A pointer to the remaining INI array or `NULL` if the remaining
array is empty
Usually @p ini_string comes from an #IniDispatch (but any other string may be
used as well).
Similarly to `strtok_r()` this function can be used only once for a given
string.
@note If @p ini_string comes from #INI_GLOBAL_IMPLICIT_VALUE this function is
no-op and will return `NULL`.
@note If @p delimiter matches a metacharacter within the format given (`'\\'`,
`'\''` or `'\"'`), its role as metacharacter will have higher priority
than its role as delimiter (i.e., the array will have no delimiters and
will contain only one member).
@include topics/ini_array_break.c
**/
char * ini_array_break (char * const ini_string, const char delimiter, const IniFormat format) {
if (ini_string && !_LIBCONFINI_IMPLICIT_RANGE_(ini_string)) {
char * remnant;
if (_LIBCONFINI_IS_ESC_CHAR_(delimiter, format)) {
/*
We have no delimiters (array has only one member)
*/
remnant = ini_string;
while (*remnant++);
rtrim_h(ini_string, remnant - ini_string - 1, _LIBCONFINI_WITH_EOL_);
} else {
remnant = ini_string + get_metachar_pos(ini_string, delimiter, format);
if (*remnant) {
*remnant = '\0';
rtrim_h(ini_string, remnant - ini_string, _LIBCONFINI_WITH_EOL_);
remnant += ltrim_h(remnant, 1, _LIBCONFINI_WITH_EOL_);
if (delimiter || *remnant) {
return remnant;
}
}
}
}
return (char *) 0;
}
/** @utility{ini_array_release} **/
/**
@brief Replaces the first delimiter found (together with the spaces
that surround it) with `\0`, then shifts the location pointed by
@p ini_strptr to the next member of the INI array, or to `NULL`
if the INI array has no more members
@param ini_strptr The memory location of the stringified array --
it cannot be `NULL`, but it can point to `NULL`
@param delimiter The delimiter between the array members -- if
zero (see #INI_ANY_SPACE), any space is
delimiter (`/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`)
@param format The format of the INI file
@return The array member that has been released
Usually @p ini_strptr comes from an #IniDispatch (but any other string may be
used as well).
Similarly to `strtok_r()` this function can be used only once for a given
string.
@note If @p ini_string comes from #INI_GLOBAL_IMPLICIT_VALUE this function is
no-op and will set @p ini_strptr to `NULL`.
@note If @p delimiter matches a metacharacter within the format given (`'\\'`,
`'\''` or `'\"'`), its role as metacharacter will have higher priority
than its role as delimiter (i.e., the array will have no delimiters and
will contain only one member).
@include topics/ini_array_release.c
**/
char * ini_array_release (char ** const ini_strptr, const char delimiter, const IniFormat format) {
char * const token = *ini_strptr;
if (token && !_LIBCONFINI_IMPLICIT_RANGE_(token) && !_LIBCONFINI_IS_ESC_CHAR_(delimiter, format)) {
*ini_strptr += get_metachar_pos(*ini_strptr, delimiter, format);
if (**ini_strptr) {
**ini_strptr = '\0';
rtrim_h(token, *ini_strptr - token, _LIBCONFINI_WITH_EOL_);
*ini_strptr += ltrim_h(*ini_strptr, 1, _LIBCONFINI_WITH_EOL_);
if (delimiter || **ini_strptr) {
return token;
}
}
}
*ini_strptr = (char *) 0;
return token;
}
/** @utility{ini_array_split} **/
/**
@brief Splits a stringified INI array into NUL-separated members and
calls a custom function for each member
@param ini_string The stringified array (it cannot be `NULL`)
@param delimiter The delimiter between the array members -- if
zero (see #INI_ANY_SPACE), any space is
delimiter (`/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`)
@param format The format of the INI file
@param f_foreach The function that will be invoked for each array
member
@param user_data A custom argument, or `NULL`
@return Zero for success, otherwise an error code (see `enum`
#ConfiniInterruptNo)
Usually @p ini_string comes from an #IniDispatch (but any other string may be
used as well).
The user given function @p f_foreach (see #IniStrHandler data type) will be
invoked with five arguments: `member` (the member of the array), `memb_length`
(the length of the member in bytes), `memb_num` (the offset of the member in
number of members), `format` (the format of the INI file), `user_data` (the
custom argument @p user_data previously passed). If @p f_foreach returns a
non-zero value the function #ini_array_split() will be interrupted.
Similarly to `strtok_r()` this function can be used only once for a given
string.
@note If @p ini_string comes from #INI_GLOBAL_IMPLICIT_VALUE or is `NULL` this
function is no-op and will return an error code.
@note If @p delimiter matches a metacharacter within the format given (`'\\'`,
`'\''` or `'\"'`), its role as metacharacter will have higher priority
than its role as delimiter (i.e., the array will have no delimiters and
will contain only one member).
Possible return values are: #CONFINI_SUCCESS, #CONFINI_EROADDR, #CONFINI_FEINTR.
@include topics/ini_array_split.c.
**/
int ini_array_split (
char * const ini_string,
const char delimiter,
const IniFormat format,
const IniStrHandler f_foreach,
void * const user_data
) {
if (!ini_string || _LIBCONFINI_IMPLICIT_RANGE_(ini_string)) {
return CONFINI_EROADDR;
}
/*
Mask `abcd` (8 bits used):
FLAG_1 Single quotes are not metacharacters (const)
FLAG_2 Double quotes are not metacharacters (const)
FLAG_4 Delimiter is not any space (const)
FLAG_8 Unescaped single quotes are odd until now
FLAG_16 Unescaped double quotes are odd until now
FLAG_32 We are in an odd sequence of backslashes
FLAG_64 This is not a delimiter
FLAG_128 Stop the loop
*/
register size_t idx;
size_t offs = ltrim_h(ini_string, 0, _LIBCONFINI_WITH_EOL_);
if (_LIBCONFINI_IS_ESC_CHAR_(delimiter, format)) {
/*
We have no delimiters (array has only one member)
*/
idx = 0;
while (ini_string[idx++]);
return f_foreach(ini_string + offs, rtrim_h(ini_string + offs, idx - offs - 1, _LIBCONFINI_WITH_EOL_), 0, format, user_data) ?
CONFINI_FEINTR
:
CONFINI_SUCCESS;
}
register uint8_t abcd = (delimiter ? 4 : 0) |
(format.no_double_quotes << 1) |
format.no_single_quotes;
size_t memb_num = 0;
idx = offs;
do {
abcd = (delimiter ? ini_string[idx] == delimiter : is_some_space(ini_string[idx], _LIBCONFINI_WITH_EOL_)) ?
abcd & 159
: ini_string[idx] == _LIBCONFINI_BACKSLASH_ ?
(abcd | 64) ^ 32
: !(abcd & 42) && ini_string[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ?
((abcd & 223) | 64) ^ 16
: !(abcd & 49) && ini_string[idx] == _LIBCONFINI_SINGLE_QUOTES_ ?
((abcd & 223) | 64) ^ 8
: ini_string[idx] ?
(abcd & 223) | 64
:
128;
if (!(abcd & 88)) {
ini_string[idx] = '\0';
if (
f_foreach(
ini_string + offs,
rtrim_h(ini_string + offs, idx - offs, _LIBCONFINI_WITH_EOL_),
memb_num++,
format,
user_data
)
) {
return CONFINI_FEINTR;
}
offs = abcd & 128 ? idx + 1 : ltrim_h(ini_string, idx + 1, _LIBCONFINI_WITH_EOL_);
}
idx = abcd & 216 ? idx + 1 : offs;
} while (!(abcd & 128) && ((abcd & 92) || ini_string[idx]));
return CONFINI_SUCCESS;
}
/** @utility{ini_global_set_lowercase_mode} **/
/**
@brief Sets the value of the global variable
#INI_GLOBAL_LOWERCASE_MODE
@param lowercase The new value
@return Nothing
If @p lowercase is `true`, key and section names in case-insensitive INI formats
will be dispatched lowercase, verbatim otherwise (default value: `true`).
@warning This function changes the value of one or more global variables. In
order to be thread-safe this function should be used only once at
beginning of execution, or otherwise a mutex logic must be
introduced.
**/
void ini_global_set_lowercase_mode (const bool lowercase) {
INI_GLOBAL_LOWERCASE_MODE = lowercase;
}
/** @utility{ini_global_set_implicit_value} **/
/**
@brief Sets the value to be to be assigned to implicit keys
@param implicit_value The string to be used as implicit value
(usually `"YES"`, or `"TRUE"`)
@param implicit_v_len The length of @p implicit_value (usually
`0`, independently of its real length)
@return Nothing
@warning This function changes the value of one or more global variables. In
order to be thread-safe this function should be used only once at
beginning of execution, or otherwise a mutex logic must be
introduced.
@include topics/ini_global_set_implicit_value.c
**/
void ini_global_set_implicit_value (char * const implicit_value, const size_t implicit_v_len) {
INI_GLOBAL_IMPLICIT_VALUE = implicit_value;
INI_GLOBAL_IMPLICIT_V_LEN = implicit_v_len;
}
/** @utility{ini_fton} **/
/**
@brief Calculates the #IniFormatNum of an #IniFormat
@param source The #IniFormat to compute
@return The unique unsigned integer that identifies the format given
**/
IniFormatNum ini_fton (const IniFormat source) {
#define __INIFORMAT_ID__(NAME, OFFSET, SIZE, DEFVAL) (source.NAME << OFFSET) |
return INIFORMAT_TABLE_AS(__INIFORMAT_ID__) 0;
#undef __INIFORMAT_ID__
}
/** @utility{ini_ntof} **/
/**
@brief Constructs a new #IniFormat according to an #IniFormatNum
@param format_num The #IniFormatNum to parse
@return The new #IniFormat constructed
@note If @p format_num `>` `16777215` it will be truncated to 24 bits.
**/
IniFormat ini_ntof (const IniFormatNum format_num) {
#define __MAX_1_BITS__ 1
#define __MAX_2_BITS__ 3
#define __MAX_3_BITS__ 7
#define __MAX_4_BITS__ 15
#define __MAX_5_BITS__ 31
#define __MAX_6_BITS__ 63
#define __MAX_7_BITS__ 127
#define __MAX_8_BITS__ 255
#define __INIFORMAT_PROPERTIES__(NAME, OFFSET, SIZE, DEFVAL) \
(unsigned char) ((format_num >> OFFSET) & __MAX_##SIZE##_BITS__),
return (IniFormat) { INIFORMAT_TABLE_AS(__INIFORMAT_PROPERTIES__) };
#undef __INIFORMAT_PROPERTIES__
#undef __MAX_8_BITS__
#undef __MAX_7_BITS__
#undef __MAX_6_BITS__
#undef __MAX_5_BITS__
#undef __MAX_4_BITS__
#undef __MAX_3_BITS__
#undef __MAX_2_BITS__
#undef __MAX_1_BITS__
}
/** @utility{ini_get_bool} **/
/**
@brief Checks whether a string matches one of the booleans listed in
the private constant #INI_BOOLEANS (case-insensitive)
@param ini_string A string to check (it can be `NULL`)
@param when_fail A value that is returned if no matching
boolean is found
@return The matching boolean (`0` or `1`) or @p when_fail if
@p ini_string does not contain a valid INI boolean
Usually @p ini_string comes from an #IniDispatch (but any other string may be
used as well).
@include miscellanea/typed_ini.c
**/
int ini_get_bool (const char * const ini_string, const int when_fail) {
if (!ini_string) {
return when_fail;
}
register uint8_t bool_idx;
register size_t pair_idx, chr_idx;
for (pair_idx = 0; pair_idx < sizeof(INI_BOOLEANS) / sizeof(const char * const [2]); pair_idx++) {
for (bool_idx = 0; bool_idx < 2; bool_idx++) {
chr_idx = 0;
while (_LIBCONFINI_CHR_CASEFOLD_(ini_string[chr_idx]) == INI_BOOLEANS[pair_idx][bool_idx][chr_idx]) {
if (!ini_string[chr_idx++]) {
return (int) bool_idx;
}
}
}
}
return when_fail;
}
/* LINKS - In case you don't have `#include <stdlib.h>` in your source */
/**
@alias{ini_get_int}
Pointer to
[`atoi()`](http://www.gnu.org/software/libc/manual/html_node/Parsing-of-Integers.html#index-atoi)
@alias{ini_get_lint}
Pointer to
[`atol()`](http://www.gnu.org/software/libc/manual/html_node/Parsing-of-Integers.html#index-atol)
@alias{ini_get_llint}
Pointer to
[`atoll()`](http://www.gnu.org/software/libc/manual/html_node/Parsing-of-Integers.html#index-atoll)
@alias{ini_get_double}
Pointer to
[`atof()`](http://www.gnu.org/software/libc/manual/html_node/Parsing-of-Integers.html#index-atof)
**/
int (* const ini_get_int) (const char * ini_string) = atoi;
long int (* const ini_get_lint) (const char * ini_string) = atol;
long long int (* const ini_get_llint) (const char * ini_string) = atoll;
double (* const ini_get_double) (const char * ini_string) = atof;
/* Legacy support -- please **do not use this**! */
#ifdef ini_get_float
#undef ini_get_float
#endif
double (* const ini_get_float) (const char * ini_string) = atof;
/* GLOBAL VARIABLES */
bool INI_GLOBAL_LOWERCASE_MODE = _LIBCONFINI_FALSE_;
char * INI_GLOBAL_IMPLICIT_VALUE = (char *) 0;
size_t INI_GLOBAL_IMPLICIT_V_LEN = 0;
/** @endfnlist **/
/* EOF */