changeset 516:1d1a4c217e38

array.sh: Implement an alist (aka map, dict) for key-pair values. Implementation is straightforward and not optimized. Is uses two arrays to implement keys and values respectively.
author Franz Glasner <fzglas.hg@dom66.de>
date Sun, 01 Sep 2024 16:57:25 +0200
parents 7c2ffcc92df6
children 2e2ba2203117
files share/local-bsdtools/array.sh
diffstat 1 files changed, 429 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/share/local-bsdtools/array.sh	Sun Sep 01 16:55:10 2024 +0200
+++ b/share/local-bsdtools/array.sh	Sun Sep 01 16:57:25 2024 +0200
@@ -31,10 +31,33 @@
 #:   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 with two corresponding arrays: one for the keys
+#: and one for the values.
+#:
+#: If the map has the name NAME then the array naming is as such:
+#:
+#: - The keys are held in an array with name alist_key_<NAME>
+#: - The values are held in an array with name alist_val_<NAME>
+#:
+#: This makes _farr_array_alist_key_<NAME> and _farr_array_alist_val_<NAME>
+#: as effective shell level variable name prefixes.
+#:
+#: An alist exists iff both of the underlying arrays exist.
+#:
+#: Important:
+#:   All names that start with ``_farr_`` or ``__farr_`` are reserved
+#:   for private use. Try to do not use such names in your scripts.
+#:
+#:   Public functions start with ``array_`` or ``alist_``.
+#:
 
 
 _farr_global_prefix=_farr_array_
 _farr_unset=_farr__UNSET_d646c21167a611efa78174d435fd3892__
+_farr_alist_key_infix=alist_key_
+_farr_alist_value_infix=alist_val_
 
 
 #:
@@ -694,3 +717,409 @@
     printf "DEBUG:     %s: \`%s'\\n" "$2" "$3" 1>&2
     return 0
 }
