# HG changeset patch # User Franz Glasner # Date 1729370411 -7200 # Node ID 33df05108ba15f99700a5d42cc5810f042eabc61 # Parent f6d296c5868e1a781f9032bf10ce1363b1cf1d3e farray.sh: New implementation alists: searching is done using a binary search now while preserving insertion order. The implementation uses two key lists: The first one is sorted and is used for searching using binary search. The second is a double-linked list and is used for remembering the insertion order. diff -r f6d296c5868e -r 33df05108ba1 .shellcheckrc --- a/.shellcheckrc Fri Oct 18 14:02:18 2024 +0200 +++ b/.shellcheckrc Sat Oct 19 22:40:11 2024 +0200 @@ -25,5 +25,7 @@ disable=SC3012 # In POSIX sh, local is undefined disable=SC3043 +# ;& is non-standard +disable=SC2127 enable=avoid-nullary-conditions diff -r f6d296c5868e -r 33df05108ba1 share/local-bsdtools/farray.sh --- a/share/local-bsdtools/farray.sh Fri Oct 18 14:02:18 2024 +0200 +++ b/share/local-bsdtools/farray.sh Sat Oct 19 22:40:11 2024 +0200 @@ -2,7 +2,7 @@ # -*- indent-tabs-mode: nil; -*- #: #: A simple library to emulate simple arrays (one-dimensional) and alists -#: in a POSIX shell. +#: in FreeBSD's POSIX shell :command:`/bin/sh`. #: #: :Author: Franz Glasner #: :Copyright: (c) 2024 Franz Glasner. @@ -94,16 +94,21 @@ #: for private use in this module. #: Do not use such names in your scripts. #: +#: Do also not use local variables starting with ``__farr_`` +#: or ``___farr_``- +#: #: Public functions start with ``farray_, ``falist_`` or ``fobject_``. #: -_farr_array_token_prefix='_farr_A[?]:' +_farr_array_token_prefix='_farr_A[?]:' # token value prefix _farr_array_prefix=_farr_A_ -_farr_alist_token_prefix='_farr_KV[?,?]:' -_farr_alist_prefix=_farr_KV_ -_farr_alist_key_prefix=_farr_K_ -_farr_alist_value_prefix=_farr_V_ +_farr_alist_token_prefix='_farr_KV[?,?]:' # token value prefix +_farr_alist_prefix=_farr_KV_ # alist as object +_farr_alist_key_bs_prefix=_farr_Kb_ # alist keys in list for binary + # search +_farr_alist_key_prefix=_farr_Ks_ # alist keys in storage +_farr_alist_value_prefix=_farr_Vs_ # alist values in storage #: @@ -169,6 +174,60 @@ #: +#: Check whether given argument string is formally a storage pointer value. +#: +#: A valid storage pointer is just a decimal number (base 10) without any +#: sign and other decorations. +#: +#: Args: +#: $1: The string to be checked +#: +#: Returns: +#: int: 0 (truish) if `$1` is a valid decimal number, +#: 1 (falsy) otherwise +#: +_farr_is_valid_storage_ptr() { + case "$1" in + '') + # empty is not allowed + return 1;; + *[!0123456789]*) + # non-decimal + return 1;; + *) + return 0;; + esac +} + + +#: +#: Check whether given argument string is formally a valid object token. +#: +#: A given valid object token is a hexadecimal value in lower-case. +#: +#: Args: +#: $1: The string to be checked +#: +#: Returns: +#: int: 0 (truish) if `$1` is a valid decimal number, +#: 1 (falsy) otherwise +#: +_farr_is_valid_object_token() { + + case "$1" in + '') + # empty is not allowed + return 1;; + *[!abcdef0123456789]*) + # non-hexadecimal + return 1;; + *) + return 0;; + esac +} + + +#: #: From an input index value compute an effective index value and store #: it into a variable with a given name. #: @@ -1926,7 +1985,8 @@ falist_create() { local __farr_name - local __farr_token __farr_objname __farr_keyname __farr_valname __farr_len + local __farr_token __farr_objname __farr_bskeyname __farr_keyname __farr_valname + local __farr_llen __farr_name="${1-}" [ -z "${__farr_name}" ] && _farr_fatal "missing falist name" @@ -1936,20 +1996,29 @@ __farr_token="$(/usr/bin/hexdump -v -e '/1 "%02x"' -n 16 '/dev/urandom')" __farr_objname=${_farr_alist_prefix}${__farr_token} + __farr_bskeyname=${_farr_alist_key_bs_prefix}${__farr_token} __farr_keyname=${_farr_alist_key_prefix}${__farr_token} __farr_valname=${_farr_alist_value_prefix}${__farr_token} # Check whether the variable already exists - eval __farr_len=\$\{${__farr_objname}__+SET\} - [ -n "${__farr_len}" ] && _farr_fatal "falist \`${__farr_name}' already exists: existing token \`${__farr_token}'" - - # Really create the storage by initializing its length ... - eval ${__farr_objname}__=0 - # ... and its reference count - eval ${__farr_objname}_C=1 - - # And associate the token with the array name - eval "${__farr_name}=\"\${_farr_alist_token_prefix}\${__farr_token}\"" + eval __farr_llen=\"\$\{"${__farr_objname}"__+SET\}\" + [ -n "${__farr_llen}" ] && _farr_fatal "falist \`${__farr_name}' already exists: existing token \`${__farr_token}'" + + # Really create the object by ... + # ... initializing its logical length ... + setvar "${__farr_objname}__" 0 + # ... initializing its reference count ... + setvar "${__farr_objname}_C" 1 + # ... initializing the storage pointers ... + setvar "${__farr_objname}_P" "0;0" # ; + # + # ... initializing the length of the binary search key list (including + # tombstones + # + setvar "${__farr_objname}_B" 0 + + # And associate the object token with the array name + setvar "${__farr_name}" "${_farr_alist_token_prefix}${__farr_token}" # # When there are key-value pairs populate the alist with. @@ -1973,31 +2042,35 @@ #: empty if a token value is given. #: __farr_token (str): The token that is the value of the name. #: __farr_objname (str): The variable prefix for the length ("object"). +#: __farr_bskeyname (str): The variable prefix for all keys in the ordered +#: binary search list #: __farr_keyname (str): The variable prefix for all item keys. #: __farr_valname (str): The variable prefix for all item values. -#: __farr_len (int): The length of the alist. +#: __farr_llen (int): The logical length of the alist. +#: __farr_bslen (int): The real length of the binsearch key list. +#: __farr_sptr_first (str): The "pointer" to the first storage item or empty. +#: __farr_sptr_last (str): The "pointer" to the last storage item or empty. #: #: Exit: #: Iff the array does not exist in some fashion (token and/or storage). #: _farr_alist_get_meta() { - local __farr_gm_name_or_value - - local __farr_tmp_refcount - - __farr_gm_name_or_value="${1-}" - case "${__farr_gm_name_or_value}" in + # __farr_gm_name_or_value $1 + + local __farr_tmp_refcount __farr_tmp_sptr + + case "${1-}" in '') _farr_fatal "missing falist name or token value" ;; "${_farr_alist_token_prefix}"*) # A prefixed token value __farr_name='' - __farr_token="${__farr_gm_name_or_value#"${_farr_alist_token_prefix}"}" + __farr_token="${1#"${_farr_alist_token_prefix}"}" ;; *) # A variable name: go through one indirection - __farr_name="${__farr_gm_name_or_value}" + __farr_name="${1}" eval __farr_token=\"\$\{"${__farr_name}"-\}\" case "${__farr_token}" in '') @@ -2015,14 +2088,21 @@ esac __farr_objname="${_farr_alist_prefix}${__farr_token}" - __farr_keyname=${_farr_alist_key_prefix}${__farr_token} - __farr_valname=${_farr_alist_value_prefix}${__farr_token} - - eval __farr_len=\$\{${__farr_objname}__:+SET\} - [ -z "${__farr_len}" ] && _farr_fatal "falist \`${__farr_name:-"${__farr_gm_name_or_value}"}' not created properly: no object for token \`${__farr_token}'" - eval __farr_tmp_refcount=\$\{${__farr_objname}_C:+SET\} - [ -z "${__farr_tmp_refcount}" ] && _farr_fatal "falist \`${__farr_name:-"${__farr_gm_name_or_value}"}' not created properly: no refcnt for token \`${__farr_token}'" - eval __farr_len="\${${__farr_objname}__}" + __farr_bskeyname="${_farr_alist_key_bs_prefix}${__farr_token}" + __farr_keyname="${_farr_alist_key_prefix}${__farr_token}" + __farr_valname="${_farr_alist_value_prefix}${__farr_token}" + + eval __farr_llen=\"\$\{"${__farr_objname}"__-\}\" + [ -z "${__farr_llen}" ] && _farr_fatal "falist \`${__farr_name:-"${1}"}' not created properly: no object for token \`${__farr_token}'" + # Check whether the object's reference count is set properly + eval __farr_tmp_refcount=\"\$\{"${__farr_objname}"_C:+SET\}\" + [ -z "${__farr_tmp_refcount}" ] && _farr_fatal "falist \`${__farr_name:-"${1}"}' not created properly: no refcnt for token \`${__farr_token}'" + # Get the storage pointers to first and last item + eval __farr_tmp_sptr=\"\$\{"${__farr_objname}"_P-\}\" + [ -z "${__farr_tmp_sptr}" ] && _farr_fatal "falist \`${__farr_name:-"${1}"}' not created properly: no storage pointers for token \`${__farr_token}'" + __farr_sptr_first="${__farr_tmp_sptr%;*}" + __farr_sptr_last="${__farr_tmp_sptr#*;}" + eval __farr_bslen=\"\$\{"${__farr_objname}"_B-\}\" return 0 } @@ -2039,20 +2119,24 @@ #: __farr_token (str): The token that is the value of the name without its #: token prefix. #: __farr_objname (str): The variable prefix for the length ("object"). +#: __farr_bskeyname (str): The variable prefix for all keys in the ordered +#: binary search list #: __farr_keyname (str): The variable prefix for all item keys. #: __farr_valname (str): The variable prefix for all item values. -#: __farr_len (int): The length of the alist. +#: __farr_llen (int): The logical length of the alist. +#: __farr_bslen (int): The real length of the binsearch key list. +#: __farr_sptr_first (str): The "pointer" to the first storage item or empty. +#: __farr_sptr_last (str): The "pointer" to the last storage item or empty. #: #: Returns: #: 0 if the array exists, 1 if something is missing. #: _farr_alist_tryget_meta() { - local __farr_gm_name_or_value - - local __farr_tmp_refcount - - __farr_gm_name_or_value="${1-}" - case "${__farr_gm_name_or_value}" in + # __farr_gm_name_or_value $1 + + local __farr_tmp_refcount __farr_tmp_sptr + + case "${1-}" in '') _farr_err "missing falist name or token value" return 1 @@ -2060,11 +2144,11 @@ "${_farr_alist_token_prefix}"*) # A prefixed token value __farr_name='' - __farr_token="${__farr_gm_name_or_value#"${_farr_alist_token_prefix}"}" + __farr_token="${1#"${_farr_alist_token_prefix}"}" ;; *) # A variable name: go through one indirection - __farr_name="${__farr_gm_name_or_value}" + __farr_name="${1}" eval __farr_token=\"\$\{"${__farr_name}"-\}\" case "${__farr_token}" in '') @@ -2084,41 +2168,120 @@ esac __farr_objname="${_farr_alist_prefix}${__farr_token}" - __farr_keyname=${_farr_alist_key_prefix}${__farr_token} - __farr_valname=${_farr_alist_value_prefix}${__farr_token} - - eval __farr_len=\$\{${__farr_objname}__:+SET\} - if [ -z "${__farr_len}" ] ; then - _farr_err "falist \`${__farr_name:-"${__farr_gm_name_or_value}"}' not created properly: no object for token \`${__farr_token}'" + __farr_bskeyname="${_farr_alist_key_bs_prefix}${__farr_token}" + __farr_keyname="${_farr_alist_key_prefix}${__farr_token}" + __farr_valname="${_farr_alist_value_prefix}${__farr_token}" + + eval __farr_llen=\"\$\{"${__farr_objname}"__-\}\" + if [ -z "${__farr_llen}" ] ; then + _farr_err "falist \`${__farr_name:-"${1}"}' not created properly: no object for token \`${__farr_token}'" + return 1 + fi + eval __farr_tmp_refcount=\"\$\{"${__farr_objname}"_C:+SET\}\" + if [ -z "${__farr_tmp_refcount}" ] ; then + _farr_err "falist \`${__farr_name:-"${1}"}' not created properly: no refcnt for token \`${__farr_token}'" + return 1 + fi + # Get the storage pointers to first and last item + eval __farr_tmp_sptr=\"\$\{"${__farr_objname}"_P-\}\" + if [ -z "${__farr_tmp_sptr}" ] ; then + _farr_err "falist \`${__farr_name:-"${1}"}' not created properly: no storage pointers for token \`${__farr_token}'" return 1 fi - eval __farr_tmp_refcount=\$\{${__farr_objname}_C:+SET\} - if [ -z "${__farr_tmp_refcount}" ] ; then - _farr_err "falist \`${__farr_name:-"${__farr_gm_name_or_value}"}' not created properly: no refcnt for token \`${__farr_token}'" + __farr_sptr_first="${__farr_tmp_sptr%;*}" + __farr_sptr_last="${__farr_tmp_sptr#*;}" + eval __farr_bslen=\"\$\{"${__farr_objname}"_B-\}\" + return 0 +} + + +#: +#: Internal helper to try to parse and get all the metadata from a storage +#: cookie. +#: +#: A storage cookie generally has the form +#: ``/;@``. +#: +#: Args: +#: $1 (str): The value of the storage cookie +#: +#: Output (Globals): +#: __farr_token (str): The token that is the value of the name without its +#: token prefix. +#: __farr_objname (str): The variable prefix for the length ("object"). +#: __farr_keyname (str): The variable prefix for all item keys. +#: __farr_valname (str): The variable prefix for all item values. +#: __farr_sptr (str): The storage pointer. +#: __farr_sptr_prev (str): The storage pointer to the previous entry. +#: __farr_sptr_next (str): The storage pointer to the next entry. +#: +#: Returns: +#: 0 if the array exists, 1 if something is missing. +#: +_farr_alist_tryparse_cookie() { + # __farr_gm_name_or_value $1 + + local __farr_tmp + + if [ -z "${1}" ] ; then + _farr_err "missing storage cookie value" return 1 fi - eval __farr_len="\${${__farr_objname}__}" + __farr_token="${1#*@}" + if [ "${__farr_token}" = "${1}" ]; then + _farr_err "not a valid storage cookie: ${1}" + return 1 + fi + if ! _farr_is_valid_object_token "${__farr_token}"; then + _farr_err "not a valid object token form: ${__farr_token}" + return 1 + fi + __farr_tmp="${1%%@*}" + __farr_sptr="${__farr_tmp%%/*}" + if [ "${__farr_sptr}" = "${__farr_tmp}" ]; then + _farr_err "not a valid storage cookie: ${1}" + return 1 + fi + if ! _farr_is_valid_storage_ptr "${__farr_sptr}"; then + _farr_err "not a valid storage pointer form: ${__farr_sptr}" + return 1 + fi + __farr_tmp="${__farr_tmp#*/}" + __farr_sptr_prev="${__farr_tmp%%;*}" + if [ "${__farr_sptr_prev}" = "${__farr_tmp}" ]; then + _farr_err "not a valid storage cookie: ${1}" + return 1 + fi + if ! _farr_is_valid_storage_ptr "${__farr_sptr_prev}"; then + _farr_err "not a valid storage pointer form: ${__farr_sptr_prev}" + return 1 + fi + __farr_sptr_next="${__farr_tmp#*;}" + + __farr_objname="${_farr_alist_prefix}${__farr_token}" + __farr_keyname="${_farr_alist_key_prefix}${__farr_token}" + __farr_valname="${_farr_alist_value_prefix}${__farr_token}" + return 0 } #: -#: Get the length of an alist and put it into a variable. +#: Get the logical length of an alist and put it into a variable. #: #: Args: #: $1 (str): The name of the variable to put the length info. #: $2 (str): The name of the alist. #: falist_length() { - local __farr_varname __farr_name - - local __farr_token __farr_objname __farr_keyname __farr_valname __farr_len - - __farr_varname="${1-}" - [ -z "${__farr_varname}" ] && _farr_fatal "missing variable name" - shift - _farr_alist_get_meta "$@" - eval "${__farr_varname}"=${__farr_len} + # __farr_varname $1 + # __farr_name $2 + + local __farr_name __farr_token __farr_objname __farr_bskeyname __farr_keyname __farr_valname __farr_llen __farr_bslen __farr_sptr_first __farr_sptr_last + + [ -z "${1-}" ] && _farr_fatal "missing variable name" + _farr_alist_get_meta "$2" + setvar "${1}" "${__farr_llen}" } @@ -2136,9 +2299,11 @@ #: 0 (truthy) #: falist_print_length() { - local __farr_vn - - ( falist_length __farr_vn "$@" && printf "%s" "${__farr_vn}"; ) \ + # $1 + + local __farr_vn_pl + + ( falist_length __farr_vn_pl "$@" && printf "%s" "${__farr_vn_pl}"; ) \ || printf "%s" "-1" } @@ -2150,28 +2315,40 @@ #: $1 (str): The name of the existing alist. #: falist_clear() { - local __farr_name - - local __farr_token __farr_objname __farr_keyname __farr_valname __farr_len - local __farr_idx __farr_del_value + # __farr_name $1 + + local __farr_name __farr_token __farr_objname __farr_bskeyname __farr_keyname __farr_valname __farr_llen __farr_bslen __farr_sptr_first __farr_sptr_last + local __farr_bsidx __farr_sptr __farr_sptr_next __farr_del_value __farr_del_key _farr_alist_get_meta "$@" - # Remove "storage" - __farr_idx=1 - while [ ${__farr_idx} -le ${__farr_len} ]; do - eval __farr_del_value=\"\$\{${__farr_valname}_${__farr_idx}\}\" + # Remove "storage" because the reference count is 0 + __farr_sptr="${__farr_sptr_first}" + while [ "${__farr_sptr}" -ne 0 ]; do + eval __farr_del_value=\"\$\{"${__farr_valname}"_"${__farr_sptr}"\}\" _farr_release_object "${__farr_del_value}" - # XXX FIXME: no arrays/alists as keys: should we check this - #eval __farr_del_value=\"\$\{${__farr_keyname}_${__farr_idx}\}\" - #_farr_release_object "${__farr_del_value}" - eval unset ${__farr_valname}_${__farr_idx} - eval unset ${__farr_keyname}_${__farr_idx} - __farr_idx=$((__farr_idx + 1)) + eval __farr_del_key=\"\$\{"${__farr_keyname}"_"${__farr_sptr}"\}\" + __farr_sptr_next="${__farr_del_key%%@*}" + __farr_sptr_next="${__farr_sptr_next#*;}" + #__farr_del_key="${__farr_del_key#*@}" + #_farr_release_object "${__farr_del_key}" + eval unset "${__farr_valname}"_"${__farr_sptr}" + eval unset "${__farr_keyname}"_"${__farr_sptr}" + __farr_sptr="${__farr_sptr_next}" + done + # Remove the binary search list + __farr_bsidx=1 + while [ "${__farr_bsidx}" -le "${__farr_bslen}" ]; do + eval unset "${__farr_bskeyname}"_"${__farr_bsidx}" + __farr_bsidx=$((__farr_bsidx + 1)) done # Reset object (length) itself - eval ${__farr_objname}__=0 + setvar "${__farr_objname}__" 0 + # Reset binary search list length + setvar "${__farr_objname}_B" 0 + # Reset storage pointer + setvar "${__farr_objname}_P" '0;0' return 0 } @@ -2192,8 +2369,9 @@ falist_release() { local __farr_name - local __farr_token __farr_objname __farr_keyname __farr_valname __farr_len - local __farr_idx __farr_del_value + local __farr_token __farr_objname __farr_bskeyname __farr_keyname __farr_valname __farr_llen __farr_bslen __farr_sptr_first __farr_sptr_last + local __farr_sptr __farr_del_value __farr_del_key + local __farr_sptr_next __farr_bsidx local __farr_refcnt _farr_alist_tryget_meta "$@" || return 1 @@ -2203,34 +2381,202 @@ # Note that the existence of the reference count proper is already # checked by `_farr_alist_tryget_meta`. # - eval __farr_refcnt=\$\{${__farr_objname}_C\} + eval __farr_refcnt=\"\$\{"${__farr_objname}"_C\}\" __farr_refcnt=$((__farr_refcnt - 1)) - eval ${__farr_objname}_C=\$\{__farr_refcnt\} + setvar "${__farr_objname}_C" "${__farr_refcnt}" if [ "${__farr_refcnt}" -ne 0 ] ; then # Clean out the alist name from the token always - [ -n "${__farr_name}" ] && eval "${__farr_name}=''" + [ -n "${__farr_name}" ] && setvar "${__farr_name}" '' return 0 fi # Remove "storage" because the reference count is 0 - __farr_idx=1 - while [ ${__farr_idx} -le ${__farr_len} ]; do - eval __farr_del_value=\"\$\{${__farr_valname}_${__farr_idx}\}\" + __farr_sptr="${__farr_sptr_first}" + while [ "${__farr_sptr}" -ne 0 ]; do + eval __farr_del_value=\"\$\{"${__farr_valname}"_"${__farr_sptr}"\}\" _farr_release_object "${__farr_del_value}" - # XXX FIXME: no arrays/alists as keys: should we check this - #eval __farr_del_value=\"\$\{${__farr_keyname}_${__farr_idx}\}\" - #_farr_release_object "${__farr_del_value}" - eval unset ${__farr_valname}_${__farr_idx} - eval unset ${__farr_keyname}_${__farr_idx} - __farr_idx=$((__farr_idx + 1)) + eval __farr_del_key=\"\$\{"${__farr_keyname}"_"${__farr_sptr}"\}\" + __farr_sptr_next="${__farr_del_key%%@*}" + __farr_sptr_next="${__farr_sptr_next#*;}" + #__farr_del_key="${__farr_del_key#*@}" + #_farr_release_object "${__farr_del_key}" + eval unset "${__farr_valname}"_"${__farr_sptr}" + eval unset "${__farr_keyname}"_"${__farr_sptr}" + __farr_sptr="${__farr_sptr_next}" + done + # Remove the binary search list + __farr_bsidx=1 + while [ "${__farr_bsidx}" -le "${__farr_bslen}" ]; do + eval unset "${__farr_bskeyname}"_"${__farr_bsidx}" + __farr_bsidx=$((__farr_bsidx + 1)) done # Remove object (length) itself ... - eval unset ${__farr_objname}__ + eval unset "${__farr_objname}__" # ... and its reference count - eval unset ${__farr_objname}_C + eval unset "${__farr_objname}_C" + # ... and its storage pointers + eval unset "${__farr_objname}_P" + # ... and its binsearch length + eval unset "${__farr_objname}_B" # Clean out the alist name from the token - [ -n "${__farr_name}" ] && eval "${__farr_name}=''" + [ -n "${__farr_name}" ] && setvar "${__farr_name}" '' + return 0 +} + + +#: +#: Do an exact binary search in the ordered key list. +#: +#: Comparisons are lexicographic. It skips tombstones. +#: +#: Args: +#: $1 (str): The name of a variable where to store a index into the +#: binary search list. This is either the index of the found +#: key (real or tombstone) or the index where the key should be +#: inserted into if `$2` is given as ``i`` (to inserted) or ``a`` +#: (to be appended). It may be the `null` value. +#: $2 (str): The "pointer" to the storage where the key and the value live. +#: These are "double-linked" lists and preserve the insertion +#: order. This variable is only set if `$2` is either ``V`` or +#: ``i``. It may be the `null` value +#: $3 (str): The key that is to be searched to. +#: +#: Input (Globals): +#: __farr_bskeyname (str): The prefix of the binary search list +#: __farr_bslen (int): The current length of the binary search list +#: (including valid items *and* tombstones). +#: +_farr_alist_bsearch() { + local __farr_varname_bsidx __farr_varname_sptr __farr_searched_value + + local __farr_lo __farr_hi __farr_mid __farr_mid_value + local __farr_bs_sptr __farr_bs_stype + + __farr_varname_bsidx="$1" + __farr_varname_sptr="$2" + __farr_searched_value="$3" + + __farr_lo=1 + __farr_hi="${__farr_bslen}" + while [ "${__farr_lo}" -le "${__farr_hi}" ]; do + __farr_mid=$((__farr_lo + ((__farr_hi-__farr_lo)/2) )) + eval __farr_mid_value=\"\$\{"${__farr_bskeyname}"_"${__farr_mid}"\}\" + # @ + __farr_bs_sptr="${__farr_mid_value%%@*}" + __farr_bs_stype="${__farr_bs_sptr#*;}" + __farr_bs_sptr="${__farr_bs_sptr%;*}" + __farr_mid_value="${__farr_mid_value#*@}" + if [ "${__farr_searched_value}" '<' "${__farr_mid_value}" ]; then + __farr_hi=$((__farr_mid - 1)) + elif [ "${__farr_searched_value}" '>' "${__farr_mid_value}" ]; then + __farr_lo=$((__farr_mid + 1)) + else + # if it is a tombstone it is not found + [ "${__farr_bs_stype}" = 'D' ] && return 1 + # yes it is not a tombstone + [ -n "${__farr_varname_bsidx}" ] && setvar "${__farr_varname_bsidx}" "${__farr_mid}" + [ -n "${__farr_varname_sptr}" ] && setvar "${__farr_varname_sptr}" "${__farr_bs_sptr}" + return 0 + fi + done + return 1 # not found +} + + +#: +#: Do a "leftmost" binary search in the ordered key list. +#: +#: Comparisons are lexicographic. +#: +#: Args: +#: $1 (str): The name of a variable where to store a index into the +#: binary search list. This is either the index of the found +#: key (real or tombstone) or the index where the key should be +#: inserted into if `$2` is given as ``i`` (to inserted) or ``a`` +#: (to be appended). +#: $2 (str): The name of a variable where to store the storage type into. +#: This is a single character value and can be one of ``V`` +#: (found an active real value), ``D`` (deleted value, this is +#: a tombstone), ``i`` (the value is not found) or +#: ``a`` (the value is not found and would be appended at the end). +#: $3 (str): The "pointer" to the storage where the key and the value live. +#: These are "double-linked" lists and preserve the insertion +#: order. This variable is only set if `$2` is either ``V`` or +#: ``i``. +#: $4 (str): The key that is to be searched to. +#: +#: Input (Globals): +#: __farr_bskeyname (str): The prefix of the binary search list +#: __farr_bslen (int): The current length of the binary search list +#: (including valid items *and* tombstones). +#: +_farr_alist_bsearch_lm() { + local __farr_varname_bsidx __farr_varname_stype __farr_varname_sptr + local __farr_searched_value + + local __farr_lo __farr_hi __farr_mid __farr_mid_value + local __farr_bs_sptr __farr_bs_stype + + __farr_varname_bsidx="$1" + __farr_varname_stype="$2" + __farr_varname_sptr="$3" + __farr_searched_value="$4" + + __farr_lo=1 + __farr_hi=$((__farr_bslen + 1)) + while [ "${__farr_lo}" -lt "${__farr_hi}" ]; do + __farr_mid=$((__farr_lo + ((__farr_hi-__farr_lo)/2) )) + eval __farr_mid_value=\"\$\{"${__farr_bskeyname}"_"${__farr_mid}"\}\" + # @ + __farr_mid_value="${__farr_mid_value#*@}" + if [ "${__farr_searched_value}" '>' "${__farr_mid_value}" ]; then + __farr_lo=$((__farr_mid + 1)) + else + __farr_hi=${__farr_mid} + fi + done + setvar "${__farr_varname_bsidx}" "${__farr_lo}" + if [ "${__farr_lo}" -gt "${__farr_bslen}" ]; then + # no slot (neither a value or a tombstone) found: append + setvar "${__farr_varname_stype}" a + setvar "${__farr_varname_sptr}" '' + else + # Reuse variable name + eval __farr_mid_value=\"\$\{"${__farr_bskeyname}"_"${__farr_lo}"\}\" + __farr_bs_sptr="${__farr_mid_value%%@*}" + __farr_bs_stype="${__farr_bs_sptr#*;}" + __farr_bs_sptr="${__farr_bs_sptr%;*}" + __farr_mid_value="${__farr_mid_value#*@}" + if [ "${__farr_searched_value}" = "${__farr_mid_value}" ]; then + # + # Key is already there: + # Analyze whether it is a real value or tombstone + # + case "${__farr_bs_stype}" in + V) + # real value + # tombstone/deleted: reuse all slots + setvar "${__farr_varname_stype}" "${__farr_bs_stype}" + setvar "${__farr_varname_sptr}" "${__farr_bs_sptr}" + ;; + D) + # tombstone/deleted: reuse search list slot, new storage ptr + setvar "${__farr_varname_stype}" "${__farr_bs_stype}" + setvar "${__farr_varname_sptr}" '' + ;; + *) + # unknown + _farr_fatal "unexpected low-level storage type in binsearch list: ${__farr_bs_stype}" + ;; + esac + else + # insert at __farr_lo, need new storage pointer + setvar "${__farr_varname_stype}" i + setvar "${__farr_varname_sptr}" "${__farr_bs_sptr}" + fi + fi + return 0 } @@ -2248,13 +2594,15 @@ #: or if an internal inconsistency will be detected. #: falist_set() { - local __farr_name __farr_key __farr_value - - local __farr_token __farr_objname __farr_keyname __farr_valname __farr_len - local __farr_idx __farr_elkey - local __farr_old_value - - _farr_alist_get_meta "$@" + # __farr_name $1 + local __farr_key __farr_value # ... + + local __farr_name __farr_token __farr_objname __farr_bskeyname __farr_keyname __farr_valname __farr_llen __farr_bslen __farr_sptr_first __farr_sptr_last + local __farr_bsidx __farr_tmp_bsidx + local __farr_tmp_value __farr_tmp_key __farr_prev_key + local __farr_stype __farr_sptr __farr_prev_sptr __farr_tmp_prev_sptr + + _farr_alist_get_meta "${1-}" shift while [ $# -ne 0 ]; do @@ -2263,35 +2611,116 @@ [ $# -lt 2 ] && _farr_fatal "missing value" __farr_value="$2" - __farr_idx=1 - while [ ${__farr_idx} -le ${__farr_len} ]; do - eval __farr_elkey=\"\$\{${__farr_keyname}_${__farr_idx}+SET\}\" - if [ -n "${__farr_elkey}" ]; then - eval __farr_elkey=\"\$\{${__farr_keyname}_${__farr_idx}\}\" - if [ "${__farr_elkey}" = "${__farr_key}" ]; then - eval __farr_old_value=\"\$\{${__farr_valname}_${__farr_idx}\}\" - _farr_release_object "${__farr_old_value}" - _farr_acquire_object "${__farr_value}" - eval ${__farr_valname}_${__farr_idx}=\"\$\{__farr_value\}\" - return 0 + _farr_alist_bsearch_lm __farr_bsidx __farr_stype __farr_sptr "${__farr_key}" + case "${__farr_stype}" in + V) + # + # Just replace the value at the found position. + # Key needs no replacement/change. + # + eval __farr_tmp_value=\"\$\{"${__farr_valname}"_"${__farr_sptr}"\}\" + _farr_release_object "${__farr_tmp_value}" + _farr_acquire_object "${__farr_value}" + setvar "${__farr_valname}_${__farr_sptr}" "${__farr_value}" + ;; + D) + __farr_sptr=$((__farr_sptr_last + 1)) + setvar "${__farr_bskeyname}_${__farr_bsidx}" "${__farr_sptr};V@${__farr_key}" + + if [ "${__farr_sptr_first}" -eq 0 ]; then + __farr_sptr_first="${__farr_sptr}" + fi + __farr_prev_sptr="${__farr_sptr_last}" # may be 0 + __farr_sptr_last="${__farr_sptr}" + # append key at the end of storage + setvar "${__farr_keyname}_${__farr_sptr}" "${__farr_prev_sptr};0@${__farr_key}" + # store the value + _farr_acquire_object "${__farr_value}" + setvar "${__farr_valname}_${__farr_sptr}" "${__farr_value}" + # increase logical length + __farr_llen=$((__farr_llen + 1)) + setvar "${__farr_objname}__" "${__farr_llen}" + setvar "${__farr_objname}_P" "${__farr_sptr_first};${__farr_sptr_last}" + # no change of bs search list length + + # adjust "next ptr" of previous last item -- if any + if [ "${__farr_prev_sptr}" -ne 0 ]; then + eval __farr_prev_key=\"\$\{"${__farr_keyname}"_"${__farr_prev_sptr}"\}\" + __farr_tmp_prev_sptr="${__farr_prev_key%%@*}" + __farr_tmp_prev_sptr="${__farr_tmp_prev_sptr%;*}" + setvar "${__farr_keyname}_${__farr_prev_sptr}" "${__farr_tmp_prev_sptr};${__farr_sptr}@${__farr_prev_key#*@}" + fi + ;; + i) + __farr_sptr=$((__farr_sptr_last + 1)) + if [ "${__farr_sptr_first}" -eq 0 ]; then + __farr_sptr_first="${__farr_sptr}" fi - else - _farr_fatal "key unexpectedly unset (index ${__farr_idx})" - fi - __farr_idx=$((__farr_idx + 1)) - done - # - # Not yet found: "append" ... - # - # NOTE: __farr_idx here already is the new correct length - # - __farr_len=${__farr_idx} - # ... the key/value pairs to storage - eval ${__farr_keyname}_${__farr_len}=\"\$\{__farr_key\}\" - _farr_acquire_object "${__farr_value}" - eval ${__farr_valname}_${__farr_len}=\"\$\{__farr_value\}\" - # ... the new length to the storage - eval ${__farr_objname}__=${__farr_len} + __farr_prev_sptr="${__farr_sptr_last}" # may be 0 + __farr_sptr_last="${__farr_sptr}" + # + # Make room and move existing binsearch list entries and + # insert the key into the binary search list + # + __farr_tmp_bsidx="${__farr_bslen}" + while [ "${__farr_tmp_bsidx}" -ge "${__farr_bsidx}" ]; do + eval __farr_tmp_key=\"\$\{"${__farr_bskeyname}"_"${__farr_tmp_bsidx}"\}\" + setvar "${__farr_bskeyname}_$((__farr_tmp_bsidx + 1))" "${__farr_tmp_key}" + __farr_tmp_bsidx=$((__farr_tmp_bsidx - 1)) + done + setvar "${__farr_bskeyname}_${__farr_bsidx}" "${__farr_sptr};V@${__farr_key}" + # append key at the end of storage + setvar "${__farr_keyname}_${__farr_sptr}" "${__farr_prev_sptr};0@${__farr_key}" + # store the value + _farr_acquire_object "${__farr_value}" + setvar "${__farr_valname}_${__farr_sptr}" "${__farr_value}" + # increase logical length + __farr_llen=$((__farr_llen + 1)) + setvar "${__farr_objname}__" "${__farr_llen}" + __farr_bslen=$((__farr_bslen + 1)) + setvar "${__farr_objname}_B" "${__farr_bslen}" + setvar "${__farr_objname}_P" "${__farr_sptr_first};${__farr_sptr_last}" + # adjust "next ptr" of previous last item -- if any + if [ "${__farr_prev_sptr}" -ne 0 ]; then + eval __farr_prev_key=\"\$\{"${__farr_keyname}"_"${__farr_prev_sptr}"\}\" + __farr_tmp_prev_sptr="${__farr_prev_key%%@*}" + __farr_tmp_prev_sptr="${__farr_tmp_prev_sptr%;*}" + setvar "${__farr_keyname}_${__farr_prev_sptr}" "${__farr_tmp_prev_sptr};${__farr_sptr}@${__farr_prev_key#*@}" + fi + ;; + a) + __farr_sptr=$((__farr_sptr_last + 1)) + setvar "${__farr_bskeyname}_$((__farr_bslen + 1))" "${__farr_sptr};V@${__farr_key}" + + if [ "${__farr_sptr_first}" -eq 0 ]; then + __farr_sptr_first="${__farr_sptr}" + fi + __farr_prev_sptr="${__farr_sptr_last}" # may be 0 + __farr_sptr_last="${__farr_sptr}" + # append key at the end of storage + setvar "${__farr_keyname}_${__farr_sptr}" "${__farr_prev_sptr};0@${__farr_key}" + # store the value + _farr_acquire_object "${__farr_value}" + setvar "${__farr_valname}_${__farr_sptr}" "${__farr_value}" + # increase logical length + __farr_llen=$((__farr_llen + 1)) + setvar "${__farr_objname}__" "${__farr_llen}" + __farr_bslen=$((__farr_bslen + 1)) + setvar "${__farr_objname}_B" "${__farr_bslen}" + setvar "${__farr_objname}_P" "${__farr_sptr_first};${__farr_sptr_last}" + # adjust "next ptr" of previous last item -- if any + if [ "${__farr_prev_sptr}" -ne 0 ]; then + eval __farr_prev_key=\"\$\{"${__farr_keyname}"_"${__farr_prev_sptr}"\}\" + __farr_tmp_key="${__farr_prev_key#*@}" + __farr_tmp_prev_sptr="${__farr_prev_key%%@*}" + __farr_tmp_prev_sptr="${__farr_tmp_prev_sptr%;*}" + setvar "${__farr_keyname}_${__farr_prev_sptr}" "${__farr_tmp_prev_sptr};${__farr_sptr}@${__farr_tmp_key}" + fi + ;; + *) + _farr_fatal "unexpected storage type: ${__farr_stype}" + ;; + esac shift 2 done return 0 @@ -2312,11 +2741,13 @@ #: or if an internal inconsistency will be detected. #: falist_add() { - local __farr_name __farr_key __farr_value - - local __farr_token __farr_objname __farr_keyname __farr_valname __farr_len - - _farr_alist_get_meta "$@" + # __farr_name $1 + local __farr_key __farr_value # ... + + local __farr_name __farr_token __farr_objname __farr_bskeyname __farr_keyname __farr_valname __farr_llen __farr_bslen __farr_sptr_first __farr_sptr_last + local __farr_prev_key __farr_sptr __farr_tmp_key __farr_tmp_prev_sptr __farr_prev_sptr + + _farr_alist_get_meta "${1-}" shift while [ $# -ne 0 ]; do @@ -2328,13 +2759,49 @@ # # We do just append ... # - __farr_len=$((__farr_len + 1)) - # ... the key/value pairs to storage - eval ${__farr_keyname}_${__farr_len}=\"\$\{__farr_key\}\" + + # + # But check first for proper key ordering + # This relies on the fact that deleting/tombstoning + # (see `falist_trydel`) removes all trailing tombstoned entries. + # + if [ "${__farr_bslen}" -gt 0 ]; then + eval __farr_prev_key=\"\$\{"${__farr_bskeyname}"_"${__farr_bslen}"\}\" + __farr_prev_key="${__farr_prev_key#*@}" + if [ \( "${__farr_key}" = "${__farr_prev_key}" \) -o \( "${__farr_key}" '<' "${__farr_prev_key}" \) ] ; then + _farr_fatal "falist_add() would violate key order" + fi + fi + + # now add as in falist_set() + __farr_sptr=$((__farr_sptr_last + 1)) + setvar "${__farr_bskeyname}_$((__farr_bslen + 1))" "${__farr_sptr};V@${__farr_key}" + + if [ "${__farr_sptr_first}" -eq 0 ]; then + __farr_sptr_first="${__farr_sptr}" + fi + __farr_prev_sptr="${__farr_sptr_last}" # may be 0 + __farr_sptr_last="${__farr_sptr}" + # append key at the end of storage + setvar "${__farr_keyname}_${__farr_sptr}" "${__farr_prev_sptr};0@${__farr_key}" + # store the value _farr_acquire_object "${__farr_value}" - eval ${__farr_valname}_${__farr_len}=\"\$\{__farr_value\}\" - # ... the new length to the storage - eval ${__farr_objname}__=${__farr_len} + setvar "${__farr_valname}_${__farr_sptr}" "${__farr_value}" + # increase logical length + __farr_llen=$((__farr_llen + 1)) + setvar "${__farr_objname}__" "${__farr_llen}" + __farr_bslen=$((__farr_bslen + 1)) + setvar "${__farr_objname}_B" "${__farr_bslen}" + setvar "${__farr_objname}_P" "${__farr_sptr_first};${__farr_sptr_last}" + # adjust "next ptr" of previous last item -- if any + if [ "${__farr_prev_sptr}" -ne 0 ]; then + eval __farr_prev_key=\"\$\{"${__farr_keyname}"_"${__farr_prev_sptr}"\}\" + __farr_tmp_key="${__farr_prev_key#*@}" + __farr_tmp_prev_sptr="${__farr_prev_key%%@*}" + __farr_tmp_prev_sptr="${__farr_tmp_prev_sptr%;*}" + setvar "${__farr_keyname}_${__farr_prev_sptr}" "${__farr_tmp_prev_sptr};${__farr_sptr}@${__farr_tmp_key}" + fi + shift 2 done return 0 @@ -2359,12 +2826,15 @@ #: or if an internal inconsistency will be detected. #: falist_set_unique() { - local __farr_name __farr_key __farr_value - - local __farr_token __farr_objname __farr_keyname __farr_valname __farr_len - local __farr_idx __farr_elkey - - _farr_alist_get_meta "$@" + # __farr_name $1 + local __farr_key __farr_value # ... + + local __farr_name __farr_token __farr_objname __farr_bskeyname __farr_keyname __farr_valname __farr_llen __farr_bslen __farr_sptr_first __farr_sptr_last + local __farr_bsidx __farr_tmp_bsidx + local __farr_tmp_value __farr_tmp_key __farr_prev_key + local __farr_stype __farr_sptr __farr_prev_sptr __farr_tmp_prev_sptr + + _farr_alist_get_meta "${1-}" shift while [ $# -ne 0 ]; do @@ -2373,32 +2843,112 @@ [ $# -lt 2 ] && _farr_fatal "missing value" __farr_value="$2" - __farr_idx=1 - while [ ${__farr_idx} -le ${__farr_len} ]; do - eval __farr_elkey=\"\$\{${__farr_keyname}_${__farr_idx}+SET\}\" - if [ -n "${__farr_elkey}" ]; then - eval __farr_elkey=\"\$\{${__farr_keyname}_${__farr_idx}\}\" - if [ "${__farr_elkey}" = "${__farr_key}" ]; then - # key is already set: would be non-unique - return 1 + _farr_alist_bsearch_lm __farr_bsidx __farr_stype __farr_sptr "${__farr_key}" + case "${__farr_stype}" in + V) + # key is already set: would be non-unique + return 1 + ;; + D) + __farr_sptr=$((__farr_sptr_last + 1)) + setvar "${__farr_bskeyname}_${__farr_bsidx}" "${__farr_sptr};V@${__farr_key}" + + if [ "${__farr_sptr_first}" -eq 0 ]; then + __farr_sptr_first="${__farr_sptr}" + fi + __farr_prev_sptr="${__farr_sptr_last}" # may be 0 + __farr_sptr_last="${__farr_sptr}" + # append key at the end of storage + setvar "${__farr_keyname}_${__farr_sptr}" "${__farr_prev_sptr};0@${__farr_key}" + # store the value + _farr_acquire_object "${__farr_value}" + setvar "${__farr_valname}_${__farr_sptr}" "${__farr_value}" + # increase logical length + __farr_llen=$((__farr_llen + 1)) + setvar "${__farr_objname}__" "${__farr_llen}" + setvar "${__farr_objname}_P" "${__farr_sptr_first};${__farr_sptr_last}" + # no change of bs search list length + + # adjust "next ptr" of previous last item -- if any + if [ "${__farr_prev_sptr}" -ne 0 ]; then + eval __farr_prev_key=\"\$\{"${__farr_keyname}"_"${__farr_prev_sptr}"\}\" + __farr_tmp_key="${__farr_prev_key#*@}" + __farr_tmp_prev_sptr="${__farr_prev_key%%@*}" + __farr_tmp_prev_sptr="${__farr_tmp_prev_sptr%;*}" + setvar "${__farr_keyname}_${__farr_prev_sptr}" "${__farr_tmp_prev_sptr};${__farr_sptr}@${__farr_tmp_key}" + fi + ;; + i) + __farr_sptr=$((__farr_sptr_last + 1)) + if [ "${__farr_sptr_first}" -eq 0 ]; then + __farr_sptr_first="${__farr_sptr}" fi - else - _farr_fatal "key unexpectedly unset (index ${__farr_idx})" - fi - __farr_idx=$((__farr_idx + 1)) - done - # - # Not yet found: "append" .. - # - # NOTE: __farr_idx here already is the new correct length - # - __farr_len=${__farr_idx} - # ... the key/value pairs to storage - eval ${__farr_keyname}_${__farr_len}=\"\$\{__farr_key\}\" - _farr_acquire_object "${__farr_value}" - eval ${__farr_valname}_${__farr_len}=\"\$\{__farr_value\}\" - # ... the new length - eval ${__farr_objname}__=${__farr_len} + __farr_prev_sptr="${__farr_sptr_last}" # may be 0 + __farr_sptr_last="${__farr_sptr}" + # + # Make room and move existing binsearch list entries and + # insert the key into the binary search list + # + __farr_tmp_bsidx="${__farr_bslen}" + while [ "${__farr_tmp_bsidx}" -ge "${__farr_bsidx}" ]; do + eval __farr_tmp_key=\"\$\{"${__farr_bskeyname}"_"${__farr_tmp_bsidx}"\}\" + setvar "${__farr_bskeyname}_$((__farr_tmp_bsidx + 1))" "${__farr_tmp_key}" + __farr_tmp_bsidx=$((__farr_tmp_bsidx - 1)) + done + setvar "${__farr_bskeyname}_${__farr_bsidx}" "${__farr_sptr};V@${__farr_key}" + # append key at the end of storage + setvar "${__farr_keyname}_${__farr_sptr}" "${__farr_prev_sptr};0@${__farr_key}" + # store the value + _farr_acquire_object "${__farr_value}" + setvar "${__farr_valname}_${__farr_sptr}" "${__farr_value}" + # increase logical length + __farr_llen=$((__farr_llen + 1)) + setvar "${__farr_objname}__" "${__farr_llen}" + __farr_bslen=$((__farr_bslen + 1)) + setvar "${__farr_objname}_B" "${__farr_bslen}" + setvar "${__farr_objname}_P" "${__farr_sptr_first};${__farr_sptr_last}" + # adjust "next ptr" of previous last item -- if any + if [ "${__farr_prev_sptr}" -ne 0 ]; then + eval __farr_prev_key=\"\$\{"${__farr_keyname}"_"${__farr_prev_sptr}"\}\" + __farr_tmp_key="${__farr_prev_key#*@}" + __farr_tmp_prev_sptr="${__farr_prev_key%%@*}" + __farr_tmp_prev_sptr="${__farr_tmp_prev_sptr%;*}" + setvar "${__farr_keyname}_${__farr_prev_sptr}" "${__farr_tmp_prev_sptr};${__farr_sptr}@${__farr_tmp_key}" + fi + ;; + a) + __farr_sptr=$((__farr_sptr_last + 1)) + setvar "${__farr_bskeyname}_$((__farr_bslen + 1))" "${__farr_sptr};V@${__farr_key}" + + if [ "${__farr_sptr_first}" -eq 0 ]; then + __farr_sptr_first="${__farr_sptr}" + fi + __farr_prev_sptr="${__farr_sptr_last}" # may be 0 + __farr_sptr_last="${__farr_sptr}" + # append key at the end of storage + setvar "${__farr_keyname}_${__farr_sptr}" "${__farr_prev_sptr};0@${__farr_key}" + # store the value + _farr_acquire_object "${__farr_value}" + setvar "${__farr_valname}_${__farr_sptr}" "${__farr_value}" + # increase logical length + __farr_llen=$((__farr_llen + 1)) + setvar "${__farr_objname}__" "${__farr_llen}" + __farr_bslen=$((__farr_bslen + 1)) + setvar "${__farr_objname}_B" "${__farr_bslen}" + setvar "${__farr_objname}_P" "${__farr_sptr_first};${__farr_sptr_last}" + # adjust "next ptr" of previous last item -- if any + if [ "${__farr_prev_sptr}" -ne 0 ]; then + eval __farr_prev_key=\"\$\{"${__farr_keyname}"_"${__farr_prev_sptr}"\}\" + __farr_tmp_key="${__farr_prev_key#*@}" + __farr_tmp_prev_sptr="${__farr_prev_key%%@*}" + __farr_tmp_prev_sptr="${__farr_tmp_prev_sptr%;*}" + setvar "${__farr_keyname}_${__farr_prev_sptr}" "${__farr_tmp_prev_sptr};${__farr_sptr}@${__farr_tmp_key}" + fi + ;; + *) + _farr_fatal "unexpected storage type: ${__farr_stype}" + ;; + esac shift 2 done return 0 @@ -2408,157 +2958,159 @@ #: #: Update an alist from another alist. #: -#: Key-values from the other alist are given precedence. +#: Key-values from the right alist are given precedence and may overwrite +#: existing items on the left. But the left array has precedence with regard +#: to insertion order. #: #: Args: -#: $1 (str): The name of the alist that is to be updated. -#: $2 (str): The name of the other alist. +#: $1 (str): The ("left") alist that is to be updated from `$2`. +#: $2 (str): The ("right") alist #: falist_update() { - - local __farr_l_name __farr_r_name - - local __farr_l_token __farr_l_objname __farr_l_keyname __farr_l_valname __farr_l_len - local __farr_r_token __farr_r_objname __farr_r_keyname __farr_r_valname __farr_r_len - - local __farr_name __farr_token __farr_objname __farr_keyname __farr_valname __farr_len - local __farr_l_idx __farr_r_idx __farr_r_key __farr_l_key __farr_value + local __farr_name __farr_r_name + + local __farr_token __farr_objname __farr_bskeyname __farr_keyname __farr_valname __farr_llen __farr_bslen __farr_sptr_first __farr_sptr_last + local __farr_r_token __farr_r_objname __farr_r_bskeyname __farr_r_keyname __farr_r_valname __farr_r_llen __farr_r_bslen __farr_r_sptr_first __farr_r_sptr_last + local __farr_bsidx __farr_stype __farr_sptr __farr_tmp_bsidx + local __farr_key __farr_value + local __farr_r_key __farr_r_sptr __farr_r_sptr_next __farr_r_value + local __farr_prev_sptr __farr_prev_key + local __farr_tmp_prev_sptr [ $# -ne 2 ] && _farr_fatal "exactly two arrays must be given" - _farr_alist_get_meta "$1" - __farr_l_name="${__farr_name}" - __farr_l_token="${__farr_token}" - __farr_l_objname="${__farr_objname}" - __farr_l_keyname="${__farr_keyname}" - __farr_l_valname="${__farr_valname}" - __farr_l_len="${__farr_len}" _farr_alist_get_meta "$2" __farr_r_name="${__farr_name}" __farr_r_token="${__farr_token}" __farr_r_objname="${__farr_objname}" + __farr_r_bskeyname="${__farr_bskeyname}" __farr_r_keyname="${__farr_keyname}" __farr_r_valname="${__farr_valname}" - __farr_r_len="${__farr_len}" - - __farr_r_idx=1 - while [ ${__farr_r_idx} -le ${__farr_r_len} ]; do - eval __farr_r_key=\"\$\{${__farr_r_keyname}_${__farr_r_idx}\}\" - - __farr_l_idx=1 - while [ ${__farr_l_idx} -le ${__farr_l_len} ]; do - eval __farr_l_key=\"\$\{${__farr_l_keyname}_${__farr_l_idx}\}\" - if [ "${__farr_l_key}" = "${__farr_r_key}" ]; then - eval __farr_value=\"\$\{${__farr_r_valname}_${__farr_r_idx}\}\" - _farr_acquire_object "${__farr_value}" - eval ${__farr_l_valname}_${__farr_l_idx}=\"\$\{__farr_value\}\" - break - fi - __farr_l_idx=$((__farr_l_idx + 1)) - done - # - # If __farr_l_idx > __farr_l_len: Not yet found: "append" .. - # - # NOTE: __farr_l_idx is here also the new correct length - # - if [ ${__farr_l_idx} -eq $((__farr_l_len + 1)) ]; then - # ... the key/value pairs to storage - eval ${__farr_l_keyname}_${__farr_l_idx}=\"\$\{__farr_r_key\}\" - eval __farr_value=\"\$\{${__farr_r_valname}_${__farr_r_idx}\}\" - _farr_acquire_object "${__farr_value}" - eval ${__farr_l_valname}_${__farr_l_idx}=\"\$\{__farr_value\}\" - # ... the new length in storage - eval ${__farr_l_objname}__=${__farr_l_idx} - # ... and temporary here - __farr_l_len=${__farr_l_idx} - fi - __farr_r_idx=$((__farr_r_idx + 1)) - done - return 0 -} - - -#: -#: Merge two *sorted* alists using the mergesort algorithm. -#: -#: A "sorted" alist is an alist where all the key-value pairs are added -#: in a sorted fashion: `falist_keys()` yields a lexicographcally sorted -#: array. -#: -#: Sources: - https://de.wikipedia.org/wiki/Mergesort -#: - https://en.wikipedia.org/wiki/Merge_sort -#: -#: Args: -#: $1 (str): The result alist where all the sorted items will be added to -#: using `falist_add` -#: $2 (str): The first sorted input alist -#: $3 (str): The second sorted input alist -#: -falist_merge() { - local __farr_result __farr_input_1 __farr_input_2 - - local __farr_name __farr_token __farr_objname __farr_valname __farr_keyname __farr_len - local __farr_name_1 __farr_token_1 __farr_objname_1 __farr_valname_1 __farr_keyname_1 __farr_len_1 - local __farr_name_2 __farr_token_2 __farr_objname_2 __farr_valname_2 __farr_keyname_2 __farr_len_2 - local __farr_idx_1 __farr_key_1 __farr_val_2 - local __farr_idx_2 __farr_key_2 __farr_val_2 - - _farr_alist_get_meta "$2" - __farr_input_1="$2" - __farr_name_1="${__farr_name}" - __farr_token_1="${__farr_token}" - __farr_objname_1="${__farr_objname}" - __farr_keyname_1="${__farr_keyname}" - __farr_valname_1="${__farr_valname}" - __farr_len_1="${__farr_len}" - _farr_alist_get_meta "$3" - __farr_input_2="$3" - __farr_name_2="${__farr_name}" - __farr_token_2="${__farr_token}" - __farr_objname_2="${__farr_objname}" - __farr_keyname_2="${__farr_keyname}" - __farr_valname_2="${__farr_valname}" - __farr_len_2="${__farr_len}" + __farr_r_llen="${__farr_llen}" + __farr_r_bslen="${__farr_bslen}" + __farr_r_sptr_first="${__farr_sptr_first}" + __farr_r_sptr_last="${__farr_sptr_last}" _farr_alist_get_meta "$1" - __farr_result="$1" - - __farr_idx_1=1 - if [ "${__farr_idx_1}" -le "${__farr_len_1}" ]; then - eval __farr_key_1=\"\$\{${__farr_keyname_1}_${__farr_idx_1}\}\" - fi - __farr_idx_2=1 - if [ "${__farr_idx_2}" -le "${__farr_len_2}" ]; then - eval __farr_key_2=\"\$\{${__farr_keyname_2}_${__farr_idx_2}\}\" - fi - while [ \( "${__farr_idx_1}" -le "${__farr_len_1}" \) -a \( "${__farr_idx_2}" -le "${__farr_len_2}" \) ]; do - if [ \( "${__farr_key_1}" '<' "${__farr_key_2}" \) -o \( "${__farr_key_1}" '=' "${__farr_key_2}" \) ] ; then - eval __farr_val_1=\"\$\{${__farr_valname_1}_${__farr_idx_1}\}\" - # shellcheck disable=SC2154 # bogus __farr_val_1 is ref but not set - falist_add "${__farr_result}" "${__farr_key_1}" "${__farr_val_1}" - __farr_idx_1=$((__farr_idx_1 + 1)) - if [ "${__farr_idx_1}" -le "${__farr_len_1}" ]; then - eval __farr_key_1=\"\$\{${__farr_keyname_1}_${__farr_idx_1}\}\" - fi - else - eval __farr_val_2=\"\$\{${__farr_valname_2}_${__farr_idx_2}\}\" - falist_add "${__farr_result}" "${__farr_key_2}" "${__farr_val_2}" - __farr_idx_2=$((__farr_idx_2 + 1)) - if [ "${__farr_idx_2}" -le "${__farr_len_2}" ]; then - eval __farr_key_2=\"\$\{${__farr_keyname_2}_${__farr_idx_2}\}\" - fi - fi - done - # Only one of the two while-loops below will be entered - while [ "${__farr_idx_1}" -le "${__farr_len_1}" ]; do - eval __farr_key_1=\"\$\{${__farr_keyname_1}_${__farr_idx_1}\}\" - eval __farr_val_1=\"\$\{${__farr_valname_1}_${__farr_idx_1}\}\" - falist_add "${__farr_result}" "${__farr_key_1}" "${__farr_val_1}" - __farr_idx_1=$((__farr_idx_1 + 1)) - done - while [ "${__farr_idx_2}" -le "${__farr_len_2}" ]; do - eval __farr_key_2=\"\$\{${__farr_keyname_2}_${__farr_idx_2}\}\" - eval __farr_val_2=\"\$\{${__farr_valname_2}_${__farr_idx_2}\}\" - falist_add "${__farr_result}" "${__farr_key_2}" "${__farr_val_2}" - __farr_idx_2=$((__farr_idx_2 + 1)) + + __farr_r_sptr="${__farr_r_sptr_first}" + while [ "${__farr_r_sptr}" -ne 0 ] ; do + eval __farr_r_key=\"\$\{"${__farr_r_keyname}"_"${__farr_r_sptr}"\}\" + eval __farr_r_value=\"\$\{"${__farr_r_valname}"_"${__farr_r_sptr}"\}\" + __farr_r_sptr_next="${__farr_r_key%%@*}" + __farr_r_key="${__farr_r_key#*@}" + __farr_r_sptr_next="${__farr_r_sptr_next#*;}" + + _farr_alist_bsearch_lm __farr_bsidx __farr_stype __farr_sptr "${__farr_r_key}" + case "${__farr_stype}" in + V) + # + # Just replace the value at the found position. + # Key needs no replacement/change. + # + eval __farr_value=\"\$\{"${__farr_valname}"_"${__farr_sptr}"\}\" + _farr_release_object "${__farr_value}" + _farr_acquire_object "${__farr_r_value}" + setvar "${__farr_valname}_${__farr_sptr}" "${__farr_r_value}" + ;; + D) + __farr_sptr=$((__farr_sptr_last + 1)) + setvar "${__farr_bskeyname}_${__farr_bsidx}" "${__farr_sptr};V@${__farr_r_key}" + + if [ "${__farr_sptr_first}" -eq 0 ]; then + __farr_sptr_first="${__farr_sptr}" + fi + __farr_prev_sptr="${__farr_sptr_last}" # may be 0 + __farr_sptr_last="${__farr_sptr}" + # append key at the end of storage + setvar "${__farr_keyname}_${__farr_sptr}" "${__farr_prev_sptr};0@${__farr_r_key}" + # store the value + _farr_acquire_object "${__farr_r_value}" + setvar "${__farr_valname}_${__farr_sptr}" "${__farr_r_value}" + # increase logical length + __farr_llen=$((__farr_llen + 1)) + setvar "${__farr_objname}__" "${__farr_llen}" + setvar "${__farr_objname}_P" "${__farr_sptr_first};${__farr_sptr_last}" + # no change of bs search list length + + # adjust "next ptr" of previous last item -- if any + if [ "${__farr_prev_sptr}" -ne 0 ]; then + eval __farr_prev_key=\"\$\{"${__farr_keyname}"_"${__farr_prev_sptr}"\}\" + __farr_tmp_prev_sptr="${__farr_prev_key%%@*}" + __farr_tmp_prev_sptr="${__farr_tmp_prev_sptr%;*}" + setvar "${__farr_keyname}_${__farr_prev_sptr}" "${__farr_tmp_prev_sptr};${__farr_sptr}@${__farr_prev_key#*@}" + fi + ;; + i) + __farr_sptr=$((__farr_sptr_last + 1)) + if [ "${__farr_sptr_first}" -eq 0 ]; then + __farr_sptr_first="${__farr_sptr}" + fi + __farr_prev_sptr="${__farr_sptr_last}" # may be 0 + __farr_sptr_last="${__farr_sptr}" + # + # Make room and move existing binsearch list entries and + # insert the key into the binary search list + # + __farr_tmp_bsidx="${__farr_bslen}" + while [ "${__farr_tmp_bsidx}" -ge "${__farr_bsidx}" ]; do + eval __farr_key=\"\$\{"${__farr_bskeyname}"_"${__farr_tmp_bsidx}"\}\" + setvar "${__farr_bskeyname}_$((__farr_tmp_bsidx + 1))" "${__farr_key}" + __farr_tmp_bsidx=$((__farr_tmp_bsidx - 1)) + done + setvar "${__farr_bskeyname}_${__farr_bsidx}" "${__farr_sptr};V@${__farr_r_key}" + # append key at the end of storage + setvar "${__farr_keyname}_${__farr_sptr}" "${__farr_prev_sptr};0@${__farr_r_key}" + # store the value + _farr_acquire_object "${__farr_r_value}" + setvar "${__farr_valname}_${__farr_sptr}" "${__farr_r_value}" + # increase logical length + __farr_llen=$((__farr_llen + 1)) + setvar "${__farr_objname}__" "${__farr_llen}" + __farr_bslen=$((__farr_bslen + 1)) + setvar "${__farr_objname}_B" "${__farr_bslen}" + setvar "${__farr_objname}_P" "${__farr_sptr_first};${__farr_sptr_last}" + # adjust "next ptr" of previous last item -- if any + if [ "${__farr_prev_sptr}" -ne 0 ]; then + eval __farr_prev_key=\"\$\{"${__farr_keyname}"_"${__farr_prev_sptr}"\}\" + __farr_tmp_prev_sptr="${__farr_prev_key%%@*}" + __farr_tmp_prev_sptr="${__farr_tmp_prev_sptr%;*}" + setvar "${__farr_keyname}_${__farr_prev_sptr}" "${__farr_tmp_prev_sptr};${__farr_sptr}@${__farr_prev_key#*@}" + fi + ;; + a) + __farr_sptr=$((__farr_sptr_last + 1)) + setvar "${__farr_bskeyname}_$((__farr_bslen + 1))" "${__farr_sptr};V@${__farr_r_key}" + + if [ "${__farr_sptr_first}" -eq 0 ]; then + __farr_sptr_first="${__farr_sptr}" + fi + __farr_prev_sptr="${__farr_sptr_last}" # may be 0 + __farr_sptr_last="${__farr_sptr}" + # append key at the end of storage + setvar "${__farr_keyname}_${__farr_sptr}" "${__farr_prev_sptr};0@${__farr_r_key}" + # store the value + _farr_acquire_object "${__farr_r_value}" + setvar "${__farr_valname}_${__farr_sptr}" "${__farr_r_value}" + # increase logical length + __farr_llen=$((__farr_llen + 1)) + setvar "${__farr_objname}__" "${__farr_llen}" + __farr_bslen=$((__farr_bslen + 1)) + setvar "${__farr_objname}_B" "${__farr_bslen}" + setvar "${__farr_objname}_P" "${__farr_sptr_first};${__farr_sptr_last}" + # adjust "next ptr" of previous last item -- if any + if [ "${__farr_prev_sptr}" -ne 0 ]; then + eval __farr_prev_key=\"\$\{"${__farr_keyname}"_"${__farr_prev_sptr}"\}\" + __farr_tmp_prev_sptr="${__farr_prev_key%%@*}" + __farr_tmp_prev_sptr="${__farr_tmp_prev_sptr%;*}" + setvar "${__farr_keyname}_${__farr_prev_sptr}" "${__farr_tmp_prev_sptr};${__farr_sptr}@${__farr_prev_key#*@}" + fi + ;; + *) + _farr_fatal "unexpected storage type: ${__farr_stype}" + ;; + esac + + # Loop: move forward + __farr_r_sptr="${__farr_r_sptr_next}" done return 0 } @@ -2571,8 +3123,8 @@ #: $1 (str): The name of the variable to put the value into. #: $2 (str): The name of an existing alist. #: $3: The key. -#: $4 (str, optional): The name of a variable where to store the index -#: of the found item into. +#: $4 (str, optional): The name of a variable where to store a storage +#: cookie (an extended form of a storage pointer) into. #: #: Exit: #: - If the key is not found. @@ -2580,38 +3132,31 @@ #: or if an internal inconsistency will be detected. #: falist_get() { - local __farr_varname __farr_name __farr_key __farr_idx_varname - - local __farr_token __farr_objname __farr_keyname __farr_valname __farr_len - local __farr_idx __farr_getkey - local __farr_get_value + local __farr_varname __farr_name __farr_key __farr_varname_scookie + + local __farr_token __farr_objname __farr_bskeyname __farr_keyname __farr_valname __farr_llen __farr_bslen __farr_sptr_first __farr_sptr_last + local __farr_sptr __farr_get_key __farr_get_value __farr_varname="${1-}" [ -z "${__farr_varname}" ] && _farr_fatal "missing variable name" - shift - _farr_alist_get_meta "$@" - [ $# -lt 2 ] && _farr_fatal "missing key" - __farr_key="$2" - __farr_idx_varname="${3-}" - - __farr_idx=1 - while [ ${__farr_idx} -le ${__farr_len} ]; do - eval __farr_getkey=\"\$\{${__farr_keyname}_${__farr_idx}+SET\}\" - if [ -n "${__farr_getkey}" ]; then - eval __farr_getkey=\"\$\{${__farr_keyname}_${__farr_idx}\}\" - if [ "${__farr_getkey}" = "${__farr_key}" ]; then - eval __farr_get_value=\"\$\{${__farr_valname}_${__farr_idx}\}\" - _farr_acquire_object "${__farr_get_value}" - eval "${__farr_varname}"=\"\$\{__farr_get_value\}\" - [ -n "${__farr_idx_varname}" ] && eval "${__farr_idx_varname}"=${__farr_idx} - return 0 - fi - else - _farr_fatal "key unexpectedly unset (index ${__farr_idx})" + _farr_alist_get_meta "${2-}" + [ $# -lt 3 ] && _farr_fatal "missing key" + __farr_key="$3" + __farr_varname_scookie="${4-}" + + if _farr_alist_bsearch '' __farr_sptr "${__farr_key}" ; then + eval __farr_get_value=\"\$\{"${__farr_valname}"_"${__farr_sptr}"\}\" + _farr_acquire_object "${__farr_get_value}" + setvar "${__farr_varname}" "${__farr_get_value}" + if [ -n "${__farr_varname_scookie}" ] ; then + # need more infos for the cookie from the corresponding key entry + eval __farr_get_key=\"\$\{"${__farr_keyname}"_"${__farr_sptr}"\}\" + # storage cookie is /;@ + setvar "${__farr_varname_scookie}" "${__farr_sptr}/${__farr_get_key%%@*}@${__farr_token}" fi - __farr_idx=$((__farr_idx + 1)) - done - _farr_fatal "alist \`${__farr_name:-"${1}"}': key \`${__farr_key}' not found" + else + _farr_fatal "alist \`${__farr_name:-"${2}"}': key \`${__farr_key}' not found" + fi } @@ -2623,58 +3168,173 @@ #: $1 (str): The name of the variable where to put the value into. #: $2 (str): The name of an existing alist. #: $3: The key. -#: $4 (str, optional): The name of a variable where to store the index -#: of the found item into. +#: $4 (str, optional): The name of a variable where to store a storage +#: cookie (an extended form of a storage pointer) into. #: #: Returns: #: 0 (truthy) on success, #: 1 (falsy) if the given key is not found. #: falist_tryget() { - local __farr_varname __farr_name __farr_key __farr_idx_varname - - local __farr_token __farr_objname __farr_keyname __farr_valname __farr_len - local __farr_idx __farr_getkey - local __farr_get_value + local __farr_varname __farr_name __farr_key __farr_varname_scookie + + local __farr_token __farr_objname __farr_bskeyname __farr_keyname __farr_valname __farr_llen __farr_bslen __farr_sptr_first __farr_sptr_last + local __farr_sptr __farr_get_key __farr_get_value __farr_varname="${1-}" [ -z "${__farr_varname}" ] && _farr_fatal "missing variable name" - shift - _farr_alist_get_meta "$@" - [ $# -lt 2 ] && _farr_fatal "missing key" - __farr_key="$2" - __farr_idx_varname="${3-}" - - __farr_idx=1 - while [ ${__farr_idx} -le ${__farr_len} ]; do - eval __farr_getkey=\"\$\{${__farr_keyname}_${__farr_idx}+SET\}\" - if [ -n "${__farr_getkey}" ]; then - eval __farr_getkey=\"\$\{${__farr_keyname}_${__farr_idx}\}\" - if [ "${__farr_getkey}" = "${__farr_key}" ]; then - eval __farr_get_value=\"\$\{${__farr_valname}_${__farr_idx}\}\" - _farr_acquire_object "${__farr_get_value}" - eval "${__farr_varname}"=\"\$\{__farr_get_value\}\" - [ -n "${__farr_idx_varname}" ] && eval "${__farr_idx_varname}"=${__farr_idx} - return 0 - fi - else - _farr_fatal "key unexpectedly unset (index ${__farr_idx})" + _farr_alist_get_meta "${2-}" + [ $# -lt 3 ] && _farr_fatal "missing key" + __farr_key="$3" + __farr_varname_scookie="${4-}" + + if _farr_alist_bsearch '' __farr_sptr "${__farr_key}" ; then + eval __farr_get_value=\"\$\{"${__farr_valname}"_"${__farr_sptr}"\}\" + _farr_acquire_object "${__farr_get_value}" + setvar "${__farr_varname}" "${__farr_get_value}" + if [ -n "${__farr_varname_scookie}" ] ; then + # need more infos for the cookie from the corresponding key entry + eval __farr_get_key=\"\$\{"${__farr_keyname}"_"${__farr_sptr}"\}\" + # storage cookie is /;@ + setvar "${__farr_varname_scookie}" "${__farr_sptr}/${__farr_get_key%%@*}@${__farr_token}" fi - __farr_idx=$((__farr_idx + 1)) - done + return 0 + fi return 1 } #: -#: Try to get the key that is associated with a storage index and store it in a variable. -#: -#: Use this for iteration over values. +#: Get the cookie to the first item +#: +#: Args: +#: $1 (str): An existing alist. +#: +#: Output (stdout): +#: The cookie. Can be used in `falist_tryget_item_at` and friends. +#: +falist_cookie_first() { + local __farr_name + + local __farr_token __farr_objname __farr_bskeyname __farr_keyname __farr_valname __farr_llen __farr_bslen __farr_sptr_first __farr_sptr_last + local __farr_sptr __farr_sptr_next __farr_sptr_prev __farr_cf_key + + _farr_alist_get_meta "$@" + + __farr_sptr="${__farr_sptr_first}" + if [ "${__farr_sptr}" -eq 0 ]; then + printf '%s' "0/0;0@${__farr_token}" + else + eval __farr_cf_key=\"\$\{"${__farr_keyname}"_"${__farr_sptr}"\}\" + __farr_sptr_next="${__farr_cf_key%%@*}" + __farr_sptr_prev="${__farr_sptr_next%;*}" + __farr_sptr_next="${__farr_sptr_next#*;}" + printf '%s/%s;%s@%s' "${__farr_sptr}" "${__farr_sptr_prev}" "${__farr_sptr_next}" "${__farr_token}" + fi +} + + +#: +#: Get the cookie to the last item +#: +#: Args: +#: $1 (str): An existing alist. +#: +#: Output (stdout): +#: The cookie. Can be used in `falist_tryget_item_at` and friends. +#: +falist_cookie_last() { + local __farr_name + + local __farr_token __farr_objname __farr_bskeyname __farr_keyname __farr_valname __farr_llen __farr_bslen __farr_sptr_first __farr_sptr_last + local __farr_sptr __farr_sptr_next __farr_sptr_prev __farr_cf_key + + _farr_alist_get_meta "$@" + + __farr_sptr="${__farr_sptr_last}" + if [ "${__farr_sptr}" -eq 0 ]; then + printf '%s' "0/0;0@${__farr_token}" + else + eval __farr_cf_key=\"\$\{"${__farr_keyname}"_"${__farr_sptr}"\}\" + __farr_sptr_next="${__farr_cf_key%%@*}" + __farr_sptr_prev="${__farr_sptr_next%;*}" + __farr_sptr_next="${__farr_sptr_next#*;}" + printf '%s/%s;%s@%s' "${__farr_sptr}" "${__farr_sptr_prev}" "${__farr_sptr_next}" "${__farr_token}" + fi +} + + +#: +#: Get the next cookie from a given cookie +#: +#: Args: +#: $1 (str): The cookie value. +#: +#: Output (stdout): +#: The next cookie. +#: +falist_cookie_next() { + # __farr_scookie $1 + + local __farr_token __farr_objname __farr_keyname __farr_valname + local __farr_sptr __farr_sptr_prev __farr_sptr_next + local __farr_cn_key + + _farr_alist_tryparse_cookie "${1-}" || return 1 + + if [ "${__farr_sptr}" -eq 0 ] || [ "${__farr_sptr_next}" -eq 0 ] ; then + printf '%s' "0/0;0@${__farr_token}" + else + __farr_sptr="${__farr_sptr_next}" + eval __farr_cn_key=\"\$\{"${__farr_keyname}"_"${__farr_sptr}"\}\" + __farr_sptr_next="${__farr_cn_key%%@*}" + __farr_sptr_prev="${__farr_sptr_next%;*}" + __farr_sptr_next="${__farr_sptr_next#*;}" + printf '%s/%s;%s@%s' "${__farr_sptr}" "${__farr_sptr_prev}" "${__farr_sptr_next}" "${__farr_token}" + fi +} + + +#: +#: Get the previous cookie from a given cookie +#: +#: Args: +#: $1 (str): The cookie value. +#: +#: Output (stdout): +#: The previous cookie. +#: +falist_cookie_prev() { + # __farr_scookie $1 + + local __farr_token __farr_objname __farr_keyname __farr_valname + local __farr_sptr __farr_sptr_prev __farr_sptr_next + local __farr_cn_key + + _farr_alist_tryparse_cookie "${1-}" || return 1 + + if [ "${__farr_sptr}" -eq 0 ] || [ "${__farr_sptr_prev}" -eq 0 ] ; then + printf '%s' "0/0;0@${__farr_token}" + else + __farr_sptr="${__farr_sptr_prev}" + eval __farr_cn_key=\"\$\{"${__farr_keyname}"_"${__farr_sptr}"\}\" + __farr_sptr_next="${__farr_cn_key%%@*}" + __farr_sptr_prev="${__farr_sptr_next%;*}" + __farr_sptr_next="${__farr_sptr_next#*;}" + printf '%s/%s;%s@%s' "${__farr_sptr}" "${__farr_sptr_prev}" "${__farr_sptr_next}" "${__farr_token}" + fi +} + + +#: +#: Try to get the key that is associated with a storage cookie and +#: store it into a variable, +#: +#: Use this for iteration over keys. #: #: Args: #: $1 (str): The name of the variable where to put the key into. -#: $2 (str): The name of an existing alist. -#: $3 (int): The index. +#: $2 (str): The storage cookie. #: #: Returns: #: 0 (truthy) on success, @@ -2684,28 +3344,25 @@ #: Other errors (missing array name, missing index value) are considered #: fatal and call `_farr_fatal` (i.e. `exit`). #: -falist_tryget_key_at_index() { - local __farr_varname __farr_name __farr_index - - local __farr_token __farr_objname __farr_keyname __farr_valname __farr_len - local __farr_idx __farr_getikey - - __farr_varname=${1-} - [ -z "${__farr_varname}" ] && _farr_fatal "missing variable name" - shift - _farr_alist_get_meta "$@" - __farr_index="${2-}" - [ -z "${__farr_index}" ] && _farr_fatal "missing index" - _farr_make_index __farr_index "${__farr_index}" "${__farr_len}" - - if [ \( ${__farr_index} -ge 1 \) -a \( ${__farr_index} -le ${__farr_len} \) ]; then - eval __farr_getikey=\"\$\{${__farr_keyname}_${__farr_index}+SET\}\" +falist_tryget_key_at() { + local __farr_key_varname __farr_scookie + + local __farr_token __farr_objname __farr_keyname __farr_valname + local __farr_sptr __farr_sptr_prev __farr_sptr_next + local __farr_getikey __farr_getival + + __farr_key_varname=${1-} + [ -z "${__farr_key_varname}" ] && _farr_fatal "missing variable name for key" + _farr_alist_tryparse_cookie "${2-}" || return 1 + + if [ "${__farr_sptr}" -ne 0 ] ; then + eval __farr_getikey=\"\$\{"${__farr_keyname}"_"${__farr_sptr}"+SET\}\" if [ -n "${__farr_getikey}" ]; then - # no refcount change because no complex values allowed yet - eval "${__farr_varname}"=\"\$\{${__farr_keyname}_${__farr_index}\}\" + eval __farr_getikey=\"\$\{"${__farr_keyname}"_"${__farr_sptr}"\}\" + setvar "${__farr_key_varname}" "${__farr_getikey#*@}" return 0 else - _farr_fatal "key unexpectedly unset (index ${__farr_index})" + _farr_fatal "key unexpectedly unset (cookie ${__farr_scookie})" fi else return 1 @@ -2714,14 +3371,17 @@ #: -#: Try to get the value that is associated with a storage index and store it in a variable. -#: -#: Use this for iteration over keys. +#: Try to get the value that is associated with a storage cookie and +#: store it into a variable, +#: +#: Use this for iteration over values. +#: +#: Comples values need to be released when they are not used any more +#: (`farray_release`, `falist_release`). #: #: Args: #: $1 (str): The name of the variable where to put the value into. -#: $2 (str): The name of an existing alist. -#: $3 (int): The index. +#: $2 (str): The storage cookie. #: #: Returns: #: 0 (truthy) on success, @@ -2731,29 +3391,26 @@ #: Other errors (missing array name, missing index value) are considered #: fatal and call `_farr_fatal` (i.e. `exit`). #: -falist_tryget_value_at_index() { - local __farr_varname __farr_name __farr_index - - local __farr_token __farr_objname __farr_keyname __farr_valname __farr_len - local __farr_idx __farr_getival - - __farr_varname=${1-} - [ -z "${__farr_varname}" ] && _farr_fatal "missing variable name" - shift - _farr_alist_get_meta "$@" - __farr_index="${2-}" - [ -z "${__farr_index}" ] && _farr_fatal "missing index" - _farr_make_index __farr_index "${__farr_index}" "${__farr_len}" - - if [ \( ${__farr_index} -ge 1 \) -a \( ${__farr_index} -le ${__farr_len} \) ]; then - eval __farr_getival=\"\$\{${__farr_valname}_${__farr_index}+SET\}\" +falist_tryget_value_at() { + local __farr_value_varname __farr_scookie + + local __farr_token __farr_objname __farr_keyname __farr_valname + local __farr_sptr __farr_sptr_prev __farr_sptr_next + local __farr_getival + + __farr_value_varname=${1-} + [ -z "${__farr_value_varname}" ] && _farr_fatal "missing variable name for value" + _farr_alist_tryparse_cookie "${2-}" || return 1 + + if [ "${__farr_sptr}" -ne 0 ] ; then + eval __farr_getival=\"\$\{"${__farr_valname}"_"${__farr_sptr}"+SET\}\" if [ -n "${__farr_getival}" ]; then - eval __farr_getival=\"\$\{${__farr_valname}_${__farr_index}\}\" + eval __farr_getival=\"\$\{"${__farr_valname}"_"${__farr_sptr}"\}\" _farr_acquire_object "${__farr_getival}" - eval "${__farr_varname}"=\"\$\{__farr_getival\}\" + setvar "${__farr_value_varname}" "${__farr_getival}" return 0 else - _farr_fatal "value unexpectedly unset (index ${__farr_index})" + _farr_fatal "value unexpectedly unset (cookie ${__farr_scookie})" fi else return 1 @@ -2762,56 +3419,56 @@ #: -#: Try to get the key-value item that is associated with a storage index and +#: Try to get the key-value item that is associated with a storage cookie and #: store it into variables. #: -#: Use this for iteration over key-value items. +#: Use this when iterating over key-value items. +#: +#: Comples values need to be released when they are not used any more +#: (`farray_release`, `falist_release`). #: #: Args: #: $1 (str): The name of the variable where to put the key into. #: $2 (str): The name of the variable where to put the value into. -#: $3 (str): The name of an existing alist. -#: $4 (int): The index. +#: $3 (str): The storage cookie. #: #: Returns: #: 0 (truthy) on success, -#: 1 (falsy) if the given index is out of bounds. +#: 1 (falsy) if the given cookie is out of bounds. #: #: Exit: #: Other errors (missing array name, missing index value) are considered #: fatal and call `_farr_fatal` (i.e. `exit`). #: -falist_tryget_item_at_index() { - local __farr_key_varname __farr_value_varname __farr_name __farr_index - - local __farr_token __farr_objname __farr_keyname __farr_valname __farr_len - local __farr_idx __farr_getikey __farr_getival +falist_tryget_item_at() { + local __farr_key_varname __farr_value_varname __farr_scookie + + local __farr_token __farr_objname __farr_keyname __farr_valname + local __farr_sptr __farr_sptr_prev __farr_sptr_next + local __farr_getikey __farr_getival __farr_key_varname=${1-} [ -z "${__farr_key_varname}" ] && _farr_fatal "missing variable name for key" __farr_value_varname=${2-} [ -z "${__farr_value_varname}" ] && _farr_fatal "missing variable name for value" - shift 2 - _farr_alist_get_meta "$@" - __farr_index="${2-}" - [ -z "${__farr_index}" ] && _farr_fatal "missing index" - _farr_make_index __farr_index "${__farr_index}" "${__farr_len}" - - if [ \( ${__farr_index} -ge 1 \) -a \( ${__farr_index} -le ${__farr_len} \) ]; then - eval __farr_getikey=\"\$\{${__farr_keyname}_${__farr_index}+SET\}\" + _farr_alist_tryparse_cookie "${3-}" || return 1 + + if [ "${__farr_sptr}" -ne 0 ] ; then + eval __farr_getikey=\"\$\{"${__farr_keyname}"_"${__farr_sptr}"+SET\}\" if [ -n "${__farr_getikey}" ]; then - eval __farr_getival=\"\$\{${__farr_valname}_${__farr_index}+SET\}\" + eval __farr_getival=\"\$\{"${__farr_valname}"_"${__farr_sptr}"+SET\}\" if [ -n "${__farr_getival}" ]; then - eval "${__farr_key_varname}"=\"\$\{${__farr_keyname}_${__farr_index}\}\" - eval __farr_getival=\"\$\{${__farr_valname}_${__farr_index}\}\" + eval __farr_getikey=\"\$\{"${__farr_keyname}"_"${__farr_sptr}"\}\" + setvar "${__farr_key_varname}" "${__farr_getikey#*@}" + eval __farr_getival=\"\$\{"${__farr_valname}"_"${__farr_sptr}"\}\" _farr_acquire_object "${__farr_getival}" - eval "${__farr_value_varname}"=\"\$\{__farr_getival\}\" + setvar "${__farr_value_varname}" "${__farr_getival}" return 0 else - _farr_fatal "value unexpectedly unset (index ${__farr_index})" + _farr_fatal "value unexpectedly unset (cookie ${__farr_scookie})" fi else - _farr_fatal "key unexpectedly unset (index ${__farr_index})" + _farr_fatal "key unexpectedly unset (cookie ${__farr_scookie})" fi else return 1 @@ -2831,44 +3488,26 @@ #: 1 (falsy) if the given key is not found. #: falist_contains() { - local __farr_name __farr_key - - local __farr_token __farr_objname __farr_keyname __farr_valname __farr_len - local __farr_idx __farr_cokey - - _farr_alist_get_meta "$@" + # __farr_name $1 + # __farr_key $2 + + local __farr_name __farr_token __farr_objname __farr_bskeyname __farr_keyname __farr_valname __farr_llen __farr_bslen __farr_sptr_first __farr_sptr_last + + _farr_alist_get_meta "${1-}" [ $# -lt 2 ] && _farr_fatal "missing key" - __farr_key="$2" - - __farr_idx=1 - while [ ${__farr_idx} -le ${__farr_len} ]; do - eval __farr_cokey=\"\$\{${__farr_keyname}_${__farr_idx}+SET\}\" - if [ -n "${__farr_cokey}" ]; then - eval __farr_cokey=\"\$\{${__farr_keyname}_${__farr_idx}\}\" - if [ "${__farr_cokey}" = "${__farr_key}" ]; then - return 0 - fi - else - _farr_fatal "key unexpectedly unset (index ${__farr_idx})" - fi - __farr_idx=$((__farr_idx + 1)) - done - return 1 + + _farr_alist_bsearch '' '' "${2}" } #: -#: Try to find the index of a given key in an alist. +#: Try to find the storage cookie of a given key in an alist. #: #: Args: -#: $1 (str): The name of a variable where to put the found index into +#: $1 (str): The name of a variable where to put the found storage cookie +#: into. #: $2 (str): The existing alist. #: $3: The key. -#: $4 (int, nuĺl, optional): The start index to search for (inclusive). -#: If not given or `null` then `1` is used. -#: $5 (int, null, optional): The index to stop (inclusive). -#: If not given or `null` then the length of -#: `$2` is used. #: #: Returns: #: 0 (truthy) on success if the key is found, @@ -2876,95 +3515,23 @@ #: falist_find() { local __farr_varname __farr_name __farr_key - local __farr_start __farr_end - - local __farr_token __farr_objname __farr_keyname __farr_valname __farr_len - local __farr_cur_find_idx __farr_fikey + + local __farr_token __farr_objname __farr_bskeyname __farr_keyname __farr_valname __farr_llen __farr_bslen __farr_sptr_first __farr_sptr_last + local __farr_sptr __farr_find_key __farr_varname="${1-}" [ -z "${__farr_varname}" ] && _farr_fatal "missing variable name" - shift - - _farr_alist_get_meta "$@" - [ $# -lt 2 ] && _farr_fatal "missing key" - __farr_key="$2" - - __farr_start="${3-}" - [ -z "${__farr_start}" ] && __farr_start=1 - _farr_make_index __farr_start "${__farr_start}" "${__farr_len}" - [ ${__farr_start} -lt 1 ] && _farr_fatal "start index must be >= 1" - __farr_end="${4-}" - [ -z "${__farr_end}" ] && __farr_end="${__farr_len}" - _farr_make_index __farr_end "${__farr_end}" "${__farr_len}" - [ ${__farr_end} -lt 1 ] && [ ${__farr_len} -gt 0 ] && _farr_fatal "end index must be >= 1" - [ ${__farr_end} -gt "${__farr_len}" ] && _farr_fatal "end index exceeds array length" - - __farr_cur_find_idx=${__farr_start} - while [ ${__farr_cur_find_idx} -le ${__farr_end} ]; do - eval __farr_fikey=\"\$\{${__farr_keyname}_${__farr_cur_find_idx}+SET\}\" - if [ -n "${__farr_fikey}" ]; then - eval __farr_fikey=\"\$\{${__farr_keyname}_${__farr_cur_find_idx}\}\" - if [ "${__farr_fikey}" = "${__farr_key}" ]; then - eval "${__farr_varname}"=${__farr_cur_find_idx} - return 0 - fi - else - _farr_fatal "key unexpectedly unset (index ${__farr_cur_find_idx})" - fi - __farr_cur_find_idx=$((__farr_cur_find_idx + 1)) - done - return 1 -} - - -#: -#: Internal helper to delete an item from an alist storage. -#: -#: Args: -#: $1 (str): The name of the storage object -#: $2 (int): The current length of the storage object (i.e. the current -#: length of the alist) -#: $3 (int): The storage index that will be deleted -#: $4: if no-null use release semantics (used for values) -#: -#: Returns: -#: int: Always 0 -#: -#: Important: -#: This is an internal function. No index and other storage consistency -#: checks are done. -#: -#: Note: -#: The implementation is very similar to `farray_del`. -#: But the new length must be managed by the caller. -#: -_farr_alist_del_storage_at_index() { - local __farr_storage_name __farr_storage_length __farr_storage_index - local __farr_use_release - - local __farr_idx __farr_del_item - - __farr_storage_name="$1" - __farr_storage_length="$2" - __farr_storage_index="$3" - __farr_use_release="$4" - - # Release the item at index -- if requested - if [ -n "${__farr_use_release}" ] ; then - eval __farr_del_item=\"\$\{${__farr_storage_name}_${__farr_idx}\}\" - _farr_release_object "${__farr_del_item}" + _farr_alist_get_meta "${2-}" + [ $# -lt 3 ] && _farr_fatal "missing key" + __farr_key="$3" + + + if _farr_alist_bsearch '' __farr_sptr "${__farr_key}" ; then + eval __farr_find_key=\"\$\{"${__farr_keyname}"_"${__farr_sptr}"\}\" + setvar "${__farr_varname}" "${__farr_sptr}/${__farr_find_key%%@*}@${__farr_token}" + return 0 fi - - # Move all other items down by one - __farr_idx=${__farr_storage_index} - while [ ${__farr_idx} -lt ${__farr_storage_length} ] ; do - # copy the following value to the current index - eval ${__farr_storage_name}_${__farr_idx}=\"\$\{${__farr_storage_name}_$((__farr_idx + 1))\}\" - __farr_idx=$((__farr_idx + 1)) - done - # Drop the last item - eval unset ${__farr_storage_name}_${__farr_idx} - return 0 + return 1 # not found } @@ -2982,32 +3549,108 @@ falist_trydel() { local __farr_name __farr_delkey - local __farr_token __farr_objname __farr_keyname __farr_valname __farr_len - local __farr_idx __farr_curkey + local __farr_token __farr_objname __farr_bskeyname __farr_keyname __farr_valname __farr_llen __farr_bslen __farr_sptr_first __farr_sptr_last + local __farr_sptr __farr_bsidx __farr_cur_value + local __farr_cur_key __farr_cur_stype __farr_cur_prev __farr_cur_next + local __farr_next_key __farr_next_prev __farr_next_next + local __farr_prev_key __farr_prev_prev __farr_prev_next _farr_alist_get_meta "$@" [ $# -lt 2 ] && _farr_fatal "missing key" __farr_delkey="$2" - __farr_idx=1 - while [ ${__farr_idx} -le ${__farr_len} ] ; do - eval __farr_curkey=\"\$\{${__farr_keyname}_${__farr_idx}+SET\}\" - if [ -n "${__farr_curkey}" ]; then - eval __farr_curkey=\"\$\{${__farr_keyname}_${__farr_idx}\}\" - if [ "${__farr_curkey}" = "${__farr_delkey}" ]; then - _farr_alist_del_storage_at_index "${__farr_valname}" "${__farr_len}" "${__farr_idx}" '1' - _farr_alist_del_storage_at_index "${__farr_keyname}" "${__farr_len}" "${__farr_idx}" "" - # Reduce the length by 1 - eval ${__farr_objname}__=$((__farr_len - 1)) - return 0 - fi - else - _farr_fatal "key unexpectedly unset (index ${__farr_idx})" - fi - __farr_idx=$((__farr_idx + 1)) - done - # Not found - return 1 + _farr_alist_bsearch __farr_bsidx __farr_sptr "${__farr_delkey}" || return 1 + + # + # Remove from storage + # + + # + # Can release the value directly + # + eval __farr_cur_value=\"\$\{"${__farr_valname}"_"${__farr_sptr}"\}\" + _farr_release_object "${__farr_cur_value}" + eval unset "${__farr_valname}_${__farr_sptr}" + + # + # Double-linked list: update all pointers (next, prev, first, last) + # and at last remove the entry. + # + eval __farr_cur_key=\"\$\{"${__farr_keyname}"_"${__farr_sptr}"\}\" + __farr_cur_prev="${__farr_cur_key%%@*}" + __farr_cur_next="${__farr_cur_prev#*;}" + __farr_cur_prev="${__farr_cur_prev%;*}" + #__farr_cur_key="${__farr_cur_key#*@}" + + if [ "${__farr_cur_prev}" -ne 0 ]; then + eval __farr_prev_key=\"\$\{"${__farr_keyname}"_"${__farr_cur_prev}"\}\" + __farr_prev_prev="${__farr_prev_key%%@*}" + #__farr_prev_next="${__farr_prev_prev#*;}" + __farr_prev_prev="${__farr_prev_prev%;*}" + __farr_prev_key="${__farr_prev_key#*@}" + setvar "${__farr_keyname}_${__farr_cur_prev}" "${__farr_prev_prev};${__farr_cur_next}@${__farr_prev_key}" + else + # it is the first key + __farr_sptr_first="${__farr_cur_next}" + fi + if [ "${__farr_cur_next}" -ne 0 ]; then + eval __farr_next_key=\"\$\{"${__farr_keyname}"_"${__farr_cur_next}"\}\" + __farr_next_prev="${__farr_next_key%%@*}" + __farr_next_next="${__farr_next_prev#*;}" + #__farr_next_prev="${__farr_next_prev%;*}" + __farr_next_key="${__farr_next_key#*@}" + setvar "${__farr_keyname}_${__farr_cur_next}" "${__farr_cur_prev};${__farr_next_next}@${__farr_next_key}" + else + # it is the last key + __farr_sptr_last="${__farr_cur_prev}" + fi + + eval unset "${__farr_keyname}_${__farr_sptr}" + if [ "${__farr_cur_prev}" -eq 0 ] || [ "${__farr_cur_next}" -eq 0 ] ; then + setvar "${__farr_objname}_P" "${__farr_sptr_first};${__farr_sptr_last}" + fi + + # + # Remove from binary search list + # + + if [ "${__farr_bsidx}" -eq "${__farr_bslen}" ]; then + # Can unset directly: no tombstone needed + eval unset "${__farr_bskeyname}_${__farr_bslen}" + # Decrement the physical length -- persist it below + __farr_bslen=$((__farr_bslen - 1)) + # + # Because __farr_bsidx is the last item we clean up all + # trailing tombstoned entries because `falist_add` relies on + # this for proper key order checks: the last item may never be + # a tombstone. + # + __farr_bsidx="${__farr_bslen}" + while [ "${__farr_bsidx}" -ge 1 ]; do + eval __farr_cur_key=\"\$\{"${__farr_bskeyname}"_"${__farr_bsidx}"\}\" + __farr_cur_stype="${__farr_cur_key%%@*}" + __farr_cur_stype="${__farr_cur_stype#*;}" + [ "${__farr_cur_stype}" = 'V' ] && break + # Just as above + eval unset "${__farr_bskeyname}_${__farr_bsidx}" + __farr_bslen=$((__farr_bslen - 1)) + __farr_bsidx=$((__farr_bsidx - 1)) + done + # Persist the new reduced physical length + setvar "${__farr_objname}_B" "${__farr_bslen}" + else + # + # Can tombstone the binsearch list entry directly: + # The storage pointer is not relevant any more and the key value + # is exactly the one we have searched for. + # + setvar "${__farr_bskeyname}_${__farr_bsidx}" "0;D@${__farr_delkey}" + # XXX TBD: compacting (criterion: difference between bslen and llen) + fi + + # Decrement the logical length + __farr_llen=$((__farr_llen - 1)) + setvar "${__farr_objname}__" "${__farr_llen}" } @@ -3022,12 +3665,12 @@ #: 1 (falsy) otherwise (empty or not created/destroyed properly). #: falist_istrue() { - local __farr_name - - local __farr_token __farr_objname __farr_keyname __farr_valname __farr_len + # name $1 + + local __farr_name __farr_token __farr_objname __farr_bskeyname __farr_keyname __farr_valname __farr_llen __farr_bslen __farr_sptr_first __farr_sptr_last _farr_alist_tryget_meta "$@" || return 1 - if [ "${__farr_len}" -gt 0 ]; then + if [ "${__farr_llen}" -gt 0 ]; then return 0 else return 1 @@ -3039,8 +3682,8 @@ #: Test whether two alists are equal. #: #: Two alists compare equal if and only if they have the same (key, value) -#: pairs (regardless of ordering). Comparison is done using the shell's -#: builtin ``=`` operator for both keys and values. +#: pairs (regardless of insertion order). Comparison is done using the shell's +#: builtin ``=`` test operator for both keys and values (i.e. lexicographic). #: #: Args: #: $1 (str): The name of the first alist @@ -3050,52 +3693,46 @@ #: int: 0 if both input alists are equal, 1 otherwise #: falist_are_equal() { - local __farr_l_name __farr_r_name - - local __farr_l_token __farr_l_objname __farr_l_keyname __farr_l_valname __farr_l_len - local __farr_r_token __farr_r_objname __farr_r_keyname __farr_r_valname __farr_r_len - - local __farr_name __farr_token __farr_objname __farr_keyname __farr_valname __farr_len - local __farr_l_idx __farr_l_key __farr_l_value - local __farr_r_idx __farr_r_key __farr_r_value + local __farr_name __farr_r_name + + local __farr_token __farr_objname __farr_bskeyname __farr_keyname __farr_valname __farr_llen __farr_bslen __farr_sptr_first __farr_sptr_last + local __farr_r_token __farr_r_objname __farr_r_bskeyname __farr_r_keyname __farr_r_valname __farr_r_llen __farr_r_bslen __farr_r_sptr_first __farr_r_sptr_last + local __farr_sptr __farr_r_sptr __farr_r_bsidx + local __farr_value __farr_r_key __farr_r_value __farr_r_stype [ $# -ne 2 ] && _farr_fatal "missing alist parameter" - _farr_alist_get_meta "$1" - __farr_l_name="${__farr_name}" - __farr_l_token="${__farr_token}" - __farr_l_objname="${__farr_objname}" - __farr_l_keyname="${__farr_keyname}" - __farr_l_valname="${__farr_valname}" - __farr_l_len="${__farr_len}" _farr_alist_get_meta "$2" __farr_r_name="${__farr_name}" __farr_r_token="${__farr_token}" __farr_r_objname="${__farr_objname}" + __farr_r_bskeyname="${__farr_bskeyname}" __farr_r_keyname="${__farr_keyname}" __farr_r_valname="${__farr_valname}" - __farr_r_len="${__farr_len}" - - [ ${__farr_l_len} -ne ${__farr_r_len} ] && return 1 - - __farr_l_idx=1 - while [ ${__farr_l_idx} -le ${__farr_l_len} ]; do - __farr_r_idx=1 - eval __farr_l_key=\"\$\{${__farr_l_keyname}_${__farr_l_idx}\}\" - # Find within the right alist - __farr_r_idx=1 - while [ ${__farr_r_idx} -le ${__farr_r_len} ]; do - eval __farr_r_key=\"\$\{${__farr_r_keyname}_${__farr_r_idx}\}\" - [ "${__farr_l_key}" = "${__farr_r_key}" ] && break - __farr_r_idx=$((__farr_r_idx + 1)) - done - # The index is above the right length if not found - [ ${__farr_r_idx} -gt ${__farr_r_len} ] && return 1 - # Also compare the values - eval __farr_l_value=\"\$\{${__farr_l_valname}_${__farr_l_idx}\}\" - eval __farr_r_value=\"\$\{${__farr_r_valname}_${__farr_r_idx}\}\" - [ "${__farr_l_value}" != "${__farr_r_value}" ] && return 1 - __farr_l_idx=$((__farr_l_idx + 1)) + __farr_r_llen="${__farr_llen}" + __farr_r_bslen="${__farr_bslen}" + __farr_r_sptr_first="${__farr_sptr_first}" + __farr_r_sptr_last="${__farr_sptr_last}" + _farr_alist_get_meta "$1" + + [ "${__farr_llen}" -ne "${__farr_r_llen}" ] && return 1 + + __farr_r_bsidx=1 + while [ "${__farr_r_bsidx}" -le "${__farr_r_bslen}" ]; do + eval __farr_r_key=\"\$\{"${__farr_r_bskeyname}"_"${__farr_r_bsidx}"\}\" + __farr_r_stype="${__farr_r_key%%@*}" + __farr_r_sptr="${__farr_r_stype%;*}" + __farr_r_stype="${__farr_r_stype#*;}" + # Skip tombstones + if [ "${__farr_r_stype}" = 'V' ]; then + _farr_alist_bsearch '' __farr_sptr "${__farr_r_key#*@}" || return 1 + # Key comparison is already done by `_farr_alist_bsearch` + # Just compare the values + eval __farr_value=\"\$\{"${__farr_valname}"_"${__farr_sptr}"\}\" + eval __farr_r_value=\"\$\{"${__farr_r_valname}"_"${__farr_r_sptr}"\}\" + [ "${__farr_value}" != "${__farr_r_value}" ] && return 1 + fi + __farr_r_bsidx=$((__farr_r_bsidx + 1)) done return 0 } @@ -3105,57 +3742,68 @@ #: Test whether two alists are equal respecting the (insertion) order. #: #: Two alists compare equal if and only if they have the same (key, value) -#: pairs **with** regard of ordering. Comparison is done using the shell's -#: builtin ``=`` operator for both keys and values. +#: pairs **with** regard of insertion ordering. Comparison is done using the +#: shell's builtin ``=`` test operator for both keys and values +#: (i.e. lexicographic). #: #: Args: -#: $1 (str): The name of the first alist -#: $2 (str): The name of the second alist +#: $1 (str): The first alist. +#: $2 (str): The second alist. #: #: Returns: #: int: 0 if both input alists are equal, 1 otherwise #: falist_are_equal_with_order() { - local __farr_l_name __farr_r_name - - local __farr_l_token __farr_l_objname __farr_l_keyname __farr_l_valname __farr_l_len - local __farr_r_token __farr_r_objname __farr_r_keyname __farr_r_valname __farr_r_len - - local __farr_name __farr_token __farr_objname __farr_keyname __farr_valname __farr_len - local __farr_idx - local __farr_l_key __farr_l_value + local __farr_name __farr_r_name + + local __farr_token __farr_objname __farr_bskeyname __farr_keyname __farr_valname __farr_llen __farr_bslen __farr_sptr_first __farr_sptr_last + local __farr_r_token __farr_r_objname __farr_r_bskeyname __farr_r_keyname __farr_r_valname __farr_r_llen __farr_r_bslen __farr_r_sptr_first __farr_r_sptr_last + local __farr_sptr __farr_sptr_next __farr_r_sptr __farr_r_sptr_next + local __farr_key __farr_value local __farr_r_key __farr_r_value [ $# -ne 2 ] && _farr_fatal "missing alist parameter" - _farr_alist_get_meta "$1" - __farr_l_name="${__farr_name}" - __farr_l_token="${__farr_token}" - __farr_l_objname="${__farr_objname}" - __farr_l_keyname="${__farr_keyname}" - __farr_l_valname="${__farr_valname}" - __farr_l_len="${__farr_len}" _farr_alist_get_meta "$2" __farr_r_name="${__farr_name}" __farr_r_token="${__farr_token}" __farr_r_objname="${__farr_objname}" + __farr_r_bskeyname="${__farr_bskeyname}" __farr_r_keyname="${__farr_keyname}" __farr_r_valname="${__farr_valname}" - __farr_r_len="${__farr_len}" - - [ ${__farr_l_len} -ne ${__farr_r_len} ] && return 1 - - __farr_idx=1 - while [ ${__farr_idx} -le ${__farr_l_len} ]; do - eval __farr_l_key=\"\$\{${__farr_l_keyname}_${__farr_idx}\}\" - eval __farr_r_key=\"\$\{${__farr_r_keyname}_${__farr_idx}\}\" - [ "${__farr_l_key}" != "${__farr_r_key}" ] && return 1 - # Also compare the values - eval __farr_l_value=\"\$\{${__farr_l_valname}_${__farr_idx}\}\" - eval __farr_r_value=\"\$\{${__farr_r_valname}_${__farr_idx}\}\" - [ "${__farr_l_value}" != "${__farr_r_value}" ] && return 1 - __farr_idx=$((__farr_idx + 1)) + __farr_r_llen="${__farr_llen}" + __farr_r_bslen="${__farr_bslen}" + __farr_r_sptr_first="${__farr_sptr_first}" + __farr_r_sptr_last="${__farr_sptr_last}" + _farr_alist_get_meta "$1" + + [ "${__farr_llen}" -ne "${__farr_r_llen}" ] && return 1 + + __farr_sptr="${__farr_sptr_first}" + __farr_r_sptr="${__farr_r_sptr_first}" + while [ "${__farr_sptr}" -ne 0 ] && [ "${__farr_r_sptr}" -ne 0 ] ; do + # Compare the keys ... + eval __farr_key=\"\$\{"${__farr_keyname}"_"${__farr_sptr}"\}\" + eval __farr_r_key=\"\$\{"${__farr_r_keyname}"_"${__farr_r_sptr}"\}\" + [ "${__farr_key#*@}" != "${__farr_r_key#*@}" ] && return 1 + # ... and the values + eval __farr_value=\"\$\{"${__farr_valname}"_"${__farr_sptr}"\}\" + eval __farr_r_value=\"\$\{"${__farr_r_valname}"_"${__farr_r_sptr}"\}\" + [ "${__farr_value}" != "${__farr_r_value}" ] && return 1 + + # Now prepare for moving to next items + __farr_sptr_next="${__farr_key%%@*}" + __farr_sptr_next="${__farr_sptr_next#*;}" + __farr_r_sptr_next="${__farr_r_key%%@*}" + __farr_r_sptr_next="${__farr_r_sptr_next#*;}" + + __farr_sptr="${__farr_sptr_next}" + __farr_r_sptr="${__farr_r_sptr_next}" done + # Check consistency with llen + if [ "${__farr_sptr}" -ne 0 ] || [ "${__farr_r_sptr}" -ne 0 ] ; then + _farr_fatal "inconsistency of storage with logical length detected" + fi return 0 } @@ -3167,15 +3815,18 @@ #: $1 (str): The name of the target array where the keys are appended to. #: $2 (str): The name of the alist from where to get the keys. #: +#: The keys are appended to `$1` in insertion order. +#: falist_keys() { - local __farr_l_result __farr_r_name - - local __farr_r_token __farr_r_objname __farr_r_keyname __farr_r_valname __farr_r_len + local __farr_l_result __farr_name + local __farr_l_name __farr_l_token __farr_l_gvrname __farr_l_len - local __farr_name __farr_token __farr_gvrname __farr_objname - local __farr_keyname __farr_valname __farr_len - local __farr_idx __farr_key + local __farr_token __farr_gvrname __farr_objname + local __farr_bskeyname __farr_keyname __farr_valname + local __farr_len __farr_llen __farr_bslen + local __farr_sptr_first __farr_sptr_last + local __farr_sptr __farr_key __farr_sptr_next # Try to get the array metadata here to provide an early error message [ $# -lt 1 ] && _ferr_fatal "missing target array" @@ -3186,24 +3837,15 @@ __farr_l_gvrname="${__farr_gvrname}" __farr_l_len="${__farr_len}" [ $# -lt 2 ] && _farr_fatal "missing alist" - _farr_alist_get_meta "$2" - __farr_r_name="${__farr_name}" - __farr_r_token="${__farr_token}" - __farr_r_objname="${__farr_objname}" - __farr_r_keyname="${__farr_keyname}" - __farr_r_valname="${__farr_valname}" - __farr_r_len="${__farr_len}" - - __farr_idx=1 - while [ ${__farr_idx} -le ${__farr_r_len} ]; do - eval __farr_key=\"\$\{${__farr_r_keyname}_${__farr_idx}+SET\}\" - if [ -n "${__farr_key}" ]; then - eval __farr_key=\"\$\{${__farr_r_keyname}_${__farr_idx}\}\" - farray_append "${__farr_l_result}" "${__farr_key}" - else - _farr_fatal "alist \`${__farr_r_name}': missing key index" - fi - __farr_idx=$((__farr_idx + 1)) + _farr_alist_get_meta "$2" # and use the standard names here + + __farr_sptr="${__farr_sptr_first}" + while [ "${__farr_sptr}" -ne 0 ]; do + eval __farr_key=\"\$\{"${__farr_keyname}"_"${__farr_sptr}"\}\" + __farr_sptr_next="${__farr_key%%@*}" + __farr_sptr_next="${__farr_sptr_next#*;}" + farray_append "${__farr_l_result}" "${__farr_key#*@}" + __farr_sptr="${__farr_sptr_next}" done return 0 } @@ -3216,15 +3858,18 @@ #: $1 (str): The name of the target array where values are appended to. #: $2 (str): The name of the alist from where to get the values. #: +#: The values are appended to `$1` in insertion order. +#: falist_values() { - local __farr_l_result __farr_r_name - - local __farr_r_token __farr_r_objname __farr_r_keyname __farr_r_valname __farr_r_len + local __farr_l_result __farr_name + local __farr_l_name __farr_l_token __farr_l_gvrname __farr_l_len - local __farr_name __farr_token __farr_gvrname __farr_objname - local __farr_keyname __farr_valname __farr_len - local __farr_idx __farr_value + local __farr_token __farr_gvrname __farr_objname + local __farr_bskeyname __farr_keyname __farr_valname + local __farr_len __farr_llen __farr_bslen + local __farr_sptr_first __farr_sptr_last + local __farr_sptr __farr_key __farr_val __farr_sptr_next # Try to get the array metadata here to provide an early error message [ $# -lt 1 ] && _ferr_fatal "missing target array" @@ -3235,24 +3880,16 @@ __farr_l_gvrname="${__farr_gvrname}" __farr_l_len="${__farr_len}" [ $# -lt 2 ] && _farr_fatal "missing alist" - _farr_alist_get_meta "$2" - __farr_r_name="${__farr_name}" - __farr_r_token="${__farr_token}" - __farr_r_objname="${__farr_objname}" - __farr_r_keyname="${__farr_keyname}" - __farr_r_valname="${__farr_valname}" - __farr_r_len="${__farr_len}" - - __farr_idx=1 - while [ ${__farr_idx} -le ${__farr_r_len} ]; do - eval __farr_value=\"\$\{${__farr_r_valname}_${__farr_idx}+SET\}\" - if [ -n "${__farr_value}" ]; then - eval __farr_value=\"\$\{${__farr_r_valname}_${__farr_idx}\}\" - farray_append "${__farr_l_result}" "${__farr_value}" - else - _farr_fatal "alist \`${__farr_r_name}': missing value index" - fi - __farr_idx=$((__farr_idx + 1)) + _farr_alist_get_meta "$2" # and use the standard names here + + __farr_sptr="${__farr_sptr_first}" + while [ "${__farr_sptr}" -ne 0 ]; do + eval __farr_key=\"\$\{"${__farr_keyname}"_"${__farr_sptr}"\}\" + __farr_sptr_next="${__farr_key%%@*}" + __farr_sptr_next="${__farr_sptr_next#*;}" + eval __farr_val=\"\$\{"${__farr_valname}"_"${__farr_sptr}"\}\" + farray_append "${__farr_l_result}" "${__farr_val}" + __farr_sptr="${__farr_sptr_next}" done return 0 } @@ -3266,15 +3903,18 @@ #: key-value pairs are to be appended to. #: $2 (str): The name of the alist from where to get the items. #: +#: The items are appended to `$1` in insertion order. +#: falist_items() { - local __farr_l_result __farr_r_name - - local __farr_r_token __farr_r_objname __farr_r_keyname __farr_r_valname __farr_r_len + local __farr_l_result __farr_name + local __farr_l_name __farr_l_token __farr_l_gvrname __farr_l_len - local __farr_name __farr_token __farr_gvrname __farr_objname - local __farr_keyname __farr_valname __farr_len - local __farr_idx __farr_key __farr_value + local __farr_token __farr_gvrname __farr_objname + local __farr_bskeyname __farr_keyname __farr_valname + local __farr_len __farr_llen __farr_bslen + local __farr_sptr_first __farr_sptr_last + local __farr_sptr __farr_key __farr_val __farr_sptr_next # Try to get the array metadata here to provide an early error message [ $# -lt 1 ] && _ferr_fatal "missing target array" @@ -3285,43 +3925,30 @@ __farr_l_gvrname="${__farr_gvrname}" __farr_l_len="${__farr_len}" [ $# -lt 2 ] && _farr_fatal "missing alist" - _farr_alist_get_meta "$2" - __farr_r_name="${__farr_name}" - __farr_r_token="${__farr_token}" - __farr_r_objname="${__farr_objname}" - __farr_r_keyname="${__farr_keyname}" - __farr_r_valname="${__farr_valname}" - __farr_r_len="${__farr_len}" - - __farr_idx=1 - while [ ${__farr_idx} -le ${__farr_r_len} ]; do - eval __farr_key=\"\$\{${__farr_r_keyname}_${__farr_idx}+SET\}\" - if [ -n "${__farr_key}" ]; then - eval __farr_key=\"\$\{${__farr_r_keyname}_${__farr_idx}\}\" - eval __farr_value=\"\$\{${__farr_r_valname}_${__farr_idx}+SET\}\" - if [ -n "${__farr_value}" ]; then - eval __farr_value=\"\$\{${__farr_r_valname}_${__farr_idx}\}\" - farray_append "${__farr_l_result}" "${__farr_key}" "${__farr_value}" - else - _farr_fatal "alist \`${__farr_r_name}': missing value index" - fi - else - _farr_fatal "alist \`${__farr_r_name}': missing key index" - fi - __farr_idx=$((__farr_idx + 1)) + _farr_alist_get_meta "$2" # and use the standard names here + + __farr_sptr="${__farr_sptr_first}" + while [ "${__farr_sptr}" -ne 0 ]; do + eval __farr_key=\"\$\{"${__farr_keyname}"_"${__farr_sptr}"\}\" + __farr_sptr_next="${__farr_key%%@*}" + __farr_sptr_next="${__farr_sptr_next#*;}" + eval __farr_val=\"\$\{"${__farr_valname}"_"${__farr_sptr}"\}\" + farray_append "${__farr_l_result}" "${__farr_key#*@}" "${__farr_val}" + __farr_sptr="${__farr_sptr_next}" done return 0 } #: -#: Call a function for every key-value pair in an alist starting in index order. -#: -#: The function to be called must accept at least three or four arguments: +#: Call a function for every key-value pair in an alist in insertion order. +#: +#: The given callback function will called with at least four arguments: +#: #: - the alist name -#: - the element key at the current index -#: - the element value at the current index -#: - the current index +#: - the element key at the current position (storage cookie) +#: - the element value at the current position (storage cookie) +#: - the corresponding storage cookie #: - all the extra arguments (`$3`, `$4`, ...) given as arguments are #: forwarded also #: @@ -3341,37 +3968,33 @@ falist_for_each() { local __farr_name __farr_callback - local __farr_token __farr_objname __farr_keyname __farr_valname __farr_len - local __farr_feidx __farr_fekey __farr_feval __farr_rv + local __farr_token __farr_objname __farr_bskeyname __farr_keyname __farr_valname __farr_llen __farr_bslen __farr_sptr_first __farr_sptr_last + local __farr_fesptr __farr_fesptr_next __farr_fescookie + local __farr_fekey __farr_feval __farr_rv local __farr_gm_name_or_value __farr_gm_name_or_value="${1-}" - _farr_alist_get_meta "$@" + _farr_alist_get_meta "${__farr_gm_name_or_value}" __farr_callback="${2-}" [ -z "${__farr_callback}" ] && _farr_fatal "missing callback function name" shift 2 - __farr_feidx=1 - while [ ${__farr_feidx} -le ${__farr_len} ]; do - eval __farr_fekey=\"\$\{${__farr_keyname}_${__farr_feidx}+SET\}\" - if [ -n "${__farr_fekey}" ]; then - eval __farr_fekey=\"\$\{${__farr_keyname}_${__farr_feidx}\}\" - eval __farr_feval=\"\$\{${__farr_valname}_${__farr_feidx}+SET\}\" - if [ -n "${__farr_feval}" ]; then - eval __farr_feval=\"\$\{${__farr_valname}_${__farr_feidx}\}\" - _farr_acquire_object "${__farr_feval}" - eval "${__farr_callback} \"\${__farr_name:-\"\${__farr_gm_name_or_value}\"}\" \"\${__farr_fekey}\" \"\${__farr_feval}\" \"\${__farr_feidx}\" \"\$@\"" - __farr_rv=$? - _farr_release_object "${__farr_feval}" - [ ${__farr_rv} -ne 0 ] && return ${__farr_rv} - else - _farr_fatal "alist \`${__farr_name:-"${__farr_gm_name_or_value}"}': missing value index" - fi - else - _farr_fatal "alist \`${__farr_name:-"${__farr_gm_name_or_value}"}': missing key index" - fi - __farr_feidx=$((__farr_feidx + 1)) + __farr_fesptr="${__farr_sptr_first}" + while [ "${__farr_fesptr}" -ne 0 ]; do + eval __farr_fekey=\"\$\{"${__farr_keyname}"_"${__farr_fesptr}"\}\" + __farr_fesptr_next="${__farr_fekey%%@*}" + # Note that __farr_fesptr_next contains ; here + __farr_fescookie="${__farr_fesptr}/${__farr_fesptr_next}@${__farr_token}" + __farr_fesptr_next="${__farr_fesptr_next#*;}" + __farr_fekey="${__farr_fekey#*@}" + eval __farr_feval=\"\$\{"${__farr_valname}"_"${__farr_fesptr}"\}\" + _farr_acquire_object "${__farr_feval}" + eval "${__farr_callback} \"\${__farr_name:-\"\${__farr_gm_name_or_value}\"}\" \"\${__farr_fekey}\" \"\${__farr_feval}\" \"\${__farr_fescookie}\" \"\$@\"" + __farr_rv=$? + _farr_release_object "${__farr_feval}" + [ ${__farr_rv} -ne 0 ] && return ${__farr_rv} + __farr_fesptr="${__farr_fesptr_next}" done return 0 } @@ -3402,10 +4025,13 @@ #: 0 #: _farr_alist_debug() { - local __farr_debug_indent __farr_name - - local __farr_token __farr_objname __farr_keyname __farr_valname __farr_len - local __farr_idx __farr_el_key __farr_el_val + local __farr_debug_indent + # __farr_name $1 + + local __farr_name __farr_token __farr_objname __farr_bskeyname __farr_keyname __farr_valname __farr_llen __farr_bslen __farr_sptr_first __farr_sptr_last + local __farr_sptr __farr_el_key __farr_el_val + local __farr_sptr_next + local __farr_debug_bsidx __farr_debug_sptr __farr_debug_indent="${1}" shift @@ -3415,41 +4041,42 @@ fi if [ -n "${__farr_name}" ]; then - printf "%sDEBUG: alist \`%s' has length %s\\n" "${__farr_debug_indent}" "${__farr_name}" "${__farr_len}" 1>&2 + printf "%sDEBUG: alist \`%s' has length %s\\n" "${__farr_debug_indent}" "${__farr_name}" "${__farr_llen}" 1>&2 else - printf "%sDEBUG: alist with token \`%s' has length %s\\n" "${__farr_debug_indent}" "${__farr_token}" "${__farr_len}" 1>&2 + printf "%sDEBUG: alist with token \`%s' has length %s\\n" "${__farr_debug_indent}" "${__farr_token}" "${__farr_llen}" 1>&2 fi - if [ ${__farr_len} -gt 0 ]; then + if [ "${__farr_llen}" -gt 0 ]; then printf '%sDEBUG: the items:\n' "${__farr_debug_indent}" 1>&2 fi - __farr_idx=1 - while [ ${__farr_idx} -le ${__farr_len} ]; do - eval __farr_el_key=\"\$\{${__farr_keyname}_${__farr_idx}+SET\}\" - if [ -z "${__farr_el_key}" ]; then - printf "%sDEBqUG: key unexpectedly unset (index %s)\\n" "${__farr_debug_indent}" "${__farr_idx}" 1>&2 - fi - eval __farr_el_val=\"\$\{${__farr_valname}_${__farr_idx}+SET\}\" - if [ -z "${__farr_el_val}" ]; then - printf "%s DEBUG: value unexpectedly unset (index %s)\\n" "${__farr_debug_indent}" "${__farr_idx}" 1>&2 + __farr_sptr="${__farr_sptr_first}" + while [ "${__farr_sptr}" -ne 0 ]; do + eval __farr_el_key=\"\$\{"${__farr_keyname}"_"${__farr_sptr}"\}\" + __farr_sptr_next="${__farr_el_key%%@*}" + __farr_sptr_next="${__farr_sptr_next#*;}" + __farr_el_key="${__farr_el_key#*@}" + # Some internal consistency checks with respect to the sptr values + if _farr_alist_bsearch __farr_debug_bsidx __farr_debug_sptr "${__farr_el_key}" ; then + if [ "${__farr_sptr}" != "${__farr_debug_sptr}" ]; then + printf "%sDEBUG: WARNING: SPTR INCONSISTENCY FOR KEY \`%s': STOR: %s - BS: %s - BSIDX: %s\\n" "${__farr_debug_indent}" "${__farr_el_key}" "${__farr_sptr}" "${__farr_debug_sptr}" "${__farr_debug_bsidx}" 1>&2 + fi + else + printf "%sDEBUG: WARNING: KEY \`%s' NOT FOUND IN BINARY SEARCH LIST\\n" "${__farr_debug_indent}" "${__farr_el_key}" 1>&2 fi - if [ \( -n "${__farr_el_key}" \) -a \( -n "${__farr_el_val}" \) ]; then - eval __farr_el_key=\"\$\{${__farr_keyname}_${__farr_idx}\}\" - eval __farr_el_val=\"\$\{${__farr_valname}_${__farr_idx}\}\" - case "${__farr_el_val}" in - "${_farr_array_token_prefix}"*) - printf "%sDEBUG: \`%s' -> >>>\\n" "${__farr_debug_indent}" "${__farr_el_key}" 1>&2 - _farr_array_debug "${__farr_debug_indent} " "${__farr_el_val}" - ;; - "${_farr_alist_token_prefix}"*) - printf "%sDEBUG: \`%s' -> >>>\\n" "${__farr_debug_indent}" "${__farr_el_key}" 1>&2 - _farr_alist_debug "${__farr_debug_indent} " "${__farr_el_val}" - ;; - *) - printf "%sDEBUG: \`%s' -> \`%s'\\n" "${__farr_debug_indent}" "${__farr_el_key}" "${__farr_el_val}" 1>&2 - ;; - esac - fi - __farr_idx=$((__farr_idx + 1)) + eval __farr_el_val=\"\$\{"${__farr_valname}"_"${__farr_sptr}"\}\" + case "${__farr_el_val}" in + "${_farr_array_token_prefix}"*) + printf "%sDEBUG: \`%s' -> >>>\\n" "${__farr_debug_indent}" "${__farr_el_key}" 1>&2 + _farr_array_debug "${__farr_debug_indent} " "${__farr_el_val}" + ;; + "${_farr_alist_token_prefix}"*) + printf "%sDEBUG: \`%s' -> >>>\\n" "${__farr_debug_indent}" "${__farr_el_key}" 1>&2 + _farr_alist_debug "${__farr_debug_indent} " "${__farr_el_val}" + ;; + *) + printf "%sDEBUG: \`%s' -> \`%s'\\n" "${__farr_debug_indent}" "${__farr_el_key}" "${__farr_el_val}" 1>&2 + ;; + esac + __farr_sptr="${__farr_sptr_next}" done return 0 } @@ -3477,20 +4104,20 @@ ;; "${_farr_array_token_prefix}"*) __farr_tmp_token="${1#"${_farr_array_token_prefix}"}" - eval __farr_tmp_refcount=\$\{${_farr_array_prefix}${__farr_tmp_token}_C:+SET\} + eval __farr_tmp_refcount=\"\$\{"${_farr_array_prefix}""${__farr_tmp_token}"_C:+SET\}\" [ -z "${__farr_tmp_refcount}" ] && return 1 - eval __farr_tmp_refcount=\$\{${_farr_array_prefix}${__farr_tmp_token}_C\} + eval __farr_tmp_refcount=\"\$\{"${_farr_array_prefix}""${__farr_tmp_token}"_C\}\" __farr_tmp_refcount=$((__farr_tmp_refcount + 1)) - eval ${_farr_array_prefix}${__farr_tmp_token}_C=\$\{__farr_tmp_refcount\} + setvar "${_farr_array_prefix}${__farr_tmp_token}_C" "${__farr_tmp_refcount}" return 0 ;; "${_farr_alist_token_prefix}"*) __farr_tmp_token="${1#"${_farr_alist_token_prefix}"}" - eval __farr_tmp_refcount=\$\{${_farr_alist_prefix}${__farr_tmp_token}_C:+SET\} + eval __farr_tmp_refcount=\"\$\{"${_farr_alist_prefix}""${__farr_tmp_token}"_C:+SET\}\" [ -z "${__farr_tmp_refcount}" ] && return 1 - eval __farr_tmp_refcount=\$\{${_farr_alist_prefix}${__farr_tmp_token}_C\} + eval __farr_tmp_refcount=\"\$\{"${_farr_alist_prefix}""${__farr_tmp_token}"_C\}\" __farr_tmp_refcount=$((__farr_tmp_refcount + 1)) - eval ${_farr_alist_prefix}${__farr_tmp_token}_C=\$\{__farr_tmp_refcount\} + setvar "${_farr_alist_prefix}${__farr_tmp_token}_C" "${__farr_tmp_refcount}" return 0 ;; *) diff -r f6d296c5868e -r 33df05108ba1 tests/farray-alist.t --- a/tests/farray-alist.t Fri Oct 18 14:02:18 2024 +0200 +++ b/tests/farray-alist.t Sat Oct 19 22:40:11 2024 +0200 @@ -18,6 +18,14 @@ Create an empty alist $ falist_create LIST +Has some initial global variables set + $ check_no_alist_artifacts + _farr_KV_[a-f0-9]+_B=0 (re) + _farr_KV_[a-f0-9]+_C=1 (re) + _farr_KV_[a-f0-9]+_P='0;0' (re) + _farr_KV_[a-f0-9]+__=0 (re) + [1] + $ falist_length _i LIST $ echo "$_i" 0 @@ -37,6 +45,161 @@ $ check_no_alist_artifacts + $ falist_create LIST + $ falist_clear LIST + $ falist_istrue LIST + [1] + $ falist_debug LIST + DEBUG: alist `LIST' has length 0 + $ falist_release LIST + $ ( falist_release LIST ) + ERROR: object `LIST' not created properly: token empty + [1] + $ check_no_alist_artifacts + + +Creation and Destruction with one Item +====================================== + +Create an alist with one item + + $ falist_create LIST K1 V1 + $ falist_length _i LIST + $ echo "$_i" + 1 + $ test "${_i}" -eq 1 + $ falist_print_length LIST + 1 (no-eol) + + $ falist_istrue LIST + $ falist_debug LIST + DEBUG: alist `LIST' has length 1 + DEBUG: the items: + DEBUG: `K1' -> `V1' + $ falist_release LIST + $ ( falist_release LIST ) + ERROR: object `LIST' not created properly: token empty + [1] + + $ check_no_alist_artifacts + +One Item that replaces the first item + + $ falist_create LIST K1 V1 K1 "V1 1" + $ falist_length _i LIST + $ echo "$_i" + 1 + $ test "${_i}" -eq 1 + $ falist_print_length LIST + 1 (no-eol) + + $ falist_istrue LIST + $ falist_debug LIST + DEBUG: alist `LIST' has length 1 + DEBUG: the items: + DEBUG: `K1' -> `V1 1' + $ falist_release LIST + $ ( falist_release LIST ) + ERROR: object `LIST' not created properly: token empty + [1] + + $ check_no_alist_artifacts + + +Creation and Destruction with more Items +======================================== + +Create an alist with two items + + $ falist_create LIST K1 V1 K2 V2 + $ falist_debug LIST + DEBUG: alist `LIST' has length 2 + DEBUG: the items: + DEBUG: `K1' -> `V1' + DEBUG: `K2' -> `V2' + + $ falist_release LIST + + $ check_no_alist_artifacts + +Create with inverse insertion order + + $ falist_create LIST K2 V2 K1 V1 + $ falist_debug LIST + DEBUG: alist `LIST' has length 2 + DEBUG: the items: + DEBUG: `K2' -> `V2' + DEBUG: `K1' -> `V1' + + $ falist_release LIST + +Insert at the beginning + + + $ falist_create LIST K2 V2 K1 V1 K0 V0 + $ falist_debug LIST + DEBUG: alist `LIST' has length 3 + DEBUG: the items: + DEBUG: `K2' -> `V2' + DEBUG: `K1' -> `V1' + DEBUG: `K0' -> `V0' + +Replace: beginning + + $ falist_set $LIST K2 $'V2 \' \\ "$abc' + $ falist_debug LIST + DEBUG: alist `LIST' has length 3 + DEBUG: the items: + DEBUG: `K2' -> `V2 ' \ "$abc' + DEBUG: `K1' -> `V1' + DEBUG: `K0' -> `V0' + +Replace: mid + + $ falist_set $LIST K1 'V1 1' + $ falist_debug LIST + DEBUG: alist `LIST' has length 3 + DEBUG: the items: + DEBUG: `K2' -> `V2 ' \ "$abc' + DEBUG: `K1' -> `V1 1' + DEBUG: `K0' -> `V0' + +Replace: end + + $ falist_set $LIST K0 'V0 1' + $ falist_debug LIST + DEBUG: alist `LIST' has length 3 + DEBUG: the items: + DEBUG: `K2' -> `V2 ' \ "$abc' + DEBUG: `K1' -> `V1 1' + DEBUG: `K0' -> `V0 1' + +Insert in the midele again + + $ falist_set LIST K1-1 'V1-1' + $ falist_debug LIST + DEBUG: alist `LIST' has length 4 + DEBUG: the items: + DEBUG: `K2' -> `V2 ' \ "$abc' + DEBUG: `K1' -> `V1 1' + DEBUG: `K0' -> `V0 1' + DEBUG: `K1-1' -> `V1-1' + +Clear resets to initial values + + $ falist_clear LIST + $ check_no_alist_artifacts + _farr_KV_[a-f0-9]+_B=0 (re) + _farr_KV_[a-f0-9]+_C=1 (re) + _farr_KV_[a-f0-9]+_P='0;0' (re) + _farr_KV_[a-f0-9]+__=0 (re) + [1] + + $ falist_print_length LIST + 0 (no-eol) + $ falist_release LIST + $ check_no_alist_artifacts + Clear ===== @@ -44,14 +207,14 @@ $ falist_create LIST $ falist_istrue LIST [1] - $ falist_set LIST K1 V1 + $ falist_set LIST K2 V2 $ falist_istrue LIST - $ falist_set LIST K2 V2 + $ falist_set LIST K1 V1 $ falist_debug LIST DEBUG: alist `LIST' has length 2 DEBUG: the items: + DEBUG: `K2' -> `V2' DEBUG: `K1' -> `V1' - DEBUG: `K2' -> `V2' $ falist_length _i LIST $ echo "$_i" 2 @@ -67,6 +230,17 @@ $ falist_print_length LIST 0 (no-eol) +Clear resets to initial values + + $ falist_clear LIST + $ check_no_alist_artifacts + _farr_KV_[a-f0-9]+_B=0 (re) + _farr_KV_[a-f0-9]+_C=1 (re) + _farr_KV_[a-f0-9]+_P='0;0' (re) + _farr_KV_[a-f0-9]+__=0 (re) + [1] + $ falist_istrue LIST + [1] $ falist_release LIST $ falist_istrue LIST ERROR: object `LIST' not created properly: token empty @@ -74,6 +248,359 @@ $ check_no_alist_artifacts +Optimized Adding +================ + + $ falist_create LIST + $ falist_add LIST k1 v1 + $ falist_add LIST k2 v2 +Would violate order requirements + $ ( falist_add LIST k0 v0 ) + ERROR: falist_add() would violate key order + [70] + $ ( falist_add LIST k2 v2-2 ) + ERROR: falist_add() would violate key order + [70] + $ falist_add $LIST k3 $'" 111222333" \\\'444555666 ' # ' + $ falist_debug LIST + DEBUG: alist `LIST' has length 3 + DEBUG: the items: + DEBUG: `k1' -> `v1' + DEBUG: `k2' -> `v2' + DEBUG: `k3' -> `" 111222333" \'444555666 ' + $ falist_add $LIST k4 'v4 1' k5 v5 + $ falist_debug LIST + DEBUG: alist `LIST' has length 5 + DEBUG: the items: + DEBUG: `k1' -> `v1' + DEBUG: `k2' -> `v2' + DEBUG: `k3' -> `" 111222333" \'444555666 ' + DEBUG: `k4' -> `v4 1' + DEBUG: `k5' -> `v5' + $ falist_release LIST + $ check_no_alist_artifacts + + +Iteration +========= + + $ falist_create LIST + $ falist_set LIST K1 V1 + $ falist_set LIST K2 "V2 2" + $ falist_set LIST K3 $'" 111222333" \\\'444555 ' # ' + +Consistency check of storage cookies + + $ _c="$(falist_cookie_first LIST)" + $ echo $_c + 1/0;2@[a-f0-9]+ (re) + $ _cnull="$(falist_cookie_prev "${_c}")" + $ echo "${_cnull}" + 0/0;0@[a-f0-9]+ (re) + $ _c2="$(falist_cookie_next "$_c")" + $ echo "${_c2}" + 2/1;3@[a-f0-9]+ (re) + $ _c3="$(falist_cookie_prev "${_c2}")" + $ test "${_c}" = "${_c3}" + $ _c=$(falist_cookie_last LIST) + $ echo $_c + 3/2;0@[a-f0-9]+ (re) + $ _cnull="$(falist_cookie_next "${_c}")" + $ echo "${_cnull}" + 0/0;0@[a-f0-9]+ (re) + +MANUAL (by cookie, forward in insertion order) + + $ _pos="$(falist_cookie_first LIST)" + > while falist_tryget_item_at _k _v "$_pos"; do + > printf $'%s -> `%s\'\\n' "$_k" "$_v" + > _pos="$(falist_cookie_next "$_pos")" + > done + K1 -> `V1' + K2 -> `V2 2' + K3 -> `" 111222333" \'444555 ' + +MANUAL (by cookie, reversed insertion order) + + $ _pos="$(falist_cookie_last LIST)" + > while falist_tryget_item_at _k _v "$_pos"; do + > printf $'`%s\' <- %s\\n' "$_v" "$_k" + > _pos="$(falist_cookie_prev "$_pos")" + > done + `" 111222333" \'444555 ' <- K3 + `V2 2' <- K2 + `V1' <- K1 + +MANUAL values (by cookie, forward in insertion order) + + $ _pos="$(falist_cookie_first LIST)" + > while falist_tryget_value_at _v "$_pos"; do + > printf $'`%s\'\\n' "$_v" + > _pos="$(falist_cookie_next "$_pos")" + > done + `V1' + `V2 2' + `" 111222333" \'444555 ' + + +MANUAL keys (by cookie, reversed insertion order) + + $ _pos="$(falist_cookie_last LIST)" + > while falist_tryget_key_at _k "$_pos"; do + > printf '%s\n' "$_k" + > _pos="$(falist_cookie_prev "$_pos")" + > done + K3 + K2 + K1 + +ITERATE (for each, by name) + + $ falist_for_each LIST $'printf "EACH: %s key \\`%s\\\', value \\`%s\\\' at cookie %s\\n"' # ` + EACH: LIST key `K1', value `V1' at cookie 1/[0-9]+;[0-9]+@[a-f0-9]+ (re) + EACH: LIST key `K2', value `V2 2' at cookie 2/[0-9]+;[0-9]+@[a-f0-9]+ (re) + EACH: LIST key `K3', value `" 111222333" \\'444555 ' at cookie 3/[0-9]+;[0-9]+@[a-f0-9]+ (re) + +ITERATE (for each, by value) + + $ falist_for_each "$LIST" $'printf "EACH: %s key \\`%s\\\', value \\`%s\\\' at cookie %s\\n"' # ` + EACH: _farr_KV\[\?,\?\]:[a-f0-9]+ key `K1', value `V1' at cookie 1/[0-9]+;[0-9]+@[a-f0-9]+ (re) + EACH: _farr_KV\[\?,\?\]:[a-f0-9]+ key `K2', value `V2 2' at cookie 2/[0-9]+;[0-9]+@[a-f0-9]+ (re) + EACH: _farr_KV\[\?,\?\]:[a-f0-9]+ key `K3', value `" 111222333" \\'444555 ' at cookie 3/[0-9]+;[0-9]+@[a-f0-9]+ (re) + + $ falist_clear LIST + + $ falist_release LIST + $ falist_release LIST + ERROR: object `LIST' not created properly: token empty + [1] + $ check_no_alist_artifacts + + +Deleting +======== + + $ falist_create LIST + $ (falist_trydel LIST foo) + [1] + + $ falist_release LIST + $ check_no_alist_artifacts + + $ falist_create LIST K1 V1 + $ (falist_trydel LIST foo) + [1] + $ falist_debug LIST + DEBUG: alist `LIST' has length 1 + DEBUG: the items: + DEBUG: `K1' -> `V1' + $ falist_istrue LIST + $ falist_trydel LIST K1 + $ falist_debug LIST + DEBUG: alist `LIST' has length 0 + $ check_no_alist_artifacts + _farr_KV_[a-f0-9]+_B=0 (re) + _farr_KV_[a-f0-9]+_C=1 (re) + _farr_KV_[a-f0-9]+_P='0;0' (re) + _farr_KV_[a-f0-9]+__=0 (re) + [1] + $ (falist_istrue LIST) + [1] + $ falist_release LIST + $ check_no_alist_artifacts + + $ falist_create LIST K1 V1 K2 V2 + $ (falist_trydel LIST foo) + [1] + $ falist_trydel LIST K1 + $ falist_debug LIST + DEBUG: alist `LIST' has length 1 + DEBUG: the items: + DEBUG: `K2' -> `V2' + $ falist_trydel LIST K2 + $ falist_debug LIST + DEBUG: alist `LIST' has length 0 + $ falist_release LIST + $ check_no_alist_artifacts + + $ falist_create LIST K1 V1 K2 V2 + $ (falist_trydel LIST foo) + [1] + $ falist_trydel LIST K2 + $ falist_debug LIST + DEBUG: alist `LIST' has length 1 + DEBUG: the items: + DEBUG: `K1' -> `V1' + $ falist_trydel LIST K1 + $ falist_debug LIST + DEBUG: alist `LIST' has length 0 + $ falist_release LIST + $ check_no_alist_artifacts + + $ falist_create LIST K1 V1 K2 V2 K3 V3 + $ (falist_trydel LIST foo) + [1] + $ falist_trydel LIST K1 + $ falist_debug LIST + DEBUG: alist `LIST' has length 2 + DEBUG: the items: + DEBUG: `K2' -> `V2' + DEBUG: `K3' -> `V3' + $ falist_release LIST + $ check_no_alist_artifacts + + $ falist_create LIST K1 V1 K2 V2 K3 V3 + $ (falist_trydel LIST foo) + [1] + $ falist_trydel LIST K2 + $ falist_debug LIST + DEBUG: alist `LIST' has length 2 + DEBUG: the items: + DEBUG: `K1' -> `V1' + DEBUG: `K3' -> `V3' + $ falist_release LIST + $ check_no_alist_artifacts + + $ falist_create LIST K1 V1 K2 V2 K3 V3 + $ (falist_trydel LIST foo) + [1] + $ falist_trydel LIST K3 + $ falist_debug LIST + DEBUG: alist `LIST' has length 2 + DEBUG: the items: + DEBUG: `K1' -> `V1' + DEBUG: `K2' -> `V2' + $ falist_release LIST + $ check_no_alist_artifacts + + $ falist_create LIST K1 V1 K2 V2 K3 V3 + $ (falist_trydel LIST foo) + [1] + $ falist_trydel LIST K2 + $ falist_trydel LIST K1 + $ falist_trydel LIST K3 + $ falist_debug LIST + DEBUG: alist `LIST' has length 0 +Check that the binary search list is cleaned up + $ check_no_alist_artifacts + _farr_KV_[a-f0-9]+_B=0 (re) + _farr_KV_[a-f0-9]+_C=1 (re) + _farr_KV_[a-f0-9]+_P='0;0' (re) + _farr_KV_[a-f0-9]+__=0 (re) + [1] + $ falist_release LIST + $ check_no_alist_artifacts + + $ falist_create LIST K1 V1 K2 V2 K3 V3 + $ (falist_trydel LIST foo) + [1] + $ falist_trydel LIST K2 +Skipping tombstones + $ (falist_trydel LIST K2) + [1] + $ falist_trydel LIST K3 + $ (falist_trydel LIST K3) + [1] + $ falist_debug LIST + DEBUG: alist `LIST' has length 1 + DEBUG: the items: + DEBUG: `K1' -> `V1' +Check that the binary search list is cleaned up properly: just the first +storage entries are left + $ check_no_alist_artifacts + _farr_KV_[a-f0-9]+_B=1 (re) + _farr_KV_[a-f0-9]+_C=1 (re) + _farr_KV_[a-f0-9]+_P='1;1' (re) + _farr_KV_[a-f0-9]+__=1 (re) + _farr_Kb_[a-f0-9]+_1='1;V@K1' (re) + _farr_Ks_[a-f0-9]+_1='0;0@K1' (re) + _farr_Vs_[a-f0-9]+_1=V1 (re) + [1] +Can re-add K2 now again + $ falist_add LIST K2 'V2 2' + $ falist_release LIST + $ check_no_alist_artifacts + +Try this with reversed insertion order also + + $ falist_create LIST K3 V3 K2 V2 K1 V1 + $ falist_find _var LIST K3 +Cookie has structure /;@ + $ echo "$_var" + 1/[0-9]+;[0-9]+@[a-f0-9]+ (re) + $ falist_contains LIST K3 + $ falist_find _var LIST K2 + $ echo "$_var" + 2/[0-9]+;[0-9]+@[a-f0-9]+ (re) + $ falist_contains LIST K2 + $ falist_find _var LIST K1 + $ echo "$_var" + 3/[0-9]+;[0-9]+@[a-f0-9]+ (re) + $ falist_contains LIST K1 + $ (falist_trydel LIST foo) + [1] + $ falist_trydel LIST K2 +Skipping tombstones + $ (falist_trydel LIST K2) + [1] + $ falist_trydel LIST K3 + $ (falist_trydel LIST K3) + [1] + $ falist_debug LIST + DEBUG: alist `LIST' has length 1 + DEBUG: the items: + DEBUG: `K1' -> `V1' +Check that the binary search list is cleaned up properly: just the first +storage entries are left + $ check_no_alist_artifacts + _farr_KV_[a-f0-9]+_B=1 (re) + _farr_KV_[a-f0-9]+_C=1 (re) + _farr_KV_[a-f0-9]+_P='3;3' (re) + _farr_KV_[a-f0-9]+__=1 (re) + _farr_Kb_[a-f0-9]+_1='3;V@K1' (re) + _farr_Ks_[a-f0-9]+_3='0;0@K1' (re) + _farr_Vs_[a-f0-9]+_3=V1 (re) + [1] +Can re-add K2 now again + $ falist_add LIST K2 'V2 2' + $ falist_find _var LIST K2 + $ falist_contains LIST K2 +But K3 is tombstoned/deleted + $ (falist_find _var LIST K3) + [1] + $ (falist_contains LIST K3) + [1] + $ farray_create ARRAY + $ falist_keys ARRAY LIST + $ farray_debug ARRAY + DEBUG: array `ARRAY' has length 2 + DEBUG: the items: + DEBUG: 1: `K1' + DEBUG: 2: `K2' + $ farray_release ARRAY + $ farray_create ARRAY + $ falist_values ARRAY LIST + $ farray_debug ARRAY + DEBUG: array `ARRAY' has length 2 + DEBUG: the items: + DEBUG: 1: `V1' + DEBUG: 2: `V2 2' + $ farray_release ARRAY + $ farray_create ARRAY + $ falist_items ARRAY LIST + $ farray_debug ARRAY + DEBUG: array `ARRAY' has length 4 + DEBUG: the items: + DEBUG: 1: `K1' + DEBUG: 2: `V1' + DEBUG: 3: `K2' + DEBUG: 4: `V2 2' + $ farray_release ARRAY + $ falist_release LIST + $ check_no_alist_artifacts + $ check_no_array_artifacts + + Get / Set / Contains / Find Index ================================= @@ -108,11 +635,12 @@ 3 (no-eol) $ falist_contains LIST K1 - $ falist_find idx LIST K1 - $ test "$idx" -eq 1 + $ falist_find cookie LIST K1 + $ echo "$cookie" + 1/[0-9]+;[0-9]+@[a-f0-9]+ (re) $ falist_contains LIST K [1] - $ falist_find idx LIST K + $ falist_find cookie LIST K [1] $ falist_get _var LIST K2 $ echo "$_var" @@ -123,17 +651,17 @@ $ falist_tryget _i LIST K [1] - $ falist_get _var LIST K2 _idx + $ falist_get _var LIST K2 _cookie $ echo "$_var" V2 2 - $ echo "$_idx" - 2 - $ falist_tryget _var LIST K1 _idx + $ echo "$_cookie" + 2/[0-9]+;[0-9]+@[a-f0-9]+ (re) + $ falist_tryget _var LIST K1 _cookie $ echo "$_var" V1 - $ echo "$_idx" - 1 - $ falist_tryget _i LIST K _idx + $ echo "$_cookie" + 1/[0-9]+;[0-9]+@[a-f0-9]+ (re) + $ falist_tryget _i LIST K _cookie [1] $ _var="$(falist_print_length NON_EXISTING_LIST)" ERROR: object `NON_EXISTING_LIST' not created properly: token empty @@ -150,160 +678,39 @@ $ falist_release LIST $ check_no_alist_artifacts -Special case empty list (because of start/stop extra indexes) - $ falist_create LIST - $ falist_find _var LIST foo - [1] - $ falist_release LIST - $ check_no_alist_artifacts - - -Add -=== - - $ falist_create LIST - $ falist_add LIST K1 'V 1' - $ falist_add LIST K2 'V 2' - $ falist_add LIST K3 $'" 111222333" \\\'444555666 ' # ' - $ falist_debug LIST - DEBUG: alist `LIST' has length 3 - DEBUG: the items: - DEBUG: `K1' -> `V 1' - DEBUG: `K2' -> `V 2' - DEBUG: `K3' -> `" 111222333" \'444555666 ' -Yes: make a duplicate key - $ falist_add LIST K3 $'" 111222333" \\\'444555666 ' # - $ falist_debug LIST - DEBUG: alist `LIST' has length 4 - DEBUG: the items: - DEBUG: `K1' -> `V 1' - DEBUG: `K2' -> `V 2' - DEBUG: `K3' -> `" 111222333" \'444555666 ' - DEBUG: `K3' -> `" 111222333" \'444555666 ' - $ falist_release LIST - $ check_no_alist_artifacts - - -Iteration -========= - -ITERATE (manual indexing) - - $ falist_create LIST - $ falist_set LIST K1 V1 - $ falist_set LIST K2 "V2 2" - $ falist_set LIST K3 $'" 111222333" \\\'444555 ' # ' - -Iteration by indexing key and values separately - - $ _i=1 - > while falist_tryget_key_at_index _k LIST ${_i}; do - > # cannot fail under "normal" circumstances - > falist_tryget_value_at_index _v LIST ${_i} - > printf " KEY: \`%s', VAL: \`%s'\\n" "${_k}" "${_v}" - > _i=$((_i + 1)) - > done - KEY: `K1', VAL: `V1' - KEY: `K2', VAL: `V2 2' - KEY: `K3', VAL: `" 111222333" \'444555 ' - -ITERATE (manual indexing over items) +Items / Keys / Values +===================== - $ _i=1 - > while falist_tryget_item_at_index _k _v LIST ${_i}; do - > printf " KEY: \`%s', VAL: \`%s'\\n" "${_k}" "${_v}" - > _i=$((_i + 1)) - > done - KEY: `K1', VAL: `V1' - KEY: `K2', VAL: `V2 2' - KEY: `K3', VAL: `" 111222333" \'444555 ' - -ITERATE (for each, by name) - - $ falist_for_each LIST $'printf "EACH: %s key \\`%s\\\', value \\`%s\\\' at idx %d\\n"' # ` - EACH: LIST key `K1', value `V1' at idx 1 - EACH: LIST key `K2', value `V2 2' at idx 2 - EACH: LIST key `K3', value `" 111222333" \'444555 ' at idx 3 - -ITERATE (for each, by value) - - $ falist_for_each "$LIST" $'printf "EACH: %s key \\`%s\\\', value \\`%s\\\' at idx %d\\n"' # ` - EACH: _farr_KV\[\?,\?\]:[a-f0-9]+ key `K1', value `V1' at idx 1 (re) - EACH: _farr_KV\[\?,\?\]:[a-f0-9]+ key `K2', value `V2 2' at idx 2 (re) - EACH: _farr_KV\[\?,\?\]:[a-f0-9]+ key `K3', value `" 111222333" \\'444555 ' at idx 3 (re) - - $ falist_clear LIST - $ falist_release LIST - $ falist_release LIST - ERROR: object `LIST' not created properly: token empty - [1] - - $ check_no_alist_artifacts - - -Valid and Invalid Indices - - $ falist_create LIST - $ falist_set LIST 'KEY 1' 'VAL 1' - $ falist_set LIST 'KEY 2' 'VAL 2' - $ falist_set LIST 'KEY 3' 'VAL 3' - - $ (falist_tryget_item_at_index _k _v LIST "") - ERROR: missing index - [70] - - $ (falist_tryget_item_at_index _k _v LIST) - ERROR: missing index - [70] - - $ falist_tryget_item_at_index _k _v LIST 4 - [1] - - $ falist_tryget_item_at_index _k _v LIST 0 - $ printf '%s:%s' "$_k" "$_v" - KEY 3:VAL 3 (no-eol) - - $ falist_tryget_item_at_index _k _v LIST -2 - $ printf '%s:%s' "$_k" "$_v" - KEY 1:VAL 1 (no-eol) - - $ falist_tryget_item_at_index _k _v LIST -3 - [1] + $ farray_create KEYS + $ farray_create VALUES + $ farray_create ITEMS + $ falist_create LIST "Key 1" "Value 1" "Key 2" 'Value 2 '\''' + $ falist_items ITEMS LIST + $ farray_debug ITEMS + DEBUG: array `ITEMS' has length 4 + DEBUG: the items: + DEBUG: 1: `Key 1' + DEBUG: 2: `Value 1' + DEBUG: 3: `Key 2' + DEBUG: 4: `Value 2 '' + $ falist_keys KEYS LIST + $ farray_debug KEYS + DEBUG: array `KEYS' has length 2 + DEBUG: the items: + DEBUG: 1: `Key 1' + DEBUG: 2: `Key 2' + $ falist_values VALUES LIST + $ farray_debug VALUES + DEBUG: array `VALUES' has length 2 + DEBUG: the items: + DEBUG: 1: `Value 1' + DEBUG: 2: `Value 2 '' $ falist_release LIST - $ check_no_alist_artifacts - - -Deletion of keys -================ - - $ falist_create LIST - $ falist_set LIST 'key 1' 'value 1' - $ falist_set LIST 'key 2' 'value 2' - $ falist_set LIST 'key 3' 'value 3' - $ falist_set LIST 'key 4' 'value 4' - $ falist_set LIST 'key 5' 'value 5' - $ falist_trydel LIST 'key 1' - $ falist_trydel LIST 'key 5' - $ falist_trydel LIST 'key 3' - $ falist_debug LIST - DEBUG: alist `LIST' has length 2 - DEBUG: the items: - DEBUG: `key 2' -> `value 2' - DEBUG: `key 4' -> `value 4' - $ falist_trydel LIST "non-existing key" - [1] - $ falist_print_length LIST - 2 (no-eol) - $ falist_get _var LIST 'key 2' - $ printf '%s' "$_var" - value 2 (no-eol) - $ falist_get _var LIST 'key 4' - $ printf '%s' "$_var" - value 4 (no-eol) - - $ falist_release LIST + $ farray_release KEYS + $ farray_release VALUES + $ farray_release ITEMS $ check_no_alist_artifacts @@ -364,191 +771,123 @@ Updating ======== - $ falist_create ARR "Key 1" "Value 1" "Key 2" 'Value 2 '\''' - $ falist_create UPDATE1 "Key 1" "Value 1" "Key 2" 'Value 2 '\''' - $ falist_create UPDATE2 "Key 2" 'Value 2 (Updated) '\''' "Key 3" "Value 3" - $ falist_create EMPTY - - $ falist_are_equal_with_order ARR UPDATE1 - $ falist_are_equal_with_order ARR UPDATE2 - [1] +Just replace existing items - $ falist_update ARR UPDATE1 - $ falist_are_equal_with_order ARR UPDATE1 - - $ falist_update ARR UPDATE2 - $ falist_debug ARR - DEBUG: alist `ARR' has length 3 + $ falist_create LIST k2 v2 k4 v4 k6 v6 k8 v8 + $ falist_debug LIST + DEBUG: alist `LIST' has length 4 DEBUG: the items: - DEBUG: `Key 1' -> `Value 1' - DEBUG: `Key 2' -> `Value 2 (Updated) '' - DEBUG: `Key 3' -> `Value 3' + DEBUG: `k2' -> `v2' + DEBUG: `k4' -> `v4' + DEBUG: `k6' -> `v6' + DEBUG: `k8' -> `v8' -Updating an into an empty alist is just a copy - - $ falist_update EMPTY UPDATE1 - $ falist_debug EMPTY - DEBUG: alist `EMPTY' has length 2 + $ falist_create UPDATE1 k2 v2-2 k4 v4-2 k8 v8-2 + $ falist_update LIST UPDATE1 + $ falist_debug LIST + DEBUG: alist `LIST' has length 4 DEBUG: the items: - DEBUG: `Key 1' -> `Value 1' - DEBUG: `Key 2' -> `Value 2 '' - $ falist_debug UPDATE1 - DEBUG: alist `UPDATE1' has length 2 - DEBUG: the items: - DEBUG: `Key 1' -> `Value 1' - DEBUG: `Key 2' -> `Value 2 '' + DEBUG: `k2' -> `v2-2' + DEBUG: `k4' -> `v4-2' + DEBUG: `k6' -> `v6' + DEBUG: `k8' -> `v8-2' - $ falist_release ARR + $ falist_release LIST $ falist_release UPDATE1 - $ falist_release UPDATE2 - $ falist_release EMPTY - $ check_no_alist_artifacts -Merging -======= - - $ falist_create RES - $ falist_create INPUT1 "K1" "V1" "K2" "V2" "K3" "V3" - $ falist_create INPUT2 - $ falist_merge "$RES" "$INPUT1" "$INPUT2" - $ falist_release INPUT1 - $ falist_release INPUT2 - $ falist_debug RES - DEBUG: alist `RES' has length 3 - DEBUG: the items: - DEBUG: `K1' -> `V1' - DEBUG: `K2' -> `V2' - DEBUG: `K3' -> `V3' - $ falist_release RES - $ check_no_alist_artifacts +Handle previously deleted items also - $ falist_create RES - $ falist_create INPUT1 - $ falist_create INPUT2 "k1" "v1" "k2" "v2" "k3" "v3" - $ falist_merge "$RES" "$INPUT1" "$INPUT2" - $ falist_release INPUT1 - $ falist_release INPUT2 - $ falist_debug RES - DEBUG: alist `RES' has length 3 + $ falist_create LIST k2 v2 k4 v4 k6 v6 k8 v8 + $ falist_trydel LIST k2 + $ falist_create UPDATE1 k2 v2-2 k4 v4-2 k8 v8-2 + $ falist_update LIST UPDATE1 + $ falist_debug LIST + DEBUG: alist `LIST' has length 4 DEBUG: the items: - DEBUG: `k1' -> `v1' - DEBUG: `k2' -> `v2' - DEBUG: `k3' -> `v3' - $ falist_release RES - $ check_no_alist_artifacts + DEBUG: `k4' -> `v4-2' + DEBUG: `k6' -> `v6' + DEBUG: `k8' -> `v8-2' + DEBUG: `k2' -> `v2-2' - $ falist_create RES - $ falist_create INPUT1 "K1" "V1" "K2" "V2" "K3" "V3" - $ falist_create INPUT2 "k1" "v1" "k2" "v2" "k3" "v3" "k4" "v4" - $ falist_merge "$RES" "$INPUT1" "$INPUT2" - $ falist_release INPUT1 - $ falist_release INPUT2 - $ falist_debug RES - DEBUG: alist `RES' has length 7 - DEBUG: the items: - DEBUG: `K1' -> `V1' - DEBUG: `K2' -> `V2' - DEBUG: `K3' -> `V3' - DEBUG: `k1' -> `v1' - DEBUG: `k2' -> `v2' - DEBUG: `k3' -> `v3' - DEBUG: `k4' -> `v4' - $ falist_release RES + $ falist_release LIST + $ falist_release UPDATE1 $ check_no_alist_artifacts - $ falist_create RES - $ falist_create INPUT1 "k1" "v1" "k2" "v2" "k3" "v3" "k4" "v4" - $ falist_create INPUT2 "K1" "V1" "K2" "V2" "K3" "V3" - $ falist_merge "$RES" "$INPUT1" "$INPUT2" - $ falist_release INPUT1 - $ falist_release INPUT2 - $ falist_debug RES - DEBUG: alist `RES' has length 7 - DEBUG: the items: - DEBUG: `K1' -> `V1' - DEBUG: `K2' -> `V2' - DEBUG: `K3' -> `V3' - DEBUG: `k1' -> `v1' - DEBUG: `k2' -> `v2' - DEBUG: `k3' -> `v3' - DEBUG: `k4' -> `v4' - $ falist_release RES - $ check_no_alist_artifacts - - $ falist_create RES - $ falist_create INPUT1 "K1" "V1" "K3" "V3" "k1" "v1" "k4" "v4" - $ falist_create INPUT2 "K2" "V2" "k2" "v2" "k3" "v3" - $ falist_merge "$RES" "$INPUT1" "$INPUT2" - $ falist_release INPUT1 - $ falist_release INPUT2 - $ falist_debug RES - DEBUG: alist `RES' has length 7 + $ falist_create LIST k2 v2 k4 v4 k6 v6 k8 v8 + $ falist_trydel LIST k4 + $ falist_create UPDATE1 k2 v2-2 k4 v4-2 k8 v8-2 + $ falist_update LIST UPDATE1 + $ falist_debug LIST + DEBUG: alist `LIST' has length 4 DEBUG: the items: - DEBUG: `K1' -> `V1' - DEBUG: `K2' -> `V2' - DEBUG: `K3' -> `V3' - DEBUG: `k1' -> `v1' - DEBUG: `k2' -> `v2' - DEBUG: `k3' -> `v3' - DEBUG: `k4' -> `v4' - $ falist_release RES - $ check_no_alist_artifacts + DEBUG: `k2' -> `v2-2' + DEBUG: `k6' -> `v6' + DEBUG: `k8' -> `v8-2' + DEBUG: `k4' -> `v4-2' - $ falist_create RES - $ falist_create INPUT1 "K1" "V1" "K3" "V3" "k1" "v1" "k4" "v4" - $ falist_create INPUT2 "K2" "V2" "k2" "v2" "k3" "v3" - $ falist_merge "$RES" INPUT2 INPUT1 - $ falist_release INPUT1 - $ falist_release INPUT2 - $ falist_debug RES - DEBUG: alist `RES' has length 7 - DEBUG: the items: - DEBUG: `K1' -> `V1' - DEBUG: `K2' -> `V2' - DEBUG: `K3' -> `V3' - DEBUG: `k1' -> `v1' - DEBUG: `k2' -> `v2' - DEBUG: `k3' -> `v3' - DEBUG: `k4' -> `v4' - $ falist_release RES + $ falist_release LIST + $ falist_release UPDATE1 $ check_no_alist_artifacts -Items / Keys / Values -===================== +Just appending - $ farray_create KEYS - $ farray_create VALUES - $ farray_create ITEMS - $ falist_create LIST "Key 1" "Value 1" "Key 2" 'Value 2 '\''' - $ falist_items ITEMS LIST - $ farray_debug ITEMS - DEBUG: array `ITEMS' has length 4 + $ falist_create LIST k2 v2 k4 v4 k6 v6 k8 v8 + $ falist_create UPDATE1 k9 v9 k91 v91 + $ falist_update LIST UPDATE1 + $ falist_debug LIST + DEBUG: alist `LIST' has length 6 DEBUG: the items: - DEBUG: 1: `Key 1' - DEBUG: 2: `Value 1' - DEBUG: 3: `Key 2' - DEBUG: 4: `Value 2 '' - $ falist_keys KEYS LIST - $ farray_debug KEYS - DEBUG: array `KEYS' has length 2 - DEBUG: the items: - DEBUG: 1: `Key 1' - DEBUG: 2: `Key 2' - $ falist_values VALUES LIST - $ farray_debug VALUES - DEBUG: array `VALUES' has length 2 - DEBUG: the items: - DEBUG: 1: `Value 1' - DEBUG: 2: `Value 2 '' + DEBUG: `k2' -> `v2' + DEBUG: `k4' -> `v4' + DEBUG: `k6' -> `v6' + DEBUG: `k8' -> `v8' + DEBUG: `k9' -> `v9' + DEBUG: `k91' -> `v91' $ falist_release LIST - $ farray_release KEYS - $ farray_release VALUES - $ farray_release ITEMS + $ falist_release UPDATE1 + $ check_no_alist_artifacts + + +Append after deletion + + $ falist_create LIST k2 v2 k4 v4 k6 v6 k8 v8 + $ falist_trydel LIST k8 + $ falist_create UPDATE1 k2 v2-2 k4 v4-2 k8 v8-2 + $ falist_update LIST UPDATE1 + $ falist_debug LIST + DEBUG: alist `LIST' has length 4 + DEBUG: the items: + DEBUG: `k2' -> `v2-2' + DEBUG: `k4' -> `v4-2' + DEBUG: `k6' -> `v6' + DEBUG: `k8' -> `v8-2' + $ falist_release LIST + $ falist_release UPDATE1 + $ check_no_alist_artifacts + +Insertions + + $ falist_create LIST k2 v2 k4 v4 k6 v6 k8 v8 + $ falist_create UPDATE1 k1 v1 k3 v3 k7 v7 + $ falist_update LIST UPDATE1 + $ falist_debug LIST + DEBUG: alist `LIST' has length 7 + DEBUG: the items: + DEBUG: `k2' -> `v2' + DEBUG: `k4' -> `v4' + DEBUG: `k6' -> `v6' + DEBUG: `k8' -> `v8' + DEBUG: `k1' -> `v1' + DEBUG: `k3' -> `v3' + DEBUG: `k7' -> `v7' + + $ falist_release LIST + $ falist_release UPDATE1 $ check_no_alist_artifacts diff -r f6d296c5868e -r 33df05108ba1 tests/testsetup.sh --- a/tests/testsetup.sh Fri Oct 18 14:02:18 2024 +0200 +++ b/tests/testsetup.sh Sat Oct 19 22:40:11 2024 +0200 @@ -27,7 +27,7 @@ #: check_no_alist_artifacts() { # This are all _farr_alist_XXX_prefix variables - if set | grep -E -e '^_farr_KV_.*=' -e '^_farr_K_.*=' -e '^_farr_V_.*='; then + if set | grep -E -e '^_farr_KV_.*=' -e '^_farr_Kb_.*=' -e '^_farr_Ks_.*=' -e '^_farr_Vs_.*='; then return 1 else return 0