2
\$\begingroup\$

I recently wrote a small INI parsing library. The code is also on GitHub, with documentation. I would like to have opinions, suggestions, and critiques.

confini.h

/**

    @file       confini.h
    @brief      libconfini header
    @author     Stefano Gioffré
    @copyright  GNU Public License v3
    @date       2016

**/

#ifndef _LIBCONFINI_HEADER_
#define _LIBCONFINI_HEADER_



/* PRIVATE (HEADER-SCOPED) MACROS */


#define _LIBCONFINI_SETTINGS_AS_DECLARATION_(SIZE, PROPERTY, VALUE) unsigned char PROPERTY:SIZE;
#define _LIBCONFINI_SETTINGS_AS_ASSIGNEMENT_(SIZE, PROPERTY, VALUE) VALUE,
#define _LIBCONFINI_FORMAT_STRUCT_ struct IniFormat { _LIBCONFINI_EXPAND_SETTINGS_MACRO_AS_(_LIBCONFINI_SETTINGS_AS_DECLARATION_) }
#define _LIBCONFINI_DEFAULT_SETTINGS_ { _LIBCONFINI_EXPAND_SETTINGS_MACRO_AS_(_LIBCONFINI_SETTINGS_AS_ASSIGNEMENT_) }



/* PUBLIC MACROS */


#define _LIBCONFINI_EXPAND_SETTINGS_MACRO_AS_(______)                          /*-*\

    ______(     BITS      NAME                        DEFAULT VALUE            )/--/
                                                                               /-*/\
    ______(     8,        delimiter_symbol,           '='                      )/*-/
                                                                               /-*/\
    ______(     2,        semicolon,                  INI_PARSE_COMMENT        )   \
    ______(     2,        hash,                       INI_PARSE_COMMENT        )   \
    ______(     2,        multiline_entries,          INI_EVERYTHING_MULTILINE )   \
    ______(     1,        no_single_quotes,           0                        )   \
    ______(     1,        no_double_quotes,           0                        )/*-/
                                                                               /-*/\
    ______(     1,        case_sensitive,             0                        )   \
    ______(     1,        preserve_spaces_in_values,  0                        )   \
    ______(     1,        implicit_is_special,        0                        )   \
    ______(     1,        disabled_can_be_implicit,   0                        )   \
    ______(     1,        no_disabled_after_spaces,   0                        )   \
    ______(     3,        _LIBCONFINI_RESERVED_,      0                        )/*-/
                                                                               /-*/



/* PUBLIC TYPEDEFS */


/** @brief  24-bit bitfield representing a unique format of an INI file (INI dialect) **/
typedef _LIBCONFINI_FORMAT_STRUCT_ IniFormat;

/** @brief  Global statistics about an INI file **/
typedef struct IniStatistics {
    const IniFormat format;
    unsigned long int bytes;
    unsigned long int members;
} IniStatistics;

/** @brief  Dispatch of a single INI member **/
typedef struct IniDispatch {
    char *append_to;
    const IniFormat format;
    unsigned short int type;
    char *data;
    char *value;
    unsigned long int d_length;
    unsigned long int v_length;
    unsigned long int dispatch_id;
} IniDispatch;

/** @brief  24-bit bitmask representing the format of an INI file **/
typedef unsigned long long int IniFormatMask;



/* PUBLIC FUNCTIONS */


extern unsigned int load_ini_file (
    const char *path,
    const IniFormat format,
    int (*f_init)(
        IniStatistics *statistics,
        void *init_other
    ),
    int (*f_foreach)(
        IniDispatch *dispatch,
        void *foreach_other
    ),
    void *user_data
);

extern void ini_set_implicit_value (char *implicit_value, unsigned long int implicit_v_length);

extern IniFormatMask ini_format_calculate_mask (const IniFormat format);

extern void ini_format_read_mask (IniFormat *dest_format, IniFormatMask mask);

extern unsigned long int ini_unquote (char *ini_string, const IniFormat format);

extern unsigned long int ini_array_getlength (const char *ini_string, const char delimiter, const IniFormat format);

extern unsigned int ini_array_foreach (
    const char *ini_string,
    const char delimiter,
    const IniFormat format,
    int (*f_foreach)(
        const char *member,
        unsigned int offset,
        unsigned int length,
        void *foreach_other
    ),
    void *user_data
);

extern unsigned int ini_split_array (
    char *ini_string,
    const char delimiter,
    const IniFormat format,
    int (*f_foreach)(
        char *element,
        unsigned int length,
        void *foreach_other
    ),
    void *user_data
);

extern signed int ini_getbool (const char *ini_string, const signed int return_value);

extern signed int ini_getlazybool (const char *ini_string, const signed int return_value);



/* PUBLIC WRAPPERS */


extern double (*ini_getfloat) (const char *ini_string);

extern long long int (*ini_getllint) (const char *ini_string);

extern long int (*ini_getlint) (const char *ini_string);

extern int (*ini_getint) (const char *ini_string);



/* PUBLIC CONSTANTS AND VARIABLES */


/** @brief  Error codes **/
enum ConfiniErrorNo {
    CONFINI_EFEINTR = 1,    /** Interrupted by the user during f_foreach **/
    CONFINI_EFEOOR = 2, /** The loop is longer than expected (out of range) **/
    CONFINI_EIINTR = 3, /** Interrupted by the user during f_init **/
    CONFINI_ENOMEM = 4, /** Error allocating memory **/
    CONFINI_EIO = 5,    /** Error reading the file **/
    CONFINI_ENOENT = 6  /** File inaccessible **/
};

/** @brief  INI nodes types **/
enum IniNodeType {
    INI_UNKNOWN = 0,
    INI_DOCUMENT = 1,
    INI_SECTION = 2,
    INI_KEY = 3,
    INI_COMMENT = 4,
    INI_INLINE_COMMENT = 5,
    INI_DISABLED_SECTION = 6,
    INI_DISABLED_KEY = 7
};

/** @brief  Behaviors of '#' and ';' **/
enum IniComments {
    INI_PARSE_COMMENT = 0,
    INI_SHOW_COMMENT = 1,
    INI_ERASE_COMMENT = 2,
    INI_NORMAL_CHARACTER = 3
};

/** @brief  Multiline entries **/
enum IniMultiline {
    INI_EVERYTHING_MULTILINE = 0,
    INI_ACTIVE_AND_DISABLED_MULTILINE = 1,
    INI_ACTIVE_MULTILINE = 2,
    INI_NO_MULTILINE = 3
};

/** @brief  A model format **/
static const IniFormat INI_DEFAULT_FORMAT = _LIBCONFINI_DEFAULT_SETTINGS_;

/** @brief  Default value of implicit keys **/
extern char *INI_IMPLICIT_VALUE;

/** @brief  Default length of implicit keys' value -- it can be set to any unsigned number, independently of #INI_IMPLICIT_VALUE **/
extern unsigned long int INI_IMPLICIT_V_LENGTH;



/* CLEAN THE PRIVATE ENVIRONMENT */


#undef _LIBCONFINI_SETTINGS_AS_DECLARATION_
#undef _LIBCONFINI_SETTINGS_AS_ASSIGNEMENT_
#undef _LIBCONFINI_FORMAT_STRUCT_
#undef _LIBCONFINI_DEFAULT_SETTINGS_



/* END OF _LIBCONFINI_HEADER_ */


