aboutsummaryrefslogtreecommitdiff
path: root/bash/types.sh
diff options
context:
space:
mode:
Diffstat (limited to 'bash/types.sh')
-rw-r--r--bash/types.sh730
1 files changed, 730 insertions, 0 deletions
diff --git a/bash/types.sh b/bash/types.sh
new file mode 100644
index 0000000..e678321
--- /dev/null
+++ b/bash/types.sh
@@ -0,0 +1,730 @@
+#
+# mal: Object Types and Functions
+#
+
+declare -A ANON
+
+__obj_magic=__5bal7
+__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}"
+}
+
+__ERROR=
+
+
+#
+# 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) r="string" ;;
+ _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
+}
+
+obj_type () {
+ _obj_type "${1}"
+ string "${r}"
+}
+
+_pr_str () {
+ local print_readably="${2}"
+ _obj_type "${1}"; local ot="${r}"
+ if [[ -z "${ot}" ]]; then
+ _error "_pr_str failed on '${1}'"
+ r="<${1}>"
+ else
+ eval ${ot}_pr_str "${1}" "${print_readably}"
+ fi
+}
+
+pr_str () {
+ local res=""
+ for x in "${@}"; do _pr_str "${x}" yes; res="${res} ${r}"; done
+ string "${res:1}"
+}
+
+str () {
+ local res=""
+ for x in "${@}"; do _pr_str "${x}"; res="${res}${r}"; done
+ string "${res}"
+}
+
+prn () {
+ local res=""
+ for x in "${@}"; do _pr_str "${x}" yes; res="${res} ${r}"; done
+ echo "${res:1}"
+ r="${__nil}";
+}
+
+println () {
+ local res=""
+ for x in "${@}"; do _pr_str "${x}"; res="${res} ${r}"; done
+ res="${res//\\n/$'\n'}"
+ echo -e "${res:1}"
+ r="${__nil}";
+}
+
+with_meta () {
+ local obj="${1}"; shift
+ local meta_data="${1}"; shift
+ __new_obj_like "${obj}"
+ ANON["${r}"]="${ANON["${obj}"]}"
+ local meta_obj="meta_${r#*_}"
+ ANON["${meta_obj}"]="${meta_data}"
+}
+
+meta () {
+ r="${ANON["meta_${1#*_}"]}"
+ [[ "${r}" ]] || r="${__nil}"
+}
+
+#
+# Constant atomic values
+#
+
+__undefined=undf_0
+__nil=_nil_0
+__true=true_0
+__false=fals_0
+
+_undefined? () { [[ ${1} =~ ^undf_ ]]; }
+undefined? () { _undefined? "${1}" && r="${__true}" || r="${__false}"; }
+
+_nil? () { [[ ${1} =~ ^_nil_ ]]; }
+nil? () { _nil? "${1}" && r="${__true}" || r="${__false}"; }
+nil_pr_str () { r="nil"; }
+
+_true? () { [[ ${1} =~ ^true_ ]]; }
+true? () { _true? "${1}" && r="${__true}" || r="${__false}"; }
+true_pr_str () { r="true"; }
+
+_false? () { [[ ${1} =~ ^fals_ ]]; }
+false? () { _false? "${1}" && r="${__false}" || r="${__false}"; }
+false_pr_str () { r="false"; }
+
+
+#
+# Numbers
+#
+
+number () {
+ __new_obj_hash_code
+ r="numb_${r}"
+ ANON["${r}"]="${1}"
+}
+_number? () { [[ ${1} =~ ^numb_ ]]; }
+number? () { _number? "${1}" && r="${__true}" || r="${__false}"; }
+number_pr_str () { r="${ANON["${1}"]}"; }
+
+num_plus () { r=$(( ${ANON["${1}"]} + ${ANON["${2}"]} )); number "${r}"; }
+num_minus () { r=$(( ${ANON["${1}"]} - ${ANON["${2}"]} )); number "${r}"; }
+num_multiply () { r=$(( ${ANON["${1}"]} * ${ANON["${2}"]} )); number "${r}"; }
+num_divide () { r=$(( ${ANON["${1}"]} / ${ANON["${2}"]} )); number "${r}"; }
+
+_num_bool () { [[ "${1}" = "1" ]] && r="${__true}" || r="${__false}"; }
+num_gt () { r=$(( ${ANON["${1}"]} > ${ANON["${2}"]} )); _num_bool "${r}"; }
+num_gte () { r=$(( ${ANON["${1}"]} >= ${ANON["${2}"]} )); _num_bool "${r}"; }
+num_lt () { r=$(( ${ANON["${1}"]} < ${ANON["${2}"]} )); _num_bool "${r}"; }
+num_lte () { r=$(( ${ANON["${1}"]} <= ${ANON["${2}"]} )); _num_bool "${r}"; }
+
+#
+# Symbols
+#
+
+symbol () {
+ __new_obj_hash_code
+ r="symb_${r}"
+ ANON["${r}"]="${1//$'\*'/__STAR__}"
+}
+_symbol? () { [[ ${1} =~ ^symb_ ]]; }
+symbol? () { _symbol? "${1}" && r="${__true}" || r="${__false}"; }
+symbol_pr_str () {
+ r="${ANON["${1}"]}"
+ r="${r//__STAR__/*}"
+}
+
+
+#
+# Strings
+#
+
+string () {
+ __new_obj_hash_code
+ r="strn_${r}"
+ ANON["${r}"]="${1//$'\*'/__STAR__}"
+}
+_string? () { [[ ${1} =~ ^strn_ ]]; }
+string? () { _string? "${1}" && r="${__true}" || r="${__false}"; }
+string_pr_str () {
+ local print_readably="${2}"
+ if [ "${print_readably}" == "yes" ]; then
+ local s="${ANON["${1}"]}"
+ s="${s//\\/\\\\}"
+ r="\"${s//\"/\\\"}\""
+ else
+ r="${ANON["${1}"]}"
+ fi
+ r="${r//__STAR__/$'*'}"
+}
+
+# TODO: subs
+
+
+#
+# Function objects
+#
+
+# Return a function object. The first parameter is the
+# function 'source'.
+new_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_ ]]; }
+function? () { _function? "${1}" && r="${__true}" || r="${__false}"; }
+function_pr_str () { r="${ANON["${1}"]}"; }
+
+
+#
+# hash maps (associative arrays)
+#
+
+hash_map () {
+ __new_obj_hash_code
+ local name="hmap_${r}"
+ local obj="${__obj_magic}_${name}"
+ declare -A -g ${obj}
+ ANON["${name}"]="${obj}"
+
+ while [[ "${1}" ]]; do
+ eval ${obj}[\"${ANON["${1}"]}\"]=\"${2}\"
+ shift; shift
+ done
+
+ r="${name}"
+}
+_hash_map? () { [[ ${1} =~ ^hmap_ ]]; }
+hash_map? () { _hash_map? "${1}" && r="${__true}" || r="${__false}"; }
+
+hash_map_pr_str () {
+ local print_readably="${2}"
+ local res=""; local val=""
+ local hm="${ANON["${1}"]}"
+ eval local keys="\${!${hm}[@]}"
+ for key in ${keys}; do
+ #res="${res} \"${ANON["${key}"]}\""
+ res="${res} \"${key//__STAR__/$'*'}\""
+ eval val="\${${hm}[\"${key}\"]}"
+ _pr_str "${val}" "${print_readably}"
+ res="${res} ${r}"
+ done
+ r="{${res:1}}"
+}
+
+_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
+}
+
+# Return new hash map with keys/values updated
+assoc () {
+ if ! _hash_map? "${1}"; then
+ _error "assoc onto non-hash-map"
+ return
+ fi
+ _copy_hash_map "${1}"; shift
+ local name="${r}"
+ local obj=${ANON["${name}"]}
+ declare -A -g ${obj}
+
+ while [[ "${1}" ]]; do
+ eval ${obj}[\"${ANON["${1}"]}\"]=\"${2}\"
+ shift; shift
+ done
+ r="${name}"
+}
+
+dissoc () {
+ if ! _hash_map? "${1}"; then
+ _error "dissoc from non-hash-map"
+ return
+ fi
+ _copy_hash_map "${1}"; shift
+ local name="${r}"
+ local obj=${ANON["${name}"]}
+ declare -A -g ${obj}
+
+ while [[ "${1}" ]]; do
+ eval unset ${obj}[\"${ANON["${1}"]}\"]
+ shift
+ done
+ r="${name}"
+}
+
+_get () {
+ _obj_type "${1}"; local ot="${r}"
+ case "${ot}" in
+ hash_map)
+ local obj="${ANON["${1}"]}"
+ eval r="\${${obj}[\"${2}\"]}" ;;
+ list|vector)
+ _nth "${1}" "${2}"
+ esac
+}
+get () {
+ _get "${1}" "${ANON["${2}"]}"
+ [[ "${r}" ]] || r="${__nil}"
+}
+
+_contains? () {
+ local obj="${ANON["${1}"]}"
+ #echo "_contains? ${1} ${2} -> \${${obj}[\"${2}\"]+isset}"
+ eval [[ "\${${obj}[\"${2}\"]+isset}" ]]
+}
+contains? () { _contains? "${1}" "${ANON["${2}"]}" && r="${__true}" || r="${__false}"; }
+
+keys () {
+ local obj="${ANON["${1}"]}"
+ local kstrs=
+ eval local keys="\${!${obj}[@]}"
+ for k in ${keys}; do
+ string "${k}"
+ kstrs="${kstrs} ${r}"
+ done
+
+ __new_obj_hash_code
+ r="list_${r}"
+ ANON["${r}"]="${kstrs:1}"
+}
+
+vals () {
+ local obj="${ANON["${1}"]}"
+ local kvals=
+ local val=
+ eval local keys="\${!${obj}[@]}"
+ for k in ${keys}; do
+ eval val="\${${obj}["\${k}"]}"
+ kvals="${kvals} ${val}"
+ done
+
+ __new_obj_hash_code
+ r="list_${r}"
+ ANON["${r}"]="${kvals:1}"
+}
+
+#
+# Exceptions/Errors
+#
+
+_error() {
+ string "${1}"
+ __ERROR="${r}"
+ r=
+}
+throw() {
+ __ERROR="${1}"
+ r=
+}
+
+#
+# vectors
+#
+
+#
+# vector (same as lists for now)
+#
+
+vector () {
+ __new_obj_hash_code
+ r="vector_${r}"
+ ANON["${r}"]="${*}"
+}
+_vector? () { [[ ${1} =~ ^vector_ ]]; }
+vector? () { _vector? "${1}" && r="${__true}" || r="${__false}"; }
+
+vector_pr_str () {
+ local print_readably="${2}"
+ local res=""
+ for elem in ${ANON["${1}"]}; do
+ _pr_str "${elem}" "${print_readably}"
+ res="${res} ${r}"
+ done
+ r="[${res:1}]"
+}
+
+
+#
+# list (same as vectors for now)
+#
+
+list () {
+ __new_obj_hash_code
+ r="list_${r}"
+ ANON["${r}"]="${*}"
+}
+_list? () { [[ ${1} =~ ^list_ ]]; }
+list? () { _list? "${1}" && r="${__true}" || r="${__false}"; }
+
+list_pr_str () {
+ local print_readably="${2}"
+ local res=""
+ for elem in ${ANON["${1}"]}; do
+ _pr_str "${elem}" "${print_readably}"
+ res="${res} ${r}"
+ done
+ r="(${res:1})"
+}
+
+cons () {
+ list ${1} ${ANON["${2}"]}
+}
+
+
+#
+# atoms
+#
+atom() {
+ __new_obj_hash_code
+ r="atom_${r}"
+ ANON["${r}"]="${*}"
+}
+_atom? () { [[ ${1} =~ ^atom_ ]]; }
+atom? () { _atom? "${1}" && r="${__true}" || r="${__false}"; }
+atom_pr_str () {
+ local print_readably="${2}"
+ _pr_str "${ANON["${1}"]}" "${print_readably}"
+ r="(atom ${r})";
+}
+deref () {
+ # TODO: double-check atom type
+ r=${ANON["${1}"]}
+}
+reset_BANG () {
+ local atm="${1}"; shift
+ ANON["${atm}"]="${*}"
+ r="${*}"
+}
+swap_BANG () {
+ local atm="${1}"; shift
+ local f="${ANON["${1}"]}"; shift
+ ${f%%@*} "${ANON["${atm}"]}" "${@}"
+ ANON["${atm}"]="${r}"
+}
+
+
+#
+# sequence operations
+#
+
+_sequential? () {
+ _list? "${1}" || _vector? "${1}"
+}
+sequential? () {
+ _sequential? "${1}" && r="${__true}" || r="${__false}"
+}
+
+_nth () {
+ local temp=(${ANON["${1}"]})
+ r=${temp[${2}]}
+}
+nth () {
+ _nth "${1}" "${ANON["${2}"]}"
+}
+
+
+_empty? () { [[ -z "${ANON["${1}"]}" ]]; }
+empty? () { _empty? "${1}" && r="${__true}" || r="${__false}"; }
+
+concat () {
+ list
+ local acc=""
+ for item in "${@}"; do
+ acc="${acc} ${ANON["${item}"]}"
+ done
+ ANON["${r}"]="${acc:1}"
+}
+
+conj () {
+ local obj="${1}"; shift
+ local obj_data="${ANON["${obj}"]}"
+ __new_obj_like "${obj}"
+ ANON["${r}"]="${obj_data:+${obj_data} }${*}"
+}
+
+# conj that mutates in place
+conj! () {
+ local obj="${1}"; shift
+ local obj_data="${ANON["${obj}"]}"
+ ANON["${obj}"]="${obj_data:+${obj_data} }${*}"
+ r="${1}"
+}
+
+
+
+_count () {
+ local temp=(${ANON["${1}"]})
+ r=${#temp[*]}
+}
+count () {
+ _count "${1}"
+ number "${r}"
+}
+
+first () {
+ local temp="${ANON["${1}"]}"
+ r="${temp%% *}"
+}
+
+last () {
+ local temp="${ANON["${1}"]}"
+ r="${temp##* }"
+}
+
+# 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}}"
+}
+
+# Creates a new vector/list of the everything after but the first
+# element
+rest () {
+ local temp="${ANON["${1}"]}"
+ __new_obj_like "${1}"
+ if [[ "${temp#* }" == "${temp}" ]]; then
+ ANON["${r}"]=
+ else
+ ANON["${r}"]="${temp#* }"
+ fi
+}
+
+apply () {
+ local f="${ANON["${1}"]}"
+ local args="${2}"
+ local items="${ANON["${2}"]}"
+ eval ${f%%@*} ${items}
+}
+
+# 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 ot="${1}"; shift
+ local f="${1}"; shift
+ local items="${ANON["${1}"]}"; shift
+ eval "${ot}"; 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 "${@}"
+}
+
+# Takes a function object and an list object and invokes the function
+# on each element of the list, returning a new list of the results.
+map () {
+ local f="${ANON["${1}"]}"; shift
+ #echo _map "${f}" "${@}"
+ _map "${f}" "${@}"
+}
+
+_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|number)
+ [[ "${ANON["${1}"]}" == "${ANON["${2}"]}" ]] ;;
+ list|vector|hash_map)
+ _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
+ ;;
+ *)
+ [[ "${1}" == "${2}" ]] ;;
+ esac
+}
+equal? () {
+ _equal? "${1}" "${2}" && r="${__true}" || r="${__false}"
+}
+
+#
+# ENV
+#
+
+# Any environment is a hash_map with an __outer__ key that refers to
+# a parent environment (or nil)
+ENV () {
+ r=
+ hash_map
+ local env="${r}"
+ if [[ "${1}" ]]; then
+ outer="${1}"; shift
+ assoc! "${env}" "__outer__" "${outer}"
+ else
+ assoc! "${env}" "__outer__" "${__nil}"
+ fi
+ r="${env}"
+
+ if [[ "${1}" && "${@}" ]]; then
+ local binds=(${ANON["${1}"]}); shift
+ local idx=0
+ while [[ "${binds["${idx}"]}" ]]; do
+ local fp="${ANON["${binds["${idx}"]}"]}"
+ if [[ "${fp}" == "&" ]]; then
+ idx=$(( idx + 1 ))
+ fp="${ANON["${binds["${idx}"]}"]}"
+ list "${@}"
+ assoc! "${env}" "${fp}" "${r}"
+ break
+ else
+ assoc! "${env}" "${fp}" "${1}"
+ shift
+ idx=$(( idx + 1 ))
+ fi
+ done
+ fi
+ r="${env}"
+}
+
+# Find the environment with the key set and return the environment
+ENV_FIND () {
+ if _contains? "${1}" "${2}"; then
+ r="${1}"
+ else
+ local obj="${ANON["${1}"]}"
+ eval local outer="\${${obj}["__outer__"]}"
+ if [[ "${outer}" && "${outer}" != "${__nil}" ]]; then
+ ENV_FIND "${outer}" "${2}"
+ else
+ r=
+ fi
+ fi
+}
+
+# Find the environment with the key set and return the value of the
+# key in that environment. If no environment contains the key then
+# return an error
+ENV_GET () {
+ ENV_FIND "${1}" "${2}"
+ local env="${r}"
+ if [[ "${r}" ]]; then
+ local obj="${ANON["${env}"]}"
+ eval r="\${${obj}["${2}"]}"
+ else
+ _error "'${2}' not found"
+ fi
+}
+
+ENV_SET () {
+ assoc! "${1}" "${2}" "${3}"
+}
+
+# TODO: memory visualizer (like Make implementation)
+
+# Namespace of type functions
+
+declare -A types_ns=(
+ [type]=obj_type
+ [pr-str]=pr_str [str]=str [prn]=prn [println]=println
+ [with-meta]=with_meta [meta]=meta
+ [=]=equal?
+ [nil?]=nil? [true?]=true? [false?]=false?
+ [symbol?]=symbol?
+ [>]=num_gt [>=]=num_gte [<]=num_lt [<=]=num_lte
+ [+]=num_plus [-]=num_minus [__STAR__]=num_multiply [/]=num_divide
+ [hash-map]=hash_map [map?]=hash_map?
+ [assoc]=assoc [dissoc]=dissoc [get]=get
+ [contains?]=contains? [keys]=keys [vals]=vals
+ [throw]=throw
+ [list]=list [list?]=list?
+ [vector]=vector [vector?]=vector?
+ [atom]=atom [atom?]=atom? [deref]=deref
+ [reset!]=reset_BANG [swap!]=swap_BANG
+ [sequential?]=sequential?
+ [cons]=cons [nth]=nth [count]=count [empty?]=empty?
+ [concat]=concat [conj]=conj [first]=first [rest]=rest
+ [apply]=apply [map]=map)