diff options
Diffstat (limited to 'bash')
| -rw-r--r-- | bash/Makefile | 25 | ||||
| -rw-r--r-- | bash/reader.sh | 153 | ||||
| -rwxr-xr-x | bash/step0_repl.sh | 21 | ||||
| -rwxr-xr-x | bash/step1_read_print.sh | 45 | ||||
| -rwxr-xr-x | bash/step2_eval.sh | 92 | ||||
| -rwxr-xr-x | bash/step3_env.sh | 116 | ||||
| -rwxr-xr-x | bash/step4_if_fn_do.sh | 140 | ||||
| -rwxr-xr-x | bash/step5_tco.sh | 157 | ||||
| -rwxr-xr-x | bash/step6_file.sh | 170 | ||||
| -rwxr-xr-x | bash/step7_quote.sh | 215 | ||||
| -rwxr-xr-x | bash/step8_macros.sh | 252 | ||||
| -rwxr-xr-x | bash/step9_interop.sh | 261 | ||||
| -rwxr-xr-x | bash/stepA_more.sh | 282 | ||||
| -rw-r--r-- | bash/tests/common.sh | 25 | ||||
| -rw-r--r-- | bash/tests/reader.sh | 88 | ||||
| -rw-r--r-- | bash/tests/types.sh | 161 | ||||
| -rw-r--r-- | bash/types.sh | 730 |
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) |
