mirror of
https://github.com/kanaka/mal.git
synced 2024-11-11 08:56:41 +03:00
a0b63ee477
- hash-map equality support for bash, c, coffee, cs, es6, java, js, julia, make, php. - also, add another test to catch another hash-map in-equality: same keys, different values
367 lines
7.2 KiB
Bash
367 lines
7.2 KiB
Bash
#
|
|
# mal (Make a Lisp) object types
|
|
#
|
|
|
|
if [ -z "${__mal_types_included__}" ]; then
|
|
__mal_types_included=true
|
|
|
|
declare -A ANON
|
|
|
|
__obj_magic=__5bal7
|
|
__keyw=$(echo -en "\xCA\x9E") # \u029E
|
|
__obj_hash_code=${__obj_hash_code:-0}
|
|
|
|
__new_obj_hash_code () {
|
|
__obj_hash_code=$(( __obj_hash_code + 1))
|
|
r="${__obj_hash_code}"
|
|
}
|
|
|
|
__new_obj () {
|
|
__new_obj_hash_code
|
|
r="${1}_${r}"
|
|
}
|
|
|
|
__new_obj_like () {
|
|
__new_obj_hash_code
|
|
r="${1%_*}_${r}"
|
|
}
|
|
|
|
|
|
# Errors/Exceptions
|
|
|
|
__ERROR=
|
|
_error() {
|
|
_string "${1}"
|
|
__ERROR="${r}"
|
|
r=
|
|
}
|
|
|
|
|
|
|
|
#
|
|
# General functions
|
|
#
|
|
|
|
# Return the type of the object (or "make" if it's not a object
|
|
_obj_type () {
|
|
local type="${1:0:4}"
|
|
r=
|
|
case "${type}" in
|
|
symb) r="symbol" ;;
|
|
list) r="list" ;;
|
|
numb) r="number" ;;
|
|
func) r="function" ;;
|
|
strn)
|
|
local s="${ANON["${1}"]}"
|
|
if [[ "${1:0:1}" = "${__keyw}" ]] \
|
|
|| [[ "${1:0:2}" = "${__keyw}" ]]; then
|
|
r="keyword"
|
|
else
|
|
r="string"
|
|
fi ;;
|
|
_nil) r="nil" ;;
|
|
true) r="true" ;;
|
|
fals) r="false" ;;
|
|
vect) r="vector" ;;
|
|
hmap) r="hash_map" ;;
|
|
atom) r="atom" ;;
|
|
undf) r="undefined" ;;
|
|
*) r="bash" ;;
|
|
esac
|
|
}
|
|
|
|
_equal? () {
|
|
_obj_type "${1}"; local ot1="${r}"
|
|
_obj_type "${2}"; local ot2="${r}"
|
|
if [[ "${ot1}" != "${ot2}" ]]; then
|
|
if ! _sequential? "${1}" || ! _sequential? "${2}"; then
|
|
return 1
|
|
fi
|
|
fi
|
|
case "${ot1}" in
|
|
string|symbol|keyword|number)
|
|
[[ "${ANON["${1}"]}" == "${ANON["${2}"]}" ]] ;;
|
|
list|vector)
|
|
_count "${1}"; local sz1="${r}"
|
|
_count "${2}"; local sz2="${r}"
|
|
[[ "${sz1}" == "${sz2}" ]] || return 1
|
|
local a1=(${ANON["${1}"]})
|
|
local a2=(${ANON["${2}"]})
|
|
for ((i=0;i<${#a1[*]};i++)); do
|
|
_equal? "${a1[${i}]}" "${a2[${i}]}" || return 1
|
|
done
|
|
;;
|
|
hash_map)
|
|
local hm1="${ANON["${1}"]}"
|
|
eval local ks1="\${!${hm1}[@]}"
|
|
local hm2="${ANON["${2}"]}"
|
|
eval local ks2="\${!${hm2}[@]}"
|
|
[[ "${#ks1}" == "${#ks2}" ]] || return 1
|
|
for k in ${ks1}; do
|
|
eval v1="\${${hm1}[\"${k}\"]}"
|
|
eval v2="\${${hm2}[\"${k}\"]}"
|
|
[ "${v1}" ] || return 1
|
|
[ "${v2}" ] || return 1
|
|
_equal? "${v1}" "${v2}" || return 1
|
|
done
|
|
;;
|
|
*)
|
|
[[ "${1}" == "${2}" ]] ;;
|
|
esac
|
|
}
|
|
|
|
# Constant atomic values
|
|
|
|
__nil=_nil_0
|
|
__true=true_0
|
|
__false=fals_0
|
|
|
|
_nil? () { [[ ${1} =~ ^_nil_ ]]; }
|
|
_true? () { [[ ${1} =~ ^true_ ]]; }
|
|
_false? () { [[ ${1} =~ ^fals_ ]]; }
|
|
|
|
|
|
# Symbols
|
|
|
|
_symbol () {
|
|
__new_obj_hash_code
|
|
r="symb_${r}"
|
|
ANON["${r}"]="${1//\*/__STAR__}"
|
|
}
|
|
_symbol? () { [[ ${1} =~ ^symb_ ]]; }
|
|
|
|
|
|
# Keywords
|
|
|
|
_keyword () {
|
|
local k="${1}"
|
|
__new_obj_hash_code
|
|
r="strn_${r}"
|
|
if [[ "${1:0:1}" = "${__keyw}" ]] \
|
|
|| [[ "${1:0:2}" = "${__keyw}" ]]; then
|
|
true
|
|
else
|
|
k="${__keyw}${1}"
|
|
fi
|
|
ANON["${r}"]="${k//\*/__STAR__}"
|
|
}
|
|
_keyword? () {
|
|
[[ ${1} =~ ^strn_ ]] || return 1
|
|
local s="${ANON["${1}"]}"
|
|
[[ "${s:0:1}" = "${__keyw}" ]] || [[ "${s:0:2}" = "${__keyw}" ]]
|
|
}
|
|
|
|
|
|
# Numbers
|
|
|
|
_number () {
|
|
__new_obj_hash_code
|
|
r="numb_${r}"
|
|
ANON["${r}"]="${1}"
|
|
}
|
|
_number? () { [[ ${1} =~ ^numb_ ]]; }
|
|
|
|
|
|
# Strings
|
|
|
|
_string () {
|
|
__new_obj_hash_code
|
|
r="strn_${r}"
|
|
ANON["${r}"]="${1//\*/__STAR__}"
|
|
}
|
|
_string? () { [[ ${1} =~ ^strn_ ]]; }
|
|
|
|
|
|
# Functions
|
|
# Return a function object. The first parameter is the
|
|
# function 'source'.
|
|
_function () {
|
|
__new_obj_hash_code
|
|
eval "function ${__obj_magic}_func_${r} () { ${1%;} ; }"
|
|
r="func_${r}"
|
|
if [[ "${2}" ]]; then
|
|
# Native function
|
|
ANON["${r}"]="${__obj_magic}_${r}@${2}@${3}@${4}"
|
|
else
|
|
# Bash function
|
|
ANON["${r}"]="${__obj_magic}_${r}"
|
|
fi
|
|
}
|
|
_function? () { [[ ${1} =~ ^func_ ]]; }
|
|
|
|
|
|
# Lists
|
|
|
|
_list () {
|
|
__new_obj_hash_code
|
|
r="list_${r}"
|
|
ANON["${r}"]="${*}"
|
|
}
|
|
_list? () { [[ ${1} =~ ^list_ ]]; }
|
|
|
|
|
|
# Vectors
|
|
|
|
_vector () {
|
|
__new_obj_hash_code
|
|
r="vector_${r}"
|
|
ANON["${r}"]="${*}"
|
|
}
|
|
_vector? () { [[ ${1} =~ ^vector_ ]]; }
|
|
|
|
|
|
# hash maps (associative arrays)
|
|
|
|
_hash_map () {
|
|
__new_obj_hash_code
|
|
local name="hmap_${r}"
|
|
local obj="${__obj_magic}_${name}"
|
|
declare -A -g ${obj}; eval "${obj}=()"
|
|
ANON["${name}"]="${obj}"
|
|
|
|
while [[ "${1}" ]]; do
|
|
eval ${obj}[\"${ANON["${1}"]}\"]=\"${2}\"
|
|
shift; shift
|
|
done
|
|
|
|
r="${name}"
|
|
}
|
|
_hash_map? () { [[ ${1} =~ ^hmap_ ]]; }
|
|
|
|
_contains? () {
|
|
local obj="${ANON["${1}"]}"
|
|
eval [[ "\${${obj}[\"${2}\"]+isset}" ]]
|
|
}
|
|
|
|
_copy_hash_map () {
|
|
local orig_obj="${ANON["${1}"]}"
|
|
_hash_map
|
|
local name="${r}"
|
|
local obj="${ANON["${name}"]}"
|
|
|
|
# Copy the existing key/values to the new object
|
|
local temp=$(typeset -p ${orig_obj})
|
|
eval ${temp/#declare -A ${orig_obj}=/declare -A -g ${obj}=}
|
|
r="${name}"
|
|
}
|
|
|
|
# Return same hash map with keys/values added/mutated in place
|
|
_assoc! () {
|
|
local obj=${ANON["${1}"]}; shift
|
|
declare -A -g ${obj}
|
|
|
|
# Set the key/values specified
|
|
while [[ "${1}" ]]; do
|
|
eval ${obj}[\"${1}\"]=\"${2}\"
|
|
shift; shift
|
|
done
|
|
}
|
|
|
|
# Return same hash map with keys/values deleted/mutated in place
|
|
_dissoc! () {
|
|
local obj=${ANON["${1}"]}; shift
|
|
declare -A -g ${obj}
|
|
|
|
# Delete the key/values specified
|
|
while [[ "${1}" ]]; do
|
|
eval unset ${obj}[\"${1}\"]
|
|
shift
|
|
done
|
|
}
|
|
|
|
|
|
# Atoms
|
|
|
|
_atom() {
|
|
__new_obj_hash_code
|
|
r="atom_${r}"
|
|
ANON["${r}"]="${*}"
|
|
}
|
|
_atom? () { [[ ${1} =~ ^atom_ ]]; }
|
|
|
|
|
|
# sequence operations
|
|
|
|
_sequential? () {
|
|
_list? "${1}" || _vector? "${1}"
|
|
}
|
|
|
|
_nth () {
|
|
local temp=(${ANON["${1}"]})
|
|
r="${temp[${2}]}"
|
|
}
|
|
|
|
_first () {
|
|
local temp="${ANON["${1}"]}"
|
|
r="${temp%% *}"
|
|
[ "${r}" ] || r="${__nil}"
|
|
}
|
|
|
|
_last () {
|
|
local temp="${ANON["${1}"]}"
|
|
r="${temp##* }"
|
|
}
|
|
|
|
# Creates a new vector/list of the everything after but the first
|
|
# element
|
|
_rest () {
|
|
local temp="${ANON["${1}"]}"
|
|
_list
|
|
if [[ "${temp#* }" == "${temp}" ]]; then
|
|
ANON["${r}"]=
|
|
else
|
|
ANON["${r}"]="${temp#* }"
|
|
fi
|
|
}
|
|
|
|
|
|
_empty? () { [[ -z "${ANON["${1}"]}" ]]; }
|
|
|
|
# conj that mutates in place (and always appends)
|
|
_conj! () {
|
|
local obj="${1}"; shift
|
|
local obj_data="${ANON["${obj}"]}"
|
|
ANON["${obj}"]="${obj_data:+${obj_data} }${*}"
|
|
r="${1}"
|
|
}
|
|
|
|
|
|
|
|
_count () {
|
|
if _nil? "${1}"; then
|
|
r="0"
|
|
else
|
|
local temp=(${ANON["${1}"]})
|
|
r=${#temp[*]}
|
|
fi
|
|
}
|
|
|
|
# Slice a sequence object $1 starting at $2 of length $3
|
|
_slice () {
|
|
local temp=(${ANON["${1}"]})
|
|
__new_obj_like "${1}"
|
|
ANON["${r}"]="${temp[@]:${2}:${3}}"
|
|
}
|
|
|
|
# Takes a bash function and an list object and invokes the function on
|
|
# each element of the list, returning a new list (or vector) of the results.
|
|
_map_with_type () {
|
|
local constructor="${1}"; shift
|
|
local f="${1}"; shift
|
|
local items="${ANON["${1}"]}"; shift
|
|
eval "${constructor}"; local new_seq="${r}"
|
|
for v in ${items}; do
|
|
#echo eval ${f%%@*} "${v}" "${@}"
|
|
eval ${f%%@*} "${v}" "${@}"
|
|
[[ "${__ERROR}" ]] && r= && return 1
|
|
_conj! "${new_seq}" "${r}"
|
|
done
|
|
r="${new_seq}"
|
|
}
|
|
|
|
_map () {
|
|
_map_with_type _list "${@}"
|
|
}
|
|
|
|
fi
|