changeset 582:22d35878f6f8

farray.sh: implement farray_splice(). This is modeled after Perl's "splice()" function. BUGS: - Docs could be improved - Tests
author Franz Glasner <fzglas.hg@dom66.de>
date Tue, 17 Sep 2024 23:03:28 +0200
parents fac8ca743e40
children 55c024c809ca
files share/local-bsdtools/farray.sh
diffstat 1 files changed, 202 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/share/local-bsdtools/farray.sh	Tue Sep 17 23:02:30 2024 +0200
+++ b/share/local-bsdtools/farray.sh	Tue Sep 17 23:03:28 2024 +0200
@@ -291,6 +291,46 @@
 
 
 #:
+#: 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
+#:
+#: Output (Globals):
+#:   __farr_name (str): The name of the array.
+#:   __farr_token (str): The token that is the value of the name.
+#:   __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() {
+    __farr_token=''
+    __farr_gvrname=''
+    __farr_len=''
+    __farr_name="${1-}"
+    [ -z "${__farr_name}" ] && return 1
+    eval __farr_token=\"\$\{"${__farr_name}"-\}\"
+    [ -z "${__farr_token}" ]  && return 1
+    __farr_gvrname="${_farr_array_prefix}${__farr_token}"
+    eval __farr_len=\$\{${__farr_gvrname}__:+SET\}
+    [ -z "${__farr_len}" ] && return 1
+    # eval __farr_len="\$((\${${__farr_gvrname}__} + 0))"
+    eval __farr_len="\$((${__farr_gvrname}__ + 0))"
+    return 0
+}
+
+
+#:
 #: Get the length of an array and put it into a variable
 #:
 #: Args:
@@ -520,6 +560,168 @@
 
 
 #:
+#: 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.
+#:                   -  `null` is len+1.
+#:                   -  0, -1, -2, ...  -(len-1) are len + given index.
+#:   $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.
+#:
+farray_splice() {
+    local __farr_del_name __farr_del_token __farr_del_gvrname __farr_del_len
+    local __farr_l_name __farr_l_token __farr_l_gvrname __farr_l_len
+    local __farr_index __farr_length
+    local __farr_r_name __farr_r_token __farr_r_gvrname __farr_r_len
+
+    #
+    # Dynamically scoped variables for _farr_array_tryget_meta_nonfatal()
+    # and _farr_array_tryget_meta().
+    #
+    local __farr_name __farr_token __farr_gvrname __farr_len
+
+    local __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_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
+
+    if [ -z "${__farr_index}" ]; then
+        __farr_index=$((__farr_l_len + 1))
+    else
+        if [ "${__farr_index}" -le 0 ]; then
+            __farr_index=$((__farr_l_len + __farr_index))
+        fi
+        # NOTE: index value 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"
+    fi
+    # also check the given length
+    if [ -z "${__farr_length}" ]; then
+        __farr_length="$((__farr_l_len - __farr_index + 1))"
+    else
+        [ \( "${__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
+            if [ -n "${__farr_del_name}" ]; then
+                eval __farr_v=\"\$\{${__farr_l_gvrname}_$((__farr_index + __farr_off))\}\"
+                farray_append "${__farr_del_name}" "${__farr_v}"
+            fi
+            eval __farr_v=\"\$\{${__farr_r_gvrname}_$((__farr_off + 1))\}\"
+            eval ${__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
+            if [ -n "${__farr_del_name}" ]; then
+                eval __farr_v=\"\$\{${__farr_l_gvrname}_$((__farr_index + __farr_off))\}\"
+                farray_append "${__farr_del_name}" "${__farr_v}"
+            fi
+            eval __farr_v=\"\$\{${__farr_r_gvrname}_$((__farr_off + 1))\}\"
+            eval ${__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
+            if [ -n "${__farr_del_name}" ]; then
+                eval __farr_v=\"\$\{${__farr_l_gvrname}_$((__farr_index + __farr_off))\}\"
+                farray_append "${__farr_del_name}" "${__farr_v}"
+            fi
+            eval unset ${__farr_l_gvrname}_$((__farr_index + __farr_off))
+            __farr_off=$((__farr_off + 1))
+        done
+        # Move the rest
+        __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}\}\"
+            eval ${__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
+        eval ${__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
+            if [ -n "${__farr_del_name}" ]; then
+                eval __farr_v=\"\$\{${__farr_l_gvrname}_$((__farr_index + __farr_off))\}\"
+                farray_append "${__farr_del_name}" "${__farr_v}"
+            fi
+            eval __farr_v=\"\$\{${__farr_r_gvrname}_$((__farr_off + 1))\}\"
+            eval ${__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
+        __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}\}\"
+            eval ${__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))\}\"
+            eval ${__farr_l_gvrname}_$((__farr_index + __farr_off))=\"\$\{__farr_v\}\"
+            __farr_off=$((__farr_off + 1))
+        done
+        # Adjust the length
+        eval ${__farr_l_gvrname}__=$((__farr_l_len + __farr_delta))
+    fi
+    return 0
+}
+
+
+#:
 #: Empty an existing array.
 #:
 #: Args: