# # mal (Make a Lisp) object types # if [ -z "${__mal_core_included__}" ]; then __mal_core_included=true source $(dirname $0)/types.sh source $(dirname $0)/reader.sh source $(dirname $0)/printer.sh # Exceptions/Errors throw() { __ERROR="${1}" r= } # General functions obj_type () { _obj_type "${1}" _string "${r}" } equal? () { _equal? "${1}" "${2}" && r="${__true}" || r="${__false}" } # Scalar functions nil? () { _nil? "${1}" && r="${__true}" || r="${__false}"; } true? () { _true? "${1}" && r="${__true}" || r="${__false}"; } false? () { _false? "${1}" && r="${__true}" || r="${__false}"; } # Symbol functions symbol () { _symbol "${ANON["${1}"]}"; } symbol? () { _symbol? "${1}" && r="${__true}" || r="${__false}"; } # Keyword functions keyword () { _keyword "${ANON["${1}"]}"; } keyword? () { _keyword? "${1}" && r="${__true}" || r="${__false}"; } # Number functions number? () { _number? "${1}" && r="${__true}" || r="${__false}"; } 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}"; } # return number of milliseconds since epoch time_ms () { local ms=$(date +%s%3N) _number "${ms}" } # String functions string? () { _string? "${1}" && ( ! _keyword? "${1}" ) && r="${__true}" || r="${__false}"; } 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 echo "${res:1}" r="${__nil}"; } readline () { READLINE "${ANON["${1}"]}" && _string "${r}" || r="${__nil}" } read_string () { READ_STR "${ANON["${1}"]}" } slurp () { local lines mapfile lines < "${ANON["${1}"]}" local text="${lines[*]}"; text=${text//$'\n' /$'\n'} _string "${text}" } # Function functions function? () { _function? "${1}" && r="${__true}" || r="${__false}"; } # List functions list? () { _list? "${1}" && r="${__true}" || r="${__false}"; } # Vector functions (same as lists for now) vector? () { _vector? "${1}" && r="${__true}" || r="${__false}"; } # Hash map (associative array) functions hash_map? () { _hash_map? "${1}" && r="${__true}" || r="${__false}"; } # 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}" ;; nil) r="${__nil}" ;; esac } get () { _get "${1}" "${ANON["${2}"]}" [[ "${r}" ]] || r="${__nil}" } 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}" } # sequence operations sequential? () { _sequential? "${1}" && r="${__true}" || r="${__false}" } cons () { _list ${1} ${ANON["${2}"]} } concat () { _list local acc="" for item in "${@}"; do acc="${acc} ${ANON["${item}"]}" done ANON["${r}"]="${acc:1}" } nth () { _nth "${1}" "${ANON["${2}"]}" if [ -z "${r}" ]; then _error "nth: index out of bounds" return fi } empty? () { _empty? "${1}" && r="${__true}" || r="${__false}"; } count () { _count "${1}" _number "${r}" } apply () { local f="${ANON["${1}"]}"; shift local items="${@:1:$(( ${#@} -1 ))} ${ANON["${!#}"]}" eval ${f%%@*} ${items} } # 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}" "${@}" } conj () { local obj="${1}"; shift local obj_data="${ANON["${obj}"]}" __new_obj_like "${obj}" if _list? "${obj}"; then ANON["${r}"]="${obj_data:+${obj_data}}" for elem in ${@}; do ANON["${r}"]="${elem} ${ANON["${r}"]}" done else ANON["${r}"]="${obj_data:+${obj_data} }${*}" fi } seq () { local obj="${1}"; shift local obj_data="${ANON["${obj}"]}" if _list? "${obj}"; then _count "${obj}" if [ "${r}" -eq 0 ]; then r="${__nil}"; return; fi r="${obj}" elif _vector? "${obj}"; then _count "${obj}" if [ "${r}" -eq 0 ]; then r="${__nil}"; return; fi __new_obj_hash_code r="list_${r}" ANON["${r}"]="${obj_data}" elif _string? "${obj}"; then if [ "${#obj_data}" -eq 0 ]; then r="${__nil}"; return; fi local i=0 acc="" for (( i=0; i < ${#obj_data}; i++ )); do _string "${obj_data:$i:1}" acc="${acc} ${r}" done _list ANON["${r}"]="${acc:1}" elif _nil? "${obj}"; then r="${__nil}" else throw "seq: called on non-sequence" fi } # Metadata functions 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}" } # Atom functions atom? () { _atom? "${1}" && r="${__true}" || r="${__false}"; } 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}" } # Namespace of core functions declare -A core_ns=( [type]=obj_type [=]=equal? [throw]=throw [nil?]=nil? [true?]=true? [false?]=false? [string?]=string? [symbol]=symbol [symbol?]=symbol? [keyword]=keyword [keyword?]=keyword? [pr-str]=pr_str [str]=str [prn]=prn [println]=println [readline]=readline [read-string]=read_string [slurp]=slurp [<]=num_lt [<=]=num_lte [>]=num_gt [>=]=num_gte [+]=num_plus [-]=num_minus [__STAR__]=num_multiply [/]=num_divide [time-ms]=time_ms [list]=_list [list?]=list? [vector]=_vector [vector?]=vector? [hash-map]=_hash_map [map?]=hash_map? [assoc]=assoc [dissoc]=dissoc [get]=get [contains?]=contains? [keys]=keys [vals]=vals [sequential?]=sequential? [cons]=cons [concat]=concat [nth]=nth [first]=_first [rest]=_rest [empty?]=empty? [count]=count [apply]=apply [map]=map [conj]=conj [seq]=seq [with-meta]=with_meta [meta]=meta [atom]=_atom [atom?]=atom? [deref]=deref [reset!]=reset_BANG [swap!]=swap_BANG) fi