# # mal (Make a Lisp) object types # if test -z $(__mal_types_included__) { setglobal __mal_types_included = 'true' declare -A ANON setglobal __obj_magic = '__5bal7' setglobal __keyw = $[echo -en "\xCA\x9E] # \u029E setglobal __obj_hash_code = $(__obj_hash_code:-0) proc __new_obj_hash_code { setglobal __obj_hash_code = $shExpr(' __obj_hash_code + 1') setglobal r = $(__obj_hash_code) } proc __new_obj { __new_obj_hash_code setglobal r = ""$(1)_$(r)"" } proc __new_obj_like { __new_obj_hash_code setglobal r = ""$(1%_*)_$(r)"" } # Errors/Exceptions setglobal __ERROR = '' proc _error { _string $(1) setglobal __ERROR = $(r) setglobal r = '' } # # General functions # # Return the type of the object (or "make" if it's not a object proc _obj_type { local type="$(1:0:4)" setglobal r = '' match $(type) { with symb setglobal r = '"symbol'" with list setglobal r = '"list'" with numb setglobal r = '"number'" with func setglobal r = '"function'" with strn local s="$(ANON["${1}"])" if [[ "${1:0:1}" = "${__keyw}" ]] \ || [[ "${1:0:2}" = "${__keyw}" ]] { setglobal r = '"keyword'" } else { setglobal r = '"string'" } with _nil setglobal r = '"nil'" with true setglobal r = '"true'" with fals setglobal r = '"false'" with vect setglobal r = '"vector'" with hmap setglobal r = '"hash_map'" with atom setglobal r = '"atom'" with undf setglobal r = '"undefined'" with * setglobal r = '"bash'" } } proc _equal? { _obj_type $(1); local ot1="$(r)" _obj_type $(2); local ot2="$(r)" if [[ "${ot1}" != "${ot2}" ]] { if ! _sequential? $(1) || ! _sequential? $(2) { return 1 } } match $(ot1) { with string|symbol|keyword|number [[ "${ANON["${1}"]}" == "${ANON["${2}"]}" ]] with 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 with 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)] { eval v1="\${$(hm1)[\"$(k)\"]}" eval v2="\${$(hm2)[\"$(k)\"]}" test $(v1) || return 1 test $(v2) || return 1 _equal? $(v1) $(v2) || return 1 } with * [[ "${1}" == "${2}" ]] } } # Constant atomic values setglobal __nil = '_nil_0' setglobal __true = 'true_0' setglobal __false = 'fals_0' proc _nil? { [[ ${1} =~ ^_nil_ ]]; } proc _true? { [[ ${1} =~ ^true_ ]]; } proc _false? { [[ ${1} =~ ^fals_ ]]; } # Symbols proc _symbol { __new_obj_hash_code setglobal r = ""symb_$(r)"" compat array-assign ANON '"${r}"' $(1//\*/__STAR__) } proc _symbol? { [[ ${1} =~ ^symb_ ]]; } # Keywords proc _keyword { local k="$(1)" __new_obj_hash_code setglobal r = ""strn_$(r)"" if [[ "${1:0:1}" = "${__keyw}" ]] \ || [[ "${1:0:2}" = "${__keyw}" ]] { true } else { setglobal k = ""$(__keyw)$(1)"" } compat array-assign ANON '"${r}"' $(k//\*/__STAR__) } proc _keyword? { [[ ${1} =~ ^strn_ ]] || return 1 local s="$(ANON["${1}"])" [[ "${s:0:1}" = "${__keyw}" ]] || [[ "${s:0:2}" = "${__keyw}" ]] } # Numbers proc _number { __new_obj_hash_code setglobal r = ""numb_$(r)"" compat array-assign ANON '"${r}"' $(1) } proc _number? { [[ ${1} =~ ^numb_ ]]; } # Strings proc _string { __new_obj_hash_code setglobal r = ""strn_$(r)"" compat array-assign ANON '"${r}"' $(1//\*/__STAR__) } proc _string? { [[ ${1} =~ ^strn_ ]]; } # Functions # Return a function object. The first parameter is the # function 'source'. proc _function { __new_obj_hash_code eval "function $(__obj_magic)_func_$(r) () { $(1%;) ; }" setglobal r = ""func_$(r)"" if [[ "${2}" ]] { # Native function compat array-assign ANON '"${r}"' ""$(__obj_magic)_$(r)@$(2)@$(3)@$(4)"" } else { # Bash function compat array-assign ANON '"${r}"' ""$(__obj_magic)_$(r)"" } } proc _function? { [[ ${1} =~ ^func_ ]]; } # Lists proc _list { __new_obj_hash_code setglobal r = ""list_$(r)"" compat array-assign ANON '"${r}"' $(*) } proc _list? { [[ ${1} =~ ^list_ ]]; } # Vectors proc _vector { __new_obj_hash_code setglobal r = ""vector_$(r)"" compat array-assign ANON '"${r}"' $(*) } proc _vector? { [[ ${1} =~ ^vector_ ]]; } # hash maps (associative arrays) proc _hash_map { __new_obj_hash_code local name="hmap_$(r)" local obj="$(__obj_magic)_$(name)" declare -A -g $(obj); eval "$(obj)=()" compat array-assign ANON '"${name}"' $(obj) { eval $(obj)['"'$(ANON["${1}"])'"']='"'$(2)'"' shift; shift } setglobal r = $(name) } proc _hash_map? { [[ ${1} =~ ^hmap_ ]]; } proc _contains? { local obj="$(ANON["${1}"])" eval [[ "\${$(obj)[\"$(2)\"]+isset}" ]] } proc _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}=) setglobal r = $(name) } # Return same hash map with keys/values added/mutated in place proc _assoc! { local obj=$(ANON["${1}"]); shift declare -A -g $(obj) { eval $(obj)['"'$(1)'"']='"'$(2)'"' shift; shift } } # Return same hash map with keys/values deleted/mutated in place proc _dissoc! { local obj=$(ANON["${1}"]); shift declare -A -g $(obj) { eval unset $(obj)['"'$(1)'"'] shift } } # Atoms proc _atom { __new_obj_hash_code setglobal r = ""atom_$(r)"" compat array-assign ANON '"${r}"' $(*) } proc _atom? { [[ ${1} =~ ^atom_ ]]; } # sequence operations proc _sequential? { _list? $(1) || _vector? $(1) } proc _nth { local temp=(${ANON["${1}"]}) setglobal r = $(temp[${2}]) } proc _first { local temp="$(ANON["${1}"])" setglobal r = $(temp%% *) test $(r) || setglobal r = $(__nil) } proc _last { local temp="$(ANON["${1}"])" setglobal r = $(temp##* ) } # Creates a new vector/list of the everything after but the first # element proc _rest { local temp="$(ANON["${1}"])" _list if [[ "${temp#* }" == "${temp}" ]] { compat array-assign ANON '"${r}"' ''ANON["${r}"]= } else { compat array-assign ANON '"${r}"' $(temp#* ) } } proc _empty? { [[ -z "${ANON["${1}"]}" ]]; } # conj that mutates in place (and always appends) proc _conj! { local obj="$(1)"; shift local obj_data="$(ANON["${obj}"])" compat array-assign ANON '"${obj}"' ""$(obj_data:+${obj_data} )$(*)"" setglobal r = $(1) } proc _count { if _nil? $(1) { setglobal r = '"0'" } else { local temp=(${ANON["${1}"]}) setglobal r = $(#temp[*]) } } # Slice a sequence object $1 starting at $2 of length $3 proc _slice { local temp=(${ANON["${1}"]}) __new_obj_like $(1) compat array-assign 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. proc _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)] { #echo eval ${f%%@*} "${v}" "${@}" eval $(f%%@*) $(v) $(@) [[ "${__ERROR}" ]] && setglobal r = '' && return 1 _conj! $(new_seq) $(r) } setglobal r = $(new_seq) } proc _map { _map_with_type _list $(@) } }