Mercurial > hgrepos > FreeBSD > ports > sysutils > local-bsdtools
view share/local-bsdtools/farray.sh @ 763:9ded61e89712
farray.sh: comment
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Mon, 21 Oct 2024 15:14:48 +0200 |
| parents | b72c111e1b76 |
| children | 711c0a11d642 |
line wrap: on
line source
#!/bin/sh # -*- indent-tabs-mode: nil; -*- #: #: A simple library to emulate simple arrays (one-dimensional) and alists #: in FreeBSD's POSIX shell :command:`/bin/sh`. #: #: :Author: Franz Glasner #: :Copyright: (c) 2024 Franz Glasner. #: All rights reserved. #: :License: BSD 3-Clause "New" or "Revised" License. #: See LICENSE for details. #: If you cannot find LICENSE see #: <https://opensource.org/licenses/BSD-3-Clause> #: :ID: @(#)@@SIMPLEVERSIONTAG@@ #: #: This implementation is somewhat inspired by #: https://unix.stackexchange.com/questions/137566/arrays-in-unix-bourne-shell #: #: Is implements one-dimensional array with one-based indexing. #: #: Hints and rules for arrays: #: #: - Every array has a NAME. #: - One-based indexing is used. #: - The array with name NAME is associated with a primary global or local #: shell variable with the same name. Its value contains a token TOKEN that #: is used in variable names that make up the backing store of the array. #: The value of this variable has the form ``_farr_A[?]:<TOKEN>``. #: - Array elements for array NAME that has associated TOKEN are stored in #: global variables named ``_farr_A_<TOKEN>_<index-number>`` (values) #: and ``_farr_A_<TOKEN>__`` (length). #: - An array name must conform to shell variable naming conventions. #: - Currently the number of of elements of an array must be >= 0. #: - Variable NAME can also be a local variable. In this case it MUST #: be initialized with an empty value first. #: - Any unset global variables ``_farr_A_<TOKEN>__`` variable is a severe #: error normally and forces an immediate error ``exit``. #: Exceptions to this rule are documented. #: #: #: This module also contains a very rough implementation of an alist #: (aka dict, aka map). #: This is implemented somewhat similar to arrays. #: #: Hints and rules for alists: #: #: - Every alist has a NAME. #: - The alist with name NAME is associated with a primary global or local #: shell variable with the same name. Its value contains a token TOKEN that #: is used in variable names that make up the backing stores of the alist. #: The value of this variable is of the form ``_farr_KV[?,?]:<TOKEN>``. #: - The logical length of the alist (i.e. the number of key-value pairs) #: is stored in a global ``_farr_KV_<TOKEN>__``. #: - Every key-value pair has an associated storage "pointer" SPTR. #: #: * Keys are stored in an associated global ``_farr_Ks_<TOKEN>_<SPTR>``. #: They are stored in the form ``<sptr-prev>;<sptr-next>@<key>``. #: They make up a double-linked list and preserve information about #: the insertion order. #: * Values are stored in an associated global ``_farr_Vs_<TOKEN>_<SPTR>``. #: They are stored in undecorated form. #: #: Deleted entries are removed instantly from this lists. #: #: "Pointers" to the first and last items are stored in a variabled named #: ``_farr_KV_<TOKEN>_P`` in the form ``<sptr-first>;<sptr-last>``. #: #: - Additionally each key is stored in an array-like lexicographically ordered #: structure at an index BSIDX. The name of the corresponding shell #: variables are ``_farr_Kb_<TOKEN>_<BSIDX>. Its length is stored in #: ``_farr_KV_<TOKEN>_B``. The form is ``<sptr>;V@<key>``. #: Deletions are stored as "tombstones" in the form ``0;D@<key>``. #: - An alist name must conform to shell variable naming conventions. #: - Currently the number of of elements of an alist must be >= 0. #: - Variable NAME can also be a local variable. In this case it MUST #: be initialized with an empty value first. #: - Any unset global variables ``_farr_KV_<TOKEN>__`` variable is a severe #: error normally and forces an immediate fatal error exit by calling #: ``exit``. #: - The same is true if other backing shell variables are unexpectedly unset. #: Exceptions to this rule are documented. #: - An alist preserves insertion order. Note that updating a key does not #: affect the order. Keys added after deletion are inserted at the end. #: - Alists compare equal if and only if they have the same (key, value) pairs #: (regardless of insertion order). #: #: Hints and rules for indexes: #: #: For all functions these facts hold: #: #: - Every object (array, alist) has a current length LEN. #: - Indexes range from 1 <= INDEX <= LEN. #: - Indexes 0, -1, -2 are evaluated to LEN + given index. #: So `0` indexes the last item, `-1` the second last and `-LEN+1` the #: first item. #: - All index (and length) values must be given as decimal numbers. #: #: `null` (aka empty) and/or missing indexes are handled differently in #: the function context. #: #: Resource management: #: #: Done by reference counting of arrays and alists. No borrowed references #: are ever returned. Every array and alist must be released using #: `farray_release` or `falist_release`. #: #: Important: #: All names that start with ``_farr_`` or ``__farr_`` are reserved #: 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[?]:' # token value prefix _farr_array_prefix=_farr_A_ _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 #: #: Internal error for fatal errors. #: #: Args: #: $@ (str): The error messages. #: _farr_fatal() { echo "ERROR:" "$@" 1>&2 exit 70 # EX_SOFTWARE } : #: Internal error for other error message. #: #: Args: #: $@ (str): The error messages. #: _farr_err() { echo "ERROR:" "$@" 1>&2 } #: #: Check whether given argument string is a decimal number string #: #: Args: #: $1: The string to be checked #: #: Returns: #: int: 0 (truish) if `$1` is a valid decimal number, #: 1 (falsy) otherwise #: #: See Also: #: https://mywiki.wooledge.org/BashFAQ/054 #: _farr_is_decimal_number() { case "$1" in '') # empty is not allowed return 1;; +|-) # just the sign is not allowed return 1;; *) # FALL THROUGH ;; esac # here we can trim the optional sign safely case "${1#[-+]}" in 0*[!01234567]*) # non-octal return 1;; *[!0123456789]*) # non-decimal return 1;; *) return 0;; esac } #: #: 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;; 0) # a single 0 is allowed return 0;; 0*) # other "octal" numbers of having a leading zero are 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. #: #: Args: #: $1 (str): The name of a variable where to store the result #: $2 (int, null): The index value to check. #: It must be a valid decimal number or the `null` value. #: If the argument is `null` then ``$3 + 1`` is used as the #: resulting effective index value. For this to work a #: valid length must be given in `$3`. #: If the argument is `<= 0` then -- if `$3` is given #: the effective index is computed as ``$3 + $2``. #: $3 (int, optional): If given a length value that is used to compute #: effective index values in some cases (`$2` is null or #: `$2` <= 0). #: _farr_make_index() { local __farr_mi_varname __farr_mi_index __farr_mi_length __farr_mi_varname="$1" __farr_mi_index="$2" __farr_mi_length="${3-}" # If it is given and non-null it must be a valid decimal length number if [ -n "${__farr_mi_length}" ]; then _farr_is_decimal_number "${__farr_mi_length}" || _farr_fatal "given length is not a valid decimal number" fi if [ -z "${__farr_mi_index}" ]; then if [ -n "${__farr_mi_length}" ]; then setvar "${__farr_mi_varname}" $((__farr_mi_length + 1)) else _farr_fatal "length not given: cannot autocompute index" fi else _farr_is_decimal_number "${__farr_mi_index}" || _farr_fatal "given index is not a valid decimal number" if [ "${__farr_mi_index}" -le 0 ]; then if [ -n "${__farr_mi_length}" ]; then setvar "${__farr_mi_varname}" $((__farr_mi_length + __farr_mi_index)) else _farr_fatal "cannot compute effective index because no length is given" fi else setvar "${__farr_mi_varname}" "${__farr_mi_index}" fi fi } #: #: Quote the given input using "Dollar-Single-Quotes" to be safely used in #: evals. #: #: Args: #: $1: The value to be quoted. #: #: Output (stdout): #: The properly quoted string including surrounding "Dollar-Single-Quotes" #: (e.g. $'...'). #: #: Using "Dollar-Single-Quotes" is not strictly posixly correct. But #: FreeBSD's :command:`/bin/sh` understands them. #: #: From FreeBSD's :manpage:`sh(1)`: #: #: Dollar-Single Quotes #: #: Enclosing characters between ``$'`` and ``'`` preserves the literal #: meaning of all characters except backslashes and single quotes. #: #: A backslash introduces a C-style escape sequence. #: Most important here: #: #: ``\\`` #: Literal backslash #: #: ``\'`` #: Literal single-quote #: _farr_quote_for_eval_dsq() { printf "\$'%s'" "$(_farr_inner_quote_for_dsq "${1}")" } #: #: Helper to quote for "Dollar-Single-Quotes". #: #: This function handles just the quoting mechanics. It does not surround #: the result with any other string decoration. #: See also `_farr_quote_for_eval_dsq`. #: _farr_inner_quote_for_dsq() { printf "%s" "${1}" \ | LC_ALL=C /usr/bin/sed -e $'s/\\\\/\\\\\\\\/g' -e $'s/\'/\\\\\'/g' # escape a backslash escape a single quote # ' # make Emacs happy for correct syntax highlighting } #: #: Quote the given input string for eval. #: #: If the argument contains a ``'`` character then "Dollar-Single-Quotes" #: are used; "Single-Quotes" otherwise. #: #: Args: #: $1: The value to be quoted. #: #: Output (stdout): #: The properly quoted string including surrounding quotes. #: #: _farr_quote_for_eval() { case "${1}" in *\'*) _farr_quote_for_eval_dsq "${1}" ;; *) printf "'%s'" "${1}" ;; esac } #: #: Quote the given input using just POSIX correct "Single-Quotes" to be safely #: used in evals. #: #: Args: #: $1: The value to be quoted. #: #: Output (stdout): #: The properly quoted string including surrounding "Single-Quotes" #: (e.g. '...'). #: #: #: From FreeBSD's :manpage:`sh(1)`: #: #: Single Quotes #: #: Enclosing characters in single quotes preserves the literal #: meaning of all the characters (except single quotes, making it #: impossible to put single-quotes in a single-quoted string). #: _farr_quote_for_eval_sq() { printf "'%s'" "$(_farr_inner_quote_for_sq "${1}")" } #: #: Helper to quote for "Single-Quotes". #: #: This function handles just the quoting mechanics. It does not surround #: the result with any other string decoration. #: See also `_farr_quote_for_eval_dsq`. #: _farr_inner_quote_for_sq() { printf "%s" "${1}" \ | LC_ALL=C /usr/bin/sed -e $'s/\'/\'\\\\\\\'\'/g' # escape a single quote # (here double-escaped: for $'...' AND sed # ' # make Emacs happy for correct syntax highlighting } #: #: Quote the given input string for `eval` and produce a strictly posixly #: correct encoding. #: #: It does not use FreeBSD's ``$'...'`` feature in its :command:`/bin/sh`. #: #: If the argument contains a ``'`` character then a proper "Single-Quotes" #: combination is used. #: #: Args: #: $1: The value to be quoted. #: #: Output (stdout): #: The properly quoted string including surrounding quotes. #: #: _farr_quote_for_eval_strict() { case "${1}" in *\'*) _farr_quote_for_eval_sq "${1}" ;; *) printf "'%s'" "${1}" ;; esac } #: #: Implementation of typing #: #: Args: #: $1 (str): The (variable) name of an object #: #: Output (stdout): #: - array: an array #: - alist: an alist #: - null: an empty thing #: - value: some other shell type (string, number, ...) #: - unknown: if the name in `$1` was not given #: #: Returns: #: int: 0 always #: fobject_type() { local __farr_type_name local __farr_type_token __farr_type_name="${1-}" if [ -n "${__farr_type_name}" ] ; then eval __farr_type_token=\"\$\{"${__farr_type_name}"+SET\}\" if [ "${__farr_type_token}" = 'SET' ] ; then if eval __farr_type_token=\"\$\{"${__farr_type_name}"\}\" ; then case "${__farr_type_token}" in '') printf '%s' 'null';; "${_farr_array_token_prefix}"*) printf '%s' 'array';; "${_farr_alist_token_prefix}"*) printf '%s' 'alist';; *) printf '%s' 'value';; esac else # error in evaluation printf '%s' 'error' fi else # unset printf '%s' 'unknown' fi else # no name given printf '%s' 'unknown' fi return 0 } #: #: Just an official alias for `fobject_type` #: farray_type() { fobject_type "$@" } #: #: Test whether `$1` is an array. #: #: Args: #: $1 (str): The name of an object #: #: Returns: #: int: 0 if `$1` is an array, 1 otherwise #: farray_isarray() { [ "$(fobject_type "$@")" = 'array' ] } #: #: Create a new array. #: #: It is assumed that the array does not exist already. #: #: Args: #: $1 (str): The name of the array. #: Must conform to shell variable naming conventions. #: $2... (optional): Initialization values that will be appended to the #: freshly created array. #: #: Exit: #: Iff the array already exists in some fashion (token and/or storage). #: farray_create() { local __farr_name local __farr_token __farr_gvrname __farr_len __farr_name="${1-}" [ -z "${__farr_name}" ] && _farr_fatal "missing farray name" eval __farr_token=\"\$\{"${__farr_name}"-\}\" [ -n "${__farr_token}" ] && _farr_fatal "object \`${__farr_name}' already created (value \`${__farr_token}')" __farr_token="$(/usr/bin/hexdump -v -e '/1 "%02x"' -n 16 '/dev/urandom')" __farr_gvrname="${_farr_array_prefix}${__farr_token}" # Check whether the variable already exists eval __farr_len=\"\$\{"${__farr_gvrname}"__+SET\}\" [ -n "${__farr_len}" ] && _farr_fatal "farray \`${__farr_name}' already exists: existing token \`${__farr_token}'" # Really create the storage by initializing its length ... setvar "${__farr_gvrname}__" 0 # ... and its reference count setvar "${__farr_gvrname}_C" 1 # And associate the token with the array name setvar "${__farr_name}" "${_farr_array_token_prefix}${__farr_token}" # # When there are values to populate the array with. # Note that the array's name is not shifted away yet and must be taken # into account. It is also needed for `farray_append`. # if [ $# -gt 1 ]; then farray_append "$@" fi } #: #: Internal helper to get all the metadata for an array. #: #: Args: #: $1 (str): The name of the array or a complete array value with prefix #: #: Output (Globals): #: __farr_name (str): The name of the array if the name is given, #: empty if a token value is given. #: __farr_token (str): The token that is the value of the name without its #: token prefix. #: __farr_gvrname (str): The variable prefix for all items. #: __farr_len (int): The length of the array. #: #: Exit: #: Iff the array does not exist in some fashion (token and/or storage). #: _farr_array_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 '') # ambiguous _farr_fatal "missing farray name or token value" ;; "${_farr_array_token_prefix}"*) # A prefixed token value __farr_name='' __farr_token="${__farr_gm_name_or_value#"${_farr_array_token_prefix}"}" ;; *) # A variable name: go through one indirection __farr_name="${__farr_gm_name_or_value}" eval __farr_token=\"\$\{"${__farr_name}"-\}\" case "${__farr_token}" in '') _farr_fatal "object \`${__farr_name}' not created properly: token empty" ;; "${_farr_array_token_prefix}"*) __farr_token="${__farr_token#"${_farr_array_token_prefix}"}" ;; *) _farr_fatal "object \`${__farr_name}' is not an array" ;; esac ;; esac __farr_gvrname="${_farr_array_prefix}${__farr_token}" eval __farr_len=\"\$\{"${__farr_gvrname}"__:+SET\}\" [ -z "${__farr_len}" ] && _farr_fatal "farray \`${__farr_name:-"${__farr_gm_name_or_value}"}' not created properly: no storage for token \`${__farr_token}'" eval __farr_tmp_refcount=\"\$\{"${__farr_gvrname}"_C:+SET\}\" [ -z "${__farr_tmp_refcount}" ] && _farr_fatal "farray \`${__farr_name:-"${__farr_gm_name_or_value}"}' not created properly: no refcnt for token \`${__farr_token}'" eval __farr_len=\"\$\{"${__farr_gvrname}"__\}\" return 0 } #: #: Internal helper to try to get all the metadata for an array. #: #: Args: #: $1 (str): The name of the array or a complete array value with prefix #: #: Output (Globals): #: __farr_name (str): The name of the array if the name is given, #: empty if a token value is given. #: __farr_token (str): The token that is the value of the name without its #: token prefix. #: __farr_gvrname (str): The variable prefix for all items. #: __farr_len (int): The length of the array #: #: Returns: #: 0 if the array exists, 1 if something is missing. #: _farr_array_tryget_meta() { local __farr_gm_name_or_value local __farr_tmp_refcount __farr_token='' __farr_gvrname='' __farr_len='' __farr_name="${1-}" __farr_gm_name_or_value="${1-}" case "${__farr_gm_name_or_value}" in '') # ambiguous _farr_err "missing farray name or token value" return 1 ;; "${_farr_array_token_prefix}"*) # A prefixed token value __farr_name='' __farr_token="${__farr_gm_name_or_value#"${_farr_array_token_prefix}"}" ;; *) # A variable name: go through one indirection __farr_name="${__farr_gm_name_or_value}" eval __farr_token=\"\$\{"${__farr_name}"-\}\" case "${__farr_token}" in '') _farr_err "object \`${__farr_name}' not created properly: token empty" return 1 ;; "${_farr_array_token_prefix}"*) __farr_token="${__farr_token#"${_farr_array_token_prefix}"}" ;; *) _farr_err "object \`${__farr_name}' is not an array" return 1 ;; esac ;; esac __farr_gvrname="${_farr_array_prefix}${__farr_token}" eval __farr_len=\"\$\{"${__farr_gvrname}"__:+SET\}\" if [ -z "${__farr_len}" ] ; then _farr_err "farray \`${__farr_name:-"${__farr_gm_name_or_value}"}' not created properly: no storage for token \`${__farr_token}'" return 1 fi eval __farr_tmp_refcount=\"\$\{"${__farr_gvrname}"_C:+SET\}\" if [ -z "${__farr_tmp_refcount}" ] ; then _farr_err "farray \`${__farr_name:-"${__farr_gm_name_or_value}"}' not created properly: no refcnt for token \`${__farr_token}'" return 1 fi eval __farr_len=\"\$\{"${__farr_gvrname}"__\}\" return 0 } #: #: Internal helper to try to get all the metadata for an array. #: #: This function is similar to `_farr_array_tryget_meta` but normally fatal #: errors are reported with a "normal" error return. #: #: All output variables are properly initialized to the `null` value. #: #: Args: #: $1 (str): The name of the array or a complete array value with prefix #: #: Output (Globals): #: __farr_name (str): The name of the array if the name is given, #: empty if a token value is given. #: __farr_token (str): The token that is the value of the name without its #: token prefix. #: __farr_gvrname (str): The variable prefix for all items. #: __farr_len (int): The length of the array #: #: Returns: #: 0 if the array exists, 1 if something is missing or `$1` is not an array #: #: Important: #: No fatal error exits should happen. #: _farr_array_tryget_meta_nonfatal() { local __farr_gm_name_or_value local __farr_tmp_refcount __farr_token='' __farr_gvrname='' __farr_len='' __farr_name="${1-}" __farr_gm_name_or_value="${1-}" case "${__farr_gm_name_or_value}" in '') # ambiguous return 1 ;; "${_farr_array_token_prefix}"*) # A prefixed token value __farr_name='' __farr_token="${__farr_gm_name_or_value#"${_farr_array_token_prefix}"}" ;; *) # A variable name: go through one indirection __farr_name="${__farr_gm_name_or_value}" eval __farr_token=\"\$\{"${__farr_name}"-\}\" case "${__farr_token}" in '') return 1 ;; "${_farr_array_token_prefix}"*) __farr_token="${__farr_token#"${_farr_array_token_prefix}"}" ;; *) return 1 ;; esac ;; esac __farr_gvrname="${_farr_array_prefix}${__farr_token}" eval __farr_len=\"\$\{"${__farr_gvrname}"__:+SET\}\" [ -z "${__farr_len}" ] && return 1 eval __farr_tmp_refcount=\"\$\{"${__farr_gvrname}"_C:+SET\}\" [ -z "${__farr_tmp_refcount}" ] && return 1 eval __farr_len=\"\$\{"${__farr_gvrname}"__\}\" return 0 } #: #: Get the length of an array and put it into a variable #: #: Args: #: $1 (str): The name of the variable to put the length into. #: $2 (str): The name of the array. #: farray_length() { local __farr_varname __farr_name local __farr_token __farr_gvrname __farr_len __farr_varname="${1-}" [ -z "${__farr_varname}" ] && _farr_fatal "missing variable name" shift _farr_array_get_meta "$@" setvar "${__farr_varname}" "${__farr_len}" } #: #: Get the length of an array and print it to stdout. #: #: Args: #: $1 (str): The name of the array. #: #: Output (stdout): #: The number of elements of the array. #: If the array does not exist the output is -1. #: #: Returns: #: 0 (truthy) #: farray_print_length() { local __farr_vn ( farray_length __farr_vn "$@" && printf '%s' "${__farr_vn}"; ) \ || printf '%s' "-1" } #: #: Append one or more values to an existing array. #: #: Args: #: $1 (str): The name of the existing array. #: $2...: The values to append. #: farray_append() { local __farr_name local __farr_token __farr_gvrname __farr_len __farr_len_1 \ __farr_newval _farr_array_get_meta "$@" shift for __farr_newval in "$@"; do __farr_len_1=$((__farr_len + 1)) # # Set value Escape properly: use $' ' and escape any # backslashes and single quotes. # _farr_acquire_object "${__farr_newval}" setvar "${__farr_gvrname}_${__farr_len_1}" "${__farr_newval}" # the implementation below line does not escape properly # eval ${__farr_gvrname}_${__farr_len_1}="\"${__farr_value}\"" # Set new array length # ... persistently in the array data storage setvar "${__farr_gvrname}__" "${__farr_len_1}" # ... and locally (to make looping happy) __farr_len="${__farr_len_1}" done } #: #: Set an array at an index to a value. #: #: Args: #: $1 (str): The name of the existing array. #: $2 (int): The index. #: $3 (optional): The value to set. If the value is not given the null #: will be set. #: #: No holes are allowed in an array: only values at existing indices are #: allowed. As an exception a value can be appended if the given index #: is exactly the current length + 1. #: farray_set() { local __farr_name __farr_index __farr_value local __farr_token __farr_gvrname __farr_len __farr_len_1 \ __farr_old_value _farr_array_get_meta "$@" _farr_make_index __farr_index "${2-}" "${__farr_len}" # first check [ "${__farr_index}" -lt 1 ] && _farr_fatal "index must be >= 1" __farr_value="${3-}" # For proper quoting: see farray_append if [ \( "${__farr_index}" -ge 1 \) -a \( "${__farr_index}" -le "${__farr_len}" \) ]; then _farr_acquire_object "${__farr_value}" # replace a value at an existing index eval __farr_old_value=\"\$\{"${__farr_gvrname}"_"${__farr_index}"\}\" _farr_release_object "${__farr_old_value}" setvar "${__farr_gvrname}_${__farr_index}" "${__farr_value}" else __farr_len_1=$((__farr_len + 1)) if [ "${__farr_index}" -eq "${__farr_len_1}" ]; then _farr_acquire_object "${__farr_value}" # append value setvar "${__farr_gvrname}_${__farr_len_1}" "${__farr_value}" # and set new length setvar "${__farr_gvrname}__" "${__farr_len_1}" else _farr_fatal "array index out of bounds (cannot create holes)" fi fi } #: #: Get an array value from a given index and put it into given variable. #: #: Args: #: $1 (str): The name of the variable to put the value into. #: $2 (str): The name of the existing array. #: $3 (int): The index. #: farray_get() { local __farr_varname __farr_name __farr_index local __farr_token __farr_gvrname __farr_len \ __farr_get_value __farr_varname="${1-}" [ -z "${__farr_varname}" ] && _farr_fatal "missing variable name" shift _farr_array_get_meta "$@" _farr_make_index __farr_index "${2-}" "${__farr_len}" # check index range if [ \( "${__farr_index}" -lt 1 \) -o \( "${__farr_index}" -gt "${__farr_len}" \) ]; then _farr_fatal "array index out of bounds" fi eval __farr_get_value=\"\$\{"${__farr_gvrname}"_"${__farr_index}"\}\" _farr_acquire_object "${__farr_get_value}" setvar "${__farr_varname}" "${__farr_get_value}" } #: #: Try to get an array value from a given index into a variable #: #: Args: #: $1 (str): The name of the variable to put the value into. #: $2 (str): The name of the existing array. #: $3 (int): The index. #: #: Returns: #: 0 (truthy) on success, #: 1 (falsy) if the given index is out of bounds (i.e. EOD). #: #: Exit: #: Other errors (missing array name, missing index value) are considered #: fatal and call `_farr_fatal` (i.e. `exit`). #: farray_tryget() { local __farr_varname __farr_name __farr_index local __farr_token __farr_gvrname __farr_len \ __farr_get_value __farr_varname="${1-}" [ -z "${__farr_varname}" ] && _farr_fatal "missing variable name" shift _farr_array_get_meta "$@" _farr_make_index __farr_index "${2-}" "${__farr_len}" # check index range if [ \( "${__farr_index}" -lt 1 \) -o \( "${__farr_index}" -gt "${__farr_len}" \) ]; then return 1 fi eval __farr_get_value=\"\$\{"${__farr_gvrname}"_"${__farr_index}"\}\" _farr_acquire_object "${__farr_get_value}" setvar "${__farr_varname}" "${__farr_get_value}" return 0 } #: #: Delete an array value at a given index. #: #: Args: #: $1 (str): The name of the existing array. #: $2 (int): The index to delete to. #: farray_del() { local __farr_name __farr_index local __farr_token __farr_gvrname __farr_len __farr_idx \ __farr_del_value _farr_array_get_meta "$@" _farr_make_index __farr_index "${2-}" "${__farr_len}" # check index range if [ \( "${__farr_index}" -lt 1 \) -o \( "${__farr_index}" -gt "${__farr_len}" \) ]; then _farr_fatal "array index out of bounds" fi # Release the item at index eval __farr_del_value=\"\$\{"${__farr_gvrname}"_"${__farr_index}"\}\" _farr_release_object "${__farr_del_value}" # Move all other items down by one __farr_idx="${__farr_index}" while [ "${__farr_idx}" -lt "${__farr_len}" ]; do # copy the following value to the current index eval "${__farr_gvrname}"_"${__farr_idx}"=\"\$\{"${__farr_gvrname}"_$((__farr_idx + 1))\}\" __farr_idx=$((__farr_idx + 1)) done # Drop the last item eval unset "${__farr_gvrname}"_"${__farr_idx}" # Set the new length setvar "${__farr_gvrname}__" $((__farr_len - 1)) } #: #: Remove the elements designated by an index and a length from an array, #: and replace them with the elements of another array list, if any. #: #: Args: #: $1 (str, null): The name of an array where the deleted items will be #: appended to. May be the `null` value if this is not #: desired: nothing will be done with the deleted elements. #: $2 (str): The name of the array that is to be spliced #: $3 (int, null): Index, where to remove and/or insert elements. #: A `null` index value is evaluated to `len + 1`. #: $4 (int, null): The length -- the number of elements to delete at given #: index. #: If `null` then the length from index to the end of the #: array is applied ("automatic"). #: $5 (str, null, optional): An array whose elements will be inserted at #: given index. #: #: Using this universal function you can insert, replace or delete items. #: #: Examples: #: #: Using ARRAY for the array to be changed and INSERTED for the new #: items and DELETED/POPPED for the returned items: #: #: Prepend (insert at the start position):: #: #: farray_splice "" ARRAY 1 0 INSERTED #: #: Extend (insert after the end position):: #: #: farray_splice "" ARRAY "" 0 INSERTED #: #: Copy INSERTED into ARRAY, replacing the complete existing content:: #: #: farray_splice "" ARRAY 1 "" INSERTED #: #: Copy INSERTED into ARRAY, replacing the complete existing content and #: returning it in DELETED:: #: #: farray_splice DELETED ARRAY 1 "" INSERTED #: #: Pop the first item:: #: #: farray_splice POPPED ARRAY 1 1 #: #: Pop the last item:: #: #: farray_splice POPPED ARRAY 0 1 #: #: Shift by one item (similar to pop the first item, but no return value):: #: #: farray_splice "" ARRAY 1 1 #: farray_splice() { local __farr_del_array __farr_l_name __farr_index __farr_length \ __farr_r_name local __farr_del_name __farr_del_token __farr_del_gvrname __farr_del_len \ __farr_l_token __farr_l_gvrname __farr_l_len \ __farr_r_token __farr_r_gvrname __farr_r_len \ __farr_name __farr_token __farr_gvrname __farr_len \ __farr_off __farr_v __farr_delta __farr_src_idx __farr_dst_idx [ $# -lt 4 ] && _farr_fatal "missing required arguments" __farr_del_name='' if [ -n "${1}" ] && _farr_array_tryget_meta "${1}"; then __farr_del_array="${1}" __farr_del_name="${__farr_name}" __farr_del_token="${__farr_token}" __farr_del_gvrname="${__farr_gvrname}" __farr_del_len="${__farr_len}" fi _farr_array_get_meta "${2}" __farr_l_name="${__farr_name}" __farr_l_token="${__farr_token}" __farr_l_gvrname="${__farr_gvrname}" __farr_l_len="${__farr_len}" __farr_index="${3}" __farr_length="${4}" if _farr_array_tryget_meta_nonfatal "${5-}"; then __farr_r_name="${__farr_name}" __farr_r_token="${__farr_token}" __farr_r_gvrname="${__farr_gvrname}" __farr_r_len="${__farr_len}" else __farr_r_name='' __farr_r_len=0 fi _farr_make_index __farr_index "${__farr_index}" "${__farr_l_len}" # NOTE: index value array_length + 1 is allowed: splice at the end [ \( "${__farr_index}" -lt 1 \) -o \( "${__farr_index}" -gt "$((__farr_l_len + 1))" \) ] && _farr_fatal "index out of range" # also check the given length if [ -z "${__farr_length}" ]; then __farr_length="$((__farr_l_len - __farr_index + 1))" else _farr_is_decimal_number "${__farr_length}" || _farr_fatal "given length is not a valid number" [ \( "${__farr_length}" -lt 0 \) -o \( "${__farr_length}" -gt $((__farr_l_len - __farr_index + 1)) \) ] && _farr_fatal "length out of valid range" fi if [ "${__farr_length}" -eq "${__farr_r_len}" ]; then # Just replace __farr_off=0 while [ "${__farr_off}" -lt "${__farr_length}" ]; do eval __farr_v=\"\$\{"${__farr_l_gvrname}"_$((__farr_index + __farr_off))\}\" if [ -n "${__farr_del_array}" ]; then farray_append "${__farr_del_array}" "${__farr_v}" fi _farr_release_object "${__farr_v}" eval __farr_v=\"\$\{"${__farr_r_gvrname}"_$((__farr_off + 1))\}\" _farr_acquire_object "${__farr_v}" setvar "${__farr_l_gvrname}"_$((__farr_index + __farr_off)) "${__farr_v}" __farr_off=$((__farr_off + 1)) done elif [ "${__farr_length}" -gt "${__farr_r_len}" ]; then # More to delete than to copy: the resulting array shrinks __farr_delta=$((__farr_length - __farr_r_len)) __farr_off=0 while [ "${__farr_off}" -lt "${__farr_r_len}" ]; do eval __farr_v=\"\$\{"${__farr_l_gvrname}"_$((__farr_index + __farr_off))\}\" if [ -n "${__farr_del_array}" ]; then farray_append "${__farr_del_array}" "${__farr_v}" fi _farr_release_object "${__farr_v}" eval __farr_v=\"\$\{"${__farr_r_gvrname}"_$((__farr_off + 1))\}\" _farr_acquire_object "${__farr_v}" setvar "${__farr_l_gvrname}"_$((__farr_index + __farr_off)) "${__farr_v}" __farr_off=$((__farr_off + 1)) done # Copy / unset the rest that is to delete while [ "${__farr_off}" -lt "${__farr_length}" ]; do eval __farr_v=\"\$\{"${__farr_l_gvrname}"_$((__farr_index + __farr_off))\}\" if [ -n "${__farr_del_array}" ]; then farray_append "${__farr_del_array}" "${__farr_v}" fi _farr_release_object "${__farr_v}" eval unset "${__farr_l_gvrname}"_$((__farr_index + __farr_off)) __farr_off=$((__farr_off + 1)) done # Move the rest -- here no refcount changes __farr_src_idx=$((__farr_index + __farr_length)) __farr_dst_idx=$((__farr_index + __farr_r_len)) while [ "${__farr_src_idx}" -le "${__farr_l_len}" ]; do eval __farr_v=\"\$\{"${__farr_l_gvrname}"_"${__farr_src_idx}"\}\" setvar "${__farr_l_gvrname}"_"${__farr_dst_idx}" "${__farr_v}" eval unset "${__farr_l_gvrname}"_"${__farr_src_idx}" __farr_src_idx=$((__farr_src_idx + 1)) __farr_dst_idx=$((__farr_dst_idx + 1)) done # Adjust the length setvar "${__farr_l_gvrname}__" $((__farr_l_len - __farr_delta)) else # More to copy than to delete: the resulting array grows __farr_delta=$((__farr_r_len - __farr_length)) __farr_off=0 while [ "${__farr_off}" -lt "${__farr_length}" ]; do eval __farr_v=\"\$\{"${__farr_l_gvrname}"_$((__farr_index + __farr_off))\}\" if [ -n "${__farr_del_array}" ]; then farray_append "${__farr_del_array}" "${__farr_v}" fi _farr_release_object "${__farr_v}" eval __farr_v=\"\$\{"${__farr_r_gvrname}"_$((__farr_off + 1))\}\" _farr_acquire_object "${__farr_v}" setvar "${__farr_l_gvrname}"_$((__farr_index + __farr_off)) "${__farr_v}" __farr_off=$((__farr_off + 1)) done # # Make room from last item to the first non-deleted # -- no refcount changes # __farr_src_idx=${__farr_l_len} __farr_dst_idx=$((__farr_src_idx + __farr_delta)) while [ "${__farr_src_idx}" -ge $((__farr_index + __farr_length)) ]; do eval __farr_v=\"\$\{"${__farr_l_gvrname}"_"${__farr_src_idx}"\}\" setvar "${__farr_l_gvrname}"_"${__farr_dst_idx}" "${__farr_v}" __farr_src_idx=$((__farr_src_idx - 1)) __farr_dst_idx=$((__farr_dst_idx - 1)) done # # Copy the rest of the given data to be inserted # # NOTE: The offset variable __farr_off from above is NOT changed in # between and valid here! # while [ "${__farr_off}" -lt "${__farr_r_len}" ]; do eval __farr_v=\"\$\{"${__farr_r_gvrname}"_$((__farr_off + 1))\}\" _farr_acquire_object "${__farr_v}" setvar "${__farr_l_gvrname}"_$((__farr_index + __farr_off)) "${__farr_v}" __farr_off=$((__farr_off + 1)) done # Adjust the length setvar "${__farr_l_gvrname}"__ $((__farr_l_len + __farr_delta)) fi return 0 } #: #: Merge two *sorted* arrays using the mergesort algorithm. #: #: Sources: - https://de.wikipedia.org/wiki/Mergesort #: - https://en.wikipedia.org/wiki/Merge_sort #: #: Args: #: $1 (str): The result array where all the sorted items will be appended to #: $2 (str): The first sorted input array #: $3 (str): The second sorted input array #: farray_merge() { local __farr_result __farr_input_1 __farr_input_2 local __farr_name __farr_token __farr_gvrname __farr_len \ __farr_name_1 __farr_token_1 __farr_gvrname_1 __farr_len_1 \ __farr_name_2 __farr_token_2 __farr_gvrname_2 __farr_len_2 \ __farr_idx_1 __farr_idx_2 __farr_item_1 __farr_item_2 _farr_array_get_meta "$2" __farr_input_1="$2" __farr_name_1="${__farr_name}" __farr_token_1="${__farr_token}" __farr_gvrname_1="${__farr_gvrname}" __farr_len_1="${__farr_len}" _farr_array_get_meta "$3" __farr_input_2="$3" __farr_name_2="${__farr_name}" __farr_token_2="${__farr_token}" __farr_gvrname_2="${__farr_gvrname}" __farr_len_2="${__farr_len}" _farr_array_get_meta "$1" __farr_result="$1" __farr_idx_1=1 if [ "${__farr_idx_1}" -le "${__farr_len_1}" ]; then eval __farr_item_1=\"\$\{"${__farr_gvrname_1}"_"${__farr_idx_1}"\}\" fi __farr_idx_2=1 if [ "${__farr_idx_2}" -le "${__farr_len_2}" ]; then eval __farr_item_2=\"\$\{"${__farr_gvrname_2}"_"${__farr_idx_2}"\}\" fi while [ \( "${__farr_idx_1}" -le "${__farr_len_1}" \) -a \( "${__farr_idx_2}" -le "${__farr_len_2}" \) ]; do if [ \( "${__farr_item_1}" '<' "${__farr_item_2}" \) -o \( "${__farr_item_1}" '=' "${__farr_item_2}" \) ] ; then farray_append "${__farr_result}" "${__farr_item_1}" __farr_idx_1=$((__farr_idx_1 + 1)) if [ "${__farr_idx_1}" -le "${__farr_len_1}" ]; then eval __farr_item_1=\"\$\{"${__farr_gvrname_1}"_"${__farr_idx_1}"\}\" fi else farray_append "${__farr_result}" "${__farr_item_2}" __farr_idx_2=$((__farr_idx_2 + 1)) if [ "${__farr_idx_2}" -le "${__farr_len_2}" ]; then eval __farr_item_2=\"\$\{"${__farr_gvrname_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_item_1=\"\$\{"${__farr_gvrname_1}"_"${__farr_idx_1}"\}\" farray_append "${__farr_result}" "${__farr_item_1}" __farr_idx_1=$((__farr_idx_1 + 1)) done while [ "${__farr_idx_2}" -le "${__farr_len_2}" ]; do eval __farr_item_2=\"\$\{"${__farr_gvrname_2}"_"${__farr_idx_2}"\}\" farray_append "${__farr_result}" "${__farr_item_2}" __farr_idx_2=$((__farr_idx_2 + 1)) done return 0 } #: #: Empty an existing array. #: #: Args: #: $1 (str): The name of the existing array. #: farray_clear() { local __farr_name local __farr_token __farr_gvrname __farr_len __farr_idx __farr_del_value _farr_array_get_meta "$@" __farr_idx=1 while [ "${__farr_idx}" -le "${__farr_len}" ]; do eval __farr_del_value=\"\$\{"${__farr_gvrname}"_"${__farr_idx}"\}\" _farr_release_object "${__farr_del_value}" eval unset "${__farr_gvrname}"_"${__farr_idx}" __farr_idx=$((__farr_idx + 1)) done # Length is now zero setvar "${__farr_gvrname}"__ 0 } #: #: Release an array. #: #: Decrement its reference count. If it reaches zero then destroy and #: unset an array and all its elements. #: #: Args: #: $1 (str): The name of an array. The array may exist or not. #: #: Returns: #: - A truthy value if the array existed and has been deleted. #: - A falsy value if the array does not exist. #: farray_release() { local __farr_name local __farr_token __farr_gvrname __farr_len \ __farr_idx __farr_del_value \ __farr_refcnt _farr_array_tryget_meta "$@" || return 1 # # Decrement the reference count. # Note that the existence of the reference count proper is already # checked by `_farr_array_tryget_meta`. # eval __farr_refcnt=\"\$\{"${__farr_gvrname}"_C\}\" __farr_refcnt=$((__farr_refcnt - 1)) setvar "${__farr_gvrname}"_C "${__farr_refcnt}" if [ "${__farr_refcnt}" -ne 0 ] ; then # Clean out the array name from the token always [ -n "${__farr_name}" ] && eval "${__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_gvrname}"_"${__farr_idx}"\}\" _farr_release_object "${__farr_del_value}" eval unset "${__farr_gvrname}"_"${__farr_idx}" __farr_idx=$((__farr_idx + 1)) done # Remove length itself ... eval unset "${__farr_gvrname}__" # ... and the reference count eval unset "${__farr_gvrname}_C" # Clean out the array name from the token [ -n "${__farr_name}" ] && setvar "${__farr_name}" '' return 0 } #: #: Test the boolean truth of an array. #: #: Args: #: $1 (str): The name of the array. #: #: Returns: #: int: 0 (truthy) if the array contains elements, #: 1 (falsy) otherwise (empty or not created/destroyed properly). #: farray_istrue() { local __farr_name local __farr_token __farr_gvrname __farr_len _farr_array_tryget_meta "$@" || return 1 if [ "${__farr_len}" -gt 0 ]; then return 0 else return 1 fi } #: #: Determine whether any of given values is found within the array. #: #: Args: #: $1 (str): The name of an existing array. #: $2...: The value to search for. #: #: Returns: #: int: 0 (truish) if any of the argument value is found within the given #: array, #: 1 (falsy) if the value is not found. #: farray_contains() { local __farr_name local __farr_token __farr_gvrname __farr_len \ __farr_idx __farr_existing_value __farr_searched_value _farr_array_get_meta "$@" shift [ $# -lt 1 ] && _farr_fatal "no search value given" for __farr_searched_value in "$@"; do __farr_idx=1 while [ "${__farr_idx}" -le "${__farr_len}" ]; do eval __farr_existing_value=\"\$\{"${__farr_gvrname}"_"${__farr_idx}"\}\" [ "${__farr_existing_value}" = "${__farr_searched_value}" ] && return 0 __farr_idx=$((__farr_idx + 1)) done done return 1 } #: #: Try to find the index of a given value in an existing array. #: #: Args: #: $1 (str): The name of a variable where to put the found index into #: $2 (str): The name of an existing array #: $3: The value to search for #: $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 (truish) if the argument value is found within the given array #: and index constraints #: - 1 (falsy) otherwise #: farray_find() { local __farr_varname __farr_name __farr_searched_value \ __farr_start __farr_end local __farr_token __farr_gvrname __farr_len \ __farr_cur_find_idx __farr_existing_value __farr_varname="${1-}" [ -z "${__farr_varname}" ] && _farr_fatal "missing variable name" shift _farr_array_get_meta "$@" __farr_searched_value="${2+SET}" [ -z "${__farr_searched_value}" ] && _farr_fatal "no search value given" __farr_searched_value="$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_existing_value=\"\$\{"${__farr_gvrname}"_"${__farr_cur_find_idx}"\}\" if [ "${__farr_existing_value}" = "${__farr_searched_value}" ]; then #printf "%d" ${__farr_cur_find_idx} setvar "${__farr_varname}" "${__farr_cur_find_idx}" return 0 fi __farr_cur_find_idx=$((__farr_cur_find_idx + 1)) done return 1 } #: #: Sort using Gnome Sort. #: #: This is a very simple stable sort (a variant of insertion sort). #: #: See: https://en.wikipedia.org/wiki/Gnome_sort #: #: Args: #: $1: The array to sort to #: farray_sort() { local __farr_name local __farr_token __farr_gvrname __farr_len \ __farr_pos __farr_val __farr_val_1 _farr_array_get_meta "$@" __farr_pos=1 while [ "${__farr_pos}" -le "${__farr_len}" ]; do if [ "${__farr_pos}" -eq 1 ]; then __farr_pos=$((__farr_pos + 1)) else eval __farr_val=\"\$\{"${__farr_gvrname}"_"${__farr_pos}"\}\" eval __farr_val_1=\"\$\{"${__farr_gvrname}"_$((__farr_pos - 1))\}\" if [ "${__farr_val}" '>' "${__farr_val_1}" ] || [ "${__farr_val}" '=' "${__farr_val_1}" ] ; then __farr_pos=$((__farr_pos + 1)) else # swap setvar "${__farr_gvrname}_${__farr_pos}" "${__farr_val_1}" setvar "${__farr_gvrname}_$((__farr_pos - 1))" "${__farr_val}" __farr_pos=$((__farr_pos - 1)) fi fi done } #: #: Try to find the index of a given value in an existing array. #: #: It assumes that the array is sorted and uses a binary search algorithm #: and uses lexicographical comparisons. #: #: Args: #: $1 (str): The name of a variable where to put the found index into #: $2 (str): The name of an existing array #: $3: The value to search for #: $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 (truish) if the argument value is found within the given array #: and index constraints #: - 1 (falsy) otherwise #: farray_binsearch() { local __farr_varname __farr_name __farr_searched_value \ __farr_start __farr_end local __farr_token __farr_gvrname __farr_len \ __farr_lo __farr_hi __farr_mid __farr_mid_value __farr_varname="${1-}" [ -z "${__farr_varname}" ] && _farr_fatal "missing variable name" shift _farr_array_get_meta "$@" __farr_searched_value="${2+SET}" [ -z "${__farr_searched_value}" ] && _farr_fatal "no search value given" __farr_searched_value="$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_lo="${__farr_start}" __farr_hi="${__farr_end}" while [ "${__farr_lo}" -le "${__farr_hi}" ]; do __farr_mid=$((__farr_lo + ((__farr_hi-__farr_lo)/2) )) eval __farr_mid_value=\"\$\{"${__farr_gvrname}"_"${__farr_mid}"\}\" 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 # found setvar "${__farr_varname}" "${__farr_mid}" return 0 fi done return 1 # not found } #: #: Try to find the index of a given value in an existing array. #: #: It assumes that the array is sorted and uses an approximate binary search #: algorithm: it finds the leftmost match: #: #: On duplicate keys it is the left mode one that is found. #: On missing keys it is the index position at which the given key #: would need to be inserted. #: #: Comparisons are lexicographic. #: #: Args: #: $1 (str): The name of a variable where to put the found index into #: $2 (str): The name of an existing array #: $3: The value to search for #: #: Returns: #: int: 0 #: farray_binsearch_leftmost() { local __farr_varname __farr_name __farr_searched_value local __farr_token __farr_gvrname __farr_len \ __farr_lo __farr_hi __farr_mid __farr_mid_value __farr_varname="${1-}" [ -z "${__farr_varname}" ] && _farr_fatal "missing variable name" shift _farr_array_get_meta "$@" __farr_searched_value="${2+SET}" [ -z "${__farr_searched_value}" ] && _farr_fatal "no search value given" __farr_searched_value="$2" __farr_lo=1 __farr_hi=$((__farr_len + 1)) while [ "${__farr_lo}" -lt "${__farr_hi}" ]; do __farr_mid=$((__farr_lo + ((__farr_hi-__farr_lo)/2) )) eval __farr_mid_value=\"\$\{"${__farr_gvrname}"_"${__farr_mid}"\}\" if [ "${__farr_searched_value}" '>' "${__farr_mid_value}" ]; then __farr_lo=$((__farr_mid + 1)) else __farr_hi=${__farr_mid} fi done setvar "${__farr_varname}" "${__farr_lo}" return 0 } #: #: Join all the elements in given array with a separator and put the result #: into a variable. #: #: Args: #: $1 (str): The name of a variable where to put joined string value into. #: $2 (str): The name of an existing array. #: $3 (str, optional): The separator (default: a space `` ``). #: farray_join() { local __farr_varname __farr_name __farr_separator local __farr_token __farr_gvrname __farr_len __farr_join_idx \ __farr_command __farr_real_separator __farr_current_value __farr_varname="${1-}" [ -z "${__farr_varname}" ] && _farr_fatal "missing variable name" shift _farr_array_get_meta "$@" __farr_separator="${2-" "}" __farr_real_separator='' __farr_command='' __farr_join_idx=1 while [ "${__farr_join_idx}" -le "${__farr_len}" ]; do eval __farr_current_value=\"\$\{"${__farr_gvrname}"_"${__farr_join_idx}"\}\" __farr_command="${__farr_command}${__farr_real_separator}${__farr_current_value}" __farr_real_separator="${__farr_separator}" __farr_join_idx=$((__farr_join_idx + 1)) done setvar "${__farr_varname}" "${__farr_command}" } #: #: Join all the elements in given array with a space `` `` character while #: escaping properly for feeding into shell's ``eval`` and put it into a #: variable. #: #: It is meant that ``eval "$(farray_join_for_eval ARRAY)"`` is safe: #: every item of the array becomes a word when the eval evaluates the #: joined string. #: #: Args: #: $1 (str): The name of a variable where to put joined and properly encoded #: string value into. #: $2 (str): The name of an existing array. #: #: Output (stdout): #: The result string. #: farray_join_for_eval() { local __farr_varname __farr_name local __farr_token __farr_gvrname __farr_len \ __farr_join_idx __farr_command __farr_real_separator \ __farr_current_value __farr_varname="${1-}" [ -z "${__farr_varname}" ] && _farr_fatal "missing variable name" shift _farr_array_get_meta "$@" __farr_real_separator='' __farr_command='' __farr_join_idx=1 while [ "${__farr_join_idx}" -le "${__farr_len}" ]; do eval __farr_current_value=\"\$\{"${__farr_gvrname}"_"${__farr_join_idx}"\}\" __farr_command="${__farr_command}${__farr_real_separator}$(_farr_quote_for_eval "${__farr_current_value}")" __farr_real_separator=' ' __farr_join_idx=$((__farr_join_idx + 1)) done setvar "${__farr_varname}" "${__farr_command}" } #: #: Join all the elements in given array with a space `` `` character while #: escaping properly for feeding into shell's ``eval``. #: #: ``eval "$(farray_join_for_eval ARRAY)"`` is safe: #: every item of the array becomes a word when the eval evaluates the #: joined string. #: #: Args: #: $1 (str): The name of an existing array. #: #: Output (stdout): #: The joined and properly encoded string. #: farray_print_join_for_eval() { local __farr_name local __farr_token __farr_gvrname __farr_len \ __farr_join_idx __farr_current_value _farr_array_get_meta "$@" __farr_join_idx=1 while [ "${__farr_join_idx}" -le "${__farr_len}" ]; do eval __farr_current_value=\"\$\{"${__farr_gvrname}"_"${__farr_join_idx}"\}\" if [ "${__farr_join_idx}" -gt 1 ]; then printf '%s' " " fi printf '%s' "$(_farr_quote_for_eval "${__farr_current_value}")" __farr_join_idx=$((__farr_join_idx + 1)) done } #: #: Test whether two arrays are equal. #: #: Two arrays are equal if they have both the same length and if #: all elements are equal (using ``test value1 = value2``). #: #: Args: #: $1 (str): The name of the first array #: $2 (str): The name of the second array #: #: Returns: #: int: 0 if both input arrays are equal, 1 otherwise #: farray_are_equal() { local __farr_l_name __farr_r_name local __farr_l_token __farr_l_gvrname __farr_l_len \ __farr_r_token __farr_r_gvrname __farr_r_len \ __farr_name __farr_token __farr_gvrname __farr_len \ __farr_idx __farr_vl __farr_vr [ $# -ne 2 ] && _farr_fatal "missing array" _farr_array_get_meta "$1" __farr_l_name="${__farr_name}" __farr_l_token="${__farr_token}" __farr_l_gvrname="${__farr_gvrname}" __farr_l_len="${__farr_len}" _farr_array_get_meta "$2" __farr_r_name="${__farr_name}" __farr_r_token="${__farr_token}" __farr_r_gvrname="${__farr_gvrname}" __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_vl=\"\$\{"${__farr_l_gvrname}"_"${__farr_idx}"\}\" eval __farr_vr=\"\$\{"${__farr_r_gvrname}"_"${__farr_idx}"\}\" [ "${__farr_vl}" != "${__farr_vr}" ] && return 1 __farr_idx=$((__farr_idx + 1)) done return 0 } #: #: Call a function for every element in an array starting at the first index. #: #: The function to be called must accept at least three arguments: #: - the array name #: - the current index #: - the element value at the current index #: - all the extra arguments (`$3`, `$4`, ...) given as arguments are #: forwarded also #: #: The iteration stops if the called function returns a falsy value. #: #: Args: #: $1 (str): The name of an existing array. #: $2 (str): The name of a function to be called with three arguments. #: $3... (optional): Extra arguments forwared to `$2` also #: #: Warning: #: If the number of elements changes while being in `farray_for_each` then #: the behaviour is undefined. #: The current implementation determines the length of the array once #: at the start of execution. #: farray_for_each() { local __farr_name __farr_callback local __farr_token __farr_gvrname __farr_len __farr_idx __farr_rv \ __farr_gm_name_or_value __farr_feval __farr_gm_name_or_value="${1-}" _farr_array_get_meta "$@" __farr_callback="${2-}" [ -z "${__farr_callback}" ] && _farr_fatal "missing callback function name" shift 2 __farr_idx=1 while [ "${__farr_idx}" -le "${__farr_len}" ]; do eval __farr_feval=\"\$\{"${__farr_gvrname}"_"${__farr_idx}"\}\" _farr_acquire_object "${__farr_feval}" eval "${__farr_callback} \"\${__farr_name:-\"\${__farr_gm_name_or_value}\"}\" \"\${__farr_idx}\" \"\${__farr_feval}\" \"\$@\"" __farr_rv=$? _farr_release_object "${__farr_feval}" [ "${__farr_rv}" -ne 0 ] && return "${__farr_rv}" __farr_idx=$((__farr_idx + 1)) done return 0 } #: #: Like `farray_for_each`, but the callback function is called in reversed #: order -- beginning with the last index. #: farray_reversed_for_each() { local __farr_name __farr_callback local __farr_token __farr_gvrname __farr_len __farr_idx __farr_rv \ __farr_gm_name_or_value __farr_feval __farr_gm_name_or_value="${1-}" _farr_array_get_meta "$@" __farr_callback="${2-}" [ -z "${__farr_callback}" ] && _farr_fatal "missing callback function name" shift 2 __farr_idx="${__farr_len}" while [ "${__farr_idx}" -gt 0 ]; do eval __farr_feval=\"\$\{"${__farr_gvrname}"_"${__farr_idx}"\}\" _farr_acquire_object "${__farr_feval}" eval "${__farr_callback} \"\${__farr_name:-\"\${__farr_gm_name_or_value}\"}\" \"\${__farr_idx}\" \"\${__farr_feval}\" \"\$@\"" __farr_rv=$? _farr_release_object "${__farr_feval}" [ "${__farr_rv}" -ne 0 ] && return ${__farr_rv} __farr_idx=$((__farr_idx - 1)) done return 0 } #: #: Print the contents of an array to stderr. #: #: Args: #: $1 (str): The name of an array. The array may exist or not. #: #: Returns: #: int: 0 #: farray_debug() { _farr_array_debug '' "$@" } #: #: Internal implementation of `farray_debug`. #: #: Args: #: $1 (str): The indent #: $2 (str): The name or the complete prefixed token of an array #: #: Returns: #: int: 0 #: _farr_array_debug() { local __farr_debug_indent __farr_name local __farr_token __farr_gvrname __farr_len __farr_name_or_token __farr_debug_indent="${1}" shift if ! _farr_array_tryget_meta "$@"; then printf "%sDEBUG: no (meta-)data for farray \`%s'\\n" "${__farr_debug_indent}" "${1-}" 1>&2 return 0 fi __farr_name_or_token="${__farr_name:-"${1}"}" if [ -n "${__farr_name}" ]; then printf "%sDEBUG: array \`%s' has length %s\\n" "${__farr_debug_indent}" "${__farr_name}" "${__farr_len}" 1>&2 else printf "%sDEBUG: array with token \`%s' has length %s\\n" "${__farr_debug_indent}" "${__farr_token}" "${__farr_len}" 1>&2 fi if [ "${__farr_len}" -gt 0 ]; then printf '%sDEBUG: the items:\n' "${__farr_debug_indent}" 1>&2 farray_for_each "${__farr_name_or_token}" _farr_debug_print_value "${__farr_debug_indent}" || true fi return 0 } #: #: Debug output helper for `farray_debug`. #: #: Args: #: $4: The current indentation string #: _farr_debug_print_value() { case "$3" in "${_farr_array_token_prefix}"*) printf "%sDEBUG: %s: >>>\\n" "$4" "$2" 1>&2 _farr_array_debug "$4 " "$3" # let the return value pass through ;; "${_farr_alist_token_prefix}"*) printf "%sDEBUG: %s: >>>\\n" "$4" "$2" 1>&2 _farr_alist_debug "$4 " "$3" # let the return value pass through ;; *) printf "%sDEBUG: %s: \`%s'\\n" "$4" "$2" "$3" 1>&2 ;; esac return 0 } #: #: Just an official alias for `fobject_type` #: falist_type() { fobject_type "$@" } #: #: Test whether `$1` is an alist. #: #: Args: #: $1 (str): The name of an object #: #: Returns: #: int: 0 if `$1` is an alist, 1 otherwise #: falist_isalist() { [ "$(fobject_type "$@")" = 'alist' ] } #: #: Create a new alist. #: #: Args: #: $1 (str): The name of the alist. #: Must conform to shell variable naming conventions. #: $2, $3 ... (optional): A sequence of key-value pairs the alist will be #: populated with. #: #: Exit: #: Iff the alist already exists. #: falist_create() { local __farr_name local __farr_token __farr_objname __farr_bskeyname __farr_keyname \ __farr_valname __farr_llen __farr_name="${1-}" [ -z "${__farr_name}" ] && _farr_fatal "missing falist name" eval __farr_token=\"\$\{"${__farr_name}"-\}\" [ -n "${__farr_token}" ] && _farr_fatal "object \`${__farr_name}' already created (value \`${__farr_token}')" __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_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" # <first>;<last> # # ... 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. # Note that the alist's name is not shifted away yet and must be taken # into account. It is also needed for `falist_set`. # if [ $# -gt 2 ]; then falist_set "$@" fi } #: #: Internal helper to get all the metadata for an alist. #: #: Args: #: $1 (str): The name of the alist or a complete alist value with prefix #: #: Output (Globals): #: __farr_name (str): The name of the alist if the name is given, #: 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_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() { # __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="${1#"${_farr_alist_token_prefix}"}" ;; *) # A variable name: go through one indirection __farr_name="${1}" eval __farr_token=\"\$\{"${__farr_name}"-\}\" case "${__farr_token}" in '') _farr_fatal "object \`${__farr_name}' not created properly: token empty" ;; "${_farr_alist_token_prefix}"*) __farr_token="${__farr_token#"${_farr_alist_token_prefix}"}" ;; *) _farr_fatal "object \`${__farr_name}' is not an alist" ;; esac ;; esac __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}" 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 } #: #: Internal helper to try to get all the metadata for an alist. #: #: Args: #: $1 (str): The name of the alist or a complete alist value with prefix #: #: Output (Globals): #: __farr_name (str): The name of the alist if the name is given, #: empty if a token value is given. #: __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_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() { # __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 ;; "${_farr_alist_token_prefix}"*) # A prefixed token value __farr_name='' __farr_token="${1#"${_farr_alist_token_prefix}"}" ;; *) # A variable name: go through one indirection __farr_name="${1}" eval __farr_token=\"\$\{"${__farr_name}"-\}\" case "${__farr_token}" in '') _farr_err "object \`${__farr_name}' not created properly: token empty" return 1 ;; "${_farr_alist_token_prefix}"*) __farr_token="${__farr_token#"${_farr_alist_token_prefix}"}" ;; *) _farr_err "object \`${__farr_name}' is not an alist" return 1 ;; esac ;; esac __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}" 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 __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 #: ``<storage-ptr>/<storage-prev-ptr>;<storage-next-ptr>@<object-token>``. #: #: 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 __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 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() { # __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}" } #: #: Get the length of an alist and print it to stdout. #: #: Args: #: $1 (str): The name of the alist. #: #: Output (stdout): #: The number of elements in the alist. #: If the array does not exist the output is -1. #: #: Returns: #: 0 (truthy) #: falist_print_length() { # $1 local __farr_vn_pl ( falist_length __farr_vn_pl "$@" && printf "%s" "${__farr_vn_pl}"; ) \ || printf "%s" "-1" } #: #: Empty an existing alist. #: #: Args: #: $1 (str): The name of the existing alist. #: falist_clear() { # __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 \ __farr_bsidx __farr_sptr __farr_sptr_next \ __farr_del_value __farr_del_key _farr_alist_get_meta "$@" # 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}" 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 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 } #: #: Release an alist. #: #: Decrement its reference count. If it reaches zero then destroy and #: unset an alist and all its items. #: #: Args: #: $1 (str): The name of an alist. The alist may exist or not. #: #: Returns: #: - A truthy value if the alist existed and has been deleted. #: - A falsy value if the alist does not exist. #: falist_release() { local __farr_name local __farr_token __farr_objname __farr_bskeyname __farr_keyname \ __farr_valname __farr_llen __farr_bslen \ __farr_sptr_first __farr_sptr_last \ __farr_sptr __farr_del_value __farr_del_key \ __farr_sptr_next __farr_bsidx \ __farr_refcnt _farr_alist_tryget_meta "$@" || return 1 # # Decrement the reference count. # Note that the existence of the reference count proper is already # checked by `_farr_alist_tryget_meta`. # eval __farr_refcnt=\"\$\{"${__farr_objname}"_C\}\" __farr_refcnt=$((__farr_refcnt - 1)) setvar "${__farr_objname}_C" "${__farr_refcnt}" if [ "${__farr_refcnt}" -ne 0 ] ; then # Clean out the alist name from the token always [ -n "${__farr_name}" ] && setvar "${__farr_name}" '' return 0 fi # 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}" 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}__" # ... and its reference count 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}" ] && 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 \ __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}"\}\" # <storage-ptr;<storage-type>@<key> __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 \ __farr_searched_value local __farr_lo __farr_hi __farr_mid __farr_mid_value \ __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}"\}\" # <storage-ptr;<storage-type>@<key> __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 } #: #: Map a key to a value. #: #: Args: #: $1 (str): The name of an existing alist. #: $2, $3 ... (optional): A sequence of key-value pairs the alist will be #: populated with. #: #: Exit: #: If one of the underlying arrays that implement the alist does not exist #: or if an internal inconsistency will be detected. #: falist_set() { # __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 \ __farr_bsidx __farr_tmp_bsidx \ __farr_tmp_value __farr_tmp_key __farr_prev_key \ __farr_stype __farr_sptr __farr_prev_sptr __farr_tmp_prev_sptr _farr_alist_get_meta "${1-}" shift while [ $# -ne 0 ]; do [ $# -lt 1 ] && _farr_fatal "missing key" __farr_key="$1" [ $# -lt 2 ] && _farr_fatal "missing value" __farr_value="$2" _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 __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_bskeyname}"_$((__farr_tmp_bsidx + 1))=\"\$\{"${__farr_bskeyname}"_"${__farr_tmp_bsidx}"\}\" __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 } #: #: Map a key to a value while assuming that the given keys are not already #: in the alist. #: #: Args: #: $1 (str): The name of an existing alist. #: $2, $3 ... (optional): A sequence of key-value pairs the alist will be #: populated with. #: #: Exit: #: If one of the underlying arrays that implement the alist does not exist #: or if an internal inconsistency will be detected. #: falist_add() { # __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 \ __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 [ $# -lt 1 ] && _farr_fatal "missing key" __farr_key="$1" [ $# -lt 2 ] && _farr_fatal "missing value" __farr_value="$2" # # We do just append ... # # # 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}" 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 } #: #: Map a key to a value but error if the alist contains already an item #: with the given key. #: #: Args: #: $1 (str): The name of an existing alist. #: $2, $3 ... (optional): A sequence of key-value pairs the alist will be #: populated with. #: #: Returns: #: int: 0 (truthy) if the item got set, 1 (falsy) if there is already an #: item with key `$1` in the alist #: #: Exit: #: If one of the underlying arrays that implement the alist does not exist #: or if an internal inconsistency will be detected. #: falist_set_unique() { # __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 \ __farr_bsidx __farr_tmp_bsidx \ __farr_tmp_value __farr_tmp_key __farr_prev_key \ __farr_stype __farr_sptr __farr_prev_sptr __farr_tmp_prev_sptr _farr_alist_get_meta "${1-}" shift while [ $# -ne 0 ]; do [ $# -lt 1 ] && _farr_fatal "missing key" __farr_key="$1" [ $# -lt 2 ] && _farr_fatal "missing value" __farr_value="$2" _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 __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_bskeyname}"_$((__farr_tmp_bsidx + 1))=\"\$\{"${__farr_bskeyname}"_"${__farr_tmp_bsidx}"\}\" __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 } #: #: Update an alist from another alist. #: #: 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 ("left") alist that is to be updated from `$2`. #: $2 (str): The ("right") alist #: falist_update() { 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 \ __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 \ __farr_bsidx __farr_stype __farr_sptr __farr_tmp_bsidx \ __farr_key __farr_value \ __farr_r_key __farr_r_sptr __farr_r_sptr_next __farr_r_value \ __farr_prev_sptr __farr_prev_key \ __farr_tmp_prev_sptr [ $# -ne 2 ] && _farr_fatal "exactly two arrays must be given" _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_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_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 } #: #: Get the value that is associated with a key and put it into a variable. #: #: Args: #: $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 a storage #: cookie (an extended form of a storage pointer) into. #: #: Exit: #: - If the key is not found. #: - If one of the underlying arrays that implement the alist does not exist. #: or if an internal inconsistency will be detected. #: falist_get() { 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 \ __farr_sptr __farr_get_key __farr_get_value __farr_varname="${1-}" [ -z "${__farr_varname}" ] && _farr_fatal "missing variable name" _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 <storage-ptr>/<storage-prev-ptr>;<storage-next-ptr>@<object-token> setvar "${__farr_varname_scookie}" "${__farr_sptr}/${__farr_get_key%%@*}@${__farr_token}" fi else _farr_fatal "alist \`${__farr_name:-"${2}"}': key \`${__farr_key}' not found" fi } #: #: Try to get the value that is associated with a key and store it in a #: variable. #: #: Args: #: $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 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_varname_scookie local __farr_token __farr_objname __farr_bskeyname __farr_keyname \ __farr_valname __farr_llen __farr_bslen \ __farr_sptr_first __farr_sptr_last \ __farr_sptr __farr_get_key __farr_get_value __farr_varname="${1-}" [ -z "${__farr_varname}" ] && _farr_fatal "missing variable name" _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 <storage-ptr>/<storage-prev-ptr>;<storage-next-ptr>@<object-token> setvar "${__farr_varname_scookie}" "${__farr_sptr}/${__farr_get_key%%@*}@${__farr_token}" fi return 0 fi return 1 } #: #: 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 \ __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 \ __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 \ __farr_sptr __farr_sptr_prev __farr_sptr_next \ __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 \ __farr_sptr __farr_sptr_prev __farr_sptr_next \ __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 storage cookie. #: #: Returns: #: 0 (truthy) on success, #: 1 (falsy) if the given index 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_key_at() { local __farr_key_varname __farr_scookie local __farr_token __farr_objname __farr_keyname __farr_valname \ __farr_sptr __farr_sptr_prev __farr_sptr_next \ __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 eval __farr_getikey=\"\$\{"${__farr_keyname}"_"${__farr_sptr}"\}\" setvar "${__farr_key_varname}" "${__farr_getikey#*@}" return 0 else _farr_fatal "key unexpectedly unset (cookie ${__farr_scookie})" fi else return 1 fi } #: #: 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 storage cookie. #: #: Returns: #: 0 (truthy) on success, #: 1 (falsy) if the given index 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_value_at() { local __farr_value_varname __farr_scookie local __farr_token __farr_objname __farr_keyname __farr_valname \ __farr_sptr __farr_sptr_prev __farr_sptr_next \ __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_sptr}"\}\" _farr_acquire_object "${__farr_getival}" setvar "${__farr_value_varname}" "${__farr_getival}" return 0 else _farr_fatal "value unexpectedly unset (cookie ${__farr_scookie})" fi else return 1 fi } #: #: Try to get the key-value item that is associated with a storage cookie and #: store it into variables. #: #: 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 storage cookie. #: #: Returns: #: 0 (truthy) on success, #: 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() { local __farr_key_varname __farr_value_varname __farr_scookie local __farr_token __farr_objname __farr_keyname __farr_valname \ __farr_sptr __farr_sptr_prev __farr_sptr_next \ __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" _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_sptr}"+SET\}\" if [ -n "${__farr_getival}" ]; then 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}" setvar "${__farr_value_varname}" "${__farr_getival}" return 0 else _farr_fatal "value unexpectedly unset (cookie ${__farr_scookie})" fi else _farr_fatal "key unexpectedly unset (cookie ${__farr_scookie})" fi else return 1 fi } #: #: Determine whether a key is found within the alist. #: #: Args: #: $1 (str): The name of an existing alist. #: $2: The key. #: #: Returns: #: 0 (truthy) on success if the key is found, #: 1 (falsy) if the given key is not found. #: falist_contains() { # __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_alist_bsearch '' '' "${2}" } #: #: 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 storage cookie #: into. #: $2 (str): The existing alist. #: $3: The key. #: #: Returns: #: 0 (truthy) on success if the key is found, #: 1 (falsy) if the given key is not found. #: falist_find() { local __farr_varname __farr_name __farr_key local __farr_token __farr_objname __farr_bskeyname __farr_keyname \ __farr_valname __farr_llen __farr_bslen \ __farr_sptr_first __farr_sptr_last \ __farr_sptr __farr_find_key __farr_varname="${1-}" [ -z "${__farr_varname}" ] && _farr_fatal "missing variable name" _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 return 1 # not found } #: #: Try to delete an item with a given key from the alist. #: #: Args: #: $1 (str): The name of the alist. #: $2: The key of the key-value pair to delete to #: #: Returns: #: int: 0 if the key has been found and is deleted, #: 1 if the key has not been found. #: falist_trydel() { local __farr_name __farr_delkey local __farr_token __farr_objname __farr_bskeyname __farr_keyname \ __farr_valname __farr_llen __farr_bslen \ __farr_sptr_first __farr_sptr_last \ __farr_sptr __farr_bsidx __farr_cur_value \ __farr_cur_key __farr_cur_stype __farr_cur_prev __farr_cur_next \ __farr_next_key __farr_next_prev __farr_next_next \ __farr_prev_key __farr_prev_prev __farr_prev_next _farr_alist_get_meta "$@" [ $# -lt 2 ] && _farr_fatal "missing key" __farr_delkey="$2" _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}" } #: #: Test the boolean truth of an alist. #: #: Args: #: $1 (str): The name of the alist. #: #: Returns: #: int: 0 (truthy) if the alist contains items, #: 1 (falsy) otherwise (empty or not created/destroyed properly). #: falist_istrue() { # 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_llen}" -gt 0 ]; then return 0 else return 1 fi } #: #: Test whether two alists are equal. #: #: Two alists compare equal if and only if they have the same (key, value) #: 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 #: $2 (str): The name of the second alist #: #: Returns: #: int: 0 if both input alists are equal, 1 otherwise #: falist_are_equal() { 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 \ __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 \ __farr_sptr __farr_r_sptr __farr_r_bsidx \ __farr_value __farr_r_key __farr_r_value __farr_r_stype [ $# -ne 2 ] && _farr_fatal "missing alist parameter" _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_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 } #: #: 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 insertion ordering. Comparison is done using the #: shell's builtin ``=`` test operator for both keys and values #: (i.e. lexicographic). #: #: Args: #: $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_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 \ __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 \ __farr_sptr __farr_sptr_next __farr_r_sptr __farr_r_sptr_next \ __farr_key __farr_value \ __farr_r_key __farr_r_value [ $# -ne 2 ] && _farr_fatal "missing alist parameter" _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_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 } #: #: Get all keys from the alist and put it into an array. #: #: Args: #: $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_name local __farr_l_name __farr_l_token __farr_l_gvrname __farr_l_len \ __farr_token __farr_gvrname __farr_objname \ __farr_bskeyname __farr_keyname __farr_valname \ __farr_len __farr_llen __farr_bslen \ __farr_sptr_first __farr_sptr_last \ __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" _farr_array_get_meta "$1" __farr_l_result="$1" __farr_l_name="${__farr_name}" __farr_l_token="${__farr_token}" __farr_l_gvrname="${__farr_gvrname}" __farr_l_len="${__farr_len}" [ $# -lt 2 ] && _farr_fatal "missing alist" _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 } #: #: Get all values from the alist and put it into an array. #: #: Args: #: $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_name local __farr_l_name __farr_l_token __farr_l_gvrname __farr_l_len \ __farr_token __farr_gvrname __farr_objname \ __farr_bskeyname __farr_keyname __farr_valname \ __farr_len __farr_llen __farr_bslen \ __farr_sptr_first __farr_sptr_last \ __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" _farr_array_get_meta "$1" __farr_l_result="$1" __farr_l_name="${__farr_name}" __farr_l_token="${__farr_token}" __farr_l_gvrname="${__farr_gvrname}" __farr_l_len="${__farr_len}" [ $# -lt 2 ] && _farr_fatal "missing alist" _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 } #: #: Get all items (key-value pairs) from the alist and put it into an array. #: #: Args: #: $1 (str): The name of the target array where the sequence of #: 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_name local __farr_l_name __farr_l_token __farr_l_gvrname __farr_l_len \ __farr_token __farr_gvrname __farr_objname \ __farr_bskeyname __farr_keyname __farr_valname \ __farr_len __farr_llen __farr_bslen \ __farr_sptr_first __farr_sptr_last \ __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" _farr_array_get_meta "$1" __farr_l_result="$1" __farr_l_name="${__farr_name}" __farr_l_token="${__farr_token}" __farr_l_gvrname="${__farr_gvrname}" __farr_l_len="${__farr_len}" [ $# -lt 2 ] && _farr_fatal "missing alist" _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 in insertion order. #: #: The given callback function will called with at least four arguments: #: #: - the alist name #: - 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 #: #: The iteration stops if the called function returns a falsy value. #: #: Args: #: $1 (str): The name of an existing array. #: $2 (str): The name of a function to be called with four arguments. #: $3... (optional): Extra arguments forwared to `$2` also #: #: Warning: #: If the number of elements changes while being in `falist_for_each` then #: the behaviour is undefined. #: The current implementation determines the length of the alist once #: at the start of execution. #: falist_for_each() { local __farr_name __farr_callback local __farr_token __farr_objname __farr_bskeyname __farr_keyname \ __farr_valname __farr_llen __farr_bslen \ __farr_sptr_first __farr_sptr_last \ __farr_fesptr __farr_fesptr_next __farr_fescookie \ __farr_fekey __farr_feval __farr_rv \ __farr_gm_name_or_value __farr_gm_name_or_value="${1-}" _farr_alist_get_meta "${__farr_gm_name_or_value}" __farr_callback="${2-}" [ -z "${__farr_callback}" ] && _farr_fatal "missing callback function name" shift 2 __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 <pref>;<next> 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 } #: #: Print the contents of an alist to stderr. #: #: Args: #: $1 (str): The name of an alist. The array may exist or not. #: #: Returns: #: 0 #: falist_debug() { _farr_alist_debug '' "$@" } #: #: Internal implementation of `falist_debug`. #: #: Args: #: $1 (str): The indent #: $2 (str): The name or the complete prefixed token of an alist. #: #: Returns: #: 0 #: _farr_alist_debug() { 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 \ __farr_sptr __farr_el_key __farr_el_val \ __farr_sptr_next \ __farr_debug_bsidx __farr_debug_sptr __farr_debug_indent="${1}" shift if ! _farr_alist_tryget_meta "$@"; then printf "%sDEBUG: no (meta-)data for falist \`%s'\\n" "${__farr_debug_indent}" "${1-}" 1>&2 return 0 fi if [ -n "${__farr_name}" ]; then 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_llen}" 1>&2 fi if [ "${__farr_llen}" -gt 0 ]; then printf '%sDEBUG: the items:\n' "${__farr_debug_indent}" 1>&2 fi __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 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 } #: #: Acquire any object. #: #: If an object represents an array or alist its reference count will be #: increased. #: #: Args: #: $1: The object's value #: #: Returns: #: int: 0 if the incrementation went properly, 1 otherwise #: _farr_acquire_object() { # $1 local __farr_tmp_refcount __farr_tmp_token case "$1" in '') return 0 ;; "${_farr_array_token_prefix}"*) __farr_tmp_token="${1#"${_farr_array_token_prefix}"}" 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\}\" __farr_tmp_refcount=$((__farr_tmp_refcount + 1)) 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\}\" [ -z "${__farr_tmp_refcount}" ] && return 1 eval __farr_tmp_refcount=\"\$\{"${_farr_alist_prefix}""${__farr_tmp_token}"_C\}\" __farr_tmp_refcount=$((__farr_tmp_refcount + 1)) setvar "${_farr_alist_prefix}${__farr_tmp_token}_C" "${__farr_tmp_refcount}" return 0 ;; *) return 0 ;; esac } #: #: Release any object. #: #: If an object represents an array or alist its reference count will be #: decreased properly. #: #: Args: #: $1: The object's value #: #: Returns: #: int: 0 if the destruction went properly without errors, 1 otherwise #: _farr_release_object() { # $1 case "$1" in '') return 0 ;; "${_farr_array_token_prefix}"*) farray_release "$1" # let the return value pass through ;; "${_farr_alist_token_prefix}"*) falist_release "$1" # let the return value pass through ;; *) return 0 ;; esac }