+
+
+#:
+#: Create a new alist.
+#:
+#: Args:
+#:   $1 (str): The name of the alist.
+#:             Must conform to shell variable naming conventions
+#:
+#: Exit:
+#:   Iff the alist already exists.
+#:
+alist_create() {
+    local __farr_name
+
+    local __farr_key_array __farr_val_array
+
+    [ $# -lt 1 ] && _farr_fatal "missing alist name"
+    __farr_name=$1
+    __farr_key_array=${_farr_alist_key_infix}${__farr_name}
+    __farr_val_array=${_farr_alist_value_infix}${__farr_name}
+
+    if array_create ${__farr_key_array}; then
+        if ! array_create ${__farr_val_array}; then
+            array_destroy ${__farr_key_array} || true
+        fi
+    fi
+}
+
+
+#:
+#: Get the length of an alist and put it into a variable.
+#:
+#: Args:
+#:   $1 (str): The name of the alist.
+#:
+#: Returns:
+#:   0 (truthy) if the array exists,
+#:   1 (falsy) if the array does not exist
+#:
+alist_length() {
+    local __farr_varname __farr_name
+
+    local __farr_key_array
+
+    [ $# -lt 1 ] && _farr_fatal "missing variable name"
+    __farr_varname=$1
+    [ $# -lt 2 ] && _farr_fatal "missing array name"
+    __farr_name=$2
+    __farr_key_array=${_farr_alist_key_infix}${__farr_name}
+
+    array_length ${__farr_varname} ${__farr_key_array}
+    return $?
+}
+
+
+#:
+#: Get the length of an alist.
+#:
+#: Args:
+#:   $1 (str): The name of the variable to put the length into
+#:   $2 (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)
+#:
+alist_print_length() {
+    local __farr_name
+
+    local __farr_pl_l __farr_key_array
+
+    [ $# -lt 1 ] && _farr_fatal "missing array name"
+    __farr_name=$1
+    __farr_key_array=${_farr_alist_key_infix}${__farr_name}
+
+    array_print_length ${__farr_key_array}
+    return 0
+}
+
+
+#:
+#: Empty an existing alist.
+#:
+#: Args:
+#:   $1 (str): The name of the existing alist
+#:
+alist_clear() {
+    local __farr_name
+
+    local __farr_key_array __farr_val_array
+
+    [ $# -lt 1 ] && _farr_fatal "missing array name"
+    __farr_name=$1
+    __farr_key_array=${_farr_alist_key_infix}${__farr_name}
+    __farr_val_array=${_farr_alist_value_infix}${__farr_name}
+
+    array_clear ${__farr_val_array}
+    array_clear ${__farr_key_array}
+}
+
+
+#:
+#: Destroy and unset an alist
+#:
+#: 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
+#:
+alist_destroy() {
+    local __farr_name
+
+    local __farr_key_array __farr_val_array __farr_rc_key __farr_rc_val
+
+    [ $# -lt 1 ] && _farr_fatal "missing alist name"
+    __farr_name=$1
+    __farr_key_array=${_farr_alist_key_infix}${__farr_name}
+    __farr_val_array=${_farr_alist_value_infix}${__farr_name}
+
+    __farr_rc_key=0
+    __farr_rc_val=0
+    array_destroy ${__farr_val_array} || __farr_rc_val=$?
+    array_destroy ${__farr_key_array} || __farr_rc_key=$?
+
+    [ ${__farr_rc_key} -gt 0 ] && return ${__farr_rc_key}
+    [ ${__farr_rc_val} -gt 0 ] && return ${__farr_rc_val}
+    return 0
+}
+
+
+#:
+#: Map a key to a value
+#:
+#: Args:
+#:   $1 (str): The name of an existing alist
+#:   $2: The key
+#:   $3: The value
+#:
+#: Exit:
+#:   If one of the underlying arrays that implement the alist does not exist
+#:   or if an internal inconsistency will be detected.
+#:
+alist_set() {
+    local __farr_name __farr_key __farr_value
+
+    local __farr_key_array __farr_val_array __farr_idx
+
+    [ $# -lt 1 ] && _farr_fatal "missing array name"
+    __farr_name=$1
+    __farr_key_array=${_farr_alist_key_infix}${__farr_name}
+    __farr_val_array=${_farr_alist_value_infix}${__farr_name}
+    [ $# -lt 2 ] && _farr_fatal "missing key"
+    __farr_key="$2"
+    [ $# -lt 3 ] && _farr_fatal "missing value"
+    __farr_value="$3"
+
+    if array_find __farr_idx ${__farr_key_array} "${__farr_key}"; then
+        # Replace existing
+        array_set ${__farr_key_array} ${__farr_idx} "${__farr_key}"
+        array_set ${__farr_val_array} ${__farr_idx} "${__farr_value}"
+    else
+        # append new
+        array_append ${__farr_key_array} "${__farr_key}"
+        array_append ${__farr_val_array} "${__farr_value}"
+        if [ "$(array_print_length ${__farr_key_array})" -ne "$(array_print_length ${__farr_val_array})" ]; then
+            _farr_fatal "alist \`${__farr_name}': could not set (append error)"
+        fi
+    fi
+}
+
+
+#:
+#: 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
+#:
+#: 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.
+#:
+alist_get() {
+    local __farr_varname __farr_name __farr_key
+
+    local __farr_key_array __farr_val_array __farr_idx
+
+    [ $# -lt 1 ] && _farr_fatal "missing variable name"
+    __farr_varname=$1
+    [ $# -lt 2 ] && _farr_fatal "missing array name"
+    __farr_name=$2
+    __farr_key_array=${_farr_alist_key_infix}${__farr_name}
+    __farr_val_array=${_farr_alist_value_infix}${__farr_name}
+    [ $# -lt 3 ] && _farr_fatal "missing key"
+    __farr_key="$3"
+
+    if array_find __farr_idx ${__farr_key_array} "${__farr_key}"; then
+        # Yes, we found an index
+        #printf "%s" "$(array_get ${__farr_val_array} ${__farr_idx})"
+        array_get ${__farr_varname} ${__farr_val_array} ${__farr_idx}
+    else
+        _farr_fatal "alist \`${__farr_name}': 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
+#:
+#: Returns:
+#:   0 (truthy) on success,
+#:   1 (falsy) if the given key is not found
+#:
+alist_tryget() {
+    local __farr_varname __farr_name __farr_key
+
+    local __farr_key_array __farr_val_array __farr_idx
+
+    [ $# -lt 1 ] && _farr_fatal "missing variable name"
+    __farr_varname=$1
+    [ $# -lt 2 ] && _farr_fatal "missing array name"
+    __farr_name=$2
+    __farr_key_array=${_farr_alist_key_infix}${__farr_name}
+    __farr_val_array=${_farr_alist_value_infix}${__farr_name}
+    [ $# -lt 3 ] && _farr_fatal "missing key"
+    __farr_key="$3"
+
+    if array_find __farr_idx ${__farr_key_array} "${__farr_key}"; then
+        # Yes, we found an index
+        #printf "%s" "$(array_get ${__farr_val_array} ${__farr_idx})"
+        array_get ${__farr_varname} ${__farr_val_array} ${__farr_idx}
+        return 0
+    else
+        return 1
+    fi
+}
+
+
+#:
+#: Try to get the key that is associated with a storage index and store it in a variable.
+#:
+#: Use this for iteration over values.
+#:
+#: Args:
+#:   $1 (str): The name of the variable where to put the key into
+#:   $2 (str): The name of an existing alist
+#:   $3 (int): The index
+#:
+#: 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`).
+#:
+alist_tryget_key_at_index() {
+    local __farr_varname __farr_name __farr_index
+
+    local __farr_key_array __farr_val_array __farr_idx
+
+    [ $# -lt 1 ] && _farr_fatal "missing variable name"
+    __farr_varname=$1
+    [ $# -lt 2 ] && _farr_fatal "missing array name"
+    __farr_name=$2
+    __farr_key_array=${_farr_alist_key_infix}${__farr_name}
+    [ $# -lt 3 ] && _farr_fatal "missing index"
+    __farr_index=$3
+
+    array_tryget ${__farr_varname} ${__farr_key_array} ${__farr_index}
+}
+
+
+#:
+#: Try to get the value that is associated with a storage index and store it in a variable.
+#:
+#: Use this for iteration over keys.
+#:
+#: Args:
+#:   $1 (str): The name of the variable where to put the value into
+#:   $2 (str): The name of an existing alist
+#:   $3 (int): The index
+#:
+#: 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`).
+#:
+alist_tryget_value_at_index() {
+    local __farr_varname __farr_name __farr_index
+
+    local __farr_key_array __farr_val_array __farr_idx
+
+    [ $# -lt 1 ] && _farr_fatal "missing variable name"
+    __farr_varname=$1
+    [ $# -lt 2 ] && _farr_fatal "missing array name"
+    __farr_name=$2
+    __farr_val_array=${_farr_alist_value_infix}${__farr_name}    
+    [ $# -lt 3 ] && _farr_fatal "missing index"
+    __farr_index=$3
+
+    array_tryget ${__farr_varname} ${__farr_val_array} ${__farr_index}
+}
+
+
+#:
+#: 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
+#:
+alist_contains() {
+    local __farr_name __farr_key
+
+    local __farr_key_array __farr_idx
+
+    [ $# -lt 1 ] && _farr_fatal "missing array name"
+    __farr_name=$1
+    __farr_key_array=${_farr_alist_key_infix}${__farr_name}
+    [ $# -lt 2 ] && _farr_fatal "missing key"
+    __farr_key="$2"
+
+    if array_find __farr_idx ${__farr_key_array} "${__farr_key}"; then
+        return 0
+    else
+        return 1
+    fi
+}
+
+
+#:
+#: Print the contents of an alist to stderr.
+#:
+#: Args:
+#:   $1 (str): The name of an alist. The array may exist or not.
+#:
+#: Returns:
+#:   0
+#:
+#: This function must know some of the basics of the array implementation
+#: also.
+#:
+alist_debug() {
+    local __farr_name
+
+    local __farr_key_array __farr_val_array
+    local __farr_key_gvrname __farr_val_gvrname
+    local __farr_key_l __farr_val_l __farr_idx __farr_i_k __farr_i_v
+
+     [ $# -lt 1 ] && _farr_fatal "missing array name"
+    __farr_name=$1
+    __farr_key_array=${_farr_alist_key_infix}${__farr_name}
+    __farr_val_array=${_farr_alist_value_infix}${__farr_name}
+
+    __farr_key_gvrname=${_farr_global_prefix}${__farr_key_array}
+    __farr_val_gvrname=${_farr_global_prefix}${__farr_val_array}
+
+    # Check whether the variables alread exist
+    eval __farr_key_l=\${${__farr_key_gvrname}__:-${_farr_unset}}
+    if [ "${__farr_key_l}" = ${_farr_unset} ]; then
+	echo "DEBUG: alist \`${__farr_name}' does not exist (key list)" 1>&2
+        return 0
+    fi
+    eval __farr_val_l=\${${__farr_val_gvrname}__:-${_farr_unset}}
+    if [ "${__farr_val_l}" = ${_farr_unset} ]; then
+	echo "DEBUG: alist \`${__farr_name}' does not exist (value list)" 1>&2
+        return 0
+    fi
+    if [ ${__farr_key_l} -ne ${__farr_val_l} ]; then
+        echo "DEBUG: length mismatch between keys and values in alist \`${__farr_name}' encountered" 1>&2
+        return 0
+    fi
+    echo "DEBUG: alist \`${__farr_name}' has length ${__farr_key_l}" 1>&2
+    if [ "${__farr_key_l}" -gt 0 ]; then
+        echo "DEBUG:   its contents:" 1>&2
+        __farr_idx=1
+        while [ ${__farr_idx} -le ${__farr_key_l} ]; do
+            eval __farr_i_k=\"\${${__farr_key_gvrname}_${__farr_idx}}\"
+            eval __farr_i_v=\"\${${__farr_val_gvrname}_${__farr_idx}}\"
+            printf "DEBUG:     \`%s' -> \`%s'\\n" "${__farr_i_k}" "${__farr_i_v}" 1>&2
+            __farr_idx=$((${__farr_idx} + 1))
+        done
+    fi
+    return 0
+}