aboutsummaryrefslogtreecommitdiff
path: root/bash
diff options
context:
space:
mode:
Diffstat (limited to 'bash')
-rw-r--r--bash/Makefile25
-rw-r--r--bash/reader.sh153
-rwxr-xr-xbash/step0_repl.sh21
-rwxr-xr-xbash/step1_read_print.sh45
-rwxr-xr-xbash/step2_eval.sh92
-rwxr-xr-xbash/step3_env.sh116
-rwxr-xr-xbash/step4_if_fn_do.sh140
-rwxr-xr-xbash/step5_tco.sh157
-rwxr-xr-xbash/step6_file.sh170
-rwxr-xr-xbash/step7_quote.sh215
-rwxr-xr-xbash/step8_macros.sh252
-rwxr-xr-xbash/step9_interop.sh261
-rwxr-xr-xbash/stepA_more.sh282
-rw-r--r--bash/tests/common.sh25
-rw-r--r--bash/tests/reader.sh88
-rw-r--r--bash/tests/types.sh161
-rw-r--r--bash/types.sh730
17 files changed, 2933 insertions, 0 deletions
diff --git a/bash/Makefile b/bash/Makefile
new file mode 100644
index 0000000..53f0d09
--- /dev/null
+++ b/bash/Makefile
@@ -0,0 +1,25 @@
+TESTS = tests/types.sh tests/reader.sh
+
+SOURCES = types.sh reader.sh stepA_more.sh
+
+all: mal.sh
+
+mal.sh: $(SOURCES)
+ cat $+ > $@
+ echo "#!/bin/bash" > $@
+ cat $+ | grep -v "^source " >> $@
+ chmod +x $@
+
+clean:
+ rm -f mal.sh
+
+.PHONY: stats tests $(TESTS)
+
+stats: $(SOURCES)
+ @wc $^
+
+tests: $(TESTS)
+
+$(TESTS):
+ @echo "Running $@"; \
+ bash $@ || exit 1; \
diff --git a/bash/reader.sh b/bash/reader.sh
new file mode 100644
index 0000000..bc32fa7
--- /dev/null
+++ b/bash/reader.sh
@@ -0,0 +1,153 @@
+#
+# mal (Make Lisp) Parser/Reader
+#
+
+source $(dirname $0)/types.sh
+
+READ_ATOM () {
+ local token=${__reader_tokens[${__reader_idx}]}
+ __reader_idx=$(( __reader_idx + 1 ))
+ case "${token}" in
+ [0-9]*) number "${token}" ;;
+ \"*) token="${token:1:-1}"
+ token="${token//\\\"/\"}"
+ string "${token}" ;;
+ nil) r="${__nil}" ;;
+ true) r="${__true}" ;;
+ false) r="${__false}" ;;
+ *) symbol "${token}" ;;
+ esac
+}
+
+# Return seqence of tokens into r.
+# ${1}: Type of r (vector, list)
+# ${2}: starting symbol
+# ${3}: ending symbol
+READ_SEQ () {
+ local start="${1}"
+ local end="${2}"
+ local items=""
+ local token=${__reader_tokens[${__reader_idx}]}
+ __reader_idx=$(( __reader_idx + 1 ))
+ if [[ "${token}" != "${start}" ]]; then
+ r=
+ _error "expected '${start}'"
+ return
+ fi
+ token=${__reader_tokens[${__reader_idx}]}
+ while [[ "${token}" != "${end}" ]]; do
+ if [[ ! "${token}" ]]; then
+ r=
+ _error "exepected '${end}', got EOF"
+ return
+ fi
+ READ_FORM
+ items="${items} ${r}"
+ token=${__reader_tokens[${__reader_idx}]}
+ done
+ __reader_idx=$(( __reader_idx + 1 ))
+ r="${items:1}"
+}
+
+# Return form in r
+READ_FORM () {
+ local token=${__reader_tokens[${__reader_idx}]}
+ case "${token}" in
+ \') __reader_idx=$(( __reader_idx + 1 ))
+ symbol quote; local q="${r}"
+ READ_FORM; local f="${r}"
+ list "${q}" "${f}" ;;
+ \`) __reader_idx=$(( __reader_idx + 1 ))
+ symbol quasiquote; local q="${r}"
+ READ_FORM; local f="${r}"
+ list "${q}" "${f}" ;;
+ \~) __reader_idx=$(( __reader_idx + 1 ))
+ symbol unquote; local q="${r}"
+ READ_FORM; local f="${r}"
+ list "${q}" "${f}" ;;
+ \~\@) __reader_idx=$(( __reader_idx + 1 ))
+ symbol splice-unquote; local q="${r}"
+ READ_FORM; local f="${r}"
+ list "${q}" "${f}" ;;
+ ^) __reader_idx=$(( __reader_idx + 1 ))
+ symbol with-meta; local wm="${r}"
+ READ_FORM; local meta="${r}"
+ READ_FORM; local obj="${r}"
+ list "${wm}" "${obj}" "${meta}" ;;
+ @) __reader_idx=$(( __reader_idx + 1 ))
+ symbol deref; local d="${r}"
+ READ_FORM; local f="${r}"
+ list "${d}" "${f}" ;;
+ \)) _error "unexpected ')'" ;;
+ \() READ_SEQ "(" ")"
+ list ${r} ;;
+ \]) _error "unexpected ']'" ;;
+ \[) READ_SEQ "[" "]"
+ vector ${r} ;;
+ \}) _error "unexpected '}'" ;;
+ \{) READ_SEQ "{" "}"
+ hash_map ${r} ;;
+ *) READ_ATOM
+ esac
+}
+
+# Returns __reader_tokens as an indexed array of tokens
+TOKENIZE () {
+ local data="${*}"
+ local datalen=${#data}
+ local idx=0
+ local chunk=0
+ local chunksz=500
+ local match=
+ local token=
+ local str=
+
+ __reader_idx=0
+ __reader_tokens=
+ while true; do
+ if (( ${#str} < ( chunksz / 2) )) && (( chunk < datalen )); then
+ str="${str}${data:${chunk}:${chunksz}}"
+ chunk=$(( chunk + ${chunksz} ))
+ fi
+ (( ${#str} == 0 )) && break
+ [[ "${str}" =~ ^^([][{}\(\)^@])|^(~@)|(\"(\\.|[^\\\"])*\")|^(;[^$'\n']*)|^([~\'\`])|^([^][ ~\`\'\";{}\(\)^@]+)|^[,]|^[[:space:]]+ ]]
+ match=${BASH_REMATCH[0]}
+ str="${str:${#match}}"
+ token="${match//$'\n'/}"
+ #echo "MATCH: '${token}' / [${str}]"
+ if ! [[ "${token}" =~ (^[,]$|^[[:space:]]*;.*$|^[[:space:]]*$) ]]; then
+ __reader_tokens[${idx}]="${token}"
+ idx=$(( idx + 1 ))
+ fi
+ if [ -z "${match}" ]; then
+ echo >&2 "Tokenizing error at: ${str:0:50}"
+ _error "Tokenizing error at: ${str:0:50}"
+ break
+ fi
+ done
+}
+
+# read-str from a raw "string" or from a string object. Retruns object
+# read in r.
+READ_STR () {
+ declare -a __reader_tokens
+ TOKENIZE "${*}" # sets __reader_tokens
+ #set | grep ^__reader_tokens
+ if [ -z "${__reader_tokens[k]}" ]; then
+ r=
+ return 1 # No tokens
+ fi
+ READ_FORM
+ #echo "Token: ${r}: <${ANON["${r}"]}>"
+ return
+}
+
+# Call readline and save the history. Returns the string read in r.
+READLINE_EOF=
+READLINE_HISTORY_FILE=${HOME}/.mal-history
+READLINE () {
+ history -r "${READLINE_HISTORY_FILE}"
+ read -r -e -p "${1}" r || return "$?"
+ history -s -- "${r}"
+ history -a "${READLINE_HISTORY_FILE}"
+}
diff --git a/bash/step0_repl.sh b/bash/step0_repl.sh
new file mode 100755
index 0000000..261ecc2
--- /dev/null
+++ b/bash/step0_repl.sh
@@ -0,0 +1,21 @@
+#!/bin/bash
+
+READ () {
+ read -u 0 -e -p "user> " r
+}
+
+EVAL () {
+ r=
+ eval "${1}"
+}
+
+PRINT () {
+ r="${1}"
+}
+
+while true; do
+ READ
+ EVAL "${r}"
+ PRINT "${r}"
+ echo "${r}"
+done
diff --git a/bash/step1_read_print.sh b/bash/step1_read_print.sh
new file mode 100755
index 0000000..ba94208
--- /dev/null
+++ b/bash/step1_read_print.sh
@@ -0,0 +1,45 @@
+#!/bin/bash
+
+INTERACTIVE=${INTERACTIVE-yes}
+
+source $(dirname $0)/reader.sh
+
+# READ: read and parse input
+READ () {
+ [ "${1}" ] && r="${1}" || READLINE
+ READ_STR "${r}"
+}
+
+# EVAL: just return the input
+EVAL () {
+ local ast="${1}"
+ local env="${2}"
+ r=
+ [[ "${__ERROR}" ]] && return 1
+ r="${ast}"
+}
+
+# PRINT:
+PRINT () {
+ if [[ "${__ERROR}" ]]; then
+ _pr_str "${__ERROR}" yes
+ r="Error: ${r}"
+ __ERROR=
+ else
+ _pr_str "${1}" yes
+ fi
+}
+
+# REPL: read, eval, print, loop
+REP () {
+ READ "${1}" || return 1
+ EVAL "${r}"
+ PRINT "${r}"
+}
+
+if [[ -n "${INTERACTIVE}" ]]; then
+ while true; do
+ READLINE "user> " || exit "$?"
+ [[ "${r}" ]] && REP "${r}" && echo "${r}"
+ done
+fi
diff --git a/bash/step2_eval.sh b/bash/step2_eval.sh
new file mode 100755
index 0000000..4d571e4
--- /dev/null
+++ b/bash/step2_eval.sh
@@ -0,0 +1,92 @@
+#!/bin/bash
+
+INTERACTIVE=${INTERACTIVE-yes}
+
+source $(dirname $0)/reader.sh
+
+# READ: read and parse input
+READ () {
+ READLINE
+ READ_STR "${r}"
+}
+
+EVAL_AST () {
+ local ast="${1}" env="${2}"
+ #_pr_str "${ast}"; echo "EVAL_AST '${ast}:${r} / ${env}'"
+ _obj_type "${ast}"; local ot="${r}"
+ case "${ot}" in
+ symbol)
+ local val="${ANON["${ast}"]}"
+ eval r="\${${env}["${val}"]}"
+ [ "${r}" ] || _error "'${val}' not found" ;;
+ list)
+ _map_with_type list EVAL "${ast}" "${env}" ;;
+ vector)
+ _map_with_type vector EVAL "${ast}" "${env}" ;;
+ hash_map)
+ local res="" val="" hm="${ANON["${ast}"]}"
+ hash_map; local new_hm="${r}"
+ eval local keys="\${!${hm}[@]}"
+ for key in ${keys}; do
+ eval val="\${${hm}[\"${key}\"]}"
+ EVAL "${val}" "${env}"
+ assoc! "${new_hm}" "${key}" "${r}"
+ done
+ r="${new_hm}" ;;
+ *)
+ r="${ast}" ;;
+ esac
+}
+
+# EVAL: evaluate the parameter
+EVAL () {
+ local ast="${1}" env="${2}"
+ r=
+ [[ "${__ERROR}" ]] && return 1
+ #_pr_str "${ast}"; echo "EVAL '${r} / ${env}'"
+ _obj_type "${ast}"; local ot="${r}"
+ if [[ "${ot}" != "list" ]]; then
+ EVAL_AST "${ast}" "${env}"
+ return
+ fi
+
+ # apply list
+ EVAL_AST "${ast}" "${env}"
+ [[ "${__ERROR}" ]] && return 1
+ local el="${r}"
+ first "${el}"; local f="${r}"
+ rest "${el}"; local args="${ANON["${r}"]}"
+ #echo "invoke: ${f} ${args}"
+ eval ${f} ${args}
+}
+
+# PRINT:
+PRINT () {
+ if [[ "${__ERROR}" ]]; then
+ _pr_str "${__ERROR}" yes
+ r="Error: ${r}"
+ __ERROR=
+ else
+ _pr_str "${1}" yes
+ fi
+}
+
+# REPL: read, eval, print, loop
+declare -A REPL_ENV
+REP () {
+ READ_STR "${1}"
+ EVAL "${r}" REPL_ENV
+ PRINT "${r}"
+}
+
+REPL_ENV["+"]=num_plus
+REPL_ENV["-"]=num_minus
+REPL_ENV["__STAR__"]=num_multiply
+REPL_ENV["/"]=num_divide
+
+if [[ -n "${INTERACTIVE}" ]]; then
+ while true; do
+ READLINE "user> " || exit "$?"
+ [[ "${r}" ]] && REP "${r}" && echo "${r}"
+ done
+fi
diff --git a/bash/step3_env.sh b/bash/step3_env.sh
new file mode 100755
index 0000000..cbc0867
--- /dev/null
+++ b/bash/step3_env.sh
@@ -0,0 +1,116 @@
+#!/bin/bash
+
+INTERACTIVE=${INTERACTIVE-yes}
+
+source $(dirname $0)/reader.sh
+
+# READ: read and parse input
+READ () {
+ READLINE
+ READ_STR "${r}"
+}
+
+EVAL_AST () {
+ local ast="${1}" env="${2}"
+ #_pr_str "${ast}"; echo "EVAL_AST '${ast}:${r} / ${env}'"
+ _obj_type "${ast}"; local ot="${r}"
+ case "${ot}" in
+ symbol)
+ local val="${ANON["${ast}"]}"
+ ENV_GET "${env}" "${val}"
+ return ;;
+ list)
+ _map_with_type list EVAL "${ast}" "${env}" ;;
+ vector)
+ _map_with_type vector EVAL "${ast}" "${env}" ;;
+ hash_map)
+ local res="" val="" hm="${ANON["${ast}"]}"
+ hash_map; local new_hm="${r}"
+ eval local keys="\${!${hm}[@]}"
+ for key in ${keys}; do
+ eval val="\${${hm}[\"${key}\"]}"
+ EVAL "${val}" "${env}"
+ assoc! "${new_hm}" "${key}" "${r}"
+ done
+ r="${new_hm}" ;;
+ *)
+ r="${ast}" ;;
+ esac
+}
+
+# EVAL: evaluate the parameter
+EVAL () {
+ local ast="${1}" env="${2}"
+ r=
+ [[ "${__ERROR}" ]] && return 1
+ #_pr_str "${ast}"; echo "EVAL '${r} / ${env}'"
+ _obj_type "${ast}"; local ot="${r}"
+ if [[ "${ot}" != "list" ]]; then
+ EVAL_AST "${ast}" "${env}"
+ return
+ fi
+
+ # apply list
+ _nth "${ast}" 0; local a0="${r}"
+ _nth "${ast}" 1; local a1="${r}"
+ _nth "${ast}" 2; local a2="${r}"
+ case "${ANON["${a0}"]}" in
+ def!) local k="${ANON["${a1}"]}"
+ #echo "def! ${k} to ${a2} in ${env}"
+ EVAL "${a2}" "${env}"
+ ENV_SET "${env}" "${k}" "${r}"
+ return ;;
+ let*) ENV "${env}"; local let_env="${r}"
+ local let_pairs=(${ANON["${a1}"]})
+ local idx=0
+ #echo "let: [${let_pairs[*]}] for ${a2}"
+ while [[ "${let_pairs["${idx}"]}" ]]; do
+ EVAL "${let_pairs[$(( idx + 1))]}" "${let_env}"
+ ENV_SET "${let_env}" "${ANON["${let_pairs[${idx}]}"]}" "${r}"
+ idx=$(( idx + 2))
+ done
+ EVAL "${a2}" "${let_env}"
+ return ;;
+ *) EVAL_AST "${ast}" "${env}"
+ [[ "${__ERROR}" ]] && r= && return 1
+ local el="${r}"
+ first "${el}"; local f="${r}"
+ rest "${el}"; local args="${ANON["${r}"]}"
+ #echo "invoke: ${f} ${args}"
+ eval ${f} ${args}
+ return ;;
+ esac
+}
+
+# PRINT:
+PRINT () {
+ if [[ "${__ERROR}" ]]; then
+ _pr_str "${__ERROR}" yes
+ r="Error: ${r}"
+ __ERROR=
+ else
+ _pr_str "${1}" yes
+ fi
+}
+
+# REPL: read, eval, print, loop
+ENV; REPL_ENV="${r}"
+REP () {
+ r=
+ READ_STR "${1}"
+ EVAL "${r}" ${REPL_ENV}
+ PRINT "${r}"
+}
+
+_ref () { ENV_SET "${REPL_ENV}" "${1}" "${2}"; }
+_ref "+" num_plus
+_ref "-" num_minus
+_ref "__STAR__" num_multiply
+_ref "/" num_divide
+
+if [[ -n "${INTERACTIVE}" ]]; then
+ while true; do
+ READLINE "user> " || exit "$?"
+ [[ "${r}" ]] && REP "${r}" && echo "${r}"
+ done
+fi
diff --git a/bash/step4_if_fn_do.sh b/bash/step4_if_fn_do.sh
new file mode 100755
index 0000000..fedb324
--- /dev/null
+++ b/bash/step4_if_fn_do.sh
@@ -0,0 +1,140 @@
+#!/bin/bash
+
+INTERACTIVE=${INTERACTIVE-yes}
+
+source $(dirname $0)/reader.sh
+
+# READ: read and parse input
+READ () {
+ READLINE
+ READ_STR "${r}"
+}
+
+EVAL_AST () {
+ local ast="${1}" env="${2}"
+ #_pr_str "${ast}"; echo "EVAL_AST '${ast}:${r} / ${env}'"
+ _obj_type "${ast}"; local ot="${r}"
+ case "${ot}" in
+ symbol)
+ local val="${ANON["${ast}"]}"
+ ENV_GET "${env}" "${val}"
+ return ;;
+ list)
+ _map_with_type list EVAL "${ast}" "${env}" ;;
+ vector)
+ _map_with_type vector EVAL "${ast}" "${env}" ;;
+ hash_map)
+ local res="" val="" hm="${ANON["${ast}"]}"
+ hash_map; local new_hm="${r}"
+ eval local keys="\${!${hm}[@]}"
+ for key in ${keys}; do
+ eval val="\${${hm}[\"${key}\"]}"
+ EVAL "${val}" "${env}"
+ assoc! "${new_hm}" "${key}" "${r}"
+ done
+ r="${new_hm}" ;;
+ *)
+ r="${ast}" ;;
+ esac
+}
+
+# EVAL: evaluate the parameter
+EVAL () {
+ local ast="${1}" env="${2}"
+ r=
+ [[ "${__ERROR}" ]] && return 1
+ #_pr_str "${ast}"; echo "EVAL '${r} / ${env}'"
+ _obj_type "${ast}"; local ot="${r}"
+ if [[ "${ot}" != "list" ]]; then
+ EVAL_AST "${ast}" "${env}"
+ return
+ fi
+
+ # apply list
+ _nth "${ast}" 0; local a0="${r}"
+ _nth "${ast}" 1; local a1="${r}"
+ _nth "${ast}" 2; local a2="${r}"
+ case "${ANON["${a0}"]}" in
+ def!) local k="${ANON["${a1}"]}"
+ #echo "def! ${k} to ${a2} in ${env}"
+ EVAL "${a2}" "${env}"
+ ENV_SET "${env}" "${k}" "${r}"
+ return ;;
+ let*) ENV "${env}"; local let_env="${r}"
+ local let_pairs=(${ANON["${a1}"]})
+ local idx=0
+ #echo "let: [${let_pairs[*]}] for ${a2}"
+ while [[ "${let_pairs["${idx}"]}" ]]; do
+ EVAL "${let_pairs[$(( idx + 1))]}" "${let_env}"
+ ENV_SET "${let_env}" "${ANON["${let_pairs[${idx}]}"]}" "${r}"
+ idx=$(( idx + 2))
+ done
+ EVAL "${a2}" "${let_env}"
+ return ;;
+ do) rest "${ast}"
+ EVAL_AST "${r}" "${env}"
+ [[ "${__ERROR}" ]] && r= && return 1
+ last "${r}"
+ return ;;
+ if) EVAL "${a1}" "${env}"
+ if [[ "${r}" == "${__false}" || "${r}" == "${__nil}" ]]; then
+ # eval false form
+ _nth "${ast}" 3; local a3="${r}"
+ if [[ "${a3}" ]]; then
+ EVAL "${a3}" "${env}"
+ else
+ r="${__nil}"
+ fi
+ else
+ # eval true condition
+ EVAL "${a2}" "${env}"
+ fi
+ return ;;
+ fn*) new_function "ENV \"${env}\" \"${a1}\" \"\${@}\"; \
+ EVAL \"${a2}\" \"\${r}\""
+ return ;;
+ *) EVAL_AST "${ast}" "${env}"
+ [[ "${__ERROR}" ]] && r= && return 1
+ local el="${r}"
+ first "${el}"; local f="${ANON["${r}"]}"
+ rest "${el}"; local args="${ANON["${r}"]}"
+ #echo "invoke: ${f} ${args}"
+ eval ${f} ${args}
+ return ;;
+ esac
+}
+
+# PRINT:
+PRINT () {
+ if [[ "${__ERROR}" ]]; then
+ _pr_str "${__ERROR}" yes
+ r="Error: ${r}"
+ __ERROR=
+ else
+ _pr_str "${1}" yes
+ fi
+}
+
+# REPL: read, eval, print, loop
+ENV; REPL_ENV="${r}"
+REP () {
+ r=
+ READ_STR "${1}"
+ EVAL "${r}" ${REPL_ENV}
+ PRINT "${r}"
+}
+
+_fref () { new_function "${2} \"\${@}\""; ENV_SET "${REPL_ENV}" "${1}" "${r}"; }
+
+# Import types functions
+for n in "${!types_ns[@]}"; do _fref "${n}" "${types_ns["${n}"]}"; done
+
+# Defined using the language itself
+REP "(def! not (fn* (a) (if a false true)))"
+
+if [[ -n "${INTERACTIVE}" ]]; then
+ while true; do
+ READLINE "user> " || exit "$?"
+ [[ "${r}" ]] && REP "${r}" && echo "${r}"
+ done
+fi
diff --git a/bash/step5_tco.sh b/bash/step5_tco.sh
new file mode 100755
index 0000000..409ec87
--- /dev/null
+++ b/bash/step5_tco.sh
@@ -0,0 +1,157 @@
+#!/bin/bash
+
+INTERACTIVE=${INTERACTIVE-yes}
+
+source $(dirname $0)/reader.sh
+
+# READ: read and parse input
+READ () {
+ READLINE
+ READ_STR "${r}"
+}
+
+EVAL_AST () {
+ local ast="${1}" env="${2}"
+ #_pr_str "${ast}"; echo "EVAL_AST '${ast}:${r} / ${env}'"
+ _obj_type "${ast}"; local ot="${r}"
+ case "${ot}" in
+ symbol)
+ local val="${ANON["${ast}"]}"
+ ENV_GET "${env}" "${val}"
+ return ;;
+ list)
+ _map_with_type list EVAL "${ast}" "${env}" ;;
+ vector)
+ _map_with_type vector EVAL "${ast}" "${env}" ;;
+ hash_map)
+ local res="" val="" hm="${ANON["${ast}"]}"
+ hash_map; local new_hm="${r}"
+ eval local keys="\${!${hm}[@]}"
+ for key in ${keys}; do
+ eval val="\${${hm}[\"${key}\"]}"
+ EVAL "${val}" "${env}"
+ assoc! "${new_hm}" "${key}" "${r}"
+ done
+ r="${new_hm}" ;;
+ *)
+ r="${ast}" ;;
+ esac
+}
+
+# EVAL: evaluate the parameter
+EVAL () {
+ local ast="${1}" env="${2}"
+ while true; do
+ r=
+ [[ "${__ERROR}" ]] && return 1
+ #_pr_str "${ast}"; echo "EVAL '${r} / ${env}'"
+ _obj_type "${ast}"; local ot="${r}"
+ if [[ "${ot}" != "list" ]]; then
+ EVAL_AST "${ast}" "${env}"
+ return
+ fi
+
+ # apply list
+ _nth "${ast}" 0; local a0="${r}"
+ _nth "${ast}" 1; local a1="${r}"
+ _nth "${ast}" 2; local a2="${r}"
+ case "${ANON["${a0}"]}" in
+ def!) local k="${ANON["${a1}"]}"
+ #echo "def! ${k} to ${a2} in ${env}"
+ EVAL "${a2}" "${env}"
+ ENV_SET "${env}" "${k}" "${r}"
+ return ;;
+ let*) ENV "${env}"; local let_env="${r}"
+ local let_pairs=(${ANON["${a1}"]})
+ local idx=0
+ #echo "let: [${let_pairs[*]}] for ${a2}"
+ while [[ "${let_pairs["${idx}"]}" ]]; do
+ EVAL "${let_pairs[$(( idx + 1))]}" "${let_env}"
+ ENV_SET "${let_env}" "${ANON["${let_pairs[${idx}]}"]}" "${r}"
+ idx=$(( idx + 2))
+ done
+ EVAL "${a2}" "${let_env}"
+ return ;;
+ do) _count "${ast}"
+ _slice "${ast}" 1 $(( ${r} - 2 ))
+ EVAL_AST "${r}" "${env}"
+ [[ "${__ERROR}" ]] && r= && return 1
+ last "${ast}"
+ ast="${r}"
+ # Continue loop
+ ;;
+ if) EVAL "${a1}" "${env}"
+ if [[ "${r}" == "${__false}" || "${r}" == "${__nil}" ]]; then
+ # eval false form
+ _nth "${ast}" 3; local a3="${r}"
+ if [[ "${a3}" ]]; then
+ ast="${a3}"
+ else
+ r="${__nil}"
+ return
+ fi
+ else
+ # eval true condition
+ ast="${a2}"
+ fi
+ # Continue loop
+ ;;
+ fn*) new_function "ENV \"${env}\" \"${a1}\" \"\${@}\"; \
+ EVAL \"${a2}\" \"\${r}\"" \
+ "${a2}" "${env}" "${a1}"
+ return ;;
+ *) EVAL_AST "${ast}" "${env}"
+ [[ "${__ERROR}" ]] && r= && return 1
+ local el="${r}"
+ first "${el}"; local f="${ANON["${r}"]}"
+ rest "${el}"; local args="${ANON["${r}"]}"
+ #echo "invoke: [${f}] ${args}"
+ if [[ "${f//@/ }" != "${f}" ]]; then
+ set -- ${f//@/ }
+ ast="${2}"
+ ENV "${3}" "${4}" ${args}
+ env="${r}"
+ else
+ eval ${f%%@*} ${args}
+ return
+ fi
+ # Continue loop
+ ;;
+ esac
+ done
+}
+
+# PRINT:
+PRINT () {
+ if [[ "${__ERROR}" ]]; then
+ _pr_str "${__ERROR}" yes
+ r="Error: ${r}"
+ __ERROR=
+ else
+ _pr_str "${1}" yes
+ fi
+}
+
+# REPL: read, eval, print, loop
+ENV; REPL_ENV="${r}"
+REP () {
+ r=
+ READ_STR "${1}"
+ EVAL "${r}" ${REPL_ENV}
+ PRINT "${r}"
+}
+
+_fref () { new_function "${2} \"\${@}\""; ENV_SET "${REPL_ENV}" "${1}" "${r}"; }
+
+# Import types functions
+for n in "${!types_ns[@]}"; do _fref "${n}" "${types_ns["${n}"]}"; done
+
+# Defined using the language itself
+REP "(def! not (fn* (a) (if a false true)))"
+
+if [[ -n "${INTERACTIVE}" ]]; then
+ while true; do
+ READLINE "user> " || exit "$?"
+ [[ "${r}" ]] && REP "${r}" && echo "${r}"
+ done
+fi
diff --git a/bash/step6_file.sh b/bash/step6_file.sh
new file mode 100755
index 0000000..9656125
--- /dev/null
+++ b/bash/step6_file.sh
@@ -0,0 +1,170 @@
+#!/bin/bash
+
+INTERACTIVE=${INTERACTIVE-yes}
+
+source $(dirname $0)/reader.sh
+
+# READ: read and parse input
+READ () {
+ READLINE
+ READ_STR "${r}"
+}
+
+EVAL_AST () {
+ local ast="${1}" env="${2}"
+ #_pr_str "${ast}"; echo "EVAL_AST '${ast}:${r} / ${env}'"
+ _obj_type "${ast}"; local ot="${r}"
+ case "${ot}" in
+ symbol)
+ local val="${ANON["${ast}"]}"
+ ENV_GET "${env}" "${val}"
+ return ;;
+ list)
+ _map_with_type list EVAL "${ast}" "${env}" ;;
+ vector)
+ _map_with_type vector EVAL "${ast}" "${env}" ;;
+ hash_map)
+ local res="" val="" hm="${ANON["${ast}"]}"
+ hash_map; local new_hm="${r}"
+ eval local keys="\${!${hm}[@]}"
+ for key in ${keys}; do
+ eval val="\${${hm}[\"${key}\"]}"
+ EVAL "${val}" "${env}"
+ assoc! "${new_hm}" "${key}" "${r}"
+ done
+ r="${new_hm}" ;;
+ *)
+ r="${ast}" ;;
+ esac
+}
+
+# EVAL: evaluate the parameter
+EVAL () {
+ local ast="${1}" env="${2}"
+ while true; do
+ r=
+ [[ "${__ERROR}" ]] && return 1
+ #_pr_str "${ast}"; echo "EVAL '${r} / ${env}'"
+ _obj_type "${ast}"; local ot="${r}"
+ if [[ "${ot}" != "list" ]]; then
+ EVAL_AST "${ast}" "${env}"
+ return
+ fi
+
+ # apply list
+ _nth "${ast}" 0; local a0="${r}"
+ _nth "${ast}" 1; local a1="${r}"
+ _nth "${ast}" 2; local a2="${r}"
+ case "${ANON["${a0}"]}" in
+ def!) local k="${ANON["${a1}"]}"
+ #echo "def! ${k} to ${a2} in ${env}"
+ EVAL "${a2}" "${env}"
+ ENV_SET "${env}" "${k}" "${r}"
+ return ;;
+ let*) ENV "${env}"; local let_env="${r}"
+ local let_pairs=(${ANON["${a1}"]})
+ local idx=0
+ #echo "let: [${let_pairs[*]}] for ${a2}"
+ while [[ "${let_pairs["${idx}"]}" ]]; do
+ EVAL "${let_pairs[$(( idx + 1))]}" "${let_env}"
+ ENV_SET "${let_env}" "${ANON["${let_pairs[${idx}]}"]}" "${r}"
+ idx=$(( idx + 2))
+ done
+ EVAL "${a2}" "${let_env}"
+ return ;;
+ do) _count "${ast}"
+ _slice "${ast}" 1 $(( ${r} - 2 ))
+ EVAL_AST "${r}" "${env}"
+ [[ "${__ERROR}" ]] && r= && return 1
+ last "${ast}"
+ ast="${r}"
+ # Continue loop
+ ;;
+ if) EVAL "${a1}" "${env}"
+ if [[ "${r}" == "${__false}" || "${r}" == "${__nil}" ]]; then
+ # eval false form
+ _nth "${ast}" 3; local a3="${r}"
+ if [[ "${a3}" ]]; then
+ ast="${a3}"
+ else
+ r="${__nil}"
+ return
+ fi
+ else
+ # eval true condition
+ ast="${a2}"
+ fi
+ # Continue loop
+ ;;
+ fn*) new_function "ENV \"${env}\" \"${a1}\" \"\${@}\"; \
+ EVAL \"${a2}\" \"\${r}\"" \
+ "${a2}" "${env}" "${a1}"
+ return ;;
+ *) EVAL_AST "${ast}" "${env}"
+ [[ "${__ERROR}" ]] && r= && return 1
+ local el="${r}"
+ first "${el}"; local f="${ANON["${r}"]}"
+ rest "${el}"; local args="${ANON["${r}"]}"
+ #echo "invoke: [${f}] ${args}"
+ if [[ "${f//@/ }" != "${f}" ]]; then
+ set -- ${f//@/ }
+ ast="${2}"
+ ENV "${3}" "${4}" ${args}
+ env="${r}"
+ else
+ eval ${f%%@*} ${args}
+ return
+ fi
+ # Continue loop
+ ;;
+ esac
+ done
+}
+
+# PRINT:
+PRINT () {
+ if [[ "${__ERROR}" ]]; then
+ _pr_str "${__ERROR}" yes
+ r="Error: ${r}"
+ __ERROR=
+ else
+ _pr_str "${1}" yes
+ fi
+}
+
+# REPL: read, eval, print, loop
+ENV; REPL_ENV="${r}"
+REP () {
+ r=
+ READ_STR "${1}"
+ EVAL "${r}" ${REPL_ENV}
+ PRINT "${r}"
+}
+
+_fref () { new_function "${2} \"\${@}\""; ENV_SET "${REPL_ENV}" "${1}" "${r}"; }
+
+# Import types functions
+for n in "${!types_ns[@]}"; do _fref "${n}" "${types_ns["${n}"]}"; done
+
+read_string () { READ_STR "${ANON["${1}"]}"; }
+_fref "read-string" read_string
+_eval () { EVAL "${1}" "${REPL_ENV}"; }
+_fref "eval" _eval
+slurp () { string "$(cat "${ANON["${1}"]}")"; }
+_fref "slurp" slurp
+slurp_do () { string "(do $(cat "${ANON["${1}"]}"))"; }
+_fref "slurp-do" slurp_do
+
+# Defined using the language itself
+REP "(def! not (fn* (a) (if a false true)))"
+REP "(def! load-file (fn* (f) (eval (read-string (slurp-do f)))))"
+
+if [[ "${1}" ]]; then
+ echo "${@}"
+ REP "(load-file \"${1}\")" && echo "${r}"
+elif [[ -n "${INTERACTIVE}" ]]; then
+ while true; do
+ READLINE "user> " || exit "$?"
+ [[ "${r}" ]] && REP "${r}" && echo "${r}"
+ done
+fi
diff --git a/bash/step7_quote.sh b/bash/step7_quote.sh
new file mode 100755
index 0000000..4bb74ba
--- /dev/null
+++ b/bash/step7_quote.sh
@@ -0,0 +1,215 @@
+#!/bin/bash
+
+INTERACTIVE=${INTERACTIVE-yes}
+
+source $(dirname $0)/reader.sh
+
+# READ: read and parse input
+READ () {
+ READLINE
+ READ_STR "${r}"
+}
+
+IS_PAIR () {
+ if _list? "${1}"; then
+ _count "${1}"
+ [[ "${r}" > 0 ]] && return 0
+ fi
+ return 1
+}
+
+QUASIQUOTE () {
+ if ! IS_PAIR "${1}"; then
+ symbol quote
+ list "${r}" "${1}"
+ return
+ else
+ _nth "${1}" 0; local a0="${r}"
+ if [[ "${ANON["${a0}"]}" == "unquote" ]]; then
+ _nth "${1}" 1
+ return
+ elif IS_PAIR "${a0}"; then
+ _nth "${a0}" 0; local a00="${r}"
+ if [[ "${ANON["${a00}"]}" == "splice-unquote" ]]; then
+ symbol concat; local a="${r}"
+ _nth "${a0}" 1; local b="${r}"
+ rest "${1}"
+ QUASIQUOTE "${r}"; local c="${r}"
+ list "${a}" "${b}" "${c}"
+ return
+ fi
+ fi
+ fi
+ symbol cons; local a="${r}"
+ QUASIQUOTE "${a0}"; local b="${r}"
+ rest "${1}"
+ QUASIQUOTE "${r}"; local c="${r}"
+ list "${a}" "${b}" "${c}"
+ return
+}
+
+EVAL_AST () {
+ local ast="${1}" env="${2}"
+ #_pr_str "${ast}"; echo "EVAL_AST '${ast}:${r} / ${env}'"
+ _obj_type "${ast}"; local ot="${r}"
+ case "${ot}" in
+ symbol)
+ local val="${ANON["${ast}"]}"
+ ENV_GET "${env}" "${val}"
+ return ;;
+ list)
+ _map_with_type list EVAL "${ast}" "${env}" ;;
+ vector)
+ _map_with_type vector EVAL "${ast}" "${env}" ;;
+ hash_map)
+ local res="" val="" hm="${ANON["${ast}"]}"
+ hash_map; local new_hm="${r}"
+ eval local keys="\${!${hm}[@]}"
+ for key in ${keys}; do
+ eval val="\${${hm}[\"${key}\"]}"
+ EVAL "${val}" "${env}"
+ assoc! "${new_hm}" "${key}" "${r}"
+ done
+ r="${new_hm}" ;;
+ *)
+ r="${ast}" ;;
+ esac
+}
+
+# EVAL: evaluate the parameter
+EVAL () {
+ local ast="${1}" env="${2}"
+ while true; do
+ r=
+ [[ "${__ERROR}" ]] && return 1
+ #_pr_str "${ast}"; echo "EVAL '${r} / ${env}'"
+ _obj_type "${ast}"; local ot="${r}"
+ if [[ "${ot}" != "list" ]]; then
+ EVAL_AST "${ast}" "${env}"
+ return
+ fi
+
+ # apply list
+ _nth "${ast}" 0; local a0="${r}"
+ _nth "${ast}" 1; local a1="${r}"
+ _nth "${ast}" 2; local a2="${r}"
+ case "${ANON["${a0}"]}" in
+ def!) local k="${ANON["${a1}"]}"
+ #echo "def! ${k} to ${a2} in ${env}"
+ EVAL "${a2}" "${env}"
+ ENV_SET "${env}" "${k}" "${r}"
+ return ;;
+ let*) ENV "${env}"; local let_env="${r}"
+ local let_pairs=(${ANON["${a1}"]})
+ local idx=0
+ #echo "let: [${let_pairs[*]}] for ${a2}"
+ while [[ "${let_pairs["${idx}"]}" ]]; do
+ EVAL "${let_pairs[$(( idx + 1))]}" "${let_env}"
+ ENV_SET "${let_env}" "${ANON["${let_pairs[${idx}]}"]}" "${r}"
+ idx=$(( idx + 2))
+ done
+ EVAL "${a2}" "${let_env}"
+ return ;;
+ quote)
+ r="${a1}"
+ return ;;
+ quasiquote)
+ QUASIQUOTE "${a1}"
+ EVAL "${r}" "${env}"
+ return ;;
+ do) _count "${ast}"
+ _slice "${ast}" 1 $(( ${r} - 2 ))
+ EVAL_AST "${r}" "${env}"
+ [[ "${__ERROR}" ]] && r= && return 1
+ last "${ast}"
+ ast="${r}"
+ # Continue loop
+ ;;
+ if) EVAL "${a1}" "${env}"
+ if [[ "${r}" == "${__false}" || "${r}" == "${__nil}" ]]; then
+ # eval false form
+ _nth "${ast}" 3; local a3="${r}"
+ if [[ "${a3}" ]]; then
+ ast="${a3}"
+ else
+ r="${__nil}"
+ return
+ fi
+ else
+ # eval true condition
+ ast="${a2}"
+ fi
+ # Continue loop
+ ;;
+ fn*) new_function "ENV \"${env}\" \"${a1}\" \"\${@}\"; \
+ EVAL \"${a2}\" \"\${r}\"" \
+ "${a2}" "${env}" "${a1}"
+ return ;;
+ *) EVAL_AST "${ast}" "${env}"
+ [[ "${__ERROR}" ]] && r= && return 1
+ local el="${r}"
+ first "${el}"; local f="${ANON["${r}"]}"
+ rest "${el}"; local args="${ANON["${r}"]}"
+ #echo "invoke: [${f}] ${args}"
+ if [[ "${f//@/ }" != "${f}" ]]; then
+ set -- ${f//@/ }
+ ast="${2}"
+ ENV "${3}" "${4}" ${args}
+ env="${r}"
+ else
+ eval ${f%%@*} ${args}
+ return
+ fi
+ # Continue loop
+ ;;
+ esac
+ done
+}
+
+# PRINT:
+PRINT () {
+ if [[ "${__ERROR}" ]]; then
+ _pr_str "${__ERROR}" yes
+ r="Error: ${r}"
+ __ERROR=
+ else
+ _pr_str "${1}" yes
+ fi
+}
+
+# REPL: read, eval, print, loop
+ENV; REPL_ENV="${r}"
+REP () {
+ r=
+ READ_STR "${1}"
+ EVAL "${r}" ${REPL_ENV}
+ PRINT "${r}"
+}
+
+_fref () { new_function "${2} \"\${@}\""; ENV_SET "${REPL_ENV}" "${1}" "${r}"; }
+
+# Import types functions
+for n in "${!types_ns[@]}"; do _fref "${n}" "${types_ns["${n}"]}"; done
+
+read_string () { READ_STR "${ANON["${1}"]}"; }
+_fref "read-string" read_string
+_eval () { EVAL "${1}" "${REPL_ENV}"; }
+_fref "eval" _eval
+slurp () { string "$(cat "${ANON["${1}"]}")"; }
+_fref "slurp" slurp
+slurp_do () { string "(do $(cat "${ANON["${1}"]}"))"; }
+_fref "slurp-do" slurp_do
+
+# Defined using the language itself
+REP "(def! not (fn* (a) (if a false true)))"
+REP "(def! load-file (fn* (f) (eval (read-string (slurp-do f)))))"
+
+if [[ "${1}" ]]; then
+ echo "${@}"
+ REP "(load-file \"${1}\")" && echo "${r}"
+elif [[ -n "${INTERACTIVE}" ]]; then
+ while true; do
+ READLINE "user> " || exit "$?"
+ [[ "${r}" ]] && REP "${r}" && echo "${r}"
+ done
+fi
diff --git a/bash/step8_macros.sh b/bash/step8_macros.sh
new file mode 100755
index 0000000..e86a032
--- /dev/null
+++ b/bash/step8_macros.sh
@@ -0,0 +1,252 @@
+#!/bin/bash
+
+INTERACTIVE=${INTERACTIVE-yes}
+
+source $(dirname $0)/reader.sh
+
+# READ: read and parse input
+READ () {
+ READLINE
+ READ_STR "${r}"
+}
+
+IS_PAIR () {
+ if _list? "${1}"; then
+ _count "${1}"
+ [[ "${r}" > 0 ]] && return 0
+ fi
+ return 1
+}
+
+QUASIQUOTE () {
+ if ! IS_PAIR "${1}"; then
+ symbol quote
+ list "${r}" "${1}"
+ return
+ else
+ _nth "${1}" 0; local a0="${r}"
+ if [[ "${ANON["${a0}"]}" == "unquote" ]]; then
+ _nth "${1}" 1
+ return
+ elif IS_PAIR "${a0}"; then
+ _nth "${a0}" 0; local a00="${r}"
+ if [[ "${ANON["${a00}"]}" == "splice-unquote" ]]; then
+ symbol concat; local a="${r}"
+ _nth "${a0}" 1; local b="${r}"
+ rest "${1}"
+ QUASIQUOTE "${r}"; local c="${r}"
+ list "${a}" "${b}" "${c}"
+ return
+ fi
+ fi
+ fi
+ symbol cons; local a="${r}"
+ QUASIQUOTE "${a0}"; local b="${r}"
+ rest "${1}"
+ QUASIQUOTE "${r}"; local c="${r}"
+ list "${a}" "${b}" "${c}"
+ return
+}
+
+IS_MACRO_CALL () {
+ if ! _list? "${1}"; then return 1; fi
+ _nth "${1}" 0; local a0="${r}"
+ if _symbol? "${a0}"; then
+ ENV_FIND "${2}" "${ANON["${a0}"]}_ismacro_"
+ if [[ "${r}" ]]; then
+ return 0
+ fi
+ fi
+ return 1
+}
+
+MACROEXPAND () {
+ local ast="${1}" env="${2}"
+ while IS_MACRO_CALL "${ast}" "${env}"; do
+ _nth "${ast}" 0; local a0="${r}"
+ ENV_GET "${env}" "${ANON["${a0}"]}"; local mac="${ANON["${r}"]}"
+ rest "${ast}"
+ ${mac%%@*} ${ANON["${r}"]}
+ ast="${r}"
+ done
+ r="${ast}"
+}
+
+
+EVAL_AST () {
+ local ast="${1}" env="${2}"
+ #_pr_str "${ast}"; echo "EVAL_AST '${ast}:${r} / ${env}'"
+ _obj_type "${ast}"; local ot="${r}"
+ case "${ot}" in
+ symbol)
+ local val="${ANON["${ast}"]}"
+ ENV_GET "${env}" "${val}"
+ return ;;
+ list)
+ _map_with_type list EVAL "${ast}" "${env}" ;;
+ vector)
+ _map_with_type vector EVAL "${ast}" "${env}" ;;
+ hash_map)
+ local res="" val="" hm="${ANON["${ast}"]}"
+ hash_map; local new_hm="${r}"
+ eval local keys="\${!${hm}[@]}"
+ for key in ${keys}; do
+ eval val="\${${hm}[\"${key}\"]}"
+ EVAL "${val}" "${env}"
+ assoc! "${new_hm}" "${key}" "${r}"
+ done
+ r="${new_hm}" ;;
+ *)
+ r="${ast}" ;;
+ esac
+}
+
+# EVAL: evaluate the parameter
+EVAL () {
+ local ast="${1}" env="${2}"
+ while true; do
+ r=
+ [[ "${__ERROR}" ]] && return 1
+ #_pr_str "${ast}"; echo "EVAL '${r} / ${env}'"
+ if ! _list? "${ast}"; then
+ EVAL_AST "${ast}" "${env}"
+ return
+ fi
+
+ # apply list
+ MACROEXPAND "${ast}" "${env}"
+ ast="${r}"
+ if ! _list? "${ast}"; then return; fi
+ _nth "${ast}" 0; local a0="${r}"
+ _nth "${ast}" 1; local a1="${r}"
+ _nth "${ast}" 2; local a2="${r}"
+ case "${ANON["${a0}"]}" in
+ def!) local k="${ANON["${a1}"]}"
+ #echo "def! ${k} to ${a2} in ${env}"
+ EVAL "${a2}" "${env}"
+ ENV_SET "${env}" "${k}" "${r}"
+ return ;;
+ let*) ENV "${env}"; local let_env="${r}"
+ local let_pairs=(${ANON["${a1}"]})
+ local idx=0
+ #echo "let: [${let_pairs[*]}] for ${a2}"
+ while [[ "${let_pairs["${idx}"]}" ]]; do
+ EVAL "${let_pairs[$(( idx + 1))]}" "${let_env}"
+ ENV_SET "${let_env}" "${ANON["${let_pairs[${idx}]}"]}" "${r}"
+ idx=$(( idx + 2))
+ done
+ EVAL "${a2}" "${let_env}"
+ return ;;
+ quote)
+ r="${a1}"
+ return ;;
+ quasiquote)
+ QUASIQUOTE "${a1}"
+ EVAL "${r}" "${env}"
+ return ;;
+ defmacro!)
+ local k="${ANON["${a1}"]}"
+ EVAL "${a2}" "${env}"
+ ENV_SET "${env}" "${k}" "${r}"
+ ENV_SET "${env}" "${k}_ismacro_" "yes"
+ return ;;
+ macroexpand)
+ MACROEXPAND "${a1}" "${env}"
+ return ;;
+ do) _count "${ast}"
+ _slice "${ast}" 1 $(( ${r} - 2 ))
+ EVAL_AST "${r}" "${env}"
+ [[ "${__ERROR}" ]] && r= && return 1
+ last "${ast}"
+ ast="${r}"
+ # Continue loop
+ ;;
+ if) EVAL "${a1}" "${env}"
+ if [[ "${r}" == "${__false}" || "${r}" == "${__nil}" ]]; then
+ # eval false form
+ _nth "${ast}" 3; local a3="${r}"
+ if [[ "${a3}" ]]; then
+ ast="${a3}"
+ else
+ r="${__nil}"
+ return
+ fi
+ else
+ # eval true condition
+ ast="${a2}"
+ fi
+ # Continue loop
+ ;;
+ fn*) new_function "ENV \"${env}\" \"${a1}\" \"\${@}\"; \
+ EVAL \"${a2}\" \"\${r}\"" \
+ "${a2}" "${env}" "${a1}"
+ return ;;
+ *) EVAL_AST "${ast}" "${env}"
+ [[ "${__ERROR}" ]] && r= && return 1
+ local el="${r}"
+ first "${el}"; local f="${ANON["${r}"]}"
+ rest "${el}"; local args="${ANON["${r}"]}"
+ #echo "invoke: [${f}] ${args}"
+ if [[ "${f//@/ }" != "${f}" ]]; then
+ set -- ${f//@/ }
+ ast="${2}"
+ ENV "${3}" "${4}" ${args}
+ env="${r}"
+ else
+ eval ${f%%@*} ${args}
+ return
+ fi
+ # Continue loop
+ ;;
+ esac
+ done
+}
+# PRINT:
+PRINT () {
+ if [[ "${__ERROR}" ]]; then
+ _pr_str "${__ERROR}" yes
+ r="Error: ${r}"
+ __ERROR=
+ else
+ _pr_str "${1}" yes
+ fi
+}
+
+# REPL: read, eval, print, loop
+ENV; REPL_ENV="${r}"
+REP () {
+ r=
+ READ_STR "${1}"
+ EVAL "${r}" "${REPL_ENV}"
+ PRINT "${r}"
+}
+
+_fref () { new_function "${2} \"\${@}\""; ENV_SET "${REPL_ENV}" "${1}" "${r}"; }
+
+# Import types functions
+for n in "${!types_ns[@]}"; do _fref "${n}" "${types_ns["${n}"]}"; done
+
+read_string () { READ_STR "${ANON["${1}"]}"; }
+_fref "read-string" read_string
+_eval () {
+ EVAL "${1}" "${REPL_ENV}"
+}
+_fref "eval" _eval
+slurp () { string "$(cat "${ANON["${1}"]}")"; }
+_fref "slurp" slurp
+slurp_do () { string "(do $(cat "${ANON["${1}"]}"))"; }
+_fref "slurp-do" slurp_do
+
+# Defined using the language itself
+REP "(def! not (fn* (a) (if a false true)))"
+REP "(def! load-file (fn* (f) (eval (read-string (slurp-do f)))))"
+
+if [[ "${1}" ]]; then
+ echo "${@}"
+ REP "(load-file \"${1}\")" && echo "${r}"
+elif [[ -n "${INTERACTIVE}" ]]; then
+ while true; do
+ READLINE "user> " || exit "$?"
+ [[ "${r}" ]] && REP "${r}" && echo "${r}"
+ done
+fi
diff --git a/bash/step9_interop.sh b/bash/step9_interop.sh
new file mode 100755
index 0000000..930aa2e
--- /dev/null
+++ b/bash/step9_interop.sh
@@ -0,0 +1,261 @@
+#!/bin/bash
+
+INTERACTIVE=${INTERACTIVE-yes}
+
+source $(dirname $0)/reader.sh
+
+# READ: read and parse input
+READ () {
+ READLINE
+ READ_STR "${r}"
+}
+
+IS_PAIR () {
+ if _list? "${1}"; then
+ _count "${1}"
+ [[ "${r}" > 0 ]] && return 0
+ fi
+ return 1
+}
+
+QUASIQUOTE () {
+ if ! IS_PAIR "${1}"; then
+ symbol quote
+ list "${r}" "${1}"
+ return
+ else
+ _nth "${1}" 0; local a0="${r}"
+ if [[ "${ANON["${a0}"]}" == "unquote" ]]; then
+ _nth "${1}" 1
+ return
+ elif IS_PAIR "${a0}"; then
+ _nth "${a0}" 0; local a00="${r}"
+ if [[ "${ANON["${a00}"]}" == "splice-unquote" ]]; then
+ symbol concat; local a="${r}"
+ _nth "${a0}" 1; local b="${r}"
+ rest "${1}"
+ QUASIQUOTE "${r}"; local c="${r}"
+ list "${a}" "${b}" "${c}"
+ return
+ fi
+ fi
+ fi
+ symbol cons; local a="${r}"
+ QUASIQUOTE "${a0}"; local b="${r}"
+ rest "${1}"
+ QUASIQUOTE "${r}"; local c="${r}"
+ list "${a}" "${b}" "${c}"
+ return
+}
+
+IS_MACRO_CALL () {
+ if ! _list? "${1}"; then return 1; fi
+ _nth "${1}" 0; local a0="${r}"
+ if _symbol? "${a0}"; then
+ ENV_FIND "${2}" "${ANON["${a0}"]}_ismacro_"
+ if [[ "${r}" ]]; then
+ return 0
+ fi
+ fi
+ return 1
+}
+
+MACROEXPAND () {
+ local ast="${1}" env="${2}"
+ while IS_MACRO_CALL "${ast}" "${env}"; do
+ _nth "${ast}" 0; local a0="${r}"
+ ENV_GET "${env}" "${ANON["${a0}"]}"; local mac="${ANON["${r}"]}"
+ rest "${ast}"
+ ${mac%%@*} ${ANON["${r}"]}
+ ast="${r}"
+ done
+ r="${ast}"
+}
+
+
+EVAL_AST () {
+ local ast="${1}" env="${2}"
+ #_pr_str "${ast}"; echo "EVAL_AST '${ast}:${r} / ${env}'"
+ _obj_type "${ast}"; local ot="${r}"
+ case "${ot}" in
+ symbol)
+ local val="${ANON["${ast}"]}"
+ ENV_GET "${env}" "${val}"
+ return ;;
+ list)
+ _map_with_type list EVAL "${ast}" "${env}" ;;
+ vector)
+ _map_with_type vector EVAL "${ast}" "${env}" ;;
+ hash_map)
+ local res="" val="" hm="${ANON["${ast}"]}"
+ hash_map; local new_hm="${r}"
+ eval local keys="\${!${hm}[@]}"
+ for key in ${keys}; do
+ eval val="\${${hm}[\"${key}\"]}"
+ EVAL "${val}" "${env}"
+ assoc! "${new_hm}" "${key}" "${r}"
+ done
+ r="${new_hm}" ;;
+ *)
+ r="${ast}" ;;
+ esac
+}
+
+# EVAL: evaluate the parameter
+EVAL () {
+ local ast="${1}" env="${2}"
+ while true; do
+ r=
+ [[ "${__ERROR}" ]] && return 1
+ #_pr_str "${ast}"; echo "EVAL '${r} / ${env}'"
+ if ! _list? "${ast}"; then
+ EVAL_AST "${ast}" "${env}"
+ return
+ fi
+
+ # apply list
+ MACROEXPAND "${ast}" "${env}"
+ ast="${r}"
+ if ! _list? "${ast}"; then return; fi
+ _nth "${ast}" 0; local a0="${r}"
+ _nth "${ast}" 1; local a1="${r}"
+ _nth "${ast}" 2; local a2="${r}"
+ case "${ANON["${a0}"]}" in
+ def!) local k="${ANON["${a1}"]}"
+ #echo "def! ${k} to ${a2} in ${env}"
+ EVAL "${a2}" "${env}"
+ ENV_SET "${env}" "${k}" "${r}"
+ return ;;
+ let*) ENV "${env}"; local let_env="${r}"
+ local let_pairs=(${ANON["${a1}"]})
+ local idx=0
+ #echo "let: [${let_pairs[*]}] for ${a2}"
+ while [[ "${let_pairs["${idx}"]}" ]]; do
+ EVAL "${let_pairs[$(( idx + 1))]}" "${let_env}"
+ ENV_SET "${let_env}" "${ANON["${let_pairs[${idx}]}"]}" "${r}"
+ idx=$(( idx + 2))
+ done
+ EVAL "${a2}" "${let_env}"
+ return ;;
+ quote)
+ r="${a1}"
+ return ;;
+ quasiquote)
+ QUASIQUOTE "${a1}"
+ EVAL "${r}" "${env}"
+ return ;;
+ defmacro!)
+ local k="${ANON["${a1}"]}"
+ EVAL "${a2}" "${env}"
+ ENV_SET "${env}" "${k}" "${r}"
+ ENV_SET "${env}" "${k}_ismacro_" "yes"
+ return ;;
+ macroexpand)
+ MACROEXPAND "${a1}" "${env}"
+ return ;;
+ sh*) MACROEXPAND "${a1}" "${env}"
+ EVAL "${r}" "${env}"
+ local output=""
+ local line=""
+ while read line; do
+ output="${output}${line}\n"
+ done < <(eval ${ANON["${r}"]})
+ string "${output}"
+ return ;;
+ do) _count "${ast}"
+ _slice "${ast}" 1 $(( ${r} - 2 ))
+ EVAL_AST "${r}" "${env}"
+ [[ "${__ERROR}" ]] && r= && return 1
+ last "${ast}"
+ ast="${r}"
+ # Continue loop
+ ;;
+ if) EVAL "${a1}" "${env}"
+ if [[ "${r}" == "${__false}" || "${r}" == "${__nil}" ]]; then
+ # eval false form
+ _nth "${ast}" 3; local a3="${r}"
+ if [[ "${a3}" ]]; then
+ ast="${a3}"
+ else
+ r="${__nil}"
+ return
+ fi
+ else
+ # eval true condition
+ ast="${a2}"
+ fi
+ # Continue loop
+ ;;
+ fn*) new_function "ENV \"${env}\" \"${a1}\" \"\${@}\"; \
+ EVAL \"${a2}\" \"\${r}\"" \
+ "${a2}" "${env}" "${a1}"
+ return ;;
+ *) EVAL_AST "${ast}" "${env}"
+ [[ "${__ERROR}" ]] && r= && return 1
+ local el="${r}"
+ first "${el}"; local f="${ANON["${r}"]}"
+ rest "${el}"; local args="${ANON["${r}"]}"
+ #echo "invoke: [${f}] ${args}"
+ if [[ "${f//@/ }" != "${f}" ]]; then
+ set -- ${f//@/ }
+ ast="${2}"
+ ENV "${3}" "${4}" ${args}
+ env="${r}"
+ else
+ eval ${f%%@*} ${args}
+ return
+ fi
+ # Continue loop
+ ;;
+ esac
+ done
+}
+# PRINT:
+PRINT () {
+ if [[ "${__ERROR}" ]]; then
+ _pr_str "${__ERROR}" yes
+ r="Error: ${r}"
+ __ERROR=
+ else
+ _pr_str "${1}" yes
+ fi
+}
+
+# REPL: read, eval, print, loop
+ENV; REPL_ENV="${r}"
+REP () {
+ r=
+ READ_STR "${1}"
+ EVAL "${r}" "${REPL_ENV}"
+ PRINT "${r}"
+}
+
+_fref () { new_function "${2} \"\${@}\""; ENV_SET "${REPL_ENV}" "${1}" "${r}"; }
+
+# Import types functions
+for n in "${!types_ns[@]}"; do _fref "${n}" "${types_ns["${n}"]}"; done
+
+read_string () { READ_STR "${ANON["${1}"]}"; }
+_fref "read-string" read_string
+_eval () {
+ EVAL "${1}" "${REPL_ENV}"
+}
+_fref "eval" _eval
+slurp () { string "$(cat "${ANON["${1}"]}")"; }
+_fref "slurp" slurp
+slurp_do () { string "(do $(cat "${ANON["${1}"]}"))"; }
+_fref "slurp-do" slurp_do
+
+# Defined using the language itself
+REP "(def! not (fn* (a) (if a false true)))"
+REP "(def! load-file (fn* (f) (eval (read-string (slurp-do f)))))"
+
+if [[ "${1}" ]]; then
+ echo "${@}"
+ REP "(load-file \"${1}\")" && echo "${r}"
+elif [[ -n "${INTERACTIVE}" ]]; then
+ while true; do
+ READLINE "user> " || exit "$?"
+ [[ "${r}" ]] && REP "${r}" && echo "${r}"
+ done
+fi
diff --git a/bash/stepA_more.sh b/bash/stepA_more.sh
new file mode 100755
index 0000000..8caa72d
--- /dev/null
+++ b/bash/stepA_more.sh
@@ -0,0 +1,282 @@
+#!/bin/bash
+
+INTERACTIVE=${INTERACTIVE-yes}
+
+source $(dirname $0)/reader.sh
+
+# READ: read and parse input
+READ () {
+ READLINE
+ READ_STR "${r}"
+}
+
+IS_PAIR () {
+ if _list? "${1}"; then
+ _count "${1}"
+ [[ "${r}" > 0 ]] && return 0
+ fi
+ return 1
+}
+
+QUASIQUOTE () {
+ if ! IS_PAIR "${1}"; then
+ symbol quote
+ list "${r}" "${1}"
+ return
+ else
+ _nth "${1}" 0; local a0="${r}"
+ if [[ "${ANON["${a0}"]}" == "unquote" ]]; then
+ _nth "${1}" 1
+ return
+ elif IS_PAIR "${a0}"; then
+ _nth "${a0}" 0; local a00="${r}"
+ if [[ "${ANON["${a00}"]}" == "splice-unquote" ]]; then
+ symbol concat; local a="${r}"
+ _nth "${a0}" 1; local b="${r}"
+ rest "${1}"
+ QUASIQUOTE "${r}"; local c="${r}"
+ list "${a}" "${b}" "${c}"
+ return
+ fi
+ fi
+ fi
+ symbol cons; local a="${r}"
+ QUASIQUOTE "${a0}"; local b="${r}"
+ rest "${1}"
+ QUASIQUOTE "${r}"; local c="${r}"
+ list "${a}" "${b}" "${c}"
+ return
+}
+
+IS_MACRO_CALL () {
+ if ! _list? "${1}"; then return 1; fi
+ _nth "${1}" 0; local a0="${r}"
+ if _symbol? "${a0}"; then
+ ENV_FIND "${2}" "${ANON["${a0}"]}_ismacro_"
+ if [[ "${r}" ]]; then
+ return 0
+ fi
+ fi
+ return 1
+}
+
+MACROEXPAND () {
+ local ast="${1}" env="${2}"
+ while IS_MACRO_CALL "${ast}" "${env}"; do
+ _nth "${ast}" 0; local a0="${r}"
+ ENV_GET "${env}" "${ANON["${a0}"]}"; local mac="${ANON["${r}"]}"
+ rest "${ast}"
+ ${mac%%@*} ${ANON["${r}"]}
+ ast="${r}"
+ done
+ r="${ast}"
+}
+
+
+EVAL_AST () {
+ local ast="${1}" env="${2}"
+ #_pr_str "${ast}"; echo "EVAL_AST '${ast}:${r} / ${env}'"
+ _obj_type "${ast}"; local ot="${r}"
+ case "${ot}" in
+ symbol)
+ local val="${ANON["${ast}"]}"
+ ENV_GET "${env}" "${val}"
+ return ;;
+ list)
+ _map_with_type list EVAL "${ast}" "${env}" ;;
+ vector)
+ _map_with_type vector EVAL "${ast}" "${env}" ;;
+ hash_map)
+ local res="" val="" hm="${ANON["${ast}"]}"
+ hash_map; local new_hm="${r}"
+ eval local keys="\${!${hm}[@]}"
+ for key in ${keys}; do
+ eval val="\${${hm}[\"${key}\"]}"
+ EVAL "${val}" "${env}"
+ assoc! "${new_hm}" "${key}" "${r}"
+ done
+ r="${new_hm}" ;;
+ *)
+ r="${ast}" ;;
+ esac
+}
+
+# EVAL: evaluate the parameter
+EVAL () {
+ local ast="${1}" env="${2}"
+ while true; do
+ r=
+ [[ "${__ERROR}" ]] && return 1
+ #_pr_str "${ast}"; echo "EVAL '${r} / ${env}'"
+ if ! _list? "${ast}"; then
+ EVAL_AST "${ast}" "${env}"
+ return
+ fi
+
+ # apply list
+ MACROEXPAND "${ast}" "${env}"
+ ast="${r}"
+ if ! _list? "${ast}"; then return; fi
+ _nth "${ast}" 0; local a0="${r}"
+ _nth "${ast}" 1; local a1="${r}"
+ _nth "${ast}" 2; local a2="${r}"
+ case "${ANON["${a0}"]}" in
+ def!) local k="${ANON["${a1}"]}"
+ #echo "def! ${k} to ${a2} in ${env}"
+ EVAL "${a2}" "${env}"
+ ENV_SET "${env}" "${k}" "${r}"
+ return ;;
+ let*) ENV "${env}"; local let_env="${r}"
+ local let_pairs=(${ANON["${a1}"]})
+ local idx=0
+ #echo "let: [${let_pairs[*]}] for ${a2}"
+ while [[ "${let_pairs["${idx}"]}" ]]; do
+ EVAL "${let_pairs[$(( idx + 1))]}" "${let_env}"
+ ENV_SET "${let_env}" "${ANON["${let_pairs[${idx}]}"]}" "${r}"
+ idx=$(( idx + 2))
+ done
+ EVAL "${a2}" "${let_env}"
+ return ;;
+ quote)
+ r="${a1}"
+ return ;;
+ quasiquote)
+ QUASIQUOTE "${a1}"
+ EVAL "${r}" "${env}"
+ return ;;
+ defmacro!)
+ local k="${ANON["${a1}"]}"
+ EVAL "${a2}" "${env}"
+ ENV_SET "${env}" "${k}" "${r}"
+ ENV_SET "${env}" "${k}_ismacro_" "yes"
+ return ;;
+ macroexpand)
+ MACROEXPAND "${a1}" "${env}"
+ return ;;
+ sh*) MACROEXPAND "${a1}" "${env}"
+ EVAL "${r}" "${env}"
+ local output=""
+ local line=""
+ while read line; do
+ output="${output}${line}\n"
+ done < <(eval ${ANON["${r}"]})
+ string "${output}"
+ return ;;
+ try*) MACROEXPAND "${a1}" "${env}"
+ EVAL "${r}" "${env}"
+ [[ -z "${__ERROR}" ]] && return
+ _nth "${a2}" 0; local a20="${r}"
+ if [ "${ANON["${a20}"]}" == "catch__STAR__" ]; then
+ _nth "${a2}" 1; local a21="${r}"
+ _nth "${a2}" 2; local a22="${r}"
+ list "${a21}"; local binds="${r}"
+ ENV "${env}" "${binds}" "${__ERROR}"
+ local try_env="${r}"
+ __ERROR=
+ MACROEXPAND "${a22}" "${try_env}"
+ EVAL "${r}" "${try_env}"
+ fi # if no catch* clause, just propagate __ERROR
+ return ;;
+ do) _count "${ast}"
+ _slice "${ast}" 1 $(( ${r} - 2 ))
+ EVAL_AST "${r}" "${env}"
+ [[ "${__ERROR}" ]] && r= && return 1
+ last "${ast}"
+ ast="${r}"
+ # Continue loop
+ ;;
+ if) EVAL "${a1}" "${env}"
+ if [[ "${r}" == "${__false}" || "${r}" == "${__nil}" ]]; then
+ # eval false form
+ _nth "${ast}" 3; local a3="${r}"
+ if [[ "${a3}" ]]; then
+ ast="${a3}"
+ else
+ r="${__nil}"
+ return
+ fi
+ else
+ # eval true condition
+ ast="${a2}"
+ fi
+ # Continue loop
+ ;;
+ fn*) new_function "ENV \"${env}\" \"${a1}\" \"\${@}\"; \
+ EVAL \"${a2}\" \"\${r}\"" \
+ "${a2}" "${env}" "${a1}"
+ return ;;
+ *) EVAL_AST "${ast}" "${env}"
+ [[ "${__ERROR}" ]] && r= && return 1
+ local el="${r}"
+ first "${el}"; local f="${ANON["${r}"]}"
+ rest "${el}"; local args="${ANON["${r}"]}"
+ #echo "invoke: [${f}] ${args}"
+ if [[ "${f//@/ }" != "${f}" ]]; then
+ set -- ${f//@/ }
+ ast="${2}"
+ ENV "${3}" "${4}" ${args}
+ env="${r}"
+ else
+ eval ${f%%@*} ${args}
+ return
+ fi
+ # Continue loop
+ ;;
+ esac
+ done
+}
+# PRINT:
+PRINT () {
+ if [[ "${__ERROR}" ]]; then
+ _pr_str "${__ERROR}" yes
+ r="Error: ${r}"
+ __ERROR=
+ else
+ _pr_str "${1}" yes
+ fi
+}
+
+# REPL: read, eval, print, loop
+ENV; REPL_ENV="${r}"
+REP () {
+ r=
+ READ_STR "${1}"
+ EVAL "${r}" "${REPL_ENV}"
+ PRINT "${r}"
+}
+
+_fref () { new_function "${2} \"\${@}\""; ENV_SET "${REPL_ENV}" "${1}" "${r}"; }
+
+# Import types functions
+for n in "${!types_ns[@]}"; do _fref "${n}" "${types_ns["${n}"]}"; done
+
+readline () {
+ READLINE "${ANON["${1}"]}" && string "${r}" || r="${__nil}";
+}
+_fref "readline" readline
+read_string () { READ_STR "${ANON["${1}"]}"; }
+_fref "read-string" read_string
+_eval () {
+ EVAL "${1}" "${REPL_ENV}"
+}
+_fref "eval" _eval
+slurp () { string "$(cat "${ANON["${1}"]}")"; }
+_fref "slurp" slurp
+slurp_do () { string "(do $(cat "${ANON["${1}"]}"))"; }
+_fref "slurp-do" slurp_do
+
+# Defined using the language itself
+REP "(def! not (fn* (a) (if a false true)))"
+REP "(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))"
+REP "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) \`(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))"
+REP "(def! load-file (fn* (f) (eval (read-string (slurp-do f)))))"
+
+if [[ "${1}" ]]; then
+ echo "${@}"
+ REP "(load-file \"${1}\")" && echo "${r}"
+elif [[ -n "${INTERACTIVE}" ]]; then
+ while true; do
+ READLINE "user> " || exit "$?"
+ [[ "${r}" ]] && REP "${r}" && echo "${r}"
+ done
+fi
diff --git a/bash/tests/common.sh b/bash/tests/common.sh
new file mode 100644
index 0000000..9924107
--- /dev/null
+++ b/bash/tests/common.sh
@@ -0,0 +1,25 @@
+
+assert () {
+ if ! eval "${2}"; then
+ echo "assert failure line ${1}"
+ exit 1
+ fi
+}
+
+assert_eq () {
+ if eval "${3}"; then
+ if [[ "${2}" != "${r}" ]]; then
+ echo "assert_eq failure line ${1}: '${2}' != '${r}'"
+ exit 1
+ fi
+ else
+ echo "assert_eq failure line ${1}: could not evaluate '${3}'"
+ exit 1
+ fi
+}
+
+TEST_RE () {
+ r=
+ READ_STR "${1}"
+ EVAL "${r}" ${REPL_ENV}
+}
diff --git a/bash/tests/reader.sh b/bash/tests/reader.sh
new file mode 100644
index 0000000..8516b06
--- /dev/null
+++ b/bash/tests/reader.sh
@@ -0,0 +1,88 @@
+#!/bin/bash
+
+INTERACTIVE=
+
+source tests/common.sh
+source reader.sh
+
+echo "Testing read of constants/strings"
+assert_eq $LINENO 2 "READ_STR '2'; number_pr_str \$r"
+assert_eq $LINENO 12345 "READ_STR '12345'; number_pr_str \$r"
+assert_eq $LINENO 12345 "READ_STR '12345 \"abc\"'; number_pr_str \$r"
+assert_eq $LINENO 'abc' "READ_STR '\"abc\"'; number_pr_str \$r"
+assert_eq $LINENO 'a string (with parens)' "READ_STR '\"a string (with parens)\"'; number_pr_str \$r"
+
+echo "Testing read of symbols"
+assert $LINENO "READ_STR 'abc'; _symbol? \$r"
+assert_eq $LINENO 'abc' "READ_STR 'abc'; symbol_pr_str \$r"
+assert_eq $LINENO '.' "READ_STR '.'; symbol_pr_str \$r"
+
+raw_val () {
+ r="${ANON["${1}"]}"
+}
+
+echo "Testing READ_STR of strings"
+assert_eq $LINENO 'a string' "READ_STR '\"a string\"'; raw_val \$r"
+assert_eq $LINENO 'a string (with parens)' "READ_STR '\"a string (with parens)\"'; raw_val \$r"
+assert_eq $LINENO 'a string' "READ_STR '\"a string\"()'; raw_val \$r"
+assert_eq $LINENO 'a string' "READ_STR '\"a string\"123'; raw_val \$r"
+assert_eq $LINENO 'a string' "READ_STR '\"a string\"abc'; raw_val \$r"
+assert_eq $LINENO '' "READ_STR '\"\"'; raw_val \$r"
+assert_eq $LINENO 'abc ' "READ_STR '\"abc \"'; raw_val \$r"
+assert_eq $LINENO ' abc' "READ_STR '\" abc\"'; raw_val \$r"
+assert_eq $LINENO '$abc' "READ_STR '\"\$abc\"'; raw_val \$r"
+assert_eq $LINENO 'abc$()' "READ_STR '\"abc\$()\"'; raw_val \$r"
+# TODO: fix parsing of escaped characters
+#assert_eq $LINENO '"xyz"' "READ_STR '\"\\\"xyz\\\"\"'; raw_val \$r"
+
+echo "Testing READ_STR of lists"
+assert_eq $LINENO 2 "READ_STR '(2 3)'; _count \$r"
+assert_eq $LINENO 2 "READ_STR '(2 3)'; first \$r; number_pr_str \$r"
+assert_eq $LINENO 3 "READ_STR '(2 3)'; rest \$r; first \$r; number_pr_str \$r"
+
+READ_STR "(+ 1 2 \"str1\" \"string (with parens) and 'single quotes'\")"
+L="${r}"
+assert_eq $LINENO 5 "_count \$r"
+assert_eq $LINENO 'str1' "_nth ${L} 3; raw_val \$r"
+assert_eq $LINENO "string (with parens) and 'single quotes'" "_nth ${L} 4; raw_val \$r"
+assert_eq $LINENO '(2 3)' "READ_STR '(2 3)'; list_pr_str \$r"
+assert_eq $LINENO '(2 3 "string (with parens)")' "READ_STR '(2 3 \"string (with parens)\")'; list_pr_str \$r yes"
+
+
+echo "Testing READ_STR of vectors"
+assert_eq $LINENO 2 "READ_STR '[2 3]'; _count \$r"
+assert_eq $LINENO 2 "READ_STR '[2 3]'; first \$r; number_pr_str \$r"
+assert_eq $LINENO 3 "READ_STR '[2 3]'; rest \$r; first \$r; number_pr_str \$r"
+
+READ_STR "[+ 1 2 \"str1\" \"string (with parens) and 'single quotes'\"]"
+L="${r}"
+assert_eq $LINENO 5 "_count \$r"
+assert_eq $LINENO 'str1' "_nth ${L} 3; raw_val \$r"
+assert_eq $LINENO "string (with parens) and 'single quotes'" "_nth ${L} 4; raw_val \$r"
+assert_eq $LINENO '[2 3]' "READ_STR '[2 3]'; vector_pr_str \$r yes"
+assert_eq $LINENO '[2 3 "string (with parens)"]' "READ_STR '[2 3 \"string (with parens)\"]'; vector_pr_str \$r yes"
+
+
+echo "Testing READ_STR of quote/quasiquote"
+assert_eq $LINENO 'quote' "READ_STR \"'1\"; _nth \$r 0; raw_val \$r"
+assert_eq $LINENO 1 "READ_STR \"'1\"; _nth \$r 1; raw_val \$r"
+assert_eq $LINENO 'quote' "READ_STR \"'(1 2 3)\"; _nth \$r 0; raw_val \$r"
+assert_eq $LINENO 3 "READ_STR \"'(1 2 3)\"; _nth \$r 1; _nth \$r 2; raw_val \$r"
+
+assert_eq $LINENO 'quasiquote' "READ_STR \"\\\`1\"; _nth \$r 0; raw_val \$r"
+assert_eq $LINENO 1 "READ_STR \"\\\`1\"; _nth \$r 1; raw_val \$r"
+assert_eq $LINENO 'quasiquote' "READ_STR \"\\\`(1 2 3)\"; _nth \$r 0; raw_val \$r"
+assert_eq $LINENO 3 "READ_STR \"\\\`(1 2 3)\"; _nth \$r 1; _nth \$r 2; raw_val \$r"
+
+assert_eq $LINENO 'unquote' "READ_STR \"~1\"; _nth \$r 0; raw_val \$r"
+assert_eq $LINENO 1 "READ_STR \"~1\"; _nth \$r 1; raw_val \$r"
+assert_eq $LINENO 'unquote' "READ_STR \"~(1 2 3)\"; _nth \$r 0; raw_val \$r"
+assert_eq $LINENO 3 "READ_STR \"~(1 2 3)\"; _nth \$r 1; _nth \$r 2; raw_val \$r"
+
+assert_eq $LINENO 'splice-unquote' "READ_STR \"~@1\"; _nth \$r 0; raw_val \$r"
+assert_eq $LINENO 1 "READ_STR \"~@1\"; _nth \$r 1; raw_val \$r"
+assert_eq $LINENO 'splice-unquote' "READ_STR \"~@(1 2 3)\"; _nth \$r 0; raw_val \$r"
+assert_eq $LINENO 3 "READ_STR \"~@(1 2 3)\"; _nth \$r 1; _nth \$r 2; raw_val \$r"
+
+
+echo "All tests completed"
diff --git a/bash/tests/types.sh b/bash/tests/types.sh
new file mode 100644
index 0000000..7ce1ce4
--- /dev/null
+++ b/bash/tests/types.sh
@@ -0,0 +1,161 @@
+#!/bin/bash
+
+source tests/common.sh
+source types.sh
+
+echo "Testing type function"
+assert_eq $LINENO bash "_obj_type xyz"
+assert_eq $LINENO nil "_obj_type ${__nil}"
+assert_eq $LINENO true "_obj_type ${__true}"
+assert_eq $LINENO false "_obj_type ${__false}"
+
+
+echo "Testing number? function"
+assert_eq $LINENO number "number 1; _obj_type \$r"
+assert_eq $LINENO number "number 10; _obj_type \$r"
+assert_eq $LINENO number "number 12345; _obj_type \$r"
+
+
+echo "Testing symbols"
+assert_eq $LINENO symbol "symbol abc; _obj_type \$r"
+symbol "a sym value"; SYM1="${r}"
+assert_eq $LINENO "a sym value" "symbol_pr_str ${SYM1} yes"
+assert_eq $LINENO ${__true} "symbol? ${SYM1}"
+
+
+echo "Testing strings"
+assert_eq $LINENO string "string abc; _obj_type \$r"
+string "a string value"; STR1="${r}"
+assert_eq $LINENO "\"a string value\"" "string_pr_str ${STR1} yes"
+assert_eq $LINENO ${__true} "string? ${STR1}"
+# TODO: fix to count characters instead of words
+#assert_eq $LINENO 14 "_count ${STR1}"
+
+string "a string (with parens)"; STR2="${r}"
+assert_eq $LINENO "\"a string (with parens)\"" "string_pr_str ${STR2} yes"
+assert_eq $LINENO ${__true} "string? ${STR2}"
+
+# TODO: test str and subs
+
+
+echo "Testing function objects"
+assert_eq $LINENO "function" "new_function \"echo hello\"; _obj_type \$r"
+new_function "r=\"arg1:'\$1' arg2:'\$2'\""; FN1="${r}"
+assert_eq $LINENO ${__true} "function? ${FN1}"
+assert_eq $LINENO "arg1:'A' arg2:'B'" "${ANON["${FN1}"]} A B"
+
+
+
+echo "Testing lists"
+list; LE="${r}"
+assert_eq $LINENO list "_obj_type ${LE}"
+
+echo "Testing lists (cons)"
+list; cons P ${r}; L1="${r}"
+cons Q ${L1}; L2="${r}"
+assert_eq $LINENO ${__true} "list? ${L1}"
+assert_eq $LINENO ${__true} "list? ${L2}"
+assert_eq $LINENO P "first ${L1}"
+assert_eq $LINENO 2 "_count ${L2}"
+assert_eq $LINENO Q "first ${L2}"
+assert_eq $LINENO P "_nth ${L2} 1"
+rest ${L2}; L2R="${r}"
+
+echo "Testing lists (concat)"
+concat ${L1} ${L2}; L1_2="${r}"
+assert_eq $LINENO 3 "_count ${L1_2}"
+assert_eq $LINENO P "first ${L1_2}"
+assert_eq $LINENO Q "_nth ${L1_2} 1"
+assert_eq $LINENO P "_nth ${L1_2} 2"
+rest ${L1_2}; L1_2R="${r}"
+
+echo "Testing lists (conj)"
+list; conj ${r} A B; L3="${r}"
+list; conj ${r} X ${L3}; L4="${r}"
+assert_eq $LINENO ${__true} "list? ${L3}"
+assert_eq $LINENO ${__true} "list? ${L4}"
+assert_eq $LINENO A "first ${L3}"
+assert_eq $LINENO X "first ${L4}"
+_nth ${L4} 1; L4_1="${r}"
+assert_eq $LINENO ${__true} "list? ${L4_1}"
+assert_eq $LINENO A "first ${L4_1}"
+
+
+echo "Testing hash maps"
+hash_map; X="${r}"
+hash_map; Y="${r}"
+assert_eq $LINENO ${__true} "hash_map? ${X}"
+assert_eq $LINENO ${__true} "hash_map? ${Y}"
+
+string "a"
+mykey="${r}"
+assert_eq $LINENO "" "_get ${X} a"
+assert_eq $LINENO ${__false} "contains? ${X} ${mykey}"
+assoc! ${X} a 'value of X a'
+assert_eq $LINENO "value of X a" "_get ${X} a"
+assert_eq $LINENO ${__true} "contains? ${X} ${mykey}"
+
+# TODO: more testing of Y, assoc!, dissoc!
+
+
+# TODO: vectors
+
+
+echo "Testing _map/map function"
+list; conj "${r}" 1 2 3; L5="${r}"
+inc () { r=$(( ${1} + 1)); }
+assert_eq $LINENO "2 3 4" "_map inc ${L5}; r=\${ANON[\$r]}"
+new_function "r=\$(( \$1 + 1 ));"; inc_func="${r}"
+assert_eq $LINENO "2 3 4" "map ${inc_func} ${L5}; r=\${ANON[\$r]}"
+
+
+echo "Testing equal? function"
+assert_eq $LINENO ${__true} "equal? 2 2"
+assert_eq $LINENO ${__false} "equal? 2 3"
+assert_eq $LINENO ${__false} "equal? 2 3"
+assert_eq $LINENO ${__true} "equal? abc abc"
+assert_eq $LINENO ${__false} "equal? abc abz"
+assert_eq $LINENO ${__false} "equal? zbc abc"
+assert_eq $LINENO ${__true} "string abc; A=\$r; string abc; B=\$r; equal? \$A \$B"
+assert_eq $LINENO ${__false} "string abc; A=\$r; string abz; B=\$r; equal? \$A \$B"
+assert_eq $LINENO ${__false} "string zbc; A=\$r; string abc; B=\$r; equal? \$A \$B"
+assert_eq $LINENO ${__true} "symbol abc; A=\$r; symbol abc; B=\$r; equal? \$A \$B"
+assert_eq $LINENO ${__false} "symbol abc; A=\$r; symbol abz; B=\$r; equal? \$A \$B"
+assert_eq $LINENO ${__false} "symbol zbc; A=\$r; symbol abc; B=\$r; equal? \$A \$B"
+list; conj "${r}" 1 2 3; L6="${r}"
+list; conj "${r}" 1 2 3; L7="${r}"
+list; conj "${r}" 1 2 Z; L8="${r}"
+list; conj "${r}" Z 2 3; L9="${r}"
+list; conj "${r}" 1 2; L10="${r}"
+assert_eq $LINENO ${__true} "equal? ${L6} ${L7}"
+assert_eq $LINENO ${__false} "equal? ${L6} ${L8}"
+assert_eq $LINENO ${__false} "equal? ${L6} ${L9}"
+assert_eq $LINENO ${__false} "equal? ${L6} ${L10}"
+assert_eq $LINENO ${__false} "equal? ${L10} ${L6}"
+
+# TODO: empty? function tests
+
+echo "Testing ENV environment (1 level)"
+ENV; env1="${r}"
+assert_eq $LINENO "" "ENV_GET \"${env1}\" a"
+ENV_SET "${env1}" a "val_a"
+ENV_SET "${env1}" b "val_b"
+ENV_SET "${env1}" = "val_eq"
+assert_eq $LINENO "val_a" "ENV_GET \"${env1}\" a"
+assert_eq $LINENO "val_b" "ENV_GET \"${env1}\" b"
+assert_eq $LINENO "val_eq" "ENV_GET \"${env1}\" ="
+assert_eq $LINENO "${env1}" "ENV_FIND \"${env1}\" ="
+
+echo "Testing ENV environment (2 levels)"
+ENV "${env1}"; env2="${r}"
+ENV_SET "${env2}" b "val_b2"
+ENV_SET "${env2}" c "val_c"
+assert_eq $LINENO "${env1}" "ENV_FIND \"${env2}\" a"
+assert_eq $LINENO "${env2}" "ENV_FIND \"${env2}\" b"
+assert_eq $LINENO "${env2}" "ENV_FIND \"${env2}\" c"
+assert_eq $LINENO "val_a" "ENV_GET \"${env2}\" a"
+assert_eq $LINENO "val_b2" "ENV_GET \"${env2}\" b"
+assert_eq $LINENO "val_c" "ENV_GET \"${env2}\" c"
+
+
+echo "All tests completed"
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)