#endif



/* EOF */

confini.c

/*  -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*-   */

/**

    @file       confini.c
    @brief      libconfini functions
    @author     Stefano Gioffré
    @copyright  GNU Public License v3
    @date       2016

**/

#include <stdio.h>
#include <stdlib.h>
#include "confini.h"


/**

    @struct     IniFormat

    @var        IniFormat::delimiter_symbol
                    The symbol to be used as delimiter; if set to '\0', any space is delimiter (/[\s]/)
    @var        IniFormat::semicolon
                    The rule of the semicolon character (use enum ::IniComments for this)
    @var        IniFormat::hash
                    The rule of the hash character (use enum ::IniComments for this)
    @var        IniFormat::multiline_entries
                    The multiline status of the document (use enum ::IniMultiline for this)
    @var        IniFormat::no_single_quotes
                    If set to 1, makes the single-quote character (') a normal character
    @var        IniFormat::no_double_quotes
                    If set to 1, makes the double-quote character (") a normal character
    @var        IniFormat::case_sensitive
                    If set to 1, key- and section- names will not be rendered in lower case
    @var        IniFormat::preserve_spaces_in_values
                    If set to 1, sequences of more than one space in values (/\s{2,}/) will not be collapsed
    @var        IniFormat::implicit_is_special
                    If set to 1, the dispatch of implicit keys (see @ref libconfini) will always
                    assign to IniDispatch::value and to IniDispatch::v_length the global variables
                    ::INI_IMPLICIT_VALUE and ::INI_IMPLICIT_V_LENGTH respectively; if set to 0,
                    implicit keys will be considered as empty keys
    @var        IniFormat::disabled_can_be_implicit
                    If set to 1, comments non containing a delimiter symbol will not be parsed as
                    (disabled) implicit keys
    @var        IniFormat::no_disabled_after_spaces
                    If set to 1, prevents that /[#;]\s+/[^\s][^\n]+/ be parsed as a disabled entry
    @var        IniFormat::_LIBCONFINI_RESERVED_
                    Unused bits -- reserved for future extensions


    @struct     IniStatistics

    @var        IniStatistics::format
                    The format of the INI file (see struct ::IniFormat)
    @var        IniStatistics::bytes
                    The size of the parsed file in bytes
    @var        IniStatistics::member
                    The size of the parsed file in members (nodes)


    @struct     IniDispatch

    @var        IniDispatch::append_to
                    The current path
    @var        IniDispatch::format
                    The format of the INI file (see struct ::IniFormat)
    @var        IniDispatch::type
                    The dispatch type (see enum ::IniNodeType)
    @var        IniDispatch::data
                    It can be a comment, a section or a key
    @var        IniDispatch::value
                    It can be a key value or an empty string
    @var        IniDispatch::d_length
                    The length of the string IniDispatch::data
    @var        IniDispatch::v_length
                    The length of the string IniDispatch::value
    @var        IniDispatch::dispatch_id
                    The dispatch id

**/



        /*\
        |*|
        |*|   PRIVATE ENVIRONMENT
        |*|
        \*/



/* ALIASES */

#define _LIBCONFINI_BOOL_ unsigned char
#define _LIBCONFINI_BYTE_ unsigned char
#define _LIBCONFINI_UINT_ unsigned long int
#define _LIBCONFINI_INT_ signed long int

#define _LIBCONFINI_FALSE_ 0
#define _LIBCONFINI_TRUE_ 1
#define _LIBCONFINI_BACKSLASH_ '\\'
#define _LIBCONFINI_OPEN_SECTION_ '['
#define _LIBCONFINI_CLOSE_SECTION_ ']'
#define _LIBCONFINI_SUBSECTION_ '.'
#define _LIBCONFINI_DOUBLE_QUOTES_ '"'
#define _LIBCONFINI_SINGLE_QUOTES_ '\''
#define _LIBCONFINI_SIMPLE_SPACE_ 32
#define _LIBCONFINI_SEMICOLON_ ';'
#define _LIBCONFINI_HASH_ '#'
#define _LIBCONFINI_LF_ '\n'
#define _LIBCONFINI_CR_ '\r'



/* FUNCTIONAL CONSTANTS */

/**

    @brief A list of possible string representations of boolean pairs

    There can be infinite pairs here, but be aware of the lazy behavior of ini_getlazybool().
    Everything lowercase in this list!

**/
static const char *_LIBCONFINI_BOOLEANS_[][2] = {
    { "no", "yes" },
    { "false", "true" },
    { "0", "1" }
};

/*
    This can be any character, in theory... But after the left-trim of each line a leading
    space works pretty well as metacharacter.
*/
#define _LIBCONFINI_INLINE_MARKER_ '\t'



/* ABSTRACT UTILITIES */

static const _LIBCONFINI_BYTE_ _LIBCONFINI_SPACES_[] = { _LIBCONFINI_SIMPLE_SPACE_, '\t', '\v', '\f', _LIBCONFINI_LF_, _LIBCONFINI_CR_ };

/* Possible lengths of array #_LIBCONFINI_SPACES_ */
#define _LIBCONFINI_JUST_S_T_ 2
#define _LIBCONFINI_NO_EOL_ 4
#define _LIBCONFINI_WITH_EOL_ 6

static inline _LIBCONFINI_BOOL_ is_some_space (const char ch, const _LIBCONFINI_BYTE_ depth) {
    _LIBCONFINI_BYTE_ idx;
    for (idx = 0; idx < depth && ch != _LIBCONFINI_SPACES_[idx]; idx++);
    return idx < depth;
}

/**

    @brief          Soft left trim -- does not change the buffer
    @param          string          The target string
    @param          start_from      The offset where to start the left trim
    @param          depth           What is actually considered a space -- a number ranging from 1 to 6
    @return         The offset of the first non-space character

**/
static inline _LIBCONFINI_UINT_ ltrim_s (const char *lt_s, const _LIBCONFINI_UINT_ start_from, const _LIBCONFINI_BYTE_ depth) {
    _LIBCONFINI_UINT_ lt_i = start_from;
    for (; lt_s[lt_i] && is_some_space(lt_s[lt_i], depth); lt_i++);
    return lt_i;
}

/**

    @brief          Hard left trim -- **does** change the buffer
    @param          string          The target string
    @param          start_from      The offset where to start the left trim
    @param          depth           What is actually considered a space -- a number ranging from 1 to 6
    @return         The offset of the first non-space character

**/
static inline _LIBCONFINI_UINT_ ltrim_h (char *lt_s, const _LIBCONFINI_UINT_ start_from, const _LIBCONFINI_BYTE_ depth) {
    _LIBCONFINI_UINT_ lt_i = start_from;
    for (; lt_s[lt_i] && is_some_space(lt_s[lt_i], depth); lt_s[lt_i++] = '\0');
    return lt_i;
}


/**

    @brief          Unescaped hard left trim (left trim of /^(\s*|\\[\n\r])+/) -- **does** change the buffer
    @param          string          The target string
    @param          start_from      The offset where to start the left trim
    @return         The offset of the first non matching character

**/
static inline _LIBCONFINI_UINT_ ultrim_h (char *lt_s, const _LIBCONFINI_UINT_ start_from) {

    _LIBCONFINI_BYTE_ abacus = 12;
    _LIBCONFINI_UINT_ ult_i = start_from;

    /*

    Mask "abacus":

        FLAG_1  this is any space
        FLAG_2  this is not a backslash
        FLAG_4  previous was not a backslash
        FLAG_8  continue the loop

    */

    for (; lt_s[ult_i] && (abacus & 8); ult_i++) {

        abacus = (abacus & 4) && is_some_space(lt_s[ult_i], _LIBCONFINI_NO_EOL_) ? 13
            : lt_s[ult_i] == _LIBCONFINI_LF_ || lt_s[ult_i] == _LIBCONFINI_CR_ ? abacus | 5
            : (abacus & 4) && lt_s[ult_i] == _LIBCONFINI_BACKSLASH_ ? 10
            : abacus & 4;

        if (abacus & 1) {
            lt_s[ult_i] = '\0';
        }

        if ((abacus & 2) && (abacus & 4)) {
            lt_s[ult_i - 1] = '\0';
        }

    }

    return abacus & 8 ? ult_i : abacus & 4 ? ult_i - 1 : ult_i - 2;

}

/**

    @brief          Soft right trim -- does not change the buffer
    @param          string          The target string
    @param          length          The length of the string
    @param          depth           What is actually considered a space -- a subset of @link #_LIBCONFINI_SPACES_ ranging from 1 to 6
    @return         The length of the string until the last non-space character

**/
static inline _LIBCONFINI_UINT_ rtrim_s (const char *rt_s, const _LIBCONFINI_UINT_ length, const _LIBCONFINI_BYTE_ depth) {
    _LIBCONFINI_UINT_ rt_i = length;
    for (; rt_i > 0 && is_some_space(rt_s[rt_i - 1], depth); rt_i--);
    return rt_i;
}

/**

    @brief          Hard right trim -- **does** change the buffer
    @param          string          The target string
    @param          length          The length of the string
    @param          depth           What is actually considered a space -- a number ranging from 1 to 6
    @return         The length of the string until the last non-space character

**/
static inline _LIBCONFINI_UINT_ rtrim_h (char *rt_s, const _LIBCONFINI_UINT_ length, const _LIBCONFINI_BYTE_ depth) {
    _LIBCONFINI_UINT_ rt_i = length;
    for (; rt_i > 0 && is_some_space(rt_s[rt_i - 1], depth); rt_s[--rt_i] = '\0');
    return rt_i;
}


/**

    @brief          Converts a string to lower case
    @param          string      The target string
    @return         Nothing

**/
static inline void string_tolower (char *str) {
    for (char *ch_ptr = str; *ch_ptr; ++ch_ptr) {
        *ch_ptr = *ch_ptr > 0x40 && *ch_ptr < 0x5b ? *ch_ptr | 0x60 : *ch_ptr;
    }
}



/* CONCRETE UTILITIES */


/**

    @brief          Checks whether a character can represent the opening of a disabled entry
                    within a given format
    @param          ch          The character to check
    @param          format      The format of the INI file
    @return         1 if @p ch matches, 0 otherwise

**/
static inline _LIBCONFINI_BOOL_ is_parsable_char (const char ch, const IniFormat format) {
    return (format.semicolon == INI_PARSE_COMMENT && ch == _LIBCONFINI_SEMICOLON_) || (format.hash == INI_PARSE_COMMENT && ch == _LIBCONFINI_HASH_);
}

/**

    @brief          Checks whether a character can represent the opening of a comment within
                    a given format
    @param          ch          The character to check
    @param          format      The format of the INI file
    @return         1 if @p ch matches, 0 otherwise

**/
static inline _LIBCONFINI_BOOL_ is_comm_char (const char ch, const IniFormat format) {
    return (format.semicolon != INI_NORMAL_CHARACTER && ch == _LIBCONFINI_SEMICOLON_) || (format.hash != INI_NORMAL_CHARACTER && ch == _LIBCONFINI_HASH_);
}

/**

    @brief          Checks whether a character can represent the opening of a comment that
                    has to be ignored within a given format
    @param          ch          The character to check
    @param          format      The format of the INI file
    @return         1 if @p ch matches, 0 otherwise

**/
static inline _LIBCONFINI_BOOL_ is_erase_char (const char ch, const IniFormat format) {
    return (format.semicolon == INI_ERASE_COMMENT && ch == _LIBCONFINI_SEMICOLON_) || (format.hash == INI_ERASE_COMMENT && ch == _LIBCONFINI_HASH_);
}

