diff options
Diffstat (limited to 'bash/types.sh')
| -rw-r--r-- | bash/types.sh | 730 |
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) |