/**

    @brief          Gets the position of the first delimiter out of quotes
    @param          string      The string to analyse
    @param          length      The length of the string
    @param          format      The format of the INI file
    @return         The offset of the delimiter or @p length if the delimiter has not
                    been not found

**/
static inline _LIBCONFINI_UINT_ get_delimiter_pos (const char *str, const _LIBCONFINI_UINT_ len, const IniFormat format) {

    _LIBCONFINI_UINT_ idx;
    _LIBCONFINI_BYTE_ qmask = 1;

    for (

        idx = 0;

            idx < len
            &&
            !(
                !(qmask & 6)
                &&
                (
                    format.delimiter_symbol ?
                        str[idx] == format.delimiter_symbol
                        : is_some_space(str[idx], _LIBCONFINI_NO_EOL_)
                )
            );

        qmask = (qmask & 1) && !format.no_double_quotes && str[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ? qmask ^ 4
                : (qmask & 1) && !format.no_single_quotes && str[idx] == _LIBCONFINI_SINGLE_QUOTES_ ? qmask ^ 2
                : str[idx] == _LIBCONFINI_BACKSLASH_ ? qmask ^ 1
                : qmask | 1,
        idx++

    );

    return idx;

}


/**

    @brief          Replaces /\\(\n\r?|\r\n?)s*[#;]/ or /\\(\n\r?|\r\n?)/ with "$1".
    @param          string          Target string
    @param          length          Length of the string
    @param          is_disabled     The string represents a disabled entry
    @return         Number of characters removed

**/
static _LIBCONFINI_UINT_ unescape_cr_lf (char * nstr, const _LIBCONFINI_UINT_ len, const _LIBCONFINI_BOOL_ is_disabled, const IniFormat format) {

    _LIBCONFINI_UINT_ idx, iter, lshift;
    _LIBCONFINI_BOOL_ is_escaped = _LIBCONFINI_FALSE_;
    _LIBCONFINI_BYTE_ cr_or_lf = 5;

    for (is_escaped = _LIBCONFINI_FALSE_, idx = 0, lshift = 0; idx < len; idx++) {

        if (is_escaped && (nstr[idx] == _LIBCONFINI_SPACES_[cr_or_lf] || nstr[idx] == _LIBCONFINI_SPACES_[cr_or_lf ^= 1])) {

            for (

                iter = idx,
                idx += nstr[idx + 1] == _LIBCONFINI_SPACES_[cr_or_lf ^ 1];

                    iter < idx + 1;

                nstr[iter - lshift - 1] = nstr[iter],
                iter++

            );

            lshift++;

            if (is_disabled) {

                iter = ltrim_s(nstr, iter, _LIBCONFINI_NO_EOL_);

                if (is_parsable_char(nstr[iter], format)) {

                    lshift += iter - idx;
                    idx = iter;

                }

            }

            is_escaped = _LIBCONFINI_FALSE_;

        } else {

            is_escaped = nstr[idx] == _LIBCONFINI_BACKSLASH_ ? !is_escaped : _LIBCONFINI_FALSE_;

            if (lshift) {

                nstr[idx - lshift] = nstr[idx];

            }

        } 

    }

    for (idx = len - lshift; idx < len; nstr[idx++] = '\0');

    return len - lshift;

}


/**

    @brief          Out of quotes similar to ECMAScript string.replace(/^[\n\r]\s*|(\s)+/g, "$1")
    @param          string      The string to collapse
    @param          format      The format of the INI file
    @return         The new length of the string

**/
static _LIBCONFINI_UINT_ collapse_spaces (char *str, const IniFormat format) {

    /*

    Mask "abacus":

        FLAG_1  backslashes immediately preceding this character are even
        FLAG_2  unescaped single quotes are odd until now
        FLAG_4  unescaped double quotes are odd until now
        FLAG_8  the string does not start with a space

    */

    _LIBCONFINI_UINT_ idx, lshift;
    _LIBCONFINI_BYTE_ abacus = 1 | (is_some_space(*str, _LIBCONFINI_WITH_EOL_) << 3);

    for (lshift = 0, idx = 0; str[idx]; idx++) {

        if (!(abacus & 6) && is_some_space(str[idx], _LIBCONFINI_WITH_EOL_)) {

            if (abacus & 8) {

                lshift++;

            } else {

                str[idx - lshift] = _LIBCONFINI_SIMPLE_SPACE_;
                abacus |= 8;

            }

                abacus |= 1;

        } else {

            abacus = (abacus & 1) && !format.no_double_quotes && str[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ? abacus ^ 4
                    : (abacus & 1) && !format.no_single_quotes && str[idx] == _LIBCONFINI_SINGLE_QUOTES_ ? abacus ^ 2
                    : str[idx] == _LIBCONFINI_BACKSLASH_ ? abacus ^ 1
                    : abacus | 1;

            if (lshift) {

                    str[idx - lshift] = str[idx];

            }

            abacus &= 7;

        }

    }

    for (idx -= (abacus & 8) ? ++lshift : lshift; str[idx]; str[idx++] = '\0');

    return idx - lshift;

}


/**

    @brief          Sanitizes the name of a section
    @param          section_name        The target string
    @param          format              The format of the INI file
    @return         The new length of the string

    Out of quotes, similar to ECMAScript `section_name.replace(/\.*\s*$|(?:\s*(\.))+\s*|^\s+/g, "$1").replace(/(\s)+/g, " ")`

    A section name can start with a dot (append), but cannot end with a dot. Spaces sorrounding dots
    will be removed. Single quotes or double quotes (if active and present) prevent changes.

**/
static _LIBCONFINI_UINT_ sanitize_section_name (char *str, const IniFormat format) {

    _LIBCONFINI_UINT_ idx, lshift;
    unsigned int abacus;

    /*

    Mask "abacus":

        FLAG_1          backslashes immediately preceding this character are even
        FLAG_2          unescaped single quotes are odd until now
        FLAG_4          unescaped double quotes are odd until now
        FLAG_8          the string does not start with a new line
        FLAG_16         more than one space is here
        FLAG_32         at least one dot is here
        FLAG_64         one or more spaces follow a dot
        FLAG_128        more than one dot is here
        FLAG_256        continue the shift

    */

    for (abacus = is_some_space(*str, _LIBCONFINI_WITH_EOL_) ? 65 : 9, lshift = 0, idx = 0; str[idx]; idx++) {

        if (!(abacus & 6) && is_some_space(str[idx], _LIBCONFINI_WITH_EOL_)) {

            if (abacus & 80) {

                lshift++;

            }

            if (!(abacus & 16)) {

                str[idx - lshift] = abacus & 32 ? _LIBCONFINI_SUBSECTION_ : _LIBCONFINI_SIMPLE_SPACE_;
                abacus &= 9;
                abacus |= 16;

            }

            abacus |= 1;

        } else if (!(abacus & 6) && str[idx] == _LIBCONFINI_SUBSECTION_) {

            if ((abacus & 144) && (abacus & 8)) {

                lshift++;

            }

            abacus |= abacus & 144 ? 361 : 489;

        } else {

                abacus &= 7;
                abacus = (abacus & 1) && !format.no_double_quotes && str[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ? abacus ^ 4
                    : (abacus & 1) && !format.no_single_quotes && str[idx] == _LIBCONFINI_SINGLE_QUOTES_ ? abacus ^ 2
                    : str[idx] == _LIBCONFINI_BACKSLASH_ ? abacus ^ 1
                    : abacus | 1;
                abacus |= 264;

        }

        if (abacus & 256) {

            str[idx - lshift] = str[idx];
            abacus &= 255;

        }

    }

    for (idx -= abacus & 240 ? ++lshift : lshift; str[idx]; str[idx++] = '\0');

    return idx - lshift;

}


/**

    @brief          Removes all comment initializers ('#' or ';') from the beginning of each line of a comment
    @param          commstr     The comment to sanitize
    @param          len         The length of commstr
    @param          format      The format of the INI file
    @return         The new length of the string

**/
static _LIBCONFINI_UINT_ uncomment (char *commstr, _LIBCONFINI_UINT_ len, const IniFormat format) {

    _LIBCONFINI_UINT_ lshift, idx;

    if (format.multiline_entries) {

        /* The comment is not multiline */

        for (lshift = 0; lshift < len && is_comm_char(commstr[lshift], format); lshift++);

        if (!lshift) {

            return len;

        }

        for (idx = lshift; idx < len; commstr[idx - lshift] = commstr[idx], idx++);

    } else {

        /* The comment is multiline */

        _LIBCONFINI_BYTE_ abacus;

        /*

        Mask "abacus":

            FLAG_1      Don't erase any character
            FLAG_2      Backslashes immediately preceding this character are odd
            FLAG_4      This character is a backslash
            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 following a new line (/(\n|\r)[\t \v\f]+/)
            FLAG_32     This character is not a new line character (/[^\r\n]/)

        */

        for (abacus = 3, lshift = 0, idx = 0; idx < len; idx++) {

            abacus      =   commstr[idx] == _LIBCONFINI_BACKSLASH_ ?
                                ((abacus & 35) | 32) ^ 2
                            : commstr[idx] == _LIBCONFINI_LF_ || commstr[idx] == _LIBCONFINI_CR_ ?
                                (abacus & 16) | ((abacus << 1) & 4)
                            : !(abacus & 32) && is_comm_char(commstr[idx], format) ?
                                (abacus & 40) | 8
                            : !(abacus & 40) && is_some_space(commstr[idx], _LIBCONFINI_NO_EOL_)?
                                (abacus & 57) | 16
                            :
                                (abacus & 33) | 32;

            if (abacus & 28) {

                lshift++;

            }

            if (!(abacus & 25)) {

                commstr[idx - lshift] = commstr[idx];

            }

        }

    }

    for (idx -= lshift; idx < len; commstr[idx++] = '\0');

    return idx - lshift;

}


/**

    @brief          Tries to determine the type of a member "as if it was active"
    @param          string              String containing an individual node
    @param          length              Length of the node
    @param          wanna_be_active     A boolean: TRUE if we are in an disabled node, FALSE otherwise
    @param          format              The format of the INI file
    @return         The node type (see header)

**/
static _LIBCONFINI_BYTE_ get_type_as_active (const char *nodestr, const _LIBCONFINI_UINT_ len, const _LIBCONFINI_BOOL_ wanna_be_active, const IniFormat format) {

    if (!len || *nodestr == format.delimiter_symbol || is_comm_char(*nodestr, format)) {

        return INI_UNKNOWN;

    }

    _LIBCONFINI_BYTE_ qmask;
    _LIBCONFINI_UINT_ idx;

    if (*nodestr == _LIBCONFINI_OPEN_SECTION_) {

        for (qmask = 1, idx = 1; idx < len; idx++) {

            if (nodestr[idx] == _LIBCONFINI_CLOSE_SECTION_ && !(qmask & 6)) {

                return INI_SECTION;

            }

            qmask = (qmask & 1) && !format.no_double_quotes && nodestr[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ? qmask ^ 4
                    : (qmask & 1) && !format.no_single_quotes && nodestr[idx] == _LIBCONFINI_SINGLE_QUOTES_ ? qmask ^ 2
                    : nodestr[idx] == _LIBCONFINI_BACKSLASH_ ? qmask ^ 1
                    : qmask | 1;

        }

        return INI_UNKNOWN;

    }

    /* It can be just a key... */

    idx = get_delimiter_pos(nodestr, len, format);

    if (wanna_be_active && (!format.disabled_can_be_implicit && idx == len)) {

        return INI_UNKNOWN;

    }

    return INI_KEY;

}


/**

    @brief          Recursive function, examines a (single-/multi- line) segment and searches for sub-segments.
    @param          segment Segment to examine
    @param          format  The format of the INI file
    @return         Number of sub-segments found, included itself

**/
static _LIBCONFINI_UINT_ further_cuts (char *segment, const IniFormat format) {

    if (!*segment) { return 0; }

    _LIBCONFINI_BOOL_ show_comments = !is_erase_char(*segment, format);
    _LIBCONFINI_BYTE_ abacus;
    _LIBCONFINI_UINT_ idx, focus_at, search_at = 0, unparse_at = 0, fragm_size = show_comments;

    /*

    Mask "abacus":

        FLAG_1  backslashes immediately preceding this character are even
        FLAG_2  unescaped single quotes are even until now
        FLAG_4  unescaped double quotes are even until now
        FLAG_8  this does not immediately follow [\n\r]\s*
        FLAG_16 the previous character was a space
        FLAG_32 this can be a disabled entry
        FLAG_64 this is nothing but a comment that can be multiline

    */

    abacus      =   is_parsable_char(*segment, format) && !(
                        format.no_disabled_after_spaces && is_some_space(segment[1], _LIBCONFINI_NO_EOL_)
                    ) ?
                        47
                    : !format.multiline_entries && (
                        is_comm_char(*segment, format) || *segment == _LIBCONFINI_INLINE_MARKER_
                    ) ?
                        79
                    :
                        15;

    if (abacus & 96) {

        for (idx = ltrim_s(segment, 1, _LIBCONFINI_NO_EOL_); segment[idx]; idx++) {

                if (is_comm_char(segment[idx], format)) {

                    /* Search for inline comments in (supposedly) disabled items */

                    if (abacus == 63) {

                        focus_at = ltrim_s(segment, 1, _LIBCONFINI_NO_EOL_);

                        if (get_type_as_active(segment + focus_at, idx - focus_at, _LIBCONFINI_TRUE_, format)) {

                            segment[idx] = _LIBCONFINI_INLINE_MARKER_;
                            segment[idx - 1] = '\0';
                            rtrim_h(segment, idx - 1, _LIBCONFINI_NO_EOL_);

                            if (show_comments) {

                                fragm_size++;

                            }

                            if (format.multiline_entries) {

                                unparse_at = idx + 1;
                                break;

                            }

                        } else if (format.multiline_entries) {

                            unparse_at = 1;
                            break;

                        }

                    }

                    abacus &= 103;
                    abacus |= 9;

                } else if (segment[idx] == _LIBCONFINI_LF_ || segment[idx] == _LIBCONFINI_CR_) {

                    /* Search for /\\(?:\n\r?|\r\n?)\s*[^;#]/ in multiline disabled items */

                    idx = ltrim_s(segment, idx + 1, _LIBCONFINI_WITH_EOL_);

                    if (
                            (format.no_disabled_after_spaces && is_some_space(segment[idx + 1], _LIBCONFINI_NO_EOL_))
                            ||
                            !(
                                (is_comm_char(segment[idx], format) && !format.multiline_entries)
                                ||
                                (is_parsable_char(segment[idx], format) && format.multiline_entries < 2)
                            )
                        ) {

                        rtrim_h(segment, idx, _LIBCONFINI_WITH_EOL_);

                        if (segment[idx]) {

                            search_at = idx + 1;

                            if (show_comments) {

                                fragm_size++;

                            }

                        }

                        break;

                    }

                    abacus &= 103;
                    abacus |= 1;

                } else if (is_some_space(segment[idx], _LIBCONFINI_NO_EOL_)) {

                    abacus &= 103;
                    abacus |= 25;

                } else {

                    abacus &= 111;
                    abacus = (abacus & 1) && !format.no_double_quotes && segment[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ? abacus ^ 4
                            : (abacus & 1) && !format.no_single_quotes && segment[idx] == _LIBCONFINI_SINGLE_QUOTES_ ? abacus ^ 2
                            : segment[idx] == _LIBCONFINI_BACKSLASH_ ? abacus ^ 1
                            : abacus | 1;
                    abacus |= 8;

                }

        }

        if (format.multiline_entries && !unparse_at) {

            focus_at = ltrim_s(segment, 1, _LIBCONFINI_NO_EOL_);

            if (segment[focus_at] && !get_type_as_active(segment + focus_at, idx - focus_at, _LIBCONFINI_TRUE_, format)) {

                unparse_at = 1;
            }

        }

    } else if (is_comm_char(*segment, format)) {

        unparse_at = 1;

    } else {

        search_at = 1;

    }

    if (unparse_at) {

        /* Cut unparsable multiline comments */

        cut_unparsable:

        for (idx = unparse_at; segment[idx]; idx++) {

            if (segment[idx] == _LIBCONFINI_LF_ || segment[idx] == _LIBCONFINI_CR_) {

                fragm_size += further_cuts(segment + ultrim_h(segment, idx), format);
                break;

            }

        }

    }

    if (search_at) {

        /* Search for inline comments in active items */

        for (abacus = 1, idx = search_at; segment[idx]; idx++) {

            if (is_comm_char(segment[idx], format) && is_some_space(segment[idx - 1], _LIBCONFINI_WITH_EOL_) && !(abacus & 6)) {

                segment[idx] = _LIBCONFINI_INLINE_MARKER_;
                segment[idx - 1] = '\0';
                rtrim_h(segment, idx - 1, _LIBCONFINI_NO_EOL_);
                search_at = 0;

                if (format.multiline_entries) {

                    if (show_comments) {

                        fragm_size++;

                    }

                    unparse_at = idx + 1;
                    goto cut_unparsable;

                }

                fragm_size += further_cuts(segment + idx, format);
                break;

            } else {

                abacus = (abacus & 1) && !format.no_double_quotes && segment[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ? abacus ^ 4
                        : (abacus & 1) && !format.no_single_quotes && segment[idx] == _LIBCONFINI_SINGLE_QUOTES_ ? abacus ^ 2
                        : segment[idx] == _LIBCONFINI_BACKSLASH_ ? abacus ^ 1
                        : abacus | 1;

            }

        }

    }

    return fragm_size;

}


/*----------------------------------------------------*/



        /*\
        |*|
        |*|   GLOBAL ENVIRONMENT
        |*|
        \*/



/* LIBRARY'S MAIN FUNCTION */

/**

    @brief          Parses an INI file and dispatches its content
    @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 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

**/
unsigned int load_ini_file (
    const char *path,
    const IniFormat format,
    int (*f_init)(
        IniStatistics *statistics,
        void *init_other
    ),
    int (*f_foreach)(
        IniDispatch *dispatch,
        void *foreach_other
    ),
    void *user_data
) {

    FILE *config_file = fopen(path, "r");

    if (config_file == NULL) {

        return CONFINI_ENOENT;

    }

    unsigned int return_value = 0;
    char *cache;
    _LIBCONFINI_UINT_ len;

    fseek(config_file, 0, SEEK_END);
    len = ftell(config_file);
    rewind(config_file);
    cache = (char *) malloc((len + 1) * sizeof(char));

    if (cache == NULL) {

        return CONFINI_ENOMEM;

    }

    if (fread(cache, sizeof(char), len, config_file) < len) {

        return_value = CONFINI_EIO;
        goto free_and_exit;

    }

    fclose(config_file);
    cache[len] = '\0';

    _LIBCONFINI_BYTE_ abacus;
    _LIBCONFINI_UINT_ idx, node_at, tmp_int;

    IniStatistics this_doc = {
            .format = format,
            .bytes = len,
            .members = 0
    };

    /* Examine and isolate each segment */

    for (tmp_int = 5, abacus = 0, node_at = 0, idx = 0; idx < len; idx++) {

        if (cache[idx] == _LIBCONFINI_SPACES_[tmp_int] || cache[idx] == _LIBCONFINI_SPACES_[tmp_int ^= 1]) {

            if (format.multiline_entries < 3 && abacus) {

                idx += cache[idx + 1] == _LIBCONFINI_SPACES_[tmp_int ^ 1];

            } else {

                cache[idx] = '\0';
                this_doc.members += further_cuts(cache + ultrim_h(cache, node_at), format);
                node_at = idx + 1;

            }

            abacus = 0;

        } else if (cache[idx] == _LIBCONFINI_BACKSLASH_) {

            abacus ^= 1;

        } else {

            /* Replace NUL characters in the buffer (if any) with simple spaces */

            if (!cache[idx]) {

                cache[idx] = _LIBCONFINI_SIMPLE_SPACE_;

            }

            abacus = 0;

        }

    }

    /* Debug */
    /*

    for (_LIBCONFINI_UINT_ tmp = 0; tmp < len; tmp++) {
        putchar(cache[tmp] > 0 ? cache[tmp] : '$');
    }
    putchar(_LIBCONFINI_LF_);

    */

    if (f_init && f_init(&this_doc, user_data)) {

        return_value = CONFINI_EIINTR;
        goto free_and_exit;

    }

    if (!f_foreach) {

        goto free_and_exit;

    }

    /* Dispatch the parsed input */

    _LIBCONFINI_BOOL_ parent_is_disabled = _LIBCONFINI_FALSE_;
    _LIBCONFINI_UINT_ parse_at, real_parent_len = 0, parent_len = 0;
    char *parent = cache + len, *subparent = parent, *real_parent = parent;
    IniDispatch this_d = {
        .format = format,
        .dispatch_id = 0
    };

    for (node_at = 0, idx = 0; idx <= len; idx++) {

        if (cache[idx]) {

            continue;

        }

        if (!cache[node_at] || is_erase_char(cache[node_at], format)) {

            node_at = idx + 1;
            continue;

        }

        if (this_d.dispatch_id >= this_doc.members) {

            return_value = CONFINI_EFEOOR;
            goto free_and_exit;

        }

        this_d.data = cache + node_at;
        this_d.value = cache + idx;
        this_d.type = 0;
        this_d.d_length = idx - node_at;
        this_d.v_length = 0;

        if (is_parsable_char(*this_d.data, format)) {

            parse_at = ltrim_s(cache, node_at + 1, _LIBCONFINI_NO_EOL_);

            /*

            Mask "abacus":

                    FLAG_1          continue the loop
                    FLAG_2          we are not in \n[ \t\v\f]*
                    FLAG_4          this is a space and FLAG_2 == TRUE

            */

            for (abacus = 1, tmp_int = 1; abacus && tmp_int < this_d.d_length; tmp_int++) {

                abacus      =   this_d.data[tmp_int] == _LIBCONFINI_LF_ || this_d.data[tmp_int] == _LIBCONFINI_CR_ ? 1
                                : (abacus & 4) && is_comm_char(this_d.data[tmp_int], format) ? 0
                                : (abacus & 2) && is_some_space(this_d.data[tmp_int], _LIBCONFINI_NO_EOL_) ? 7
                                : 3;

            }

            this_d.type = tmp_int == this_d.d_length ? get_type_as_active(cache + parse_at, idx - parse_at, _LIBCONFINI_TRUE_, format) : 0;

            if (this_d.type) {

                this_d.data = cache + parse_at;
                this_d.d_length = idx - parse_at;

            }

            this_d.type |= 4;

        } else if (is_comm_char(*this_d.data, format)) {

            this_d.type = INI_COMMENT;

        } else if (*this_d.data == _LIBCONFINI_INLINE_MARKER_) {

            this_d.type = INI_INLINE_COMMENT;

        } else {

            this_d.type = get_type_as_active(this_d.data, this_d.d_length, _LIBCONFINI_FALSE_, format);

        }

        if (parent_len && *subparent) {

            tmp_int = 0;

            do {

                parent[tmp_int + parent_len] = subparent[tmp_int];

            } while (subparent[tmp_int++]);

            parent_len += tmp_int - 1;
            subparent = parent + parent_len;

        }

        if (parent_is_disabled && !(this_d.type & 4)) {

            real_parent[real_parent_len] = '\0';
            parent_len = real_parent_len;
            parent = real_parent;
            parent_is_disabled = _LIBCONFINI_FALSE_;

        } else if (!parent_is_disabled && this_d.type == INI_DISABLED_SECTION) {

            real_parent_len = parent_len;
            real_parent = parent;
            parent_is_disabled = _LIBCONFINI_TRUE_;

        }

        this_d.append_to = parent;

        this_d.d_length     =   this_d.type == INI_COMMENT ? 
                                    uncomment(this_d.data, idx - node_at, format)
                                : this_d.type == INI_INLINE_COMMENT ? 
                                    uncomment(++this_d.data, idx - node_at - 1, format)
                                : format.multiline_entries < 3 ?
                                    unescape_cr_lf(this_d.data, idx - node_at, this_d.type & 4, format)
                                :
                                    idx - node_at;

        switch (this_d.type) {

            /*

            case INI_UNKNOWN:

                break;

            */

            /* Do nothing */

            case INI_SECTION:
            case INI_DISABLED_SECTION:

                *this_d.data++ = '\0';

                /*

                    Mask "abacus":

                        FLAG_1          backslashes immediately preceding this character are even
                        FLAG_2          unescaped single quotes are odd until now
                        FLAG_4          unescaped double quotes are odd until now

                */

                for (

                    abacus = 1, tmp_int = 0;

                        this_d.data[tmp_int]
                        &&
                        ((abacus & 6) || this_d.data[tmp_int] != _LIBCONFINI_CLOSE_SECTION_);

                    abacus = (abacus & 1) && !format.no_double_quotes && this_d.data[tmp_int] == _LIBCONFINI_DOUBLE_QUOTES_ ? abacus ^ 4
                            : (abacus & 1) && !format.no_single_quotes && this_d.data[tmp_int] == _LIBCONFINI_SINGLE_QUOTES_ ? abacus ^ 2
                            : this_d.data[tmp_int] == _LIBCONFINI_BACKSLASH_ ? abacus ^ 1
                            : abacus | 1,
                    tmp_int++

                );

                while (this_d.data[tmp_int]) {

                    this_d.data[tmp_int++] = '\0';

                }

                this_d.d_length = sanitize_section_name(this_d.data, format);

                if (parent_len && *this_d.data == _LIBCONFINI_SUBSECTION_) {

                    subparent = this_d.data;

                } else {

                    parent = this_d.data + (!parent_len && *this_d.data == _LIBCONFINI_SUBSECTION_);
                    parent_len = this_d.d_length - (!parent_len && *this_d.data == _LIBCONFINI_SUBSECTION_);
                    subparent = cache + idx;
                    this_d.append_to = subparent;

                }

                if (!format.case_sensitive) {

                    string_tolower(this_d.data);

                }

                break;

            case INI_KEY:
            case INI_DISABLED_KEY:

                tmp_int = get_delimiter_pos(this_d.data, this_d.d_length, format);

                if (this_d.d_length && tmp_int && tmp_int < this_d.d_length) {

                    this_d.data[tmp_int] = '\0';

                    if (format.preserve_spaces_in_values) {

                        this_d.v_length = this_d.d_length - ltrim_h(this_d.data, tmp_int + 1, _LIBCONFINI_NO_EOL_);
                        this_d.value = this_d.data + this_d.d_length - this_d.v_length;

                    } else {

                        this_d.value = this_d.data + tmp_int + 1;
                        this_d.v_length = collapse_spaces(this_d.value, format);

                    }

                    this_d.d_length = collapse_spaces(this_d.data, format);

                } else if (format.implicit_is_special) {

                    this_d.value = INI_IMPLICIT_VALUE;
                    this_d.v_length = INI_IMPLICIT_V_LENGTH;
                }

                if (!format.case_sensitive) {

                    string_tolower(this_d.data);

                }

                break;

            case INI_COMMENT:
            case INI_INLINE_COMMENT:

                this_d.append_to = cache + idx;

        }

        if (f_foreach(&this_d, user_data)) {

            return_value = CONFINI_EFEINTR;
            goto free_and_exit;

        }

        this_d.dispatch_id++;
        node_at = idx + 1;

    }

    free_and_exit:

    free(cache);
    return return_value;

}



/* FUNCTIONS FOR THE USER (NOT USED BY LIBCONFINI) */


/**

    @brief          Sets the valued used for implicit keys
    @param          implicit_value      The string to be used as implicit value (usually `"YES"`, or `"TRUE"`)
    @param          implicit_v_length   The length of @p implicit_value (usually 0, independently of its real length)
    @return         Nothing

**/
void ini_set_implicit_value (char *implicit_value, unsigned long int implicit_v_length) {

    INI_IMPLICIT_VALUE = implicit_value;
    INI_IMPLICIT_V_LENGTH = implicit_v_length;

}


/**

    @brief          Converts an IniFormat into an ::IniFormatMask
    @param          source      The IniFormat to be read
    @return         The mask representing the format

**/
IniFormatMask ini_format_calculate_mask (const IniFormat source) {

    IniFormatMask mask = 0;

    unsigned int bitpos = 0;

    #define _LIBCONFINI_ASSIGN_TO_MASK_(SIZE, PROPERTY, IGNORE_ME) \
        mask |= source.PROPERTY << bitpos;\
        bitpos += SIZE;

    _LIBCONFINI_EXPAND_SETTINGS_MACRO_AS_(_LIBCONFINI_ASSIGN_TO_MASK_)

    #undef _LIBCONFINI_ASSIGN_TO_MASK_

    return mask;

}


/**

    @brief          Sets all the values of an IniFormat by reading them from an ::IniFormatMask
    @param          dest_format     The IniFormat to be set
    @param          mask            The ::IniFormatMask to be read
    @return         Nothing

**/
void ini_format_read_mask (IniFormat *dest_format, IniFormatMask mask) {

    #define _LIBCONFINI_ASSIGN_TO_INI_TYPE_(SIZE, PROPERTY, IGNORE_ME) \
        dest_format->PROPERTY = mask;\
        mask >>= SIZE;

    _LIBCONFINI_EXPAND_SETTINGS_MACRO_AS_(_LIBCONFINI_ASSIGN_TO_INI_TYPE_)

    #undef _LIBCONFINI_ASSIGN_TO_INI_TYPE_

}


/**

    @brief          Unescapes a string and removes all its unescaped quotes (if single/double quotes are active)
    @param          ini_string  The string to be unescaped
    @param          format      The format of the INI file
    @return         The new length of the string

**/
unsigned long int ini_unquote (char *ini_string, const IniFormat format) {

    _LIBCONFINI_BYTE_ qmask;
    _LIBCONFINI_UINT_ lshift, nbacksl, idx;

    /*

    Mask "qmask":

        FLAG_1      unescaped single quotes are even until now
        FLAG_2      unescaped double quotes are even until now
        FLAG_4      this is an unescaped single quote and single quotes are metacharacters
        FLAG_8      this is an unescaped double quote and double quotes are metacharacters

    */

    for (qmask = 3, lshift = 0, nbacksl = 0, idx = 0; ini_string[idx]; idx++) {

        qmask = (qmask & 3)
                | (
                    !format.no_double_quotes && ini_string[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ? 8
                    : !format.no_single_quotes && ini_string[idx] == _LIBCONFINI_SINGLE_QUOTES_ ? 4
                    : 0
                );

        if (!(nbacksl & 1) && !((~qmask & 9) && (~qmask & 6))) {

            qmask ^= qmask >> 2;
            lshift++;
            continue;

        }

        if (ini_string[idx] == _LIBCONFINI_BACKSLASH_) {

            nbacksl++;

        } else {

            if ((nbacksl & 1) && (qmask & 12)) {

                lshift++;

            }

            lshift += nbacksl >> 1;
            nbacksl = 0;

        }

        if (lshift) {

            ini_string[idx - lshift] = ini_string[idx];

        }

    }

    for (idx -= lshift + (nbacksl >> 1); ini_string[idx]; ini_string[idx++] = '\0');

    return idx - lshift;

}


/**

    @brief          Gets the length of an INI array
    @param          ini_string      The stringified array
    @param          delimiter       The delimiter of the array members
    @param          format          The format of the INI file
    @return         The length of the INI array

**/
unsigned long int ini_array_getlength (const char *ini_string, const char delimiter, const IniFormat format) {

    unsigned long int arr_length = 0;
    _LIBCONFINI_UINT_ idx;
    _LIBCONFINI_BYTE_ abacus;

    /*

    Mask "abacus":

        FLAG_1  backslashes immediately preceding this character are even
        FLAG_2  unescaped single quotes are odd until now
        FLAG_4  unescaped double quotes are odd until now
        FLAG_8  this is not a delimiter
        FLAG_16 continue the loop

    */

    for (abacus = 17, idx = 0; abacus; idx++) {

        abacus =    ini_string[idx] == delimiter ? abacus & 23
                    : ini_string[idx] == _LIBCONFINI_BACKSLASH_ ? (abacus | 8) ^ 1
                    : ini_string[idx] ? (
                        (abacus | 9)
                        ^ (
                            !format.no_double_quotes && ini_string[idx] == _LIBCONFINI_DOUBLE_QUOTES_ && (abacus & 1) ? 4
                            : !format.no_single_quotes && ini_string[idx] == _LIBCONFINI_SINGLE_QUOTES_ && (abacus & 1) ? 2
                            : 0
                        )
                    )
                    : 0;

        if (!(abacus & 14)) {

            arr_length++;

        }

    }

    return arr_length;

}


/**

    @brief          Calls a custom function for each member of an INI array -- useful for read-only (const) stringified arrays
    @param          ini_string          The stringified array
    @param          delimiter           The delimiter of the array members
    @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

**/
unsigned int ini_array_foreach (
    const char *ini_string,
    const char delimiter,
    const IniFormat format,
    int (*f_foreach)(
        const char *member,
        unsigned int offset,
        unsigned int length,
        void *foreach_other
    ),
    void *user_data
) {

    _LIBCONFINI_BYTE_ abacus;
    _LIBCONFINI_UINT_ idx, offs;

    /* Mask "abacus": as above */

    for (abacus = 17, offs = 0, idx = 0; abacus; idx++) {

        abacus      =   ini_string[idx] == delimiter ? abacus & 23
                        : ini_string[idx] == _LIBCONFINI_BACKSLASH_ ? (abacus | 8) ^ 1
                        : ini_string[idx] ? (
                            (abacus | 9)
                            ^ (
                                !format.no_double_quotes && ini_string[idx] == _LIBCONFINI_DOUBLE_QUOTES_ && (abacus & 1) ? 4
                                : !format.no_single_quotes && ini_string[idx] == _LIBCONFINI_SINGLE_QUOTES_ && (abacus & 1) ? 2
                                : 0
                            )
                        )
                        : 0;

        if (!(abacus & 14)) {

            offs = ltrim_s(ini_string, offs, _LIBCONFINI_WITH_EOL_);

            if (f_foreach(ini_string, offs, rtrim_s(ini_string + offs, idx - offs, _LIBCONFINI_WITH_EOL_), user_data)) {

                return CONFINI_EFEINTR;

            }

            offs = idx + 1;

        }

    }

    return 0;

}


/**

    @brief          Splits an INI array and calls a custom function for each member.
    @param          ini_string          The stringified array
    @param          delimiter           The delimiter of the array members
    @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

**/
unsigned int ini_split_array (
    char *ini_string,
    const char delimiter,
    const IniFormat format,
    int (*f_foreach)(
        char *element,
        unsigned int length,
        void *foreach_other
    ),
    void *user_data
) {

    _LIBCONFINI_BYTE_ abacus;
    _LIBCONFINI_UINT_ offs, idx;

    /* Mask "abacus": as above */

    for (offs = 0, abacus = 17, idx = 0; abacus; idx++) {

        abacus      =   ini_string[idx] == delimiter ?
                            abacus & 23
                        : ini_string[idx] == _LIBCONFINI_BACKSLASH_ ?
                            (abacus | 8) ^ 1
                        : ini_string[idx] ?
                            (
                                (abacus | 9)
                                ^ (
                                    !format.no_double_quotes && ini_string[idx] == _LIBCONFINI_DOUBLE_QUOTES_ && (abacus & 1) ? 4
                                    : !format.no_single_quotes && ini_string[idx] == _LIBCONFINI_SINGLE_QUOTES_ && (abacus & 1) ? 2
                                    : 0
                                )
                            )
                        :
                            0;

        if (!(abacus & 14)) {

            ini_string[idx] = '\0';
            offs = ltrim_h(ini_string, offs, _LIBCONFINI_WITH_EOL_);

            if (f_foreach(ini_string + offs, rtrim_h(ini_string + offs, idx - offs, _LIBCONFINI_WITH_EOL_), user_data)) {

                return CONFINI_EFEINTR;

            }

            offs = idx + 1;

        }

    }

    return 0;

}


/**

    @brief          Checks whether a string matches *exactly* one of the booleans listed in
                    the private constant @link #_LIBCONFINI_BOOLEANS_
    @param          ini_string          A string to be checked
    @param          return_value        A value that is returned if no matching boolean has been found
    @return         The matching boolean value (0 or 1) or @p return_value if no boolean has been found

**/
signed int ini_getbool (const char *ini_string, const signed int return_value) {

    unsigned short pair_idx, bool_idx, chr_idx;

    for (pair_idx = 0; pair_idx < sizeof(_LIBCONFINI_BOOLEANS_) / 2 / sizeof(char *); pair_idx++) {

        for (bool_idx = 0; bool_idx < 2; bool_idx++) {

            for (

                chr_idx = 0;

                        (
                            ini_string[chr_idx] > 0x40 && ini_string[chr_idx] < 0x5b ?
                                ini_string[chr_idx] | 0x60
                            :
                                ini_string[chr_idx]
                        ) == _LIBCONFINI_BOOLEANS_[pair_idx][bool_idx][chr_idx];

                chr_idx++

            ) {

                if (!ini_string[chr_idx]) {

                    return bool_idx;

                }

            }

        }

    }

    return return_value;

}


/**

    @brief          Checks whether the first letter of a string matches the first letter of one of the
                    booleans listed in the private constant @link #_LIBCONFINI_BOOLEANS_
    @param          ini_string          A string to be checked
    @param          return_value        A value that is returned if no matching boolean has been found
    @return         The matching boolean value (0 or 1) or @p return_value if no boolean has been found

**/
signed int ini_getlazybool (const char *ini_string, const signed int return_value) {

    unsigned short pair_idx, bool_idx;
    unsigned char ch = *ini_string > 0x40 && *ini_string < 0x5b ? *ini_string | 0x60 : *ini_string;

    for (pair_idx = 0; pair_idx < sizeof(_LIBCONFINI_BOOLEANS_) / 2 / sizeof(char *); pair_idx++) {

        for (bool_idx = 0; bool_idx < 2; bool_idx++) {

            if (ch == *_LIBCONFINI_BOOLEANS_[pair_idx][bool_idx]) {

                return bool_idx;

            }

        }

    }

    return return_value;

}



/* WRAPPERS -- In case you don't want to "#include <stdlib.h>" in your source */

/*
long double (*ini_getlfloat)(const char *ini_string) = &atolf;
*/
/* Where is atolf?? */

double (*ini_getfloat) (const char *ini_string) = &atof;

long long int (*ini_getllint) (const char *ini_string) = &atoll;

long int (*ini_getlint) (const char *ini_string) = &atol;

int (*ini_getint) (const char *ini_string) = &atoi;



/* VARIABLES */

char *INI_IMPLICIT_VALUE = (char *) 0;
unsigned long int INI_IMPLICIT_V_LENGTH = 0;



/* EOF */
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.