aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoel Martin <github@martintribe.org>2014-03-24 16:32:24 -0500
committerJoel Martin <github@martintribe.org>2014-03-24 16:32:24 -0500
commit3169070063b2cb877200117ebb384269d73bcb93 (patch)
tree23de3db1ea5c37afd21a45b6ed7771f56a08c0c4
downloadmal-3169070063b2cb877200117ebb384269d73bcb93.tar.gz
mal-3169070063b2cb877200117ebb384269d73bcb93.zip
Current state of mal for Clojure West lighting talk.
-rw-r--r--.gitignore34
-rw-r--r--.gitmodules3
-rw-r--r--Makefile105
-rw-r--r--README2
-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
-rw-r--r--c/Makefile61
-rw-r--r--c/interop.c165
-rw-r--r--c/interop.h6
-rw-r--r--c/reader.c285
-rw-r--r--c/reader.h23
-rw-r--r--c/readline.c69
-rw-r--r--c/readline.h6
-rw-r--r--c/step0_repl.c44
-rw-r--r--c/step1_read_print.c81
-rw-r--r--c/step2_eval.c145
-rw-r--r--c/step3_env.c171
-rw-r--r--c/step4_if_fn_do.c215
-rw-r--r--c/step5_tco.c222
-rw-r--r--c/step6_file.c282
-rw-r--r--c/step7_quote.c318
-rw-r--r--c/step8_macros.c357
-rw-r--r--c/step9_interop.c362
-rw-r--r--c/stepA_more.c393
-rw-r--r--c/tests/step9_interop.mal23
-rw-r--r--c/types.c1038
-rw-r--r--c/types.h162
-rw-r--r--clojure/Makefile17
-rw-r--r--clojure/project.clj25
-rw-r--r--clojure/src/reader.clj32
-rw-r--r--clojure/src/readline.clj36
-rw-r--r--clojure/src/step0_repl.clj26
-rw-r--r--clojure/src/step1_read_print.clj33
-rw-r--r--clojure/src/step2_eval.clj61
-rw-r--r--clojure/src/step3_env.clj76
-rw-r--r--clojure/src/step4_if_fn_do.clj92
-rw-r--r--clojure/src/step5_tco.clj101
-rw-r--r--clojure/src/step6_file.clj109
-rw-r--r--clojure/src/step7_quote.clj132
-rw-r--r--clojure/src/step8_macros.clj158
-rw-r--r--clojure/src/step9_interop.clj161
-rw-r--r--clojure/src/stepA_more.clj178
-rw-r--r--clojure/src/types.clj71
-rw-r--r--core.mal83
-rw-r--r--docs/TODO95
-rw-r--r--docs/step_notes.txt181
-rw-r--r--java/Makefile19
-rw-r--r--java/pom.xml81
-rw-r--r--java/src/main/java/mal/reader.java147
-rw-r--r--java/src/main/java/mal/readline.java101
-rw-r--r--java/src/main/java/mal/step0_repl.java48
-rw-r--r--java/src/main/java/mal/step1_read_print.java60
-rw-r--r--java/src/main/java/mal/step2_eval.java140
-rw-r--r--java/src/main/java/mal/step3_env.java137
-rw-r--r--java/src/main/java/mal/step4_if_fn_do.java163
-rw-r--r--java/src/main/java/mal/step5_tco.java174
-rw-r--r--java/src/main/java/mal/step6_file.java216
-rw-r--r--java/src/main/java/mal/step7_quote.java247
-rw-r--r--java/src/main/java/mal/step8_macros.java285
-rw-r--r--java/src/main/java/mal/stepA_more.java333
-rw-r--r--java/src/main/java/mal/types.java882
-rw-r--r--js/Makefile29
-rw-r--r--js/josh_readline.js402
-rw-r--r--js/node_readline.js38
-rw-r--r--js/package.json8
-rw-r--r--js/reader.js127
-rw-r--r--js/step0_repl.js42
-rw-r--r--js/step1_read_print.js47
-rw-r--r--js/step2_eval.js83
-rw-r--r--js/step3_env.js97
-rw-r--r--js/step4_if_fn_do.js112
-rw-r--r--js/step5_tco.js119
-rw-r--r--js/step6_file.js133
-rw-r--r--js/step7_quote.js154
-rw-r--r--js/step8_macros.js178
-rw-r--r--js/step9_interop.js184
-rw-r--r--js/stepA_more.js198
-rw-r--r--js/tests/common.js15
l---------js/tests/node_modules1
-rw-r--r--js/tests/reader.js68
-rw-r--r--js/tests/step5_tco.js22
-rw-r--r--js/tests/types.js94
-rw-r--r--js/types.js429
-rw-r--r--make/Makefile23
-rw-r--r--make/gmsl.mk115
-rwxr-xr-xmake/reader.mk170
-rw-r--r--make/readline.mk15
-rw-r--r--make/step0_repl.mk26
-rw-r--r--make/step1_read_print.mk31
-rw-r--r--make/step2_eval.mk71
-rw-r--r--make/step3_env.mk93
-rw-r--r--make/step4_if_fn_do.mk112
-rw-r--r--make/step6_file.mk130
-rw-r--r--make/step7_quote.mk147
-rw-r--r--make/step8_macros.mk170
-rw-r--r--make/step9_interop.mk174
-rw-r--r--make/stepA_more.mk192
-rw-r--r--make/tests/common.mk18
-rw-r--r--make/tests/reader.mk76
-rw-r--r--make/tests/step9_interop.mk14
-rw-r--r--make/tests/types.mk304
-rw-r--r--make/types.mk484
-rw-r--r--make/util.mk72
-rw-r--r--mal.html52
-rw-r--r--mal/Makefile17
-rw-r--r--mal/env.mal40
-rwxr-xr-xmal/presentation.mal125
-rw-r--r--mal/step1_read_print.mal26
-rw-r--r--mal/step2_eval.mal59
-rw-r--r--mal/step3_env.mal80
-rw-r--r--mal/step4_if_fn_do.mal99
-rw-r--r--mal/step6_file.mal105
-rw-r--r--mal/step7_quote.mal133
-rw-r--r--mal/step8_macros.mal165
-rw-r--r--mal/stepA_more.mal179
-rw-r--r--mal/types.mal16
-rw-r--r--php/reader.php115
-rw-r--r--php/readline.php34
-rw-r--r--php/step0_repl.php33
-rw-r--r--php/step1_read_print.php42
-rw-r--r--php/step2_eval.php77
-rw-r--r--php/step3_env.php94
-rw-r--r--php/step4_if_fn_do.php112
-rw-r--r--php/step5_tco.php124
-rw-r--r--php/step6_file.php142
-rw-r--r--php/step7_quote.php165
-rw-r--r--php/step8_macros.php190
-rw-r--r--php/step9_interop.php192
-rw-r--r--php/stepA_more.php213
-rw-r--r--php/types.php488
-rw-r--r--python/Makefile27
-rw-r--r--python/mal_readline.py24
-rw-r--r--python/mal_types.py268
-rw-r--r--python/reader.py104
-rw-r--r--python/step0_repl.py32
-rw-r--r--python/step1_read_print.py32
-rw-r--r--python/step2_eval.py60
-rw-r--r--python/step3_env.py76
-rw-r--r--python/step4_if_fn_do.py91
-rw-r--r--python/step5_tco.py99
-rw-r--r--python/step6_file.py108
-rw-r--r--python/step7_quote.py125
-rw-r--r--python/step8_macros.py145
-rw-r--r--python/step9_interop.py154
-rw-r--r--python/stepA_more.py168
-rwxr-xr-xruntest.py115
-rw-r--r--tests/inc.mal4
-rw-r--r--tests/incB.mal14
-rw-r--r--tests/step1_read_print.mal112
-rw-r--r--tests/step2_eval.mal19
-rw-r--r--tests/step3_env.mal38
-rw-r--r--tests/step4_if_fn_do.mal345
-rw-r--r--tests/step6_file.mal17
-rw-r--r--tests/step7_quote.mal69
-rw-r--r--tests/step8_macros.mal94
-rw-r--r--tests/stepA_more.mal294
171 files changed, 22973 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..2d3759a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,34 @@
+make/mal.mk
+js/node_modules
+js/mal.js
+js/mal_web.js
+bash/mal.sh
+c/*.o
+*.pyc
+c/mal
+c/step0_repl
+c/step1_read_print
+c/step2_eval
+c/step3_env
+c/step4_if_fn_do
+c/step5_tco
+c/step6_file
+c/step7_quote
+c/step8_macros
+c/step9_interop
+c/stepA_more
+clojure/target
+clojure/.lein-repl-history
+java/target/
+java/dependency-reduced-pom.xml
+rust/step0_repl
+rust/step1_read_print
+rust/step2_eval
+rust/step3_env
+rust/step4_if_fn_do
+rust/step5_tco
+rust/step6_file
+rust/step7_quote
+rust/step8_macros
+rust/step9_interop
+rust/stepA_more
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..247fccb
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "josh.js"]
+ path = js/josh.js
+ url = https://github.com/sdether/josh.js/
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..2bfa3c4
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,105 @@
+#
+# Command line settings
+#
+
+MAL_IMPL = js
+
+#
+# Settings
+#
+
+IMPLS = bash c clojure java js make php python mal
+
+step0 = step0_repl
+step1 = step1_read_print
+step2 = step2_eval
+step3 = step3_env
+step4 = step4_if_fn_do
+step5 = step5_tco
+step6 = step6_file
+step7 = step7_quote
+step8 = step8_macros
+step9 = step9_interop
+stepA = stepA_more
+
+EXCLUDE_TESTS = test^make^step5 test^mal^step0 test^mal^step5 test^mal^step9 test^java^step9
+
+#
+# Utility functions
+#
+
+STEP_TEST_FILES = $(strip $(wildcard $(1)/tests/$($(2)).mal) $(wildcard tests/$($(2)).mal))
+
+bash_STEP_TO_PROG = bash/$($(1)).sh
+c_STEP_TO_PROG = c/$($(1))
+clojure_STEP_TO_PROG = clojure/src/$($(1)).clj
+java_STEP_TO_PROG = java/src/main/java/mal/$($(1)).java
+js_STEP_TO_PROG = js/$($(1)).js
+make_STEP_TO_PROG = make/$($(1)).mk
+php_STEP_TO_PROG = php/$($(1)).php
+python_STEP_TO_PROG = python/$($(1)).py
+mal_STEP_TO_PROG = mal/$($(1)).mal
+
+
+bash_RUNTEST = ../runtest.py $(4) ../$(1) -- bash ../$(2) $(5)
+c_RUNTEST = ../runtest.py $(4) ../$(1) -- ../$(2) $(5)
+clojure_RUNTEST = ../runtest.py $(4) ../$(1) -- lein with-profile +$(3) trampoline run $(5)
+java_RUNTEST = ../runtest.py $(4) ../$(1) -- mvn -quiet exec:java -Dexec.mainClass="mal.$($(3))" -Dexec.args="--raw$(if $(5), $(5),)"
+js_RUNTEST = ../runtest.py $(4) ../$(1) -- node ../$(2) $(5)
+make_RUNTEST = ../runtest.py $(4) ../$(1) -- make -f ../$(2) $(5)
+php_RUNTEST = ../runtest.py $(4) ../$(1) -- php ../$(2) $(5)
+python_RUNTEST = ../runtest.py $(4) ../$(1) -- python ../$(2) $(5)
+mal_RUNTEST = $(call $(MAL_IMPL)_RUNTEST,$(1),$(call $(MAL_IMPL)_STEP_TO_PROG,stepA),stepA,--start-timeout 30 --test-timeout 120,../$(2))
+
+
+# Derived lists
+STEPS = $(sort $(filter step%,$(.VARIABLES)))
+IMPL_TESTS = $(foreach impl,$(IMPLS),test^$(impl))
+STEP_TESTS = $(foreach step,$(STEPS),test^$(step))
+ALL_TESTS = $(filter-out $(EXCLUDE_TESTS),\
+ $(strip $(sort \
+ $(foreach impl,$(IMPLS),\
+ $(foreach step,$(STEPS),test^$(impl)^$(step))))))
+
+IMPL_STATS = $(foreach impl,$(IMPLS),stats^$(impl))
+
+#
+# Build rules
+#
+
+# Build a program in 'c' directory
+c/%:
+ $(MAKE) -C $(dir $(@)) $(notdir $(@))
+
+# Allow test, test^STEP, test^IMPL, and test^IMPL^STEP
+.SECONDEXPANSION:
+$(IMPL_TESTS): $$(filter $$@^%,$$(ALL_TESTS))
+
+.SECONDEXPANSION:
+$(STEP_TESTS): $$(foreach step,$$(subst test^,,$$@),$$(filter %^$$(step),$$(ALL_TESTS)))
+
+.SECONDEXPANSION:
+$(ALL_TESTS): $$(call $$(word 2,$$(subst ^, ,$$(@)))_STEP_TO_PROG,$$(word 3,$$(subst ^, ,$$(@))))
+ @$(foreach impl,$(word 2,$(subst ^, ,$(@))),\
+ $(foreach step,$(word 3,$(subst ^, ,$(@))),\
+ cd $(if $(filter mal,$(impl)),$(MAL_IMPL),$(impl)); \
+ $(foreach test,$(call STEP_TEST_FILES,$(impl),$(step)),\
+ echo '----------------------------------------------'; \
+ echo 'Testing $@, step file: $+, test file: $(test)'; \
+ echo 'Running: $(call $(impl)_RUNTEST,$(test),$(+),$(step))'; \
+ $(call $(impl)_RUNTEST,$(test),$(+),$(step)))))
+
+test: $(ALL_TESTS)
+tests: $(ALL_TESTS)
+
+
+# Stats rules
+
+.SECONDEXPANSION:
+$(IMPL_STATS):
+ @echo "----------------------------------------------"; \
+ $(foreach impl,$(word 2,$(subst ^, ,$(@))),\
+ echo "Stats for $(impl):"; \
+ $(MAKE) --no-print-directory -C $(impl) stats)
+
+stats: $(IMPL_STATS)
diff --git a/README b/README
new file mode 100644
index 0000000..3f7379c
--- /dev/null
+++ b/README
@@ -0,0 +1,2 @@
+http://norvig.com/lispy.html
+ftp://ftp.cs.wpi.edu/pub/techreports/pdf/05-07.pdf
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)
diff --git a/c/Makefile b/c/Makefile
new file mode 100644
index 0000000..397bcbf
--- /dev/null
+++ b/c/Makefile
@@ -0,0 +1,61 @@
+USE_READLINE ?=
+CFLAGS += -g
+LDFLAGS += -g
+
+#####################
+
+TESTS =
+
+SOURCES = types.h types.c readline.h readline.c reader.h reader.c \
+ interop.h interop.c stepA_more.c
+
+#####################
+
+SRCS = step0_repl.c step1_read_print.c step2_eval.c step3_env.c \
+ step4_if_fn_do.c step5_tco.c step6_file.c step7_quote.c \
+ step8_macros.c step9_interop.c stepA_more.c
+OBJS = $(SRCS:%.c=%.o)
+BINS = $(OBJS:%.o=%)
+OTHER_OBJS = types.o readline.o reader.o interop.o
+OTHER_HDRS = types.h readline.h reader.h interop.h
+
+GLIB_CFLAGS ?= $(shell pkg-config --cflags glib-2.0)
+GLIB_LDFLAGS ?= $(shell pkg-config --libs glib-2.0)
+
+ifeq (,$(USE_READLINE))
+RL_LIBRARY ?= edit
+else
+RL_LIBRARY ?= readline
+CFLAGS += -DUSE_READLINE=1
+endif
+
+CFLAGS += $(GLIB_CFLAGS)
+LDFLAGS += -l$(RL_LIBRARY) $(GLIB_LDFLAGS) -ldl -lffi
+
+#####################
+
+all: $(BINS) mal
+
+mal: $(word $(words $(BINS)),$(BINS))
+ cp $< $@
+
+$(OBJS) $(OTHER_OBJS): %.o: %.c $(OTHER_HDRS)
+ gcc $(CFLAGS) -c $(@:%.o=%.c) -o $@
+
+$(patsubst %.o,%,$(filter step%,$(OBJS))): $(OTHER_OBJS)
+$(BINS): %: %.o
+ gcc $+ -o $@ $(LDFLAGS)
+
+clean:
+ rm -f $(OBJS) $(BINS) $(OTHER_OBJS) mal
+
+.PHONY: stats tests $(TESTS)
+
+stats: $(SOURCES)
+ @wc $^
+
+tests: $(TESTS)
+
+$(TESTS):
+ @echo "Running $@"; \
+ ./$@ || exit 1; \
diff --git a/c/interop.c b/c/interop.c
new file mode 100644
index 0000000..276b99e
--- /dev/null
+++ b/c/interop.c
@@ -0,0 +1,165 @@
+#include <dlfcn.h>
+#include <ffi.h>
+#include "types.h"
+
+
+GHashTable *loaded_dls = NULL;
+
+int get_byte_size(char *type) {
+}
+
+typedef struct Raw64 {
+ union {
+ gdouble floatnum;
+ gint64 integernum;
+ char *string;
+ } v;
+} Raw64;
+
+
+// obj must be a pointer to the object to store
+ffi_type *_get_ffi_type(char *type) {
+ if ((strcmp("void", type) == 0)) {
+ return &ffi_type_void;
+ } else if ((strcmp("string", type) == 0) ||
+ (strcmp("char*", type) == 0) ||
+ (strcmp("char *", type) == 0)) {
+ return &ffi_type_pointer;
+ } else if ((strcmp("integer", type) == 0) ||
+ (strcmp("int64", type) == 0)) {
+ return &ffi_type_sint64;
+ } else if ((strcmp("int32", type) == 0)) {
+ return &ffi_type_sint32;
+ } else if (strcmp("double", type) == 0) {
+ return &ffi_type_double;
+ } else if (strcmp("float", type) == 0) {
+ return &ffi_type_float;
+ } else {
+ abort("_get_ffi_type of unknown type '%s'", type);
+ }
+}
+
+MalVal *_malval_new_by_type(char *type) {
+ if ((strcmp("void", type) == 0)) {
+ return NULL;
+ } else if ((strcmp("string", type) == 0) ||
+ (strcmp("char*", type) == 0) ||
+ (strcmp("char *", type) == 0)) {
+ return malval_new(MAL_STRING, NULL);
+ } else if ((strcmp("integer", type) == 0) ||
+ (strcmp("int64", type) == 0)) {
+ return malval_new(MAL_INTEGER, NULL);
+ } else if ((strcmp("int32", type) == 0)) {
+ return malval_new(MAL_INTEGER, NULL);
+ } else if (strcmp("double", type) == 0) {
+ return malval_new(MAL_FLOAT, NULL);
+ } else if (strcmp("float", type) == 0) {
+ return malval_new(MAL_FLOAT, NULL);
+ } else {
+ abort("_malval_new_by_type of unknown type '%s'", type);
+ }
+}
+
+
+
+// Mal syntax:
+// (. {DYN_LIB_FILE|nil} RETURN_TYPE FUNC_NAME [ARG_TYPE ARG]...)
+MalVal *invoke_native(MalVal *call_data) {
+ //g_print("invoke_native %s\n", pr_str(call_data));
+ int cd_len = call_data->val.array->len;
+ int arg_len = (cd_len - 3)/2;
+ char *error;
+ void *dl_handle;
+
+ assert_type(call_data, MAL_LIST,
+ "invoke_native called with non-list call_data: %s",
+ _pr_str(call_data,1));
+ assert(cd_len >= 3,
+ "invoke_native called with %d args, needs at least 3",
+ cd_len);
+ assert((cd_len % 2) == 1,
+ "invoke_native called with an even number of args (%d)",
+ cd_len);
+ assert(arg_len <= 3,
+ "invoke_native called with more than 3 native args (%d)",
+ arg_len);
+ MalVal *dl_file = _nth(call_data, 0),
+ *ftype = _nth(call_data, 1),
+ *fname = _nth(call_data, 2);
+ assert_type(dl_file, MAL_STRING|MAL_NIL,
+ "invoke_native arg 1 (DYN_LIB_NAME) must be a string or nil");
+ assert_type(ftype, MAL_STRING,
+ "invoke_native arg 2 (RETURN_TYPE) must be a string");
+ assert_type(fname, MAL_STRING,
+ "invoke_native arg 3 (FUNC_NAME) must be a string");
+
+ // Cached load of the dynamic library handle
+ if (dl_file->type == MAL_NIL) {
+ dl_handle = dlopen(NULL, RTLD_LAZY);
+ } else {
+ // Load the library
+ if (loaded_dls == NULL) {
+ loaded_dls = g_hash_table_new(g_str_hash, g_str_equal);
+ }
+ dl_handle = g_hash_table_lookup(loaded_dls, dl_file->val.string);
+ dlerror(); // clear any existing error
+ if (!dl_handle) {
+ dl_handle = dlopen(dl_file->val.string, RTLD_LAZY);
+ }
+ if ((error = dlerror()) != NULL) {
+ abort("Could not dlopen '%s': %s", dl_file->val.string, error);
+ }
+ g_hash_table_insert(loaded_dls, dl_file->val.string, dl_handle);
+ }
+
+ void * func = dlsym(dl_handle, fname->val.string);
+ if ((error = dlerror()) != NULL) {
+ abort("Could not dlsym '%s': %s", fname->val.string, error);
+ }
+
+
+ //
+ // Use FFI library to make a dynamic call
+ //
+
+ // Based on:
+ // http://eli.thegreenplace.net/2013/03/04/flexible-runtime-interface-to-shared-libraries-with-libffi/
+ ffi_cif cif;
+ ffi_type *ret_type;
+ ffi_type *arg_types[20];
+ void *arg_vals[20];
+ ffi_status status;
+ MalVal *ret_mv;
+
+ // Set return type
+ ret_type = _get_ffi_type(ftype->val.string);
+ ret_mv = _malval_new_by_type(ftype->val.string);
+ if (mal_error) { return NULL; }
+
+ // Set the argument types and values
+ int i;
+ for (i=0; i < arg_len; i++) {
+ arg_types[i] = _get_ffi_type(_nth(call_data, 3+i*2)->val.string);
+ if (arg_types[i] == NULL) {
+ return NULL;
+ }
+ arg_vals[i] = &_nth(call_data, 4+i*2)->val;
+ }
+
+ status = ffi_prep_cif(&cif, FFI_DEFAULT_ABI, arg_len,
+ ret_type, arg_types);
+ if (status != FFI_OK) {
+ abort("ffi_prep_cif failed: %d\n", status);
+ }
+
+ // Perform the call
+ //g_print("Calling %s[%p](%d)\n", fname->val.string, func, arg_len);
+ ffi_call(&cif, FFI_FN(func), &ret_mv->val, arg_vals);
+
+ if (ret_type == &ffi_type_void) {
+ return &mal_nil;
+ } else {
+ return ret_mv;
+ }
+}
+
diff --git a/c/interop.h b/c/interop.h
new file mode 100644
index 0000000..bcb2350
--- /dev/null
+++ b/c/interop.h
@@ -0,0 +1,6 @@
+#ifndef __MAL_INTEROP__
+#define __MAL_INTEROP__
+
+MalVal *invoke_native(MalVal *call_data);
+
+#endif
diff --git a/c/reader.c b/c/reader.c
new file mode 100644
index 0000000..044bb84
--- /dev/null
+++ b/c/reader.c
@@ -0,0 +1,285 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <glib/gregex.h>
+#include <glib-object.h>
+
+#include "types.h"
+#include "reader.h"
+
+// Declare
+MalVal *read_form(Reader *reader);
+
+Reader *reader_new() {
+ Reader *reader = (Reader*)malloc(sizeof(Reader));
+ reader->array = g_array_sized_new(TRUE, FALSE, sizeof(char *), 8);
+ reader->position = 0;
+ return reader;
+}
+
+int reader_append(Reader *reader, char* token) {
+ g_array_append_val(reader->array, token);
+ return TRUE;
+}
+
+char *reader_peek(Reader *reader) {
+ return g_array_index(reader->array, char*, reader->position);
+}
+
+char *reader_next(Reader *reader) {
+ if (reader->position >= reader->array->len) {
+ return NULL;
+ } else {
+ return g_array_index(reader->array, char*, reader->position++);
+ }
+}
+
+void reader_free(Reader *reader) {
+ int i;
+ for(i=0; i < reader->array->len; i++) {
+ free(g_array_index(reader->array, char*, i));
+ }
+ g_array_free(reader->array, TRUE);
+ free(reader);
+}
+
+Reader *tokenize(char *line) {
+ GRegex *regex;
+ GMatchInfo *matchInfo;
+ GError *err = NULL;
+
+ Reader *reader = reader_new();
+
+ regex = g_regex_new ("[\\s ,]*(~@|[\\[\\]{}()'`~@]|\"(?:[\\\\].|[^\\\\\"])*\"|;.*|[^\\s \\[\\]{}()'\"`~@,;]*)", 0, 0, &err);
+ g_regex_match (regex, line, 0, &matchInfo);
+
+ if (err != NULL) {
+ fprintf(stderr, "Tokenize error: %s\n", err->message);
+ return NULL;
+ }
+
+ while (g_match_info_matches(matchInfo)) {
+ gchar *result = g_match_info_fetch(matchInfo, 1);
+ if (result[0] != '\0' && result[0] != ';') {
+ reader_append(reader, result);
+ }
+ g_match_info_next(matchInfo, &err);
+ }
+ g_match_info_free(matchInfo);
+ g_regex_unref(regex);
+ if (reader->array->len == 0) {
+ reader_free(reader);
+ return NULL;
+ } else {
+ return reader;
+ }
+}
+
+
+// From http://creativeandcritical.net/str-replace-c/ - Laird Shaw
+char *replace_str(const char *str, const char *old, const char *new)
+{
+ char *ret, *r;
+ const char *p, *q;
+ size_t oldlen = strlen(old);
+ size_t count, retlen, newlen = strlen(new);
+
+ if (oldlen != newlen) {
+ for (count = 0, p = str; (q = strstr(p, old)) != NULL; p = q + oldlen)
+ count++;
+ /* this is undefined if p - str > PTRDIFF_MAX */
+ retlen = p - str + strlen(p) + count * (newlen - oldlen);
+ } else
+ retlen = strlen(str);
+
+ if ((ret = malloc(retlen + 1)) == NULL)
+ return NULL;
+
+ for (r = ret, p = str; (q = strstr(p, old)) != NULL; p = q + oldlen) {
+ /* this is undefined if q - p > PTRDIFF_MAX */
+ ptrdiff_t l = q - p;
+ memcpy(r, p, l);
+ r += l;
+ memcpy(r, new, newlen);
+ r += newlen;
+ }
+ strcpy(r, p);
+
+ return ret;
+}
+
+
+MalVal *read_atom(Reader *reader) {
+ char *token;
+ GRegex *regex;
+ GMatchInfo *matchInfo;
+ GError *err = NULL;
+ gint pos;
+ MalVal *atom;
+
+ token = reader_next(reader);
+ //g_print("read_atom token: %s\n", token);
+
+ regex = g_regex_new ("(^-?[0-9]+$)|(^-?[0-9][0-9.]*$)|(^nil$)|(^true$)|(^false$)|^\"(.*)\"$|(^[^\"]*$)", 0, 0, &err);
+ g_regex_match (regex, token, 0, &matchInfo);
+
+ if (g_match_info_fetch_pos(matchInfo, 1, &pos, NULL) && pos != -1) {
+ //g_print("read_atom integer\n");
+ atom = malval_new_integer(g_ascii_strtoll(token, NULL, 10));
+ } else if (g_match_info_fetch_pos(matchInfo, 2, &pos, NULL) && pos != -1) {
+ //g_print("read_atom float\n");
+ atom = malval_new_float(g_ascii_strtod(token, NULL));
+ } else if (g_match_info_fetch_pos(matchInfo, 3, &pos, NULL) && pos != -1) {
+ //g_print("read_atom nil\n");
+ atom = &mal_nil;
+ } else if (g_match_info_fetch_pos(matchInfo, 4, &pos, NULL) && pos != -1) {
+ //g_print("read_atom true\n");
+ atom = &mal_true;
+ } else if (g_match_info_fetch_pos(matchInfo, 5, &pos, NULL) && pos != -1) {
+ //g_print("read_atom false\n");
+ atom = &mal_false;
+ } else if (g_match_info_fetch_pos(matchInfo, 6, &pos, NULL) && pos != -1) {
+ //g_print("read_atom string: %s\n", token);
+ char *str_tmp = replace_str(g_match_info_fetch(matchInfo, 6), "\\\"", "\"");
+ atom = malval_new_string(str_tmp);
+ } else if (g_match_info_fetch_pos(matchInfo, 7, &pos, NULL) && pos != -1) {
+ //g_print("read_atom symbol\n");
+ atom = malval_new_symbol(g_match_info_fetch(matchInfo, 7));
+ } else {
+ malval_free(atom);
+ atom = NULL;
+ }
+ return atom;
+}
+
+MalVal *read_list(Reader *reader, MalType type, char start, char end) {
+ MalVal *ast, *form;
+ char *token = reader_next(reader);
+ //g_print("read_list start token: %s\n", token);
+ if (token[0] != start) { abort("expected '(' or '['"); }
+
+ ast = malval_new_list(type, g_array_new(TRUE, TRUE, sizeof(MalVal*)));
+
+ while ((token = reader_peek(reader)) &&
+ token[0] != end) {
+ //g_print("read_list internal token %s\n", token);
+ form = read_form(reader);
+ if (!form) {
+ if (!mal_error) { abort("unknown read_list failure"); }
+ g_array_free(ast->val.array, TRUE);
+ malval_free(ast);
+ return NULL;
+ }
+ g_array_append_val(ast->val.array, form);
+ }
+ if (!token) { abort("expected ')' or ']', got EOF"); }
+ reader_next(reader);
+ //g_print("read_list end token: %s\n", token);
+ return ast;
+}
+
+MalVal *read_hash_map(Reader *reader) {
+ MalVal *lst = read_list(reader, MAL_LIST, '{', '}');
+ MalVal *hm = hash_map(lst);
+ malval_free(lst);
+ return hm;
+}
+
+
+MalVal *read_form(Reader *reader) {
+ char *token;
+ MalVal *form = NULL, *tmp;
+
+// while(token = reader_next(reader)) {
+// printf("token: %s\n", token);
+// }
+// return NULL;
+
+ token = reader_peek(reader);
+
+ if (!token) { return NULL; }
+ //g_print("read_form token: %s\n", token);
+
+ switch (token[0]) {
+ case ';':
+ abort("comments not yet implemented");
+ break;
+ case '\'':
+ reader_next(reader);
+ form = _list(2, malval_new_symbol("quote"),
+ read_form(reader));
+ break;
+ case '`':
+ reader_next(reader);
+ form = _list(2, malval_new_symbol("quasiquote"),
+ read_form(reader));
+ break;
+ case '~':
+ reader_next(reader);
+ if (token[1] == '@') {
+ form = _list(2, malval_new_symbol("splice-unquote"),
+ read_form(reader));
+ } else {
+ form = _list(2, malval_new_symbol("unquote"),
+ read_form(reader));
+ };
+ break;
+ case '^':
+ reader_next(reader);
+ MalVal *meta = read_form(reader);
+ form = _list(3, malval_new_symbol("with-meta"),
+ read_form(reader), meta);
+ break;
+ case '@':
+ reader_next(reader);
+ form = _list(2, malval_new_symbol("deref"),
+ read_form(reader));
+ break;
+
+
+ // list
+ case ')':
+ abort("unexpected ')'");
+ break;
+ case '(':
+ form = read_list(reader, MAL_LIST, '(', ')');
+ break;
+
+ // vector
+ case ']':
+ abort("unexpected ']'");
+ break;
+ case '[':
+ form = read_list(reader, MAL_VECTOR, '[', ']');
+ break;
+
+ // hash-map
+ case '}':
+ abort("unexpected '}'");
+ break;
+ case '{':
+ form = read_hash_map(reader);
+ break;
+
+ default:
+ form = read_atom(reader);
+ break;
+ }
+ return form;
+
+}
+
+MalVal *read_str (char *str) {
+ Reader *reader;
+ char *token;
+ MalVal *ast = NULL;
+
+ reader = tokenize(str);
+ if (reader) {
+ ast = read_form(reader);
+ reader_free(reader);
+ }
+
+ return ast;
+}
diff --git a/c/reader.h b/c/reader.h
new file mode 100644
index 0000000..90f07ed
--- /dev/null
+++ b/c/reader.h
@@ -0,0 +1,23 @@
+#ifndef __MAL_READER__
+#define __MAL_READER__
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include "types.h"
+
+typedef struct {
+ GArray *array;
+ int position;
+} Reader;
+
+Reader *reader_new();
+int reader_append(Reader *reader, char* token);
+char *reader_peek(Reader *reader);
+char *reader_next(Reader *reader);
+void reader_free(Reader *reader);
+
+char *_readline (char prompt[]);
+MalVal *read_str ();
+
+#endif
diff --git a/c/readline.c b/c/readline.c
new file mode 100644
index 0000000..b981ee7
--- /dev/null
+++ b/c/readline.c
@@ -0,0 +1,69 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#if USE_READLINE
+ #include <readline/readline.h>
+ #include <readline/history.h>
+ #include <readline/tilde.h>
+#else
+ #include <editline/readline.h>
+ #include <editline/history.h>
+#endif
+
+int history_loaded = 0;
+
+char HISTORY_FILE[] = "~/.mal-history";
+
+int load_history() {
+ if (history_loaded) { return 0; }
+ int ret;
+ char *hf = tilde_expand(HISTORY_FILE);
+ if (access(hf, F_OK) != -1) {
+ // TODO: check if file exists first, use non-static path
+#if USE_READLINE
+ ret = read_history(hf);
+#else
+ FILE *fp = fopen(hf, "r");
+ char *line = malloc(80); // getline reallocs as necessary
+ size_t sz = 80;
+ while ((ret = getline(&line, &sz, fp)) > 0) {
+ add_history(line); // Add line to in-memory history
+ }
+ free(line);
+ fclose(fp);
+#endif
+ history_loaded = 1;
+ }
+ free(hf);
+}
+
+int append_to_history() {
+ char *hf = tilde_expand(HISTORY_FILE);
+#ifdef USE_READLINE
+ append_history(1, hf);
+#else
+ HIST_ENTRY *he = history_get(history_length-1);
+ FILE *fp = fopen(hf, "a");
+ fprintf(fp, "%s\n", he->line);
+ fclose(fp);
+#endif
+ free(hf);
+}
+
+
+// line must be freed by caller
+char *_readline (char prompt[]) {
+ char *line;
+
+ load_history();
+
+ line = readline(prompt);
+ if (!line) return NULL; // EOF
+ add_history(line); // Add input to in-memory history
+
+ append_to_history(); // Flush new line of history to disk
+
+ return line;
+}
+
diff --git a/c/readline.h b/c/readline.h
new file mode 100644
index 0000000..d524f4a
--- /dev/null
+++ b/c/readline.h
@@ -0,0 +1,6 @@
+#ifndef __MAL_READLINE__
+#define __MAL_READLINE__
+
+char *_readline (char prompt[]);
+
+#endif
diff --git a/c/step0_repl.c b/c/step0_repl.c
new file mode 100644
index 0000000..f6d8048
--- /dev/null
+++ b/c/step0_repl.c
@@ -0,0 +1,44 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#ifdef USE_READLINE
+ #include <readline/readline.h>
+ #include <readline/history.h>
+#else
+ #include <editline/readline.h>
+#endif
+
+char *READ(char prompt[]) {
+ char *line;
+ line = readline(prompt);
+ if (!line) return NULL; // EOF
+ add_history(line); // Add input to history.
+ return line;
+}
+
+char *EVAL(char *ast, void *env) {
+ return ast;
+}
+
+char *PRINT(char *exp) {
+ return exp;
+}
+
+int main()
+{
+ char *ast, *exp;
+ char prompt[100];
+
+ // Set the initial prompt
+ snprintf(prompt, sizeof(prompt), "user> ");
+
+ for(;;) {
+ ast = READ(prompt);
+ if (!ast) return 0;
+ exp = EVAL(ast, NULL);
+ g_print("%s\n", PRINT(exp));
+
+ free(ast); // Free input string
+ }
+}
diff --git a/c/step1_read_print.c b/c/step1_read_print.c
new file mode 100644
index 0000000..3612373
--- /dev/null
+++ b/c/step1_read_print.c
@@ -0,0 +1,81 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include "types.h"
+#include "readline.h"
+#include "reader.h"
+
+// read
+MalVal *READ(char prompt[], char *str) {
+ char *line;
+ MalVal *ast;
+ if (str) {
+ line = str;
+ } else {
+ line = _readline(prompt);
+ if (!line) {
+ _error("EOF");
+ return NULL;
+ }
+ }
+ ast = read_str(line);
+ if (!str) { free(line); }
+ return ast;
+}
+
+// eval
+MalVal *EVAL(MalVal *ast, GHashTable *env) {
+ if (!ast || mal_error) return NULL;
+ return ast;
+}
+
+// print
+char *PRINT(MalVal *exp) {
+ if (mal_error) {
+ fprintf(stderr, "Error: %s\n", mal_error->val.string);
+ malval_free(mal_error);
+ mal_error = NULL;
+ return NULL;
+ }
+ return _pr_str(exp,1);
+}
+
+// repl
+
+// read and eval
+MalVal *RE(GHashTable *env, char *prompt, char *str) {
+ MalVal *ast, *exp;
+ ast = READ(prompt, str);
+ if (!ast || mal_error) return NULL;
+ exp = EVAL(ast, env);
+ if (ast != exp) {
+ malval_free(ast); // Free input structure
+ }
+ return exp;
+}
+
+int main()
+{
+ MalVal *exp;
+ char *output;
+ char prompt[100];
+
+ // Set the initial prompt
+ snprintf(prompt, sizeof(prompt), "user> ");
+
+ // REPL loop
+ for(;;) {
+ exp = RE(NULL, prompt, NULL);
+ if (mal_error && strcmp("EOF", mal_error) == 0) {
+ return 0;
+ }
+ output = PRINT(exp);
+
+ if (output) {
+ g_print("%s\n", output);
+ free(output); // Free output string
+ }
+
+ //malval_free(exp); // Free evaluated expression
+ }
+}
diff --git a/c/step2_eval.c b/c/step2_eval.c
new file mode 100644
index 0000000..509e795
--- /dev/null
+++ b/c/step2_eval.c
@@ -0,0 +1,145 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include "types.h"
+#include "readline.h"
+#include "reader.h"
+
+// Declarations
+MalVal *EVAL(MalVal *ast, GHashTable *env);
+
+// read
+MalVal *READ(char prompt[], char *str) {
+ char *line;
+ MalVal *ast;
+ if (str) {
+ line = str;
+ } else {
+ line = _readline(prompt);
+ if (!line) {
+ _error("EOF");
+ return NULL;
+ }
+ }
+ ast = read_str(line);
+ if (!str) { free(line); }
+ return ast;
+}
+
+// eval
+MalVal *eval_ast(MalVal *ast, GHashTable *env) {
+ if (!ast || mal_error) return NULL;
+ if (ast->type == MAL_SYMBOL) {
+ //g_print("EVAL symbol: %s\n", ast->val.string);
+ // TODO: check if not found
+ return g_hash_table_lookup(env, ast->val.string);
+ } else if ((ast->type == MAL_LIST) || (ast->type == MAL_VECTOR)) {
+ //g_print("EVAL sequential: %s\n", _pr_str(ast,1));
+ MalVal *el = _map2((MalVal *(*)(void*, void*))EVAL, ast, env);
+ if (!el || mal_error) return NULL;
+ el->type = ast->type;
+ return el;
+ } else if (ast->type == MAL_HASH_MAP) {
+ //g_print("EVAL hash_map: %s\n", _pr_str(ast,1));
+ GHashTableIter iter;
+ gpointer key, value;
+ MalVal *seq = malval_new_list(MAL_LIST,
+ g_array_sized_new(TRUE, TRUE, sizeof(MalVal*),
+ _count(ast)));
+ g_hash_table_iter_init (&iter, ast->val.hash_table);
+ while (g_hash_table_iter_next (&iter, &key, &value)) {
+ MalVal *kname = malval_new_string((char *)key);
+ g_array_append_val(seq->val.array, kname);
+ MalVal *new_val = EVAL((MalVal *)value, env);
+ g_array_append_val(seq->val.array, new_val);
+ }
+ return hash_map(seq);
+ } else {
+ //g_print("EVAL scalar: %s\n", _pr_str(ast,1));
+ return ast;
+ }
+}
+
+MalVal *EVAL(MalVal *ast, GHashTable *env) {
+ //g_print("EVAL: %s\n", _pr_str(ast,1));
+ if (!ast || mal_error) return NULL;
+ if (ast->type != MAL_LIST) {
+ return eval_ast(ast, env);
+ }
+ if (!ast || mal_error) return NULL;
+
+ // apply list
+ //g_print("EVAL apply list: %s\n", _pr_str(ast,1));
+ if (_count(ast) == 0) { return ast; }
+ MalVal *a0 = _nth(ast, 0);
+ assert_type(a0, MAL_SYMBOL, "Cannot invoke %s", _pr_str(a0,1));
+ MalVal *el = eval_ast(ast, env);
+ if (!el || mal_error) { return NULL; }
+ MalVal *(*f)(void *, void*) = (MalVal *(*)(void*, void*))first(el);
+ //g_print("eval_invoke el: %s\n", _pr_str(el,1));
+ return f(_nth(el, 1), _nth(el, 2));
+}
+
+// print
+char *PRINT(MalVal *exp) {
+ if (mal_error) {
+ fprintf(stderr, "Error: %s\n", mal_error->val.string);
+ malval_free(mal_error);
+ mal_error = NULL;
+ return NULL;
+ }
+ return _pr_str(exp,1);
+}
+
+// repl
+
+// read and eval
+MalVal *RE(GHashTable *env, char *prompt, char *str) {
+ MalVal *ast, *exp;
+ ast = READ(prompt, str);
+ if (!ast || mal_error) return NULL;
+ exp = EVAL(ast, env);
+ if (ast != exp) {
+ malval_free(ast); // Free input structure
+ }
+ return exp;
+}
+
+// Setup the initial REPL environment
+GHashTable *repl_env;
+
+void init_repl_env() {
+ repl_env = g_hash_table_new(g_str_hash, g_str_equal);
+
+ g_hash_table_insert(repl_env, "+", int_plus);
+ g_hash_table_insert(repl_env, "-", int_minus);
+ g_hash_table_insert(repl_env, "*", int_multiply);
+ g_hash_table_insert(repl_env, "/", int_divide);
+}
+
+int main()
+{
+ MalVal *exp;
+ char *output;
+ char prompt[100];
+
+ // Set the initial prompt and environment
+ snprintf(prompt, sizeof(prompt), "user> ");
+ init_repl_env();
+
+ // REPL loop
+ for(;;) {
+ exp = RE(repl_env, prompt, NULL);
+ if (mal_error && strcmp("EOF", mal_error->val.string) == 0) {
+ return 0;
+ }
+ output = PRINT(exp);
+
+ if (output) {
+ g_print("%s\n", output);
+ free(output); // Free output string
+ }
+
+ //malval_free(exp); // Free evaluated expression
+ }
+}
diff --git a/c/step3_env.c b/c/step3_env.c
new file mode 100644
index 0000000..bc645b8
--- /dev/null
+++ b/c/step3_env.c
@@ -0,0 +1,171 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include "types.h"
+#include "readline.h"
+#include "reader.h"
+
+// Declarations
+MalVal *EVAL(MalVal *ast, Env *env);
+
+// read
+MalVal *READ(char prompt[], char *str) {
+ char *line;
+ MalVal *ast;
+ if (str) {
+ line = str;
+ } else {
+ line = _readline(prompt);
+ if (!line) {
+ _error("EOF");
+ return NULL;
+ }
+ }
+ ast = read_str(line);
+ if (!str) { free(line); }
+ return ast;
+}
+
+// eval
+MalVal *eval_ast(MalVal *ast, Env *env) {
+ if (!ast || mal_error) return NULL;
+ if (ast->type == MAL_SYMBOL) {
+ //g_print("EVAL symbol: %s\n", ast->val.string);
+ return env_get(env, ast->val.string);
+ } else if ((ast->type == MAL_LIST) || (ast->type == MAL_VECTOR)) {
+ //g_print("EVAL sequential: %s\n", _pr_str(ast,1));
+ MalVal *el = _map2((MalVal *(*)(void*, void*))EVAL, ast, env);
+ if (!el || mal_error) return NULL;
+ el->type = ast->type;
+ return el;
+ } else if (ast->type == MAL_HASH_MAP) {
+ //g_print("EVAL hash_map: %s\n", _pr_str(ast,1));
+ GHashTableIter iter;
+ gpointer key, value;
+ MalVal *seq = malval_new_list(MAL_LIST,
+ g_array_sized_new(TRUE, TRUE, sizeof(MalVal*),
+ _count(ast)));
+ g_hash_table_iter_init (&iter, ast->val.hash_table);
+ while (g_hash_table_iter_next (&iter, &key, &value)) {
+ MalVal *kname = malval_new_string((char *)key);
+ g_array_append_val(seq->val.array, kname);
+ MalVal *new_val = EVAL((MalVal *)value, env);
+ g_array_append_val(seq->val.array, new_val);
+ }
+ return hash_map(seq);
+ } else {
+ //g_print("EVAL scalar: %s\n", _pr_str(ast,1));
+ return ast;
+ }
+}
+
+MalVal *EVAL(MalVal *ast, Env *env) {
+ //g_print("EVAL: %s\n", _pr_str(ast,1));
+ if (!ast || mal_error) return NULL;
+ if (ast->type != MAL_LIST) {
+ return eval_ast(ast, env);
+ }
+ if (!ast || mal_error) return NULL;
+
+ // apply list
+ //g_print("EVAL apply list: %s\n", _pr_str(ast,1));
+ int i, len;
+ if (_count(ast) == 0) { return ast; }
+ MalVal *a0 = _nth(ast, 0);
+ assert_type(a0, MAL_SYMBOL, "Cannot apply %s", _pr_str(a0,1));
+ if (strcmp("def!", a0->val.string) == 0) {
+ //g_print("eval apply def!\n");
+ MalVal *a1 = _nth(ast, 1),
+ *a2 = _nth(ast, 2);
+ MalVal *res = EVAL(a2, env);
+ env_set(env, a1->val.string, res);
+ return res;
+ } else if (strcmp("let*", a0->val.string) == 0) {
+ //g_print("eval apply let*\n");
+ MalVal *a1 = _nth(ast, 1),
+ *a2 = _nth(ast, 2),
+ *key, *val;
+ assert_type(a1, MAL_LIST|MAL_VECTOR,
+ "let* bindings must be list or vector");
+ len = _count(a1);
+ assert((len % 2) == 0, "odd number of let* bindings forms");
+ Env *let_env = new_env(env, NULL, NULL);
+ for(i=0; i<len; i+=2) {
+ key = g_array_index(a1->val.array, MalVal*, i);
+ val = g_array_index(a1->val.array, MalVal*, i+1);
+ assert_type(key, MAL_SYMBOL, "let* bind to non-symbol");
+ env_set(let_env, key->val.string, EVAL(val, let_env));
+ }
+ return EVAL(a2, let_env);
+ } else {
+ //g_print("eval apply\n");
+ MalVal *el = eval_ast(ast, env);
+ if (!el || mal_error) { return NULL; }
+ MalVal *(*f)(void *, void*) = (MalVal *(*)(void*, void*))first(el);
+ return f(_nth(el, 1), _nth(el, 2));
+ }
+}
+
+// print
+char *PRINT(MalVal *exp) {
+ if (mal_error) {
+ fprintf(stderr, "Error: %s\n", mal_error->val.string);
+ malval_free(mal_error);
+ mal_error = NULL;
+ return NULL;
+ }
+ return _pr_str(exp,1);
+}
+
+// repl
+
+// read and eval
+MalVal *RE(Env *env, char *prompt, char *str) {
+ MalVal *ast, *exp;
+ ast = READ(prompt, str);
+ if (!ast || mal_error) return NULL;
+ exp = EVAL(ast, env);
+ if (ast != exp) {
+ malval_free(ast); // Free input structure
+ }
+ return exp;
+}
+
+// Setup the initial REPL environment
+Env *repl_env;
+
+void init_repl_env() {
+ repl_env = new_env(NULL, NULL, NULL);
+
+ env_set(repl_env, "+", (MalVal *)int_plus);
+ env_set(repl_env, "-", (MalVal *)int_minus);
+ env_set(repl_env, "*", (MalVal *)int_multiply);
+ env_set(repl_env, "/", (MalVal *)int_divide);
+}
+
+int main()
+{
+ MalVal *exp;
+ char *output;
+ char prompt[100];
+
+ // Set the initial prompt and environment
+ snprintf(prompt, sizeof(prompt), "user> ");
+ init_repl_env();
+
+ // REPL loop
+ for(;;) {
+ exp = RE(repl_env, prompt, NULL);
+ if (mal_error && strcmp("EOF", mal_error->val.string) == 0) {
+ return 0;
+ }
+ output = PRINT(exp);
+
+ if (output) {
+ g_print("%s\n", output);
+ free(output); // Free output string
+ }
+
+ //malval_free(exp); // Free evaluated expression
+ }
+}
diff --git a/c/step4_if_fn_do.c b/c/step4_if_fn_do.c
new file mode 100644
index 0000000..a96641e
--- /dev/null
+++ b/c/step4_if_fn_do.c
@@ -0,0 +1,215 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include "types.h"
+#include "readline.h"
+#include "reader.h"
+
+// Declarations
+MalVal *EVAL(MalVal *ast, Env *env);
+
+// read
+MalVal *READ(char prompt[], char *str) {
+ char *line;
+ MalVal *ast;
+ if (str) {
+ line = str;
+ } else {
+ line = _readline(prompt);
+ if (!line) {
+ _error("EOF");
+ return NULL;
+ }
+ }
+ ast = read_str(line);
+ if (!str) { free(line); }
+ return ast;
+}
+
+// eval
+MalVal *eval_ast(MalVal *ast, Env *env) {
+ if (!ast || mal_error) return NULL;
+ if (ast->type == MAL_SYMBOL) {
+ //g_print("EVAL symbol: %s\n", ast->val.string);
+ return env_get(env, ast->val.string);
+ } else if ((ast->type == MAL_LIST) || (ast->type == MAL_VECTOR)) {
+ //g_print("EVAL sequential: %s\n", _pr_str(ast,1));
+ MalVal *el = _map2((MalVal *(*)(void*, void*))EVAL, ast, env);
+ if (!el || mal_error) return NULL;
+ el->type = ast->type;
+ return el;
+ } else if (ast->type == MAL_HASH_MAP) {
+ //g_print("EVAL hash_map: %s\n", _pr_str(ast,1));
+ GHashTableIter iter;
+ gpointer key, value;
+ MalVal *seq = malval_new_list(MAL_LIST,
+ g_array_sized_new(TRUE, TRUE, sizeof(MalVal*),
+ _count(ast)));
+ g_hash_table_iter_init (&iter, ast->val.hash_table);
+ while (g_hash_table_iter_next (&iter, &key, &value)) {
+ MalVal *kname = malval_new_string((char *)key);
+ g_array_append_val(seq->val.array, kname);
+ MalVal *new_val = EVAL((MalVal *)value, env);
+ g_array_append_val(seq->val.array, new_val);
+ }
+ return hash_map(seq);
+ } else {
+ //g_print("EVAL scalar: %s\n", _pr_str(ast,1));
+ return ast;
+ }
+}
+
+MalVal *EVAL(MalVal *ast, Env *env) {
+ //g_print("EVAL: %s\n", _pr_str(ast,1));
+ if (!ast || mal_error) return NULL;
+ if (ast->type != MAL_LIST) {
+ return eval_ast(ast, env);
+ }
+ if (!ast || mal_error) return NULL;
+
+ // apply list
+ //g_print("EVAL apply list: %s\n", _pr_str(ast,1));
+ int i, len;
+ if (_count(ast) == 0) { return ast; }
+ MalVal *a0 = _nth(ast, 0);
+ if ((a0->type & MAL_SYMBOL) &&
+ strcmp("def!", a0->val.string) == 0) {
+ //g_print("eval apply def!\n");
+ MalVal *a1 = _nth(ast, 1),
+ *a2 = _nth(ast, 2);
+ MalVal *res = EVAL(a2, env);
+ env_set(env, a1->val.string, res);
+ return res;
+ } else if ((a0->type & MAL_SYMBOL) &&
+ strcmp("let*", a0->val.string) == 0) {
+ //g_print("eval apply let*\n");
+ MalVal *a1 = _nth(ast, 1),
+ *a2 = _nth(ast, 2),
+ *key, *val;
+ assert_type(a1, MAL_LIST|MAL_VECTOR,
+ "let* bindings must be list or vector");
+ len = _count(a1);
+ assert((len % 2) == 0, "odd number of let* bindings forms");
+ Env *let_env = new_env(env, NULL, NULL);
+ for(i=0; i<len; i+=2) {
+ key = g_array_index(a1->val.array, MalVal*, i);
+ val = g_array_index(a1->val.array, MalVal*, i+1);
+ assert_type(key, MAL_SYMBOL, "let* bind to non-symbol");
+ env_set(let_env, key->val.string, EVAL(val, let_env));
+ }
+ return EVAL(a2, let_env);
+ } else if ((a0->type & MAL_SYMBOL) &&
+ strcmp("do", a0->val.string) == 0) {
+ //g_print("eval apply do\n");
+ MalVal *el = eval_ast(rest(ast), env);
+ return last(el);
+ } else if ((a0->type & MAL_SYMBOL) &&
+ strcmp("if", a0->val.string) == 0) {
+ //g_print("eval apply if\n");
+ MalVal *a1 = _nth(ast, 1);
+ MalVal *cond = EVAL(a1, env);
+ if (!ast || mal_error) return NULL;
+ if (cond->type & (MAL_FALSE|MAL_NIL)) {
+ // eval false slot form
+ MalVal *a3 = _nth(ast, 3);
+ if (a3) {
+ return EVAL(a3, env);
+ } else {
+ return &mal_nil;
+ }
+ } else {
+ // eval true slot form
+ MalVal *a2 = _nth(ast, 2);
+ return EVAL(a2, env);
+ }
+ } else if ((a0->type & MAL_SYMBOL) &&
+ strcmp("fn*", a0->val.string) == 0) {
+ //g_print("eval apply fn*\n");
+ MalVal *mf = malval_new(MAL_FUNCTION_MAL, NULL);
+ mf->val.func.evaluator = EVAL;
+ mf->val.func.args = _nth(ast, 1);
+ mf->val.func.body = _nth(ast, 2);
+ mf->val.func.env = env;
+ return mf;
+ } else {
+ //g_print("eval apply\n");
+ MalVal *el = eval_ast(ast, env);
+ if (!el || mal_error) { return NULL; }
+ MalVal *f = first(el),
+ *args = rest(el);
+ assert_type(f, MAL_FUNCTION_C|MAL_FUNCTION_MAL,
+ "cannot apply '%s'", _pr_str(f,1));
+ return apply(f, args);
+ }
+}
+
+// print
+char *PRINT(MalVal *exp) {
+ if (mal_error) {
+ fprintf(stderr, "Error: %s\n", mal_error->val.string);
+ malval_free(mal_error);
+ mal_error = NULL;
+ return NULL;
+ }
+ return _pr_str(exp,1);
+}
+
+// repl
+
+// read and eval
+MalVal *RE(Env *env, char *prompt, char *str) {
+ MalVal *ast, *exp;
+ ast = READ(prompt, str);
+ if (!ast || mal_error) return NULL;
+ exp = EVAL(ast, env);
+ if (ast != exp) {
+ malval_free(ast); // Free input structure
+ }
+ return exp;
+}
+
+// Setup the initial REPL environment
+Env *repl_env;
+
+void init_repl_env() {
+ void _ref(char *name, MalVal*(*func)(MalVal*), int arg_cnt) {
+ void *(*f)(void *) = (void*(*)(void*))func;
+ env_set(repl_env, name, malval_new_function(f, arg_cnt, NULL));
+ }
+ repl_env = new_env(NULL, NULL, NULL);
+
+ int i;
+ for(i=0; i< (sizeof(types_ns) / sizeof(types_ns[0])); i++) {
+ MalVal *(*f)(MalVal *) = (MalVal*(*)(MalVal*))types_ns[i].func;
+ _ref(types_ns[i].name, f, types_ns[i].arg_cnt);
+ }
+
+ RE(repl_env, "", "(def! not (fn* (a) (if a false true)))");
+}
+
+int main()
+{
+ MalVal *exp;
+ char *output;
+ char prompt[100];
+
+ // Set the initial prompt and environment
+ snprintf(prompt, sizeof(prompt), "user> ");
+ init_repl_env();
+
+ // REPL loop
+ for(;;) {
+ exp = RE(repl_env, prompt, NULL);
+ if (mal_error && strcmp("EOF", mal_error->val.string) == 0) {
+ return 0;
+ }
+ output = PRINT(exp);
+
+ if (output) {
+ g_print("%s\n", output);
+ free(output); // Free output string
+ }
+
+ //malval_free(exp); // Free evaluated expression
+ }
+}
diff --git a/c/step5_tco.c b/c/step5_tco.c
new file mode 100644
index 0000000..dc0b28e
--- /dev/null
+++ b/c/step5_tco.c
@@ -0,0 +1,222 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include "types.h"
+#include "readline.h"
+#include "reader.h"
+
+// Declarations
+MalVal *EVAL(MalVal *ast, Env *env);
+
+// read
+MalVal *READ(char prompt[], char *str) {
+ char *line;
+ MalVal *ast;
+ if (str) {
+ line = str;
+ } else {
+ line = _readline(prompt);
+ if (!line) {
+ _error("EOF");
+ return NULL;
+ }
+ }
+ ast = read_str(line);
+ if (!str) { free(line); }
+ return ast;
+}
+
+// eval
+MalVal *eval_ast(MalVal *ast, Env *env) {
+ if (!ast || mal_error) return NULL;
+ if (ast->type == MAL_SYMBOL) {
+ //g_print("EVAL symbol: %s\n", ast->val.string);
+ return env_get(env, ast->val.string);
+ } else if ((ast->type == MAL_LIST) || (ast->type == MAL_VECTOR)) {
+ //g_print("EVAL sequential: %s\n", _pr_str(ast,1));
+ MalVal *el = _map2((MalVal *(*)(void*, void*))EVAL, ast, env);
+ if (!el || mal_error) return NULL;
+ el->type = ast->type;
+ return el;
+ } else if (ast->type == MAL_HASH_MAP) {
+ //g_print("EVAL hash_map: %s\n", _pr_str(ast,1));
+ GHashTableIter iter;
+ gpointer key, value;
+ MalVal *seq = malval_new_list(MAL_LIST,
+ g_array_sized_new(TRUE, TRUE, sizeof(MalVal*),
+ _count(ast)));
+ g_hash_table_iter_init (&iter, ast->val.hash_table);
+ while (g_hash_table_iter_next (&iter, &key, &value)) {
+ MalVal *kname = malval_new_string((char *)key);
+ g_array_append_val(seq->val.array, kname);
+ MalVal *new_val = EVAL((MalVal *)value, env);
+ g_array_append_val(seq->val.array, new_val);
+ }
+ return hash_map(seq);
+ } else {
+ //g_print("EVAL scalar: %s\n", _pr_str(ast,1));
+ return ast;
+ }
+}
+
+MalVal *EVAL(MalVal *ast, Env *env) {
+ while (TRUE) {
+ //g_print("EVAL: %s\n", _pr_str(ast,1));
+ if (!ast || mal_error) return NULL;
+ if (ast->type != MAL_LIST) {
+ return eval_ast(ast, env);
+ }
+ if (!ast || mal_error) return NULL;
+
+ // apply list
+ //g_print("EVAL apply list: %s\n", _pr_str(ast,1));
+ int i, len;
+ if (_count(ast) == 0) { return ast; }
+ MalVal *a0 = _nth(ast, 0);
+ if ((a0->type & MAL_SYMBOL) &&
+ strcmp("def!", a0->val.string) == 0) {
+ //g_print("eval apply def!\n");
+ MalVal *a1 = _nth(ast, 1),
+ *a2 = _nth(ast, 2);
+ MalVal *res = EVAL(a2, env);
+ env_set(env, a1->val.string, res);
+ return res;
+ } else if ((a0->type & MAL_SYMBOL) &&
+ strcmp("let*", a0->val.string) == 0) {
+ //g_print("eval apply let*\n");
+ MalVal *a1 = _nth(ast, 1),
+ *a2 = _nth(ast, 2),
+ *key, *val;
+ assert_type(a1, MAL_LIST|MAL_VECTOR,
+ "let* bindings must be list or vector");
+ len = _count(a1);
+ assert((len % 2) == 0, "odd number of let* bindings forms");
+ Env *let_env = new_env(env, NULL, NULL);
+ for(i=0; i<len; i+=2) {
+ key = g_array_index(a1->val.array, MalVal*, i);
+ val = g_array_index(a1->val.array, MalVal*, i+1);
+ assert_type(key, MAL_SYMBOL, "let* bind to non-symbol");
+ env_set(let_env, key->val.string, EVAL(val, let_env));
+ }
+ return EVAL(a2, let_env);
+ } else if ((a0->type & MAL_SYMBOL) &&
+ strcmp("do", a0->val.string) == 0) {
+ //g_print("eval apply do\n");
+ eval_ast(_slice(ast, 1, _count(ast)-1), env);
+ ast = last(ast);
+ // Continue loop
+ } else if ((a0->type & MAL_SYMBOL) &&
+ strcmp("if", a0->val.string) == 0) {
+ //g_print("eval apply if\n");
+ MalVal *a1 = _nth(ast, 1);
+ MalVal *cond = EVAL(a1, env);
+ if (!cond || mal_error) return NULL;
+ if (cond->type & (MAL_FALSE|MAL_NIL)) {
+ // eval false slot form
+ ast = _nth(ast, 3);
+ if (!ast) {
+ return &mal_nil;
+ }
+ } else {
+ // eval true slot form
+ ast = _nth(ast, 2);
+ }
+ // Continue loop
+ } else if ((a0->type & MAL_SYMBOL) &&
+ strcmp("fn*", a0->val.string) == 0) {
+ //g_print("eval apply fn*\n");
+ MalVal *mf = malval_new(MAL_FUNCTION_MAL, NULL);
+ mf->val.func.evaluator = EVAL;
+ mf->val.func.args = _nth(ast, 1);
+ mf->val.func.body = _nth(ast, 2);
+ mf->val.func.env = env;
+ return mf;
+ } else {
+ //g_print("eval apply\n");
+ MalVal *el = eval_ast(ast, env);
+ if (!el || mal_error) { return NULL; }
+ MalVal *f = first(el),
+ *args = rest(el);
+ assert_type(f, MAL_FUNCTION_C|MAL_FUNCTION_MAL,
+ "cannot invoke '%s'", _pr_str(f,1));
+ if (f->type & MAL_FUNCTION_MAL) {
+ ast = f->val.func.body;
+ env = new_env(f->val.func.env, f->val.func.args, args);
+ // Continue loop
+ } else {
+ return apply(f, args);
+ }
+ }
+ }
+}
+
+// print
+char *PRINT(MalVal *exp) {
+ if (mal_error) {
+ fprintf(stderr, "Error: %s\n", mal_error->val.string);
+ malval_free(mal_error);
+ mal_error = NULL;
+ return NULL;
+ }
+ return _pr_str(exp,1);
+}
+
+// repl
+
+// read and eval
+MalVal *RE(Env *env, char *prompt, char *str) {
+ MalVal *ast, *exp;
+ ast = READ(prompt, str);
+ if (!ast || mal_error) return NULL;
+ exp = EVAL(ast, env);
+ if (ast != exp) {
+ malval_free(ast); // Free input structure
+ }
+ return exp;
+}
+
+// Setup the initial REPL environment
+Env *repl_env;
+
+void init_repl_env() {
+ void _ref(char *name, MalVal*(*func)(MalVal*), int arg_cnt) {
+ void *(*f)(void *) = (void*(*)(void*))func;
+ env_set(repl_env, name, malval_new_function(f, arg_cnt, NULL));
+ }
+ repl_env = new_env(NULL, NULL, NULL);
+
+ int i;
+ for(i=0; i< (sizeof(types_ns) / sizeof(types_ns[0])); i++) {
+ MalVal *(*f)(MalVal *) = (MalVal*(*)(MalVal*))types_ns[i].func;
+ _ref(types_ns[i].name, f, types_ns[i].arg_cnt);
+ }
+
+ RE(repl_env, "", "(def! not (fn* (a) (if a false true)))");
+}
+
+int main()
+{
+ MalVal *exp;
+ char *output;
+ char prompt[100];
+
+ // Set the initial prompt and environment
+ snprintf(prompt, sizeof(prompt), "user> ");
+ init_repl_env();
+
+ // REPL loop
+ for(;;) {
+ exp = RE(repl_env, prompt, NULL);
+ if (mal_error && strcmp("EOF", mal_error->val.string) == 0) {
+ return 0;
+ }
+ output = PRINT(exp);
+
+ if (output) {
+ g_print("%s\n", output);
+ free(output); // Free output string
+ }
+
+ //malval_free(exp); // Free evaluated expression
+ }
+}
diff --git a/c/step6_file.c b/c/step6_file.c
new file mode 100644
index 0000000..875c32c
--- /dev/null
+++ b/c/step6_file.c
@@ -0,0 +1,282 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include "types.h"
+#include "readline.h"
+#include "reader.h"
+#include "interop.h"
+
+// Declarations
+MalVal *EVAL(MalVal *ast, Env *env);
+
+// read
+MalVal *READ(char prompt[], char *str) {
+ char *line;
+ MalVal *ast;
+ if (str) {
+ line = str;
+ } else {
+ line = _readline(prompt);
+ if (!line) {
+ _error("EOF");
+ return NULL;
+ }
+ }
+ ast = read_str(line);
+ if (!str) { free(line); }
+ return ast;
+}
+
+// eval
+MalVal *eval_ast(MalVal *ast, Env *env) {
+ if (!ast || mal_error) return NULL;
+ if (ast->type == MAL_SYMBOL) {
+ //g_print("EVAL symbol: %s\n", ast->val.string);
+ return env_get(env, ast->val.string);
+ } else if ((ast->type == MAL_LIST) || (ast->type == MAL_VECTOR)) {
+ //g_print("EVAL sequential: %s\n", _pr_str(ast,1));
+ MalVal *el = _map2((MalVal *(*)(void*, void*))EVAL, ast, env);
+ if (!el || mal_error) return NULL;
+ el->type = ast->type;
+ return el;
+ } else if (ast->type == MAL_HASH_MAP) {
+ //g_print("EVAL hash_map: %s\n", _pr_str(ast,1));
+ GHashTableIter iter;
+ gpointer key, value;
+ MalVal *seq = malval_new_list(MAL_LIST,
+ g_array_sized_new(TRUE, TRUE, sizeof(MalVal*),
+ _count(ast)));
+ g_hash_table_iter_init (&iter, ast->val.hash_table);
+ while (g_hash_table_iter_next (&iter, &key, &value)) {
+ MalVal *kname = malval_new_string((char *)key);
+ g_array_append_val(seq->val.array, kname);
+ MalVal *new_val = EVAL((MalVal *)value, env);
+ g_array_append_val(seq->val.array, new_val);
+ }
+ return hash_map(seq);
+ } else {
+ //g_print("EVAL scalar: %s\n", _pr_str(ast,1));
+ return ast;
+ }
+}
+
+MalVal *EVAL(MalVal *ast, Env *env) {
+ while (TRUE) {
+ //g_print("EVAL: %s\n", _pr_str(ast,1));
+ if (!ast || mal_error) return NULL;
+ if (ast->type != MAL_LIST) {
+ return eval_ast(ast, env);
+ }
+ if (!ast || mal_error) return NULL;
+
+ // apply list
+ //g_print("EVAL apply list: %s\n", _pr_str(ast,1));
+ int i, len;
+ if (_count(ast) == 0) { return ast; }
+ MalVal *a0 = _nth(ast, 0);
+ if ((a0->type & MAL_SYMBOL) &&
+ strcmp("def!", a0->val.string) == 0) {
+ //g_print("eval apply def!\n");
+ MalVal *a1 = _nth(ast, 1),
+ *a2 = _nth(ast, 2);
+ MalVal *res = EVAL(a2, env);
+ env_set(env, a1->val.string, res);
+ return res;
+ } else if ((a0->type & MAL_SYMBOL) &&
+ strcmp("let*", a0->val.string) == 0) {
+ //g_print("eval apply let*\n");
+ MalVal *a1 = _nth(ast, 1),
+ *a2 = _nth(ast, 2),
+ *key, *val;
+ assert_type(a1, MAL_LIST|MAL_VECTOR,
+ "let* bindings must be list or vector");
+ len = _count(a1);
+ assert((len % 2) == 0, "odd number of let* bindings forms");
+ Env *let_env = new_env(env, NULL, NULL);
+ for(i=0; i<len; i+=2) {
+ key = g_array_index(a1->val.array, MalVal*, i);
+ val = g_array_index(a1->val.array, MalVal*, i+1);
+ assert_type(key, MAL_SYMBOL, "let* bind to non-symbol");
+ env_set(let_env, key->val.string, EVAL(val, let_env));
+ }
+ return EVAL(a2, let_env);
+ } else if ((a0->type & MAL_SYMBOL) &&
+ strcmp("do", a0->val.string) == 0) {
+ //g_print("eval apply do\n");
+ eval_ast(_slice(ast, 1, _count(ast)-1), env);
+ ast = last(ast);
+ // Continue loop
+ } else if ((a0->type & MAL_SYMBOL) &&
+ strcmp("if", a0->val.string) == 0) {
+ //g_print("eval apply if\n");
+ MalVal *a1 = _nth(ast, 1);
+ MalVal *cond = EVAL(a1, env);
+ if (!cond || mal_error) return NULL;
+ if (cond->type & (MAL_FALSE|MAL_NIL)) {
+ // eval false slot form
+ ast = _nth(ast, 3);
+ if (!ast) {
+ return &mal_nil;
+ }
+ } else {
+ // eval true slot form
+ ast = _nth(ast, 2);
+ }
+ // Continue loop
+ } else if ((a0->type & MAL_SYMBOL) &&
+ strcmp("fn*", a0->val.string) == 0) {
+ //g_print("eval apply fn*\n");
+ MalVal *mf = malval_new(MAL_FUNCTION_MAL, NULL);
+ mf->val.func.evaluator = EVAL;
+ mf->val.func.args = _nth(ast, 1);
+ mf->val.func.body = _nth(ast, 2);
+ mf->val.func.env = env;
+ return mf;
+ } else {
+ //g_print("eval apply\n");
+ MalVal *el = eval_ast(ast, env);
+ if (!el || mal_error) { return NULL; }
+ MalVal *f = first(el),
+ *args = rest(el);
+ assert_type(f, MAL_FUNCTION_C|MAL_FUNCTION_MAL,
+ "cannot invoke '%s'", _pr_str(f,1));
+ if (f->type & MAL_FUNCTION_MAL) {
+ ast = f->val.func.body;
+ env = new_env(f->val.func.env, f->val.func.args, args);
+ // Continue loop
+ } else {
+ return apply(f, args);
+ }
+ }
+ }
+}
+
+// print
+char *PRINT(MalVal *exp) {
+ if (mal_error) {
+ fprintf(stderr, "Error: %s\n", mal_error->val.string);
+ malval_free(mal_error);
+ mal_error = NULL;
+ return NULL;
+ }
+ return _pr_str(exp,1);
+}
+
+// repl
+
+// read and eval
+MalVal *RE(Env *env, char *prompt, char *str) {
+ MalVal *ast, *exp;
+ ast = READ(prompt, str);
+ if (!ast || mal_error) return NULL;
+ exp = EVAL(ast, env);
+ if (ast != exp) {
+ malval_free(ast); // Free input structure
+ }
+ return exp;
+}
+
+// Setup the initial REPL environment
+Env *repl_env;
+
+char *slurp_raw(char *path) {
+ char *data;
+ struct stat fst;
+ int fd = open(path, O_RDONLY),
+ sz;
+ if (fd < 0) {
+ abort("slurp failed to open '%s'", path);
+ }
+ if (fstat(fd, &fst) < 0) {
+ abort("slurp failed to stat '%s'", path);
+ }
+ data = malloc(fst.st_size+1);
+ sz = read(fd, data, fst.st_size);
+ if (sz < fst.st_size) {
+ abort("slurp failed to read '%s'", path);
+ }
+ data[sz] = '\0';
+ return data;
+}
+
+void init_repl_env() {
+ void _ref(char *name, MalVal*(*func)(MalVal*), int arg_cnt) {
+ void *(*f)(void *) = (void*(*)(void*))func;
+ env_set(repl_env, name, malval_new_function(f, arg_cnt, NULL));
+ }
+ repl_env = new_env(NULL, NULL, NULL);
+
+ int i;
+ for(i=0; i< (sizeof(types_ns) / sizeof(types_ns[0])); i++) {
+ MalVal *(*f)(MalVal *) = (MalVal*(*)(MalVal*))types_ns[i].func;
+ _ref(types_ns[i].name, f, types_ns[i].arg_cnt);
+ }
+
+ MalVal *read_string(MalVal *str) {
+ assert_type(str, MAL_STRING, "read_string of non-string");
+ return read_str(str->val.string);
+ }
+ _ref("read-string", read_string, 1);
+
+ MalVal *do_eval(MalVal *ast) {
+ return EVAL(ast, repl_env);
+ }
+ _ref("eval", do_eval, 1);
+
+ MalVal *slurp(MalVal *path) {
+ assert_type(path, MAL_STRING, "slurp of non-string");
+ char *data = slurp_raw(path->val.string);
+ if (!data || mal_error) { return NULL; }
+ return malval_new_string(data);
+ }
+ _ref("slurp", slurp, 1);
+
+ MalVal *slurp_do(MalVal *path) {
+ assert_type(path, MAL_STRING, "slurp of non-string");
+ char *data = slurp_raw(path->val.string),
+ *wrapped_data;
+ if (!data || mal_error) { return NULL; }
+ wrapped_data = g_strdup_printf("(do %s)", data);
+ free(data);
+ return malval_new_string(wrapped_data);
+ }
+ _ref("slurp-do", slurp_do, 1);
+
+ RE(repl_env, "", "(def! not (fn* (a) (if a false true)))");
+ RE(repl_env, "",
+ "(def! load-file (fn* (f) (eval (read-string (slurp-do f)))))");
+}
+
+int main(int argc, char *argv[])
+{
+ MalVal *exp;
+ char *output;
+ char prompt[100];
+
+ // Set the initial prompt and environment
+ snprintf(prompt, sizeof(prompt), "user> ");
+ init_repl_env();
+
+ if (argc > 1) {
+ char *cmd = g_strdup_printf("(load-file \"%s\")", argv[1]);
+ RE(repl_env, "", cmd);
+ } else {
+ // REPL loop
+ for(;;) {
+ exp = RE(repl_env, prompt, NULL);
+ if (mal_error && strcmp("EOF", mal_error->val.string) == 0) {
+ return 0;
+ }
+ output = PRINT(exp);
+
+ if (output) {
+ g_print("%s\n", output);
+ free(output); // Free output string
+ }
+
+ //malval_free(exp); // Free evaluated expression
+ }
+ }
+}
diff --git a/c/step7_quote.c b/c/step7_quote.c
new file mode 100644
index 0000000..46ac6a9
--- /dev/null
+++ b/c/step7_quote.c
@@ -0,0 +1,318 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include "types.h"
+#include "readline.h"
+#include "reader.h"
+#include "interop.h"
+
+// Declarations
+MalVal *EVAL(MalVal *ast, Env *env);
+
+// read
+MalVal *READ(char prompt[], char *str) {
+ char *line;
+ MalVal *ast;
+ if (str) {
+ line = str;
+ } else {
+ line = _readline(prompt);
+ if (!line) {
+ _error("EOF");
+ return NULL;
+ }
+ }
+ ast = read_str(line);
+ if (!str) { free(line); }
+ return ast;
+}
+
+// eval
+int is_pair(MalVal *x) {
+ return _sequential_Q(x) && (_count(x) > 0);
+}
+
+MalVal *quasiquote(MalVal *ast) {
+ if (!is_pair(ast)) {
+ return _list(2, malval_new_symbol("quote"), ast);
+ } else {
+ MalVal *a0 = _nth(ast, 0);
+ if ((a0->type & MAL_SYMBOL) &&
+ strcmp("unquote", a0->val.string) == 0) {
+ return _nth(ast, 1);
+ } else if (is_pair(a0)) {
+ MalVal *a00 = _nth(a0, 0);
+ if ((a00->type & MAL_SYMBOL) &&
+ strcmp("splice-unquote", a00->val.string) == 0) {
+ return _list(3, malval_new_symbol("concat"),
+ _nth(a0, 1),
+ quasiquote(rest(ast)));
+ }
+ }
+ return _list(3, malval_new_symbol("cons"),
+ quasiquote(a0),
+ quasiquote(rest(ast)));
+ }
+}
+
+MalVal *eval_ast(MalVal *ast, Env *env) {
+ if (!ast || mal_error) return NULL;
+ if (ast->type == MAL_SYMBOL) {
+ //g_print("EVAL symbol: %s\n", ast->val.string);
+ return env_get(env, ast->val.string);
+ } else if ((ast->type == MAL_LIST) || (ast->type == MAL_VECTOR)) {
+ //g_print("EVAL sequential: %s\n", _pr_str(ast,1));
+ MalVal *el = _map2((MalVal *(*)(void*, void*))EVAL, ast, env);
+ if (!el || mal_error) return NULL;
+ el->type = ast->type;
+ return el;
+ } else if (ast->type == MAL_HASH_MAP) {
+ //g_print("EVAL hash_map: %s\n", _pr_str(ast,1));
+ GHashTableIter iter;
+ gpointer key, value;
+ MalVal *seq = malval_new_list(MAL_LIST,
+ g_array_sized_new(TRUE, TRUE, sizeof(MalVal*),
+ _count(ast)));
+ g_hash_table_iter_init (&iter, ast->val.hash_table);
+ while (g_hash_table_iter_next (&iter, &key, &value)) {
+ MalVal *kname = malval_new_string((char *)key);
+ g_array_append_val(seq->val.array, kname);
+ MalVal *new_val = EVAL((MalVal *)value, env);
+ g_array_append_val(seq->val.array, new_val);
+ }
+ return hash_map(seq);
+ } else {
+ //g_print("EVAL scalar: %s\n", _pr_str(ast,1));
+ return ast;
+ }
+}
+
+MalVal *EVAL(MalVal *ast, Env *env) {
+ while (TRUE) {
+ //g_print("EVAL: %s\n", _pr_str(ast,1));
+ if (!ast || mal_error) return NULL;
+ if (ast->type != MAL_LIST) {
+ return eval_ast(ast, env);
+ }
+ if (!ast || mal_error) return NULL;
+
+ // apply list
+ //g_print("EVAL apply list: %s\n", _pr_str(ast,1));
+ int i, len;
+ if (_count(ast) == 0) { return ast; }
+ MalVal *a0 = _nth(ast, 0);
+ if ((a0->type & MAL_SYMBOL) &&
+ strcmp("def!", a0->val.string) == 0) {
+ //g_print("eval apply def!\n");
+ MalVal *a1 = _nth(ast, 1),
+ *a2 = _nth(ast, 2);
+ MalVal *res = EVAL(a2, env);
+ env_set(env, a1->val.string, res);
+ return res;
+ } else if ((a0->type & MAL_SYMBOL) &&
+ strcmp("let*", a0->val.string) == 0) {
+ //g_print("eval apply let*\n");
+ MalVal *a1 = _nth(ast, 1),
+ *a2 = _nth(ast, 2),
+ *key, *val;
+ assert_type(a1, MAL_LIST|MAL_VECTOR,
+ "let* bindings must be list or vector");
+ len = _count(a1);
+ assert((len % 2) == 0, "odd number of let* bindings forms");
+ Env *let_env = new_env(env, NULL, NULL);
+ for(i=0; i<len; i+=2) {
+ key = g_array_index(a1->val.array, MalVal*, i);
+ val = g_array_index(a1->val.array, MalVal*, i+1);
+ assert_type(key, MAL_SYMBOL, "let* bind to non-symbol");
+ env_set(let_env, key->val.string, EVAL(val, let_env));
+ }
+ return EVAL(a2, let_env);
+ } else if ((a0->type & MAL_SYMBOL) &&
+ strcmp("quote", a0->val.string) == 0) {
+ //g_print("eval apply quote\n");
+ return _nth(ast, 1);
+ } else if ((a0->type & MAL_SYMBOL) &&
+ strcmp("quasiquote", a0->val.string) == 0) {
+ //g_print("eval apply quasiquote\n");
+ MalVal *a1 = _nth(ast, 1);
+ return EVAL(quasiquote(a1), env);
+ } else if ((a0->type & MAL_SYMBOL) &&
+ strcmp("do", a0->val.string) == 0) {
+ //g_print("eval apply do\n");
+ eval_ast(_slice(ast, 1, _count(ast)-1), env);
+ ast = last(ast);
+ // Continue loop
+ } else if ((a0->type & MAL_SYMBOL) &&
+ strcmp("if", a0->val.string) == 0) {
+ //g_print("eval apply if\n");
+ MalVal *a1 = _nth(ast, 1);
+ MalVal *cond = EVAL(a1, env);
+ if (!cond || mal_error) return NULL;
+ if (cond->type & (MAL_FALSE|MAL_NIL)) {
+ // eval false slot form
+ ast = _nth(ast, 3);
+ if (!ast) {
+ return &mal_nil;
+ }
+ } else {
+ // eval true slot form
+ ast = _nth(ast, 2);
+ }
+ // Continue loop
+ } else if ((a0->type & MAL_SYMBOL) &&
+ strcmp("fn*", a0->val.string) == 0) {
+ //g_print("eval apply fn*\n");
+ MalVal *mf = malval_new(MAL_FUNCTION_MAL, NULL);
+ mf->val.func.evaluator = EVAL;
+ mf->val.func.args = _nth(ast, 1);
+ mf->val.func.body = _nth(ast, 2);
+ mf->val.func.env = env;
+ return mf;
+ } else {
+ //g_print("eval apply\n");
+ MalVal *el = eval_ast(ast, env);
+ if (!el || mal_error) { return NULL; }
+ MalVal *f = first(el),
+ *args = rest(el);
+ assert_type(f, MAL_FUNCTION_C|MAL_FUNCTION_MAL,
+ "cannot invoke '%s'", _pr_str(f,1));
+ if (f->type & MAL_FUNCTION_MAL) {
+ ast = f->val.func.body;
+ env = new_env(f->val.func.env, f->val.func.args, args);
+ // Continue loop
+ } else {
+ return apply(f, args);
+ }
+ }
+ }
+}
+
+// print
+char *PRINT(MalVal *exp) {
+ if (mal_error) {
+ fprintf(stderr, "Error: %s\n", mal_error->val.string);
+ malval_free(mal_error);
+ mal_error = NULL;
+ return NULL;
+ }
+ return _pr_str(exp,1);
+}
+
+// repl
+
+// read and eval
+MalVal *RE(Env *env, char *prompt, char *str) {
+ MalVal *ast, *exp;
+ ast = READ(prompt, str);
+ if (!ast || mal_error) return NULL;
+ exp = EVAL(ast, env);
+ if (ast != exp) {
+ malval_free(ast); // Free input structure
+ }
+ return exp;
+}
+
+// Setup the initial REPL environment
+Env *repl_env;
+
+char *slurp_raw(char *path) {
+ char *data;
+ struct stat fst;
+ int fd = open(path, O_RDONLY),
+ sz;
+ if (fd < 0) {
+ abort("slurp failed to open '%s'", path);
+ }
+ if (fstat(fd, &fst) < 0) {
+ abort("slurp failed to stat '%s'", path);
+ }
+ data = malloc(fst.st_size+1);
+ sz = read(fd, data, fst.st_size);
+ if (sz < fst.st_size) {
+ abort("slurp failed to read '%s'", path);
+ }
+ data[sz] = '\0';
+ return data;
+}
+
+void init_repl_env() {
+ void _ref(char *name, MalVal*(*func)(MalVal*), int arg_cnt) {
+ void *(*f)(void *) = (void*(*)(void*))func;
+ env_set(repl_env, name, malval_new_function(f, arg_cnt, NULL));
+ }
+ repl_env = new_env(NULL, NULL, NULL);
+
+ int i;
+ for(i=0; i< (sizeof(types_ns) / sizeof(types_ns[0])); i++) {
+ MalVal *(*f)(MalVal *) = (MalVal*(*)(MalVal*))types_ns[i].func;
+ _ref(types_ns[i].name, f, types_ns[i].arg_cnt);
+ }
+
+ MalVal *read_string(MalVal *str) {
+ assert_type(str, MAL_STRING, "read_string of non-string");
+ return read_str(str->val.string);
+ }
+ _ref("read-string", read_string, 1);
+
+ MalVal *do_eval(MalVal *ast) {
+ return EVAL(ast, repl_env);
+ }
+ _ref("eval", do_eval, 1);
+
+ MalVal *slurp(MalVal *path) {
+ assert_type(path, MAL_STRING, "slurp of non-string");
+ char *data = slurp_raw(path->val.string);
+ if (!data || mal_error) { return NULL; }
+ return malval_new_string(data);
+ }
+ _ref("slurp", slurp, 1);
+
+ MalVal *slurp_do(MalVal *path) {
+ assert_type(path, MAL_STRING, "slurp of non-string");
+ char *data = slurp_raw(path->val.string),
+ *wrapped_data;
+ if (!data || mal_error) { return NULL; }
+ wrapped_data = g_strdup_printf("(do %s)", data);
+ free(data);
+ return malval_new_string(wrapped_data);
+ }
+ _ref("slurp-do", slurp_do, 1);
+
+ RE(repl_env, "", "(def! not (fn* (a) (if a false true)))");
+ RE(repl_env, "",
+ "(def! load-file (fn* (f) (eval (read-string (slurp-do f)))))");
+}
+
+int main(int argc, char *argv[])
+{
+ MalVal *exp;
+ char *output;
+ char prompt[100];
+
+ // Set the initial prompt and environment
+ snprintf(prompt, sizeof(prompt), "user> ");
+ init_repl_env();
+
+ if (argc > 1) {
+ char *cmd = g_strdup_printf("(load-file \"%s\")", argv[1]);
+ RE(repl_env, "", cmd);
+ } else {
+ // REPL loop
+ for(;;) {
+ exp = RE(repl_env, prompt, NULL);
+ if (mal_error && strcmp("EOF", mal_error->val.string) == 0) {
+ return 0;
+ }
+ output = PRINT(exp);
+
+ if (output) {
+ g_print("%s\n", output);
+ free(output); // Free output string
+ }
+
+ //malval_free(exp); // Free evaluated expression
+ }
+ }
+}
diff --git a/c/step8_macros.c b/c/step8_macros.c
new file mode 100644
index 0000000..23afc33
--- /dev/null
+++ b/c/step8_macros.c
@@ -0,0 +1,357 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include "types.h"
+#include "readline.h"
+#include "reader.h"
+#include "interop.h"
+
+// Declarations
+MalVal *EVAL(MalVal *ast, Env *env);
+MalVal *macroexpand(MalVal *ast, Env *env);
+
+// read
+MalVal *READ(char prompt[], char *str) {
+ char *line;
+ MalVal *ast;
+ if (str) {
+ line = str;
+ } else {
+ line = _readline(prompt);
+ if (!line) {
+ _error("EOF");
+ return NULL;
+ }
+ }
+ ast = read_str(line);
+ if (!str) { free(line); }
+ return ast;
+}
+
+// eval
+int is_pair(MalVal *x) {
+ return _sequential_Q(x) && (_count(x) > 0);
+}
+
+MalVal *quasiquote(MalVal *ast) {
+ if (!is_pair(ast)) {
+ return _list(2, malval_new_symbol("quote"), ast);
+ } else {
+ MalVal *a0 = _nth(ast, 0);
+ if ((a0->type & MAL_SYMBOL) &&
+ strcmp("unquote", a0->val.string) == 0) {
+ return _nth(ast, 1);
+ } else if (is_pair(a0)) {
+ MalVal *a00 = _nth(a0, 0);
+ if ((a00->type & MAL_SYMBOL) &&
+ strcmp("splice-unquote", a00->val.string) == 0) {
+ return _list(3, malval_new_symbol("concat"),
+ _nth(a0, 1),
+ quasiquote(rest(ast)));
+ }
+ }
+ return _list(3, malval_new_symbol("cons"),
+ quasiquote(a0),
+ quasiquote(rest(ast)));
+ }
+}
+
+int is_macro_call(MalVal *ast, Env *env) {
+ if (!ast || ast->type != MAL_LIST) { return 0; }
+ MalVal *a0 = _nth(ast, 0);
+ return (a0->type & MAL_SYMBOL) &&
+ env_find(env, a0->val.string) &&
+ env_get(env, a0->val.string)->ismacro;
+}
+
+MalVal *macroexpand(MalVal *ast, Env *env) {
+ if (!ast || mal_error) return NULL;
+ while (is_macro_call(ast, env)) {
+ MalVal *a0 = _nth(ast, 0);
+ MalVal *mac = env_get(env, a0->val.string);
+ // TODO: this is weird and limits it to 20. FIXME
+ ast = apply(mac, rest(ast));
+ }
+ return ast;
+}
+
+MalVal *eval_ast(MalVal *ast, Env *env) {
+ if (!ast || mal_error) return NULL;
+ if (ast->type == MAL_SYMBOL) {
+ //g_print("EVAL symbol: %s\n", ast->val.string);
+ return env_get(env, ast->val.string);
+ } else if ((ast->type == MAL_LIST) || (ast->type == MAL_VECTOR)) {
+ //g_print("EVAL sequential: %s\n", _pr_str(ast,1));
+ MalVal *el = _map2((MalVal *(*)(void*, void*))EVAL, ast, env);
+ if (!el || mal_error) return NULL;
+ el->type = ast->type;
+ return el;
+ } else if (ast->type == MAL_HASH_MAP) {
+ //g_print("EVAL hash_map: %s\n", _pr_str(ast,1));
+ GHashTableIter iter;
+ gpointer key, value;
+ MalVal *seq = malval_new_list(MAL_LIST,
+ g_array_sized_new(TRUE, TRUE, sizeof(MalVal*),
+ _count(ast)));
+ g_hash_table_iter_init (&iter, ast->val.hash_table);
+ while (g_hash_table_iter_next (&iter, &key, &value)) {
+ MalVal *kname = malval_new_string((char *)key);
+ g_array_append_val(seq->val.array, kname);
+ MalVal *new_val = EVAL((MalVal *)value, env);
+ g_array_append_val(seq->val.array, new_val);
+ }
+ return hash_map(seq);
+ } else {
+ //g_print("EVAL scalar: %s\n", _pr_str(ast,1));
+ return ast;
+ }
+}
+
+MalVal *EVAL(MalVal *ast, Env *env) {
+ while (TRUE) {
+ if (!ast || mal_error) return NULL;
+ //g_print("EVAL: %s\n", _pr_str(ast,1));
+ if (ast->type != MAL_LIST) {
+ return eval_ast(ast, env);
+ }
+ if (!ast || mal_error) return NULL;
+
+ // apply list
+ //g_print("EVAL apply list: %s\n", _pr_str(ast,1));
+ ast = macroexpand(ast, env);
+ if (!ast || mal_error) return NULL;
+ if (ast->type != MAL_LIST) { return ast; }
+ if (_count(ast) == 0) { return ast; }
+
+ int i, len;
+ MalVal *a0 = _nth(ast, 0);
+ if ((a0->type & MAL_SYMBOL) &&
+ strcmp("def!", a0->val.string) == 0) {
+ //g_print("eval apply def!\n");
+ MalVal *a1 = _nth(ast, 1),
+ *a2 = _nth(ast, 2);
+ MalVal *res = EVAL(a2, env);
+ env_set(env, a1->val.string, res);
+ return res;
+ } else if ((a0->type & MAL_SYMBOL) &&
+ strcmp("let*", a0->val.string) == 0) {
+ //g_print("eval apply let*\n");
+ MalVal *a1 = _nth(ast, 1),
+ *a2 = _nth(ast, 2),
+ *key, *val;
+ assert_type(a1, MAL_LIST|MAL_VECTOR,
+ "let* bindings must be list or vector");
+ len = _count(a1);
+ assert((len % 2) == 0, "odd number of let* bindings forms");
+ Env *let_env = new_env(env, NULL, NULL);
+ for(i=0; i<len; i+=2) {
+ key = g_array_index(a1->val.array, MalVal*, i);
+ val = g_array_index(a1->val.array, MalVal*, i+1);
+ assert_type(key, MAL_SYMBOL, "let* bind to non-symbol");
+ env_set(let_env, key->val.string, EVAL(val, let_env));
+ }
+ return EVAL(a2, let_env);
+ } else if ((a0->type & MAL_SYMBOL) &&
+ strcmp("quote", a0->val.string) == 0) {
+ //g_print("eval apply quote\n");
+ return _nth(ast, 1);
+ } else if ((a0->type & MAL_SYMBOL) &&
+ strcmp("quasiquote", a0->val.string) == 0) {
+ //g_print("eval apply quasiquote\n");
+ MalVal *a1 = _nth(ast, 1);
+ return EVAL(quasiquote(a1), env);
+ } else if ((a0->type & MAL_SYMBOL) &&
+ strcmp("defmacro!", a0->val.string) == 0) {
+ //g_print("eval apply defmacro!\n");
+ MalVal *a1 = _nth(ast, 1),
+ *a2 = _nth(ast, 2);
+ MalVal *res = EVAL(a2, env);
+ res->ismacro = TRUE;
+ env_set(env, a1->val.string, res);
+ return res;
+ } else if ((a0->type & MAL_SYMBOL) &&
+ strcmp("macroexpand", a0->val.string) == 0) {
+ //g_print("eval apply macroexpand\n");
+ MalVal *a1 = _nth(ast, 1);
+ return macroexpand(a1, env);
+ } else if ((a0->type & MAL_SYMBOL) &&
+ strcmp("do", a0->val.string) == 0) {
+ //g_print("eval apply do\n");
+ eval_ast(_slice(ast, 1, _count(ast)-1), env);
+ ast = last(ast);
+ // Continue loop
+ } else if ((a0->type & MAL_SYMBOL) &&
+ strcmp("if", a0->val.string) == 0) {
+ //g_print("eval apply if\n");
+ MalVal *a1 = _nth(ast, 1);
+ MalVal *cond = EVAL(a1, env);
+ if (!cond || mal_error) return NULL;
+ if (cond->type & (MAL_FALSE|MAL_NIL)) {
+ // eval false slot form
+ ast = _nth(ast, 3);
+ if (!ast) {
+ return &mal_nil;
+ }
+ } else {
+ // eval true slot form
+ ast = _nth(ast, 2);
+ }
+ // Continue loop
+ } else if ((a0->type & MAL_SYMBOL) &&
+ strcmp("fn*", a0->val.string) == 0) {
+ //g_print("eval apply fn*\n");
+ MalVal *mf = malval_new(MAL_FUNCTION_MAL, NULL);
+ mf->ismacro = FALSE;
+ mf->val.func.evaluator = EVAL;
+ mf->val.func.args = _nth(ast, 1);
+ mf->val.func.body = _nth(ast, 2);
+ mf->val.func.env = env;
+ return mf;
+ } else {
+ //g_print("eval apply\n");
+ MalVal *el = eval_ast(ast, env);
+ if (!el || mal_error) { return NULL; }
+ MalVal *f = first(el),
+ *args = rest(el);
+ assert_type(f, MAL_FUNCTION_C|MAL_FUNCTION_MAL,
+ "cannot invoke '%s'", _pr_str(f,1));
+ if (f->type & MAL_FUNCTION_MAL) {
+ ast = f->val.func.body;
+ env = new_env(f->val.func.env, f->val.func.args, args);
+ // Continue loop
+ } else {
+ return apply(f, args);
+ }
+ }
+ }
+}
+
+// print
+char *PRINT(MalVal *exp) {
+ if (mal_error) {
+ fprintf(stderr, "Error: %s\n", mal_error->val.string);
+ malval_free(mal_error);
+ mal_error = NULL;
+ return NULL;
+ }
+ return _pr_str(exp,1);
+}
+
+// repl
+
+// read and eval
+MalVal *RE(Env *env, char *prompt, char *str) {
+ MalVal *ast, *exp;
+ ast = READ(prompt, str);
+ if (!ast || mal_error) return NULL;
+ exp = EVAL(ast, env);
+ if (ast != exp) {
+ malval_free(ast); // Free input structure
+ }
+ return exp;
+}
+
+// Setup the initial REPL environment
+Env *repl_env;
+
+char *slurp_raw(char *path) {
+ char *data;
+ struct stat fst;
+ int fd = open(path, O_RDONLY),
+ sz;
+ if (fd < 0) {
+ abort("slurp failed to open '%s'", path);
+ }
+ if (fstat(fd, &fst) < 0) {
+ abort("slurp failed to stat '%s'", path);
+ }
+ data = malloc(fst.st_size+1);
+ sz = read(fd, data, fst.st_size);
+ if (sz < fst.st_size) {
+ abort("slurp failed to read '%s'", path);
+ }
+ data[sz] = '\0';
+ return data;
+}
+
+void init_repl_env() {
+ void _ref(char *name, MalVal*(*func)(MalVal*), int arg_cnt) {
+ void *(*f)(void *) = (void*(*)(void*))func;
+ env_set(repl_env, name, malval_new_function(f, arg_cnt, NULL));
+ }
+ repl_env = new_env(NULL, NULL, NULL);
+
+ int i;
+ for(i=0; i< (sizeof(types_ns) / sizeof(types_ns[0])); i++) {
+ MalVal *(*f)(MalVal *) = (MalVal*(*)(MalVal*))types_ns[i].func;
+ _ref(types_ns[i].name, f, types_ns[i].arg_cnt);
+ }
+
+ MalVal *read_string(MalVal *str) {
+ assert_type(str, MAL_STRING, "read_string of non-string");
+ return read_str(str->val.string);
+ }
+ _ref("read-string", read_string, 1);
+
+ MalVal *do_eval(MalVal *ast) {
+ return EVAL(ast, repl_env);
+ }
+ _ref("eval", do_eval, 1);
+
+ MalVal *slurp(MalVal *path) {
+ assert_type(path, MAL_STRING, "slurp of non-string");
+ char *data = slurp_raw(path->val.string);
+ if (!data || mal_error) { return NULL; }
+ return malval_new_string(data);
+ }
+ _ref("slurp", slurp, 1);
+
+ MalVal *slurp_do(MalVal *path) {
+ assert_type(path, MAL_STRING, "slurp of non-string");
+ char *data = slurp_raw(path->val.string),
+ *wrapped_data;
+ if (!data || mal_error) { return NULL; }
+ wrapped_data = g_strdup_printf("(do %s)", data);
+ free(data);
+ return malval_new_string(wrapped_data);
+ }
+ _ref("slurp-do", slurp_do, 1);
+
+ RE(repl_env, "", "(def! not (fn* (a) (if a false true)))");
+ RE(repl_env, "",
+ "(def! load-file (fn* (f) (eval (read-string (slurp-do f)))))");
+}
+
+int main(int argc, char *argv[])
+{
+ MalVal *exp;
+ char *output;
+ char prompt[100];
+
+ // Set the initial prompt and environment
+ snprintf(prompt, sizeof(prompt), "user> ");
+ init_repl_env();
+
+ if (argc > 1) {
+ char *cmd = g_strdup_printf("(load-file \"%s\")", argv[1]);
+ RE(repl_env, "", cmd);
+ } else {
+ // REPL loop
+ for(;;) {
+ exp = RE(repl_env, prompt, NULL);
+ if (mal_error && strcmp("EOF", mal_error->val.string) == 0) {
+ return 0;
+ }
+ output = PRINT(exp);
+
+ if (output) {
+ g_print("%s\n", output);
+ free(output); // Free output string
+ }
+
+ //malval_free(exp); // Free evaluated expression
+ }
+ }
+}
diff --git a/c/step9_interop.c b/c/step9_interop.c
new file mode 100644
index 0000000..2a98dd8
--- /dev/null
+++ b/c/step9_interop.c
@@ -0,0 +1,362 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include "types.h"
+#include "readline.h"
+#include "reader.h"
+#include "interop.h"
+
+// Declarations
+MalVal *EVAL(MalVal *ast, Env *env);
+MalVal *macroexpand(MalVal *ast, Env *env);
+
+// read
+MalVal *READ(char prompt[], char *str) {
+ char *line;
+ MalVal *ast;
+ if (str) {
+ line = str;
+ } else {
+ line = _readline(prompt);
+ if (!line) {
+ _error("EOF");
+ return NULL;
+ }
+ }
+ ast = read_str(line);
+ if (!str) { free(line); }
+ return ast;
+}
+
+// eval
+int is_pair(MalVal *x) {
+ return _sequential_Q(x) && (_count(x) > 0);
+}
+
+MalVal *quasiquote(MalVal *ast) {
+ if (!is_pair(ast)) {
+ return _list(2, malval_new_symbol("quote"), ast);
+ } else {
+ MalVal *a0 = _nth(ast, 0);
+ if ((a0->type & MAL_SYMBOL) &&
+ strcmp("unquote", a0->val.string) == 0) {
+ return _nth(ast, 1);
+ } else if (is_pair(a0)) {
+ MalVal *a00 = _nth(a0, 0);
+ if ((a00->type & MAL_SYMBOL) &&
+ strcmp("splice-unquote", a00->val.string) == 0) {
+ return _list(3, malval_new_symbol("concat"),
+ _nth(a0, 1),
+ quasiquote(rest(ast)));
+ }
+ }
+ return _list(3, malval_new_symbol("cons"),
+ quasiquote(a0),
+ quasiquote(rest(ast)));
+ }
+}
+
+int is_macro_call(MalVal *ast, Env *env) {
+ if (!ast || ast->type != MAL_LIST) { return 0; }
+ MalVal *a0 = _nth(ast, 0);
+ return (a0->type & MAL_SYMBOL) &&
+ env_find(env, a0->val.string) &&
+ env_get(env, a0->val.string)->ismacro;
+}
+
+MalVal *macroexpand(MalVal *ast, Env *env) {
+ if (!ast || mal_error) return NULL;
+ while (is_macro_call(ast, env)) {
+ MalVal *a0 = _nth(ast, 0);
+ MalVal *mac = env_get(env, a0->val.string);
+ // TODO: this is weird and limits it to 20. FIXME
+ ast = apply(mac, rest(ast));
+ }
+ return ast;
+}
+
+MalVal *eval_ast(MalVal *ast, Env *env) {
+ if (!ast || mal_error) return NULL;
+ if (ast->type == MAL_SYMBOL) {
+ //g_print("EVAL symbol: %s\n", ast->val.string);
+ return env_get(env, ast->val.string);
+ } else if ((ast->type == MAL_LIST) || (ast->type == MAL_VECTOR)) {
+ //g_print("EVAL sequential: %s\n", _pr_str(ast,1));
+ MalVal *el = _map2((MalVal *(*)(void*, void*))EVAL, ast, env);
+ if (!el || mal_error) return NULL;
+ el->type = ast->type;
+ return el;
+ } else if (ast->type == MAL_HASH_MAP) {
+ //g_print("EVAL hash_map: %s\n", _pr_str(ast,1));
+ GHashTableIter iter;
+ gpointer key, value;
+ MalVal *seq = malval_new_list(MAL_LIST,
+ g_array_sized_new(TRUE, TRUE, sizeof(MalVal*),
+ _count(ast)));
+ g_hash_table_iter_init (&iter, ast->val.hash_table);
+ while (g_hash_table_iter_next (&iter, &key, &value)) {
+ MalVal *kname = malval_new_string((char *)key);
+ g_array_append_val(seq->val.array, kname);
+ MalVal *new_val = EVAL((MalVal *)value, env);
+ g_array_append_val(seq->val.array, new_val);
+ }
+ return hash_map(seq);
+ } else {
+ //g_print("EVAL scalar: %s\n", _pr_str(ast,1));
+ return ast;
+ }
+}
+
+MalVal *EVAL(MalVal *ast, Env *env) {
+ while (TRUE) {
+ if (!ast || mal_error) return NULL;
+ //g_print("EVAL: %s\n", _pr_str(ast,1));
+ if (ast->type != MAL_LIST) {
+ return eval_ast(ast, env);
+ }
+ if (!ast || mal_error) return NULL;
+
+ // apply list
+ //g_print("EVAL apply list: %s\n", _pr_str(ast,1));
+ ast = macroexpand(ast, env);
+ if (!ast || mal_error) return NULL;
+ if (ast->type != MAL_LIST) { return ast; }
+ if (_count(ast) == 0) { return ast; }
+
+ int i, len;
+ MalVal *a0 = _nth(ast, 0);
+ if ((a0->type & MAL_SYMBOL) &&
+ strcmp("def!", a0->val.string) == 0) {
+ //g_print("eval apply def!\n");
+ MalVal *a1 = _nth(ast, 1),
+ *a2 = _nth(ast, 2);
+ MalVal *res = EVAL(a2, env);
+ env_set(env, a1->val.string, res);
+ return res;
+ } else if ((a0->type & MAL_SYMBOL) &&
+ strcmp("let*", a0->val.string) == 0) {
+ //g_print("eval apply let*\n");
+ MalVal *a1 = _nth(ast, 1),
+ *a2 = _nth(ast, 2),
+ *key, *val;
+ assert_type(a1, MAL_LIST|MAL_VECTOR,
+ "let* bindings must be list or vector");
+ len = _count(a1);
+ assert((len % 2) == 0, "odd number of let* bindings forms");
+ Env *let_env = new_env(env, NULL, NULL);
+ for(i=0; i<len; i+=2) {
+ key = g_array_index(a1->val.array, MalVal*, i);
+ val = g_array_index(a1->val.array, MalVal*, i+1);
+ assert_type(key, MAL_SYMBOL, "let* bind to non-symbol");
+ env_set(let_env, key->val.string, EVAL(val, let_env));
+ }
+ return EVAL(a2, let_env);
+ } else if ((a0->type & MAL_SYMBOL) &&
+ strcmp("quote", a0->val.string) == 0) {
+ //g_print("eval apply quote\n");
+ return _nth(ast, 1);
+ } else if ((a0->type & MAL_SYMBOL) &&
+ strcmp("quasiquote", a0->val.string) == 0) {
+ //g_print("eval apply quasiquote\n");
+ MalVal *a1 = _nth(ast, 1);
+ return EVAL(quasiquote(a1), env);
+ } else if ((a0->type & MAL_SYMBOL) &&
+ strcmp("defmacro!", a0->val.string) == 0) {
+ //g_print("eval apply defmacro!\n");
+ MalVal *a1 = _nth(ast, 1),
+ *a2 = _nth(ast, 2);
+ MalVal *res = EVAL(a2, env);
+ res->ismacro = TRUE;
+ env_set(env, a1->val.string, res);
+ return res;
+ } else if ((a0->type & MAL_SYMBOL) &&
+ strcmp("macroexpand", a0->val.string) == 0) {
+ //g_print("eval apply macroexpand\n");
+ MalVal *a1 = _nth(ast, 1);
+ return macroexpand(a1, env);
+ } else if ((a0->type & MAL_SYMBOL) &&
+ strcmp(".", a0->val.string) == 0) {
+ //g_print("eval apply .\n");
+ MalVal *el = eval_ast(_slice(ast, 1, _count(ast)), env);
+ return invoke_native(el);
+ } else if ((a0->type & MAL_SYMBOL) &&
+ strcmp("do", a0->val.string) == 0) {
+ //g_print("eval apply do\n");
+ eval_ast(_slice(ast, 1, _count(ast)-1), env);
+ ast = last(ast);
+ // Continue loop
+ } else if ((a0->type & MAL_SYMBOL) &&
+ strcmp("if", a0->val.string) == 0) {
+ //g_print("eval apply if\n");
+ MalVal *a1 = _nth(ast, 1);
+ MalVal *cond = EVAL(a1, env);
+ if (!cond || mal_error) return NULL;
+ if (cond->type & (MAL_FALSE|MAL_NIL)) {
+ // eval false slot form
+ ast = _nth(ast, 3);
+ if (!ast) {
+ return &mal_nil;
+ }
+ } else {
+ // eval true slot form
+ ast = _nth(ast, 2);
+ }
+ // Continue loop
+ } else if ((a0->type & MAL_SYMBOL) &&
+ strcmp("fn*", a0->val.string) == 0) {
+ //g_print("eval apply fn*\n");
+ MalVal *mf = malval_new(MAL_FUNCTION_MAL, NULL);
+ mf->ismacro = FALSE;
+ mf->val.func.evaluator = EVAL;
+ mf->val.func.args = _nth(ast, 1);
+ mf->val.func.body = _nth(ast, 2);
+ mf->val.func.env = env;
+ return mf;
+ } else {
+ //g_print("eval apply\n");
+ MalVal *el = eval_ast(ast, env);
+ if (!el || mal_error) { return NULL; }
+ MalVal *f = first(el),
+ *args = rest(el);
+ assert_type(f, MAL_FUNCTION_C|MAL_FUNCTION_MAL,
+ "cannot invoke '%s'", _pr_str(f,1));
+ if (f->type & MAL_FUNCTION_MAL) {
+ ast = f->val.func.body;
+ env = new_env(f->val.func.env, f->val.func.args, args);
+ // Continue loop
+ } else {
+ return apply(f, args);
+ }
+ }
+ }
+}
+
+// print
+char *PRINT(MalVal *exp) {
+ if (mal_error) {
+ fprintf(stderr, "Error: %s\n", mal_error->val.string);
+ malval_free(mal_error);
+ mal_error = NULL;
+ return NULL;
+ }
+ return _pr_str(exp,1);
+}
+
+// repl
+
+// read and eval
+MalVal *RE(Env *env, char *prompt, char *str) {
+ MalVal *ast, *exp;
+ ast = READ(prompt, str);
+ if (!ast || mal_error) return NULL;
+ exp = EVAL(ast, env);
+ if (ast != exp) {
+ malval_free(ast); // Free input structure
+ }
+ return exp;
+}
+
+// Setup the initial REPL environment
+Env *repl_env;
+
+char *slurp_raw(char *path) {
+ char *data;
+ struct stat fst;
+ int fd = open(path, O_RDONLY),
+ sz;
+ if (fd < 0) {
+ abort("slurp failed to open '%s'", path);
+ }
+ if (fstat(fd, &fst) < 0) {
+ abort("slurp failed to stat '%s'", path);
+ }
+ data = malloc(fst.st_size+1);
+ sz = read(fd, data, fst.st_size);
+ if (sz < fst.st_size) {
+ abort("slurp failed to read '%s'", path);
+ }
+ data[sz] = '\0';
+ return data;
+}
+
+void init_repl_env() {
+ void _ref(char *name, MalVal*(*func)(MalVal*), int arg_cnt) {
+ void *(*f)(void *) = (void*(*)(void*))func;
+ env_set(repl_env, name, malval_new_function(f, arg_cnt, NULL));
+ }
+ repl_env = new_env(NULL, NULL, NULL);
+
+ int i;
+ for(i=0; i< (sizeof(types_ns) / sizeof(types_ns[0])); i++) {
+ MalVal *(*f)(MalVal *) = (MalVal*(*)(MalVal*))types_ns[i].func;
+ _ref(types_ns[i].name, f, types_ns[i].arg_cnt);
+ }
+
+ MalVal *read_string(MalVal *str) {
+ assert_type(str, MAL_STRING, "read_string of non-string");
+ return read_str(str->val.string);
+ }
+ _ref("read-string", read_string, 1);
+
+ MalVal *do_eval(MalVal *ast) {
+ return EVAL(ast, repl_env);
+ }
+ _ref("eval", do_eval, 1);
+
+ MalVal *slurp(MalVal *path) {
+ assert_type(path, MAL_STRING, "slurp of non-string");
+ char *data = slurp_raw(path->val.string);
+ if (!data || mal_error) { return NULL; }
+ return malval_new_string(data);
+ }
+ _ref("slurp", slurp, 1);
+
+ MalVal *slurp_do(MalVal *path) {
+ assert_type(path, MAL_STRING, "slurp of non-string");
+ char *data = slurp_raw(path->val.string),
+ *wrapped_data;
+ if (!data || mal_error) { return NULL; }
+ wrapped_data = g_strdup_printf("(do %s)", data);
+ free(data);
+ return malval_new_string(wrapped_data);
+ }
+ _ref("slurp-do", slurp_do, 1);
+
+ RE(repl_env, "", "(def! not (fn* (a) (if a false true)))");
+ RE(repl_env, "",
+ "(def! load-file (fn* (f) (eval (read-string (slurp-do f)))))");
+}
+
+int main(int argc, char *argv[])
+{
+ MalVal *exp;
+ char *output;
+ char prompt[100];
+
+ // Set the initial prompt and environment
+ snprintf(prompt, sizeof(prompt), "user> ");
+ init_repl_env();
+
+ if (argc > 1) {
+ char *cmd = g_strdup_printf("(load-file \"%s\")", argv[1]);
+ RE(repl_env, "", cmd);
+ } else {
+ // REPL loop
+ for(;;) {
+ exp = RE(repl_env, prompt, NULL);
+ if (mal_error && strcmp("EOF", mal_error->val.string) == 0) {
+ return 0;
+ }
+ output = PRINT(exp);
+
+ if (output) {
+ g_print("%s\n", output);
+ free(output); // Free output string
+ }
+
+ //malval_free(exp); // Free evaluated expression
+ }
+ }
+}
diff --git a/c/stepA_more.c b/c/stepA_more.c
new file mode 100644
index 0000000..037848a
--- /dev/null
+++ b/c/stepA_more.c
@@ -0,0 +1,393 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include "types.h"
+#include "readline.h"
+#include "reader.h"
+#include "interop.h"
+
+// Declarations
+MalVal *EVAL(MalVal *ast, Env *env);
+MalVal *macroexpand(MalVal *ast, Env *env);
+
+// read
+MalVal *READ(char prompt[], char *str) {
+ char *line;
+ MalVal *ast;
+ if (str) {
+ line = str;
+ } else {
+ line = _readline(prompt);
+ if (!line) {
+ _error("EOF");
+ return NULL;
+ }
+ }
+ ast = read_str(line);
+ if (!str) { free(line); }
+ return ast;
+}
+
+// eval
+int is_pair(MalVal *x) {
+ return _sequential_Q(x) && (_count(x) > 0);
+}
+
+MalVal *quasiquote(MalVal *ast) {
+ if (!is_pair(ast)) {
+ return _list(2, malval_new_symbol("quote"), ast);
+ } else {
+ MalVal *a0 = _nth(ast, 0);
+ if ((a0->type & MAL_SYMBOL) &&
+ strcmp("unquote", a0->val.string) == 0) {
+ return _nth(ast, 1);
+ } else if (is_pair(a0)) {
+ MalVal *a00 = _nth(a0, 0);
+ if ((a00->type & MAL_SYMBOL) &&
+ strcmp("splice-unquote", a00->val.string) == 0) {
+ return _list(3, malval_new_symbol("concat"),
+ _nth(a0, 1),
+ quasiquote(rest(ast)));
+ }
+ }
+ return _list(3, malval_new_symbol("cons"),
+ quasiquote(a0),
+ quasiquote(rest(ast)));
+ }
+}
+
+int is_macro_call(MalVal *ast, Env *env) {
+ if (!ast || ast->type != MAL_LIST) { return 0; }
+ MalVal *a0 = _nth(ast, 0);
+ return (a0->type & MAL_SYMBOL) &&
+ env_find(env, a0->val.string) &&
+ env_get(env, a0->val.string)->ismacro;
+}
+
+MalVal *macroexpand(MalVal *ast, Env *env) {
+ if (!ast || mal_error) return NULL;
+ while (is_macro_call(ast, env)) {
+ MalVal *a0 = _nth(ast, 0);
+ MalVal *mac = env_get(env, a0->val.string);
+ // TODO: this is weird and limits it to 20. FIXME
+ ast = apply(mac, rest(ast));
+ }
+ return ast;
+}
+
+MalVal *eval_ast(MalVal *ast, Env *env) {
+ if (!ast || mal_error) return NULL;
+ if (ast->type == MAL_SYMBOL) {
+ //g_print("EVAL symbol: %s\n", ast->val.string);
+ return env_get(env, ast->val.string);
+ } else if ((ast->type == MAL_LIST) || (ast->type == MAL_VECTOR)) {
+ //g_print("EVAL sequential: %s\n", _pr_str(ast,1));
+ MalVal *el = _map2((MalVal *(*)(void*, void*))EVAL, ast, env);
+ if (!el || mal_error) return NULL;
+ el->type = ast->type;
+ return el;
+ } else if (ast->type == MAL_HASH_MAP) {
+ //g_print("EVAL hash_map: %s\n", _pr_str(ast,1));
+ GHashTableIter iter;
+ gpointer key, value;
+ MalVal *seq = malval_new_list(MAL_LIST,
+ g_array_sized_new(TRUE, TRUE, sizeof(MalVal*),
+ _count(ast)));
+ g_hash_table_iter_init (&iter, ast->val.hash_table);
+ while (g_hash_table_iter_next (&iter, &key, &value)) {
+ MalVal *kname = malval_new_string((char *)key);
+ g_array_append_val(seq->val.array, kname);
+ MalVal *new_val = EVAL((MalVal *)value, env);
+ g_array_append_val(seq->val.array, new_val);
+ }
+ return hash_map(seq);
+ } else {
+ //g_print("EVAL scalar: %s\n", _pr_str(ast,1));
+ return ast;
+ }
+}
+
+MalVal *EVAL(MalVal *ast, Env *env) {
+ while (TRUE) {
+ if (!ast || mal_error) return NULL;
+ //g_print("EVAL: %s\n", _pr_str(ast,1));
+ if (ast->type != MAL_LIST) {
+ return eval_ast(ast, env);
+ }
+ if (!ast || mal_error) return NULL;
+
+ // apply list
+ //g_print("EVAL apply list: %s\n", _pr_str(ast,1));
+ ast = macroexpand(ast, env);
+ if (!ast || mal_error) return NULL;
+ if (ast->type != MAL_LIST) { return ast; }
+ if (_count(ast) == 0) { return ast; }
+
+ int i, len;
+ MalVal *a0 = _nth(ast, 0);
+ if ((a0->type & MAL_SYMBOL) &&
+ strcmp("def!", a0->val.string) == 0) {
+ //g_print("eval apply def!\n");
+ MalVal *a1 = _nth(ast, 1),
+ *a2 = _nth(ast, 2);
+ MalVal *res = EVAL(a2, env);
+ env_set(env, a1->val.string, res);
+ return res;
+ } else if ((a0->type & MAL_SYMBOL) &&
+ strcmp("let*", a0->val.string) == 0) {
+ //g_print("eval apply let*\n");
+ MalVal *a1 = _nth(ast, 1),
+ *a2 = _nth(ast, 2),
+ *key, *val;
+ assert_type(a1, MAL_LIST|MAL_VECTOR,
+ "let* bindings must be list or vector");
+ len = _count(a1);
+ assert((len % 2) == 0, "odd number of let* bindings forms");
+ Env *let_env = new_env(env, NULL, NULL);
+ for(i=0; i<len; i+=2) {
+ key = g_array_index(a1->val.array, MalVal*, i);
+ val = g_array_index(a1->val.array, MalVal*, i+1);
+ assert_type(key, MAL_SYMBOL, "let* bind to non-symbol");
+ env_set(let_env, key->val.string, EVAL(val, let_env));
+ }
+ return EVAL(a2, let_env);
+ } else if ((a0->type & MAL_SYMBOL) &&
+ strcmp("quote", a0->val.string) == 0) {
+ //g_print("eval apply quote\n");
+ return _nth(ast, 1);
+ } else if ((a0->type & MAL_SYMBOL) &&
+ strcmp("quasiquote", a0->val.string) == 0) {
+ //g_print("eval apply quasiquote\n");
+ MalVal *a1 = _nth(ast, 1);
+ return EVAL(quasiquote(a1), env);
+ } else if ((a0->type & MAL_SYMBOL) &&
+ strcmp("defmacro!", a0->val.string) == 0) {
+ //g_print("eval apply defmacro!\n");
+ MalVal *a1 = _nth(ast, 1),
+ *a2 = _nth(ast, 2);
+ MalVal *res = EVAL(a2, env);
+ res->ismacro = TRUE;
+ env_set(env, a1->val.string, res);
+ return res;
+ } else if ((a0->type & MAL_SYMBOL) &&
+ strcmp("macroexpand", a0->val.string) == 0) {
+ //g_print("eval apply macroexpand\n");
+ MalVal *a1 = _nth(ast, 1);
+ return macroexpand(a1, env);
+ } else if ((a0->type & MAL_SYMBOL) &&
+ strcmp(".", a0->val.string) == 0) {
+ //g_print("eval apply .\n");
+ MalVal *el = eval_ast(_slice(ast, 1, _count(ast)), env);
+ return invoke_native(el);
+ } else if ((a0->type & MAL_SYMBOL) &&
+ strcmp("try*", a0->val.string) == 0) {
+ //g_print("eval apply try*\n");
+ MalVal *a1 = _nth(ast, 1);
+ MalVal *a2 = _nth(ast, 2);
+ MalVal *res = EVAL(a1, env);
+ if (!mal_error) { return res; }
+ MalVal *a20 = _nth(a2, 0);
+ if (strcmp("catch*", a20->val.string) == 0) {
+ MalVal *a21 = _nth(a2, 1);
+ MalVal *a22 = _nth(a2, 2);
+ Env *catch_env = new_env(env,
+ _list(1, a21),
+ _list(1, mal_error));
+ //malval_free(mal_error);
+ mal_error = NULL;
+ res = EVAL(a22, catch_env);
+ return res;
+ } else {
+ return &mal_nil;
+ }
+ } else if ((a0->type & MAL_SYMBOL) &&
+ strcmp("do", a0->val.string) == 0) {
+ //g_print("eval apply do\n");
+ eval_ast(_slice(ast, 1, _count(ast)-1), env);
+ ast = last(ast);
+ // Continue loop
+ } else if ((a0->type & MAL_SYMBOL) &&
+ strcmp("if", a0->val.string) == 0) {
+ //g_print("eval apply if\n");
+ MalVal *a1 = _nth(ast, 1);
+ MalVal *cond = EVAL(a1, env);
+ if (!cond || mal_error) return NULL;
+ if (cond->type & (MAL_FALSE|MAL_NIL)) {
+ // eval false slot form
+ ast = _nth(ast, 3);
+ if (!ast) {
+ return &mal_nil;
+ }
+ } else {
+ // eval true slot form
+ ast = _nth(ast, 2);
+ }
+ // Continue loop
+ } else if ((a0->type & MAL_SYMBOL) &&
+ strcmp("fn*", a0->val.string) == 0) {
+ //g_print("eval apply fn*\n");
+ MalVal *mf = malval_new(MAL_FUNCTION_MAL, NULL);
+ mf->ismacro = FALSE;
+ mf->val.func.evaluator = EVAL;
+ mf->val.func.args = _nth(ast, 1);
+ mf->val.func.body = _nth(ast, 2);
+ mf->val.func.env = env;
+ return mf;
+ } else {
+ //g_print("eval apply\n");
+ MalVal *el = eval_ast(ast, env);
+ if (!el || mal_error) { return NULL; }
+ MalVal *f = first(el),
+ *args = rest(el);
+ assert_type(f, MAL_FUNCTION_C|MAL_FUNCTION_MAL,
+ "cannot invoke '%s'", _pr_str(f,1));
+ if (f->type & MAL_FUNCTION_MAL) {
+ ast = f->val.func.body;
+ env = new_env(f->val.func.env, f->val.func.args, args);
+ // Continue loop
+ } else {
+ return apply(f, args);
+ }
+ }
+ }
+}
+
+// print
+char *PRINT(MalVal *exp) {
+ if (mal_error) {
+ fprintf(stderr, "Error: %s\n", mal_error->val.string);
+ malval_free(mal_error);
+ mal_error = NULL;
+ return NULL;
+ }
+ return _pr_str(exp,1);
+}
+
+// repl
+
+// read and eval
+MalVal *RE(Env *env, char *prompt, char *str) {
+ MalVal *ast, *exp;
+ ast = READ(prompt, str);
+ if (!ast || mal_error) return NULL;
+ exp = EVAL(ast, env);
+ if (ast != exp) {
+ malval_free(ast); // Free input structure
+ }
+ return exp;
+}
+
+// Setup the initial REPL environment
+Env *repl_env;
+
+char *slurp_raw(char *path) {
+ char *data;
+ struct stat fst;
+ int fd = open(path, O_RDONLY),
+ sz;
+ if (fd < 0) {
+ abort("slurp failed to open '%s'", path);
+ }
+ if (fstat(fd, &fst) < 0) {
+ abort("slurp failed to stat '%s'", path);
+ }
+ data = malloc(fst.st_size+1);
+ sz = read(fd, data, fst.st_size);
+ if (sz < fst.st_size) {
+ abort("slurp failed to read '%s'", path);
+ }
+ data[sz] = '\0';
+ return data;
+}
+
+void init_repl_env() {
+ void _ref(char *name, MalVal*(*func)(MalVal*), int arg_cnt) {
+ void *(*f)(void *) = (void*(*)(void*))func;
+ env_set(repl_env, name, malval_new_function(f, arg_cnt, NULL));
+ }
+ repl_env = new_env(NULL, NULL, NULL);
+
+ int i;
+ for(i=0; i< (sizeof(types_ns) / sizeof(types_ns[0])); i++) {
+ MalVal *(*f)(MalVal *) = (MalVal*(*)(MalVal*))types_ns[i].func;
+ _ref(types_ns[i].name, f, types_ns[i].arg_cnt);
+ }
+
+ MalVal *readline(MalVal *str) {
+ assert_type(str, MAL_STRING, "readline of non-string");
+ char * line = _readline(str->val.string);
+ if (line) { return malval_new_string(line); }
+ else { return &mal_nil; }
+ }
+ _ref("readline", readline, 1);
+
+ MalVal *read_string(MalVal *str) {
+ assert_type(str, MAL_STRING, "read_string of non-string");
+ return read_str(str->val.string);
+ }
+ _ref("read-string", read_string, 1);
+
+ MalVal *do_eval(MalVal *ast) {
+ return EVAL(ast, repl_env);
+ }
+ _ref("eval", do_eval, 1);
+
+ MalVal *slurp(MalVal *path) {
+ assert_type(path, MAL_STRING, "slurp of non-string");
+ char *data = slurp_raw(path->val.string);
+ if (!data || mal_error) { return NULL; }
+ return malval_new_string(data);
+ }
+ _ref("slurp", slurp, 1);
+
+ MalVal *slurp_do(MalVal *path) {
+ assert_type(path, MAL_STRING, "slurp of non-string");
+ char *data = slurp_raw(path->val.string),
+ *wrapped_data;
+ if (!data || mal_error) { return NULL; }
+ wrapped_data = g_strdup_printf("(do %s)", data);
+ free(data);
+ return malval_new_string(wrapped_data);
+ }
+ _ref("slurp-do", slurp_do, 1);
+
+ RE(repl_env, "", "(def! not (fn* (a) (if a false true)))");
+ RE(repl_env, "", "(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)))))))");
+ RE(repl_env, "", "(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))))))))");
+ RE(repl_env, "",
+ "(def! load-file (fn* (f) (eval (read-string (slurp-do f)))))");
+}
+
+int main(int argc, char *argv[])
+{
+ MalVal *exp;
+ char *output;
+ char prompt[100];
+
+ // Set the initial prompt and environment
+ snprintf(prompt, sizeof(prompt), "user> ");
+ init_repl_env();
+
+ if (argc > 1) {
+ char *cmd = g_strdup_printf("(load-file \"%s\")", argv[1]);
+ RE(repl_env, "", cmd);
+ } else {
+ // REPL loop
+ for(;;) {
+ exp = RE(repl_env, prompt, NULL);
+ if (mal_error && strcmp("EOF", mal_error->val.string) == 0) {
+ return 0;
+ }
+ output = PRINT(exp);
+
+ if (output) {
+ g_print("%s\n", output);
+ free(output); // Free output string
+ }
+
+ //malval_free(exp); // Free evaluated expression
+ }
+ }
+}
diff --git a/c/tests/step9_interop.mal b/c/tests/step9_interop.mal
new file mode 100644
index 0000000..657e3e7
--- /dev/null
+++ b/c/tests/step9_interop.mal
@@ -0,0 +1,23 @@
+
+;; Testing FFI of "strlen"
+(. nil "int32" "strlen" "string" "abcde")
+;=>5
+(. nil "int32" "strlen" "string" "")
+;=>0
+
+;; Testing FFI of "strcmp"
+
+(. nil "int32" "strcmp" "string" "abc" "string" "abcA")
+;=>-65
+(. nil "int32" "strcmp" "string" "abcA" "string" "abc")
+;=>65
+(. nil "int32" "strcmp" "string" "abc" "string" "abc")
+;=>0
+
+
+;; Testing FFI of "pow" (libm.so)
+
+(. "libm.so" "double" "pow" "double" 2.0 "double" 3.0)
+;=>8.000000
+(. "libm.so" "double" "pow" "double" 3.0 "double" 2.0)
+;=>9.000000
diff --git a/c/types.c b/c/types.c
new file mode 100644
index 0000000..1308aac
--- /dev/null
+++ b/c/types.c
@@ -0,0 +1,1038 @@
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "types.h"
+
+// State
+
+MalVal *mal_error = NULL;
+
+
+// Constant atomic values
+
+MalVal mal_nil = {MAL_NIL, NULL, {0}, 0};
+MalVal mal_true = {MAL_TRUE, NULL, {0}, 0};
+MalVal mal_false = {MAL_FALSE, NULL, {0}, 0};
+
+
+// Pre-declarations
+
+MalVal *cons(MalVal *x, MalVal *seq);
+
+// General Functions
+
+// Print a hash table
+#include <glib-object.h>
+void g_hash_table_print(GHashTable *hash_table) {
+ GHashTableIter iter;
+ gpointer key, value;
+
+ g_hash_table_iter_init (&iter, hash_table);
+ while (g_hash_table_iter_next (&iter, &key, &value)) {
+ g_print ("%s/%p ", (const char *) key, (void *) value);
+ //g_print ("%s ", (const char *) key);
+ }
+}
+
+GHashTable *g_hash_table_copy(GHashTable *src_table) {
+ GHashTable *new_table = g_hash_table_new(g_str_hash, g_str_equal);
+ GHashTableIter iter;
+ gpointer key, value;
+
+ g_hash_table_iter_init (&iter, src_table);
+ while (g_hash_table_iter_next (&iter, &key, &value)) {
+ g_hash_table_insert(new_table, key, value);
+ }
+ return new_table;
+}
+
+int min(int a, int b) { return a < b ? a : b; }
+int max(int a, int b) { return a > b ? a : b; }
+
+int _count(MalVal *obj) {
+ switch (obj->type) {
+ case MAL_NIL: return 0;
+ case MAL_LIST: return obj->val.array->len;
+ case MAL_VECTOR: return obj->val.array->len;
+ case MAL_HASH_MAP: return g_hash_table_size(obj->val.hash_table);
+ case MAL_STRING: return strlen(obj->val.string);
+ default:
+ _error("count unsupported for type %d\n", obj->type);
+ return 0;
+ }
+}
+
+// Allocate a malval and set its type and value
+MalVal *malval_new(MalType type, MalVal *metadata) {
+ MalVal *mv = (MalVal*)malloc(sizeof(MalVal));
+ mv->type = type;
+ mv->metadata = metadata;
+ return mv;
+}
+
+//
+int malval_free(MalVal *mv) {
+ // TODO: free collection items
+ if (!(mv->type & (MAL_NIL|MAL_TRUE|MAL_FALSE))) {
+ free(mv);
+ }
+}
+
+MalVal *malval_new_integer(gint64 val) {
+ MalVal *mv = malval_new(MAL_INTEGER, NULL);
+ mv->val.intnum = val;
+ return mv;
+}
+
+MalVal *malval_new_float(gdouble val) {
+ MalVal *mv = malval_new(MAL_FLOAT, NULL);
+ mv->val.floatnum = val;
+ return mv;
+}
+
+MalVal *malval_new_string(char *val) {
+ MalVal *mv = malval_new(MAL_STRING, NULL);
+ mv->val.string = val;
+ return mv;
+}
+
+MalVal *malval_new_symbol(char *val) {
+ MalVal *mv = malval_new(MAL_SYMBOL, NULL);
+ mv->val.string = val;
+ return mv;
+}
+
+MalVal *malval_new_hash_map(GHashTable *val) {
+ MalVal *mv = malval_new(MAL_HASH_MAP, NULL);
+ mv->val.hash_table = val;
+ return mv;
+}
+
+MalVal *malval_new_list(MalType type, GArray *val) {
+ MalVal *mv = malval_new(type, NULL);
+ mv->val.array = val;
+ return mv;
+}
+
+MalVal *malval_new_atom(MalVal *val) {
+ MalVal *mv = malval_new(MAL_ATOM, NULL);
+ mv->val.atom_val = val;
+ return mv;
+}
+
+
+MalVal *malval_new_function(void *(*func)(void *), int arg_cnt, MalVal* metadata) {
+ MalVal *mv = malval_new(MAL_FUNCTION_C, metadata);
+ mv->func_arg_cnt = arg_cnt;
+ assert(mv->func_arg_cnt <= 20,
+ "native function restricted to 20 args (%d given)",
+ mv->func_arg_cnt);
+ mv->ismacro = FALSE;
+ switch (arg_cnt) {
+ case -1: mv->val.f1 = (void *(*)(void*))func; break;
+ case 0: mv->val.f0 = (void *(*)())func; break;
+ case 1: mv->val.f1 = (void *(*)(void*))func; break;
+ case 2: mv->val.f2 = (void *(*)(void*,void*))func; break;
+ case 3: mv->val.f3 = (void *(*)(void*,void*,void*))func; break;
+ case 4: mv->val.f4 = (void *(*)(void*,void*,void*,void*))func; break;
+ case 5: mv->val.f5 = (void *(*)(void*,void*,void*,void*,void*))func; break;
+ case 6: mv->val.f6 = (void *(*)(void*,void*,void*,void*,void*,
+ void*))func; break;
+ case 7: mv->val.f7 = (void *(*)(void*,void*,void*,void*,void*,
+ void*,void*))func; break;
+ case 8: mv->val.f8 = (void *(*)(void*,void*,void*,void*,void*,
+ void*,void*,void*))func; break;
+ case 9: mv->val.f9 = (void *(*)(void*,void*,void*,void*,void*,
+ void*,void*,void*,void*))func; break;
+ case 10: mv->val.f10 = (void *(*)(void*,void*,void*,void*,void*,
+ void*,void*,void*,void*,void*))func; break;
+ case 11: mv->val.f11 = (void *(*)(void*,void*,void*,void*,void*,
+ void*,void*,void*,void*,void*,
+ void*))func; break;
+ case 12: mv->val.f12 = (void *(*)(void*,void*,void*,void*,void*,
+ void*,void*,void*,void*,void*,
+ void*,void*))func; break;
+ case 13: mv->val.f13 = (void *(*)(void*,void*,void*,void*,void*,
+ void*,void*,void*,void*,void*,
+ void*,void*,void*))func; break;
+ case 14: mv->val.f14 = (void *(*)(void*,void*,void*,void*,void*,
+ void*,void*,void*,void*,void*,
+ void*,void*,void*,void*))func; break;
+ case 15: mv->val.f15 = (void *(*)(void*,void*,void*,void*,void*,
+ void*,void*,void*,void*,void*,
+ void*,void*,void*,void*,void*))func; break;
+ case 16: mv->val.f16 = (void *(*)(void*,void*,void*,void*,void*,
+ void*,void*,void*,void*,void*,
+ void*,void*,void*,void*,void*,
+ void*))func; break;
+ case 17: mv->val.f17 = (void *(*)(void*,void*,void*,void*,void*,
+ void*,void*,void*,void*,void*,
+ void*,void*,void*,void*,void*,
+ void*,void*))func; break;
+ case 18: mv->val.f18 = (void *(*)(void*,void*,void*,void*,void*,
+ void*,void*,void*,void*,void*,
+ void*,void*,void*,void*,void*,
+ void*,void*,void*))func; break;
+ case 19: mv->val.f19 = (void *(*)(void*,void*,void*,void*,void*,
+ void*,void*,void*,void*,void*,
+ void*,void*,void*,void*,void*,
+ void*,void*,void*,void*))func; break;
+ case 20: mv->val.f20 = (void *(*)(void*,void*,void*,void*,void*,
+ void*,void*,void*,void*,void*,
+ void*,void*,void*,void*,void*,
+ void*,void*,void*,void*,void*))func; break;
+ }
+ return mv;
+}
+
+MalVal *apply(MalVal *f, MalVal *args) {
+ MalVal *res;
+ assert_type(f, MAL_FUNCTION_C|MAL_FUNCTION_MAL,
+ "Cannot invoke %s", _pr_str(f,1));
+ if (f->type & MAL_FUNCTION_MAL) {
+ Env *fn_env = new_env(f->val.func.env, f->val.func.args, args);
+ res = f->val.func.evaluator(f->val.func.body, fn_env);
+ return res;
+ } else {
+ MalVal *a = args;
+ assert((f->func_arg_cnt == -1) ||
+ (f->func_arg_cnt == _count(args)),
+ "Length of formal params (%d) does not match actual parameters (%d)",
+ f->func_arg_cnt, _count(args));
+ switch (f->func_arg_cnt) {
+ case -1: res=f->val.f1 (a); break;
+ case 0: res=f->val.f0 (); break;
+ case 1: res=f->val.f1 (_nth(a,0)); break;
+ case 2: res=f->val.f2 (_nth(a,0),_nth(a,1)); break;
+ case 3: res=f->val.f3 (_nth(a,0),_nth(a,1),_nth(a,2)); break;
+ case 4: res=f->val.f4 (_nth(a,0),_nth(a,1),_nth(a,2),_nth(a,3)); break;
+ case 5: res=f->val.f5 (_nth(a,0),_nth(a,1),_nth(a,2),_nth(a,3),_nth(a,4)); break;
+ case 6: res=f->val.f6 (_nth(a,0),_nth(a,1),_nth(a,2),_nth(a,3),_nth(a,4),
+ _nth(a,5)); break;
+ case 7: res=f->val.f7 (_nth(a,0),_nth(a,1),_nth(a,2),_nth(a,3),_nth(a,4),
+ _nth(a,5),_nth(a,6)); break;
+ case 8: res=f->val.f8 (_nth(a,0),_nth(a,1),_nth(a,2),_nth(a,3),_nth(a,4),
+ _nth(a,5),_nth(a,6),_nth(a,7)); break;
+ case 9: res=f->val.f9 (_nth(a,0),_nth(a,1),_nth(a,2),_nth(a,3),_nth(a,4),
+ _nth(a,5),_nth(a,6),_nth(a,7),_nth(a,8)); break;
+ case 10: res=f->val.f10(_nth(a,0),_nth(a,1),_nth(a,2),_nth(a,3),_nth(a,4),
+ _nth(a,5),_nth(a,6),_nth(a,7),_nth(a,8),_nth(a,9)); break;
+ case 11: res=f->val.f11(_nth(a,0),_nth(a,1),_nth(a,2),_nth(a,3),_nth(a,4),
+ _nth(a,5),_nth(a,6),_nth(a,7),_nth(a,8),_nth(a,9),
+ _nth(a,10)); break;
+ case 12: res=f->val.f12(_nth(a,0),_nth(a,1),_nth(a,2),_nth(a,3),_nth(a,4),
+ _nth(a,5),_nth(a,6),_nth(a,7),_nth(a,8),_nth(a,9),
+ _nth(a,10),_nth(a,11)); break;
+ case 13: res=f->val.f13(_nth(a,0),_nth(a,1),_nth(a,2),_nth(a,3),_nth(a,4),
+ _nth(a,5),_nth(a,6),_nth(a,7),_nth(a,8),_nth(a,9),
+ _nth(a,10),_nth(a,11),_nth(a,12)); break;
+ case 14: res=f->val.f14(_nth(a,0),_nth(a,1),_nth(a,2),_nth(a,3),_nth(a,4),
+ _nth(a,5),_nth(a,6),_nth(a,7),_nth(a,8),_nth(a,9),
+ _nth(a,10),_nth(a,11),_nth(a,12),_nth(a,13)); break;
+ case 15: res=f->val.f15(_nth(a,0),_nth(a,1),_nth(a,2),_nth(a,3),_nth(a,4),
+ _nth(a,5),_nth(a,6),_nth(a,7),_nth(a,8),_nth(a,9),
+ _nth(a,10),_nth(a,11),_nth(a,12),_nth(a,13),_nth(a,14)); break;
+ case 16: res=f->val.f16(_nth(a,0),_nth(a,1),_nth(a,2),_nth(a,3),_nth(a,4),
+ _nth(a,5),_nth(a,6),_nth(a,7),_nth(a,8),_nth(a,9),
+ _nth(a,10),_nth(a,11),_nth(a,12),_nth(a,13),_nth(a,14),
+ _nth(a,15)); break;
+ case 17: res=f->val.f17(_nth(a,0),_nth(a,1),_nth(a,2),_nth(a,3),_nth(a,4),
+ _nth(a,5),_nth(a,6),_nth(a,7),_nth(a,8),_nth(a,9),
+ _nth(a,10),_nth(a,11),_nth(a,12),_nth(a,13),_nth(a,14),
+ _nth(a,15),_nth(a,16)); break;
+ case 18: res=f->val.f18(_nth(a,0),_nth(a,1),_nth(a,2),_nth(a,3),_nth(a,4),
+ _nth(a,5),_nth(a,6),_nth(a,7),_nth(a,8),_nth(a,9),
+ _nth(a,10),_nth(a,11),_nth(a,12),_nth(a,13),_nth(a,14),
+ _nth(a,15),_nth(a,16),_nth(a,17)); break;
+ case 19: res=f->val.f19(_nth(a,0),_nth(a,1),_nth(a,2),_nth(a,3),_nth(a,4),
+ _nth(a,5),_nth(a,6),_nth(a,7),_nth(a,8),_nth(a,9),
+ _nth(a,10),_nth(a,11),_nth(a,12),_nth(a,13),_nth(a,14),
+ _nth(a,15),_nth(a,16),_nth(a,17),_nth(a,18)); break;
+ case 20: res=f->val.f20(_nth(a,0),_nth(a,1),_nth(a,2),_nth(a,3),_nth(a,4),
+ _nth(a,5),_nth(a,6),_nth(a,7),_nth(a,8),_nth(a,9),
+ _nth(a,10),_nth(a,11),_nth(a,12),_nth(a,13),_nth(a,14),
+ _nth(a,15),_nth(a,16),_nth(a,17),_nth(a,18),_nth(a,19)); break;
+ }
+ return res;
+ }
+}
+
+
+char *_pr_str_hash_map(MalVal *obj, int print_readably) {
+ int start = 1;
+ char *repr = NULL, *repr_tmp1 = NULL, *repr_tmp2 = NULL;
+ GHashTableIter iter;
+ gpointer key, value;
+
+ repr = g_strdup_printf("{");
+
+ g_hash_table_iter_init (&iter, obj->val.hash_table);
+ while (g_hash_table_iter_next (&iter, &key, &value)) {
+ //g_print ("%s/%p ", (const char *) key, (void *) value);
+
+ repr_tmp1 = _pr_str((MalVal*)value, print_readably);
+ if (start) {
+ start = 0;
+ repr = g_strdup_printf("{\"%s\" %s", (char *)key, repr_tmp1);
+ } else {
+ repr_tmp2 = repr;
+ repr = g_strdup_printf("%s \"%s\" %s", repr_tmp2, (char *)key, repr_tmp1);
+ free(repr_tmp2);
+ }
+ free(repr_tmp1);
+ }
+ repr_tmp2 = repr;
+ repr = g_strdup_printf("%s}", repr_tmp2);
+ free(repr_tmp2);
+ return repr;
+}
+
+char *_pr_str_list(MalVal *obj, int print_readably, char start, char end) {
+ int i;
+ char *repr = NULL, *repr_tmp1 = NULL, *repr_tmp2 = NULL;
+ repr = g_strdup_printf("%c", start);
+ for (i=0; i<_count(obj); i++) {
+ repr_tmp1 = _pr_str(g_array_index(obj->val.array, MalVal*, i),
+ print_readably);
+ if (i == 0) {
+ repr = g_strdup_printf("%c%s", start, repr_tmp1);
+ } else {
+ repr_tmp2 = repr;
+ repr = g_strdup_printf("%s %s", repr_tmp2, repr_tmp1);
+ free(repr_tmp2);
+ }
+ free(repr_tmp1);
+ }
+ repr_tmp2 = repr;
+ repr = g_strdup_printf("%s%c", repr_tmp2, end);
+ free(repr_tmp2);
+ return repr;
+}
+
+// Return a string representation of the MalVal object. Returned string must
+// be freed by caller.
+char *_pr_str(MalVal *obj, int print_readably) {
+ char *repr = NULL;
+ if (obj == NULL) { return NULL; }
+ switch (obj->type) {
+ case MAL_NIL:
+ repr = g_strdup_printf("nil");
+ break;
+ case MAL_TRUE:
+ repr = g_strdup_printf("true");
+ break;
+ case MAL_FALSE:
+ repr = g_strdup_printf("false");
+ break;
+ case MAL_STRING:
+ if (print_readably) {
+ char *repr_tmp = g_strescape(obj->val.string, "");
+ repr = g_strdup_printf("\"%s\"", repr_tmp);
+ free(repr_tmp);
+ } else {
+ repr = g_strdup_printf("%s", obj->val.string);
+ }
+ break;
+ case MAL_SYMBOL:
+ repr = g_strdup_printf("%s", obj->val.string);
+ break;
+ case MAL_INTEGER:
+ repr = g_strdup_printf("%" G_GINT64_FORMAT, obj->val.intnum);
+ break;
+ case MAL_FLOAT:
+ repr = g_strdup_printf("%f", obj->val.floatnum);
+ break;
+ case MAL_HASH_MAP:
+ repr = _pr_str_hash_map(obj, print_readably);
+ break;
+ case MAL_LIST:
+ repr = _pr_str_list(obj, print_readably, '(', ')');
+ break;
+ case MAL_VECTOR:
+ repr = _pr_str_list(obj, print_readably, '[', ']');
+ break;
+ case MAL_ATOM:
+ repr = g_strdup_printf("(atom %s)",
+ _pr_str(obj->val.atom_val, print_readably));
+ break;
+ case MAL_FUNCTION_C:
+ repr = g_strdup_printf("#<function@%p>", obj->val.f0);
+ break;
+ case MAL_FUNCTION_MAL:
+ repr = g_strdup_printf("#<Function: (fn* %s %s)>",
+ _pr_str(obj->val.func.args, print_readably),
+ _pr_str(obj->val.func.body, print_readably));
+ break;
+ default:
+ printf("pr_str unknown type %d\n", obj->type);
+ repr = g_strdup_printf("<unknown>");
+ }
+ return repr;
+}
+
+// Return a string representation of the MalVal arguments. Returned string must
+// be freed by caller.
+char *_pr_str_args(MalVal *args, char *sep, int print_readably) {
+ assert_type(args, MAL_LIST|MAL_VECTOR,
+ "_pr_str called with non-sequential args");
+ int i;
+ char *repr = g_strdup_printf(""),
+ *repr2 = NULL;
+ for (i=0; i<_count(args); i++) {
+ MalVal *obj = g_array_index(args->val.array, MalVal*, i);
+ if (i != 0) {
+ repr2 = repr;
+ repr = g_strdup_printf("%s%s", repr2, sep);
+ free(repr2);
+ }
+ repr2 = repr;
+ repr = g_strdup_printf("%s%s",
+ repr2, _pr_str(obj, print_readably));
+ free(repr2);
+ }
+ return repr;
+}
+
+// Return a string representation of a MalVal sequence (in a format that can
+// be read by the reader). Returned string must be freed by caller.
+MalVal *pr_str(MalVal *args) {
+ assert_type(args, MAL_LIST|MAL_VECTOR,
+ "pr_str called with non-sequential args");
+ return malval_new_string(_pr_str_args(args, " ", 1));
+}
+
+// Return a string representation of a MalVal sequence with every item
+// concatenated together. Returned string must be freed by caller.
+MalVal *str(MalVal *args) {
+ assert_type(args, MAL_LIST|MAL_VECTOR,
+ "str called with non-sequential args");
+ return malval_new_string(_pr_str_args(args, "", 0));
+}
+
+// Print a string representation of a MalVal sequence (in a format that can
+// be read by the reader) followed by a newline. Returns nil.
+MalVal *prn(MalVal *args) {
+ assert_type(args, MAL_LIST|MAL_VECTOR,
+ "prn called with non-sequential args");
+ char *repr = _pr_str_args(args, " ", 1);
+ g_print("%s\n", repr);
+ free(repr);
+ return &mal_nil;
+}
+
+// Print a string representation of a MalVal sequence (for human consumption)
+// followed by a newline. Returns nil.
+MalVal *println(MalVal *args) {
+ assert_type(args, MAL_LIST|MAL_VECTOR,
+ "println called with non-sequential args");
+ char *repr = _pr_str_args(args, " ", 0);
+ g_print("%s\n", repr);
+ free(repr);
+ return &mal_nil;
+}
+
+MalVal *with_meta(MalVal *obj, MalVal *meta) {
+ MalVal *new_obj = malval_new(obj->type, meta);
+ new_obj->val = obj->val;
+ return new_obj;
+}
+
+MalVal *meta(MalVal *obj) {
+ assert_type(obj, MAL_LIST|MAL_VECTOR|MAL_HASH_MAP|MAL_FUNCTION_C|MAL_FUNCTION_MAL,
+ "attempt to get metadata from non-collection type");
+ if (obj->metadata == NULL) {
+ return &mal_nil;
+ } else {
+ return obj->metadata;
+ }
+}
+
+
+int _equal_Q(MalVal *a, MalVal *b) {
+ if (a == NULL || b == NULL) { return FALSE; }
+
+ // If types are the same or both are sequential then they might be equal
+ if (!((a->type == b->type) ||
+ (_sequential_Q(a) && _sequential_Q(b)))) {
+ return FALSE;
+ }
+ switch (a->type) {
+ case MAL_NIL:
+ case MAL_TRUE:
+ case MAL_FALSE:
+ return a->type == b->type;
+ case MAL_INTEGER:
+ return a->val.intnum == b->val.intnum;
+ case MAL_FLOAT:
+ return a->val.floatnum == b->val.floatnum;
+ case MAL_SYMBOL:
+ case MAL_STRING:
+ if (strcmp(a->val.string, b->val.string) == 0) {
+ return TRUE;
+ } else {
+ return FALSE;
+ }
+ case MAL_LIST:
+ case MAL_VECTOR:
+ if (a->val.array->len != b->val.array->len) {
+ return FALSE;
+ }
+ int i;
+ for (i=0; i<a->val.array->len; i++) {
+ if (! _equal_Q(g_array_index(a->val.array, MalVal*, i),
+ g_array_index(b->val.array, MalVal*, i))) {
+ return FALSE;
+ }
+ }
+ return TRUE;
+ case MAL_HASH_MAP:
+ _error("_equal_Q does not support hash-maps yet");
+ return FALSE;
+ case MAL_FUNCTION_C:
+ case MAL_FUNCTION_MAL:
+ return a->val.f0 == b->val.f0;
+ default:
+ _error("_equal_Q unsupported comparison type %d\n", a->type);
+ return FALSE;
+ }
+}
+
+MalVal *equal_Q(MalVal *a, MalVal *b) {
+ if (_equal_Q(a, b)) { return &mal_true; }
+ else { return &mal_false; }
+}
+
+//
+// nil, true, false, string
+MalVal *nil_Q(MalVal *seq) { return seq->type & MAL_NIL ? &mal_true : &mal_false; }
+MalVal *true_Q(MalVal *seq) { return seq->type & MAL_TRUE ? &mal_true : &mal_false; }
+MalVal *false_Q(MalVal *seq) { return seq->type & MAL_FALSE ? &mal_true : &mal_false; }
+MalVal *string_Q(MalVal *seq) { return seq->type & MAL_STRING ? &mal_true : &mal_false; }
+
+//
+// Numbers
+#define WRAP_INTEGER_OP(name, op) \
+ MalVal *int_ ## name(MalVal *a, MalVal *b) { \
+ return malval_new_integer(a->val.intnum op b->val.intnum); \
+ }
+#define WRAP_INTEGER_CMP_OP(name, op) \
+ MalVal *int_ ## name(MalVal *a, MalVal *b) { \
+ return a->val.intnum op b->val.intnum ? &mal_true : &mal_false; \
+ }
+WRAP_INTEGER_OP(plus,+)
+WRAP_INTEGER_OP(minus,-)
+WRAP_INTEGER_OP(multiply,*)
+WRAP_INTEGER_OP(divide,/)
+WRAP_INTEGER_CMP_OP(gt,>)
+WRAP_INTEGER_CMP_OP(gte,>=)
+WRAP_INTEGER_CMP_OP(lt,<)
+WRAP_INTEGER_CMP_OP(lte,<=)
+
+
+//
+// Symbols
+MalVal *symbol_Q(MalVal *seq) { return seq->type & MAL_SYMBOL ? &mal_true : &mal_false; }
+
+
+// Hash maps
+//
+MalVal *_hash_map(int count, ...) {
+ assert((count % 2) == 0,
+ "odd number of parameters to hash-map");
+ GHashTable *htable = g_hash_table_new(g_str_hash, g_str_equal);
+ MalVal *hm = malval_new_hash_map(htable);
+ char *k;
+ MalVal *v;
+ va_list ap;
+ va_start(ap, count);
+ while (count > 0) {
+ k = va_arg(ap, char*);
+ v = va_arg(ap, MalVal*);
+ g_hash_table_insert(htable, k, v);
+ count = count - 2;
+ }
+ va_end(ap);
+ return hm;
+}
+
+MalVal *hash_map(MalVal *args) {
+ assert_type(args, MAL_LIST|MAL_VECTOR,
+ "hash-map called with non-sequential arguments");
+ assert((args->val.array->len % 2) == 0,
+ "odd number of parameters to hash-map");
+ GHashTable *htable = g_hash_table_new(g_str_hash, g_str_equal);
+ MalVal *hm = malval_new_hash_map(htable);
+ int i;
+ MalVal *k, *v;
+ for(i=0; i< args->val.array->len; i+=2) {
+ k = g_array_index(args->val.array, MalVal*, i);
+ assert_type(k, MAL_STRING,
+ "hash-map called with non-string key");
+ v = g_array_index(args->val.array, MalVal*, i+1);
+ g_hash_table_insert(htable, k->val.string, v);
+ }
+ return hm;
+}
+
+int _hash_map_Q(MalVal *seq) {
+ return seq->type & MAL_HASH_MAP;
+}
+MalVal *hash_map_Q(MalVal *seq) { return _hash_map_Q(seq) ? &mal_true : &mal_false; }
+
+// TODO: support multiple key/values
+MalVal *assoc(MalVal *hm, MalVal *key, MalVal *val) {
+ GHashTable *htable = g_hash_table_copy(hm->val.hash_table);
+ MalVal *new_hm = malval_new_hash_map(htable);
+ g_hash_table_insert(htable, key->val.string, val);
+ return new_hm;
+}
+
+// TODO: support multiple keys
+MalVal *dissoc(MalVal *hm, MalVal *key) {
+ GHashTable *htable = g_hash_table_copy(hm->val.hash_table);
+ MalVal *new_hm = malval_new_hash_map(htable);
+ g_hash_table_remove(htable, key->val.string);
+ return new_hm;
+}
+
+MalVal *keys(MalVal *obj) {
+ assert_type(obj, MAL_HASH_MAP,
+ "keys called on non-hash-map");
+
+ GHashTableIter iter;
+ gpointer key, value;
+ MalVal *seq = malval_new_list(MAL_LIST,
+ g_array_sized_new(TRUE, TRUE, sizeof(MalVal*),
+ _count(obj)));
+ g_hash_table_iter_init (&iter, obj->val.hash_table);
+ while (g_hash_table_iter_next (&iter, &key, &value)) {
+ MalVal *kname = malval_new_string((char *)key);
+ g_array_append_val(seq->val.array, kname);
+ }
+ return seq;
+}
+
+MalVal *vals(MalVal *obj) {
+ assert_type(obj, MAL_HASH_MAP,
+ "vals called on non-hash-map");
+
+ GHashTableIter iter;
+ gpointer key, value;
+ MalVal *seq = malval_new_list(MAL_LIST,
+ g_array_sized_new(TRUE, TRUE, sizeof(MalVal*),
+ _count(obj)));
+ g_hash_table_iter_init (&iter, obj->val.hash_table);
+ while (g_hash_table_iter_next (&iter, &key, &value)) {
+ g_array_append_val(seq->val.array, value);
+ }
+ return seq;
+}
+
+
+// Errors/Exceptions
+void _error(const char *fmt, ...) {
+ va_list args;
+ va_start(args, fmt);
+ mal_error = malval_new_string(g_strdup_vprintf(fmt, args));
+}
+void throw(MalVal *obj) {
+ mal_error = obj;
+}
+
+
+// Lists
+
+MalVal *_list(int count, ...) {
+ MalVal *seq = malval_new_list(MAL_LIST,
+ g_array_sized_new(TRUE, TRUE, sizeof(MalVal*),
+ count));
+ MalVal *v;
+ va_list ap;
+ va_start(ap, count);
+ while (count-- > 0) {
+ v = va_arg(ap, MalVal*);
+ g_array_append_val(seq->val.array, v);
+ }
+ va_end(ap);
+ return seq;
+}
+MalVal *list(MalVal *args) {
+ assert_type(args, MAL_LIST|MAL_VECTOR,
+ "list called with invalid arguments");
+ args->type = MAL_LIST;
+ return args;
+}
+
+int _list_Q(MalVal *seq) {
+ return seq->type & MAL_LIST;
+}
+MalVal *list_Q(MalVal *seq) { return _list_Q(seq) ? &mal_true : &mal_false; }
+
+
+// Vectors
+
+MalVal *_vector(int count, ...) {
+ MalVal *seq = malval_new_list(MAL_VECTOR,
+ g_array_sized_new(TRUE, TRUE, sizeof(MalVal*),
+ count));
+ MalVal *v;
+ va_list ap;
+ va_start(ap, count);
+ while (count-- > 0) {
+ v = va_arg(ap, MalVal*);
+ g_array_append_val(seq->val.array, v);
+ }
+ va_end(ap);
+ return seq;
+}
+MalVal *vector(MalVal *args) {
+ assert_type(args, MAL_LIST|MAL_VECTOR,
+ "vector called with invalid arguments");
+ args->type = MAL_VECTOR;
+ return args;
+}
+
+
+int _vector_Q(MalVal *seq) {
+ return seq->type & MAL_VECTOR;
+}
+MalVal *vector_Q(MalVal *seq) { return _vector_Q(seq) ? &mal_true : &mal_false; }
+
+
+// hash map and vector functions
+MalVal *get(MalVal *obj, MalVal *key) {
+ MalVal *val;
+ switch (obj->type) {
+ case MAL_VECTOR:
+ return _nth(obj, key->val.intnum);
+ case MAL_HASH_MAP:
+ if (g_hash_table_lookup_extended(obj->val.hash_table,
+ key->val.string,
+ NULL, (gpointer*)&val)) {
+ return val;
+ } else {
+ return &mal_nil;
+ }
+ default:
+ abort("get called on unsupported type %d", obj->type);
+ }
+}
+
+MalVal *contains_Q(MalVal *obj, MalVal *key) {
+ switch (obj->type) {
+ case MAL_VECTOR:
+ if (key->val.intnum < obj->val.array->len) {
+ return &mal_true;
+ } else {
+ return &mal_false;
+ }
+ case MAL_HASH_MAP:
+ if (g_hash_table_contains(obj->val.hash_table, key->val.string)) {
+ return &mal_true;
+ } else {
+ return &mal_false;
+ }
+ default:
+ abort("contains? called on unsupported type %d", obj->type);
+ }
+}
+
+
+// Atoms
+MalVal *atom(MalVal *val) {
+ return malval_new_atom(val);
+}
+
+int _atom_Q(MalVal *exp) {
+ return exp->type & MAL_ATOM;
+}
+MalVal *atom_Q(MalVal *exp) { return _atom_Q(exp) ? &mal_true : &mal_false; }
+
+MalVal *deref(MalVal *atm) {
+ assert_type(atm, MAL_ATOM,
+ "deref called on non-atom");
+ return atm->val.atom_val;
+}
+
+MalVal *reset_BANG(MalVal *atm, MalVal *val) {
+ assert_type(atm, MAL_ATOM,
+ "reset! called with non-atom");
+ atm->val.atom_val = val;
+ return val;
+}
+
+MalVal *swap_BANG(MalVal *args) {
+ assert_type(args, MAL_LIST|MAL_VECTOR,
+ "swap! called with invalid arguments");
+ assert(_count(args) >= 2,
+ "swap! called with %d args, needs at least 2", _count(args));
+ MalVal *atm = _nth(args, 0),
+ *f = _nth(args, 1),
+ *sargs = _slice(args, 2, _count(args)),
+ *fargs = cons(atm->val.atom_val, sargs),
+ *new_val = apply(f, fargs);
+ if (mal_error) { return NULL; }
+ atm->val.atom_val = new_val;
+ return new_val;
+}
+
+
+
+// Sequence functions
+MalVal *_slice(MalVal *seq, int start, int end) {
+ int i, new_len = max(0, min(end-start,
+ _count(seq)-start));
+ GArray *new_arr = g_array_sized_new(TRUE, TRUE, sizeof(MalVal*),
+ new_len);
+ for (i=start; i<start+new_len; i++) {
+ g_array_append_val(new_arr, g_array_index(seq->val.array, MalVal*, i));
+ }
+ return malval_new_list(MAL_LIST, new_arr);
+}
+
+
+int _sequential_Q(MalVal *seq) {
+ return seq->type & (MAL_LIST|MAL_VECTOR);
+}
+MalVal *sequential_Q(MalVal *seq) {
+ return _sequential_Q(seq) ? &mal_true : &mal_false;
+}
+
+MalVal *cons(MalVal *x, MalVal *seq) {
+ assert_type(seq, MAL_LIST|MAL_VECTOR,
+ "second argument to cons is non-sequential");
+ int i, len = _count(seq);
+ GArray *new_arr = g_array_sized_new(TRUE, TRUE, sizeof(MalVal*),
+ len+1);
+ g_array_append_val(new_arr, x);
+ for (i=0; i<len; i++) {
+ g_array_append_val(new_arr, g_array_index(seq->val.array, MalVal*, i));
+ }
+ return malval_new_list(MAL_LIST, new_arr);
+}
+
+MalVal *count(MalVal *seq) {
+ return malval_new_integer(_count(seq));
+}
+
+MalVal *empty_Q(MalVal *seq) {
+ assert_type(seq, MAL_LIST|MAL_VECTOR,
+ "empty? called with non-sequential");
+ return (seq->val.array->len == 0) ? &mal_true : &mal_false;
+}
+
+MalVal *concat(MalVal *args) {
+ MalVal *arg, *e, *lst;
+ int i, j, arg_cnt = _count(args);
+ lst = malval_new_list(MAL_LIST,
+ g_array_sized_new(TRUE, TRUE, sizeof(MalVal*), arg_cnt));
+ for (i=0; i<arg_cnt; i++) {
+ arg = g_array_index(args->val.array, MalVal*, i);
+ assert_type(arg, MAL_LIST|MAL_VECTOR,
+ "concat called with non-sequential");
+ for (j=0; j<_count(arg); j++) {
+ e = g_array_index(arg->val.array, MalVal*, j);
+ g_array_append_val(lst->val.array, e);
+ }
+ }
+
+ return lst;
+}
+
+MalVal *sconj(MalVal *args) {
+ assert_type(args, MAL_LIST|MAL_VECTOR,
+ "conj called with non-sequential");
+ MalVal *src_lst = _nth(args, 0);
+ assert_type(args, MAL_LIST|MAL_VECTOR,
+ "first argument to conj is non-sequential");
+ int i, len = _count(src_lst) + _count(args) - 1;
+ GArray *new_arr = g_array_sized_new(TRUE, TRUE, sizeof(MalVal*),
+ len);
+ for (i=1; i<len; i++) {
+ g_array_append_val(new_arr, g_array_index(args->val.array, MalVal*, i));
+ }
+ return malval_new_list(MAL_LIST, new_arr);
+}
+
+MalVal *first(MalVal *seq) {
+ assert_type(seq, MAL_LIST|MAL_VECTOR,
+ "first called with non-sequential");
+ if (_count(seq) == 0) {
+ return &mal_nil;
+ }
+ return g_array_index(seq->val.array, MalVal*, 0);
+}
+
+MalVal *last(MalVal *seq) {
+ assert_type(seq, MAL_LIST|MAL_VECTOR,
+ "last called with non-sequential");
+ if (_count(seq) == 0) {
+ return &mal_nil;
+ }
+ return g_array_index(seq->val.array, MalVal*, _count(seq)-1);
+}
+
+MalVal *rest(MalVal *seq) {
+ return _slice(seq, 1, _count(seq));
+}
+
+MalVal *_nth(MalVal *seq, int idx) {
+ assert_type(seq, MAL_LIST|MAL_VECTOR,
+ "nth called with non-sequential");
+ if (idx >= _count(seq)) {
+ return &mal_nil;
+ }
+ return g_array_index(seq->val.array, MalVal*, idx);
+}
+MalVal *nth(MalVal *seq, MalVal *idx) {
+ return _nth(seq, idx->val.intnum);
+}
+
+MalVal *_map2(MalVal *(*func)(void*, void*), MalVal *lst, void *arg2) {
+ MalVal *e, *el;
+ assert_type(lst, MAL_LIST|MAL_VECTOR,
+ "_map called with non-sequential");
+ int i, len = _count(lst);
+ el = malval_new_list(MAL_LIST,
+ g_array_sized_new(TRUE, TRUE, sizeof(MalVal*), len));
+ for (i=0; i<len; i++) {
+ e = func(g_array_index(lst->val.array, MalVal*, i), arg2);
+ if (!e || mal_error) return NULL;
+ g_array_append_val(el->val.array, e);
+ }
+ return el;
+}
+
+MalVal *map(MalVal *mvf, MalVal *lst) {
+ MalVal *res, *el;
+ assert_type(mvf, MAL_FUNCTION_C|MAL_FUNCTION_MAL,
+ "map called with non-function");
+ assert_type(lst, MAL_LIST|MAL_VECTOR,
+ "map called with non-sequential");
+ int i, len = _count(lst);
+ el = malval_new_list(MAL_LIST,
+ g_array_sized_new(TRUE, TRUE, sizeof(MalVal*), len));
+ for (i=0; i<len; i++) {
+ // TODO: this is replicating some of apply functionality
+ if (mvf->type & MAL_FUNCTION_MAL) {
+ Env *fn_env = new_env(mvf->val.func.env,
+ mvf->val.func.args,
+ _slice(lst, i, i+1));
+ res = mvf->val.func.evaluator(mvf->val.func.body, fn_env);
+ } else {
+ res = mvf->val.f1(g_array_index(lst->val.array, MalVal*, i));
+ }
+ if (!res || mal_error) return NULL;
+ g_array_append_val(el->val.array, res);
+ }
+ return el;
+}
+
+
+// Env
+
+Env *new_env(Env *outer, MalVal* binds, MalVal *exprs) {
+ Env *e = malloc(sizeof(Env));
+ e->table = g_hash_table_new(g_str_hash, g_str_equal);
+ e->outer = outer;
+
+ if (binds && exprs) {
+ assert_type(binds, MAL_LIST|MAL_VECTOR,
+ "new_env called with non-sequential bindings");
+ assert_type(exprs, MAL_LIST|MAL_VECTOR,
+ "new_env called with non-sequential expressions");
+ int binds_len = _count(binds),
+ exprs_len = _count(exprs),
+ varargs = 0, i;
+ for (i=0; i<binds_len; i++) {
+ if (i > exprs_len) { break; }
+ if (_nth(binds, i)->val.string[0] == '&') {
+ varargs = 1;
+ env_set(e, _nth(binds, i+1)->val.string, _slice(exprs, i, _count(exprs)));
+ break;
+ } else {
+ env_set(e, _nth(binds, i)->val.string, _nth(exprs, i));
+ }
+ }
+ assert(varargs || (binds_len == exprs_len),
+ "Arity mismatch: %d formal params vs %d actual params",
+ binds_len, exprs_len);
+
+ }
+ return e;
+}
+
+Env *env_find(Env *env, char *key) {
+ void *val = g_hash_table_lookup(env->table, key);
+ if (val) {
+ return env;
+ } else if (env->outer) {
+ return env_find(env->outer, key);
+ } else {
+ return NULL;
+ }
+}
+
+MalVal *env_get(Env *env, char *key) {
+ Env *e = env_find(env, key);
+ assert(e, "'%s' not found", key);
+ return g_hash_table_lookup(e->table, key);
+}
+
+Env *env_set(Env *env, char *key, MalVal *val) {
+ g_hash_table_insert(env->table, key, val);
+ return env;
+}
+
+types_ns_entry types_ns[49] = {
+ {"pr-str", (void*(*)(void*))pr_str, -1},
+ {"str", (void*(*)(void*))str, -1},
+ {"prn", (void*(*)(void*))prn, -1},
+ {"println", (void*(*)(void*))println, -1},
+ {"with-meta", (void*(*)(void*))with_meta, 2},
+ {"meta", (void*(*)(void*))meta, 1},
+ {"=", (void*(*)(void*))equal_Q, 2},
+ {"symbol?", (void*(*)(void*))symbol_Q, 1},
+ {"nil?", (void*(*)(void*))nil_Q, 1},
+ {"true?", (void*(*)(void*))true_Q, 1},
+ {"false?", (void*(*)(void*))false_Q, 1},
+ {"+", (void*(*)(void*))int_plus, 2},
+ {"-", (void*(*)(void*))int_minus, 2},
+ {"*", (void*(*)(void*))int_multiply, 2},
+ {"/", (void*(*)(void*))int_divide, 2},
+ {">", (void*(*)(void*))int_gt, 2},
+ {">=", (void*(*)(void*))int_gte, 2},
+ {"<", (void*(*)(void*))int_lt, 2},
+ {"<=", (void*(*)(void*))int_lte, 2},
+ {"hash-map", (void*(*)(void*))hash_map, -1},
+ {"map?", (void*(*)(void*))hash_map_Q, 1},
+ {"assoc", (void*(*)(void*))assoc, 3},
+ {"dissoc", (void*(*)(void*))dissoc, 2},
+ {"get", (void*(*)(void*))get, 2},
+ {"contains?", (void*(*)(void*))contains_Q, 2},
+ {"keys", (void*(*)(void*))keys, 1},
+ {"vals", (void*(*)(void*))vals, 1},
+ {"throw", (void*(*)(void*))throw, 1},
+ {"list", (void*(*)(void*))list, -1},
+ {"list?", (void*(*)(void*))list_Q, 1},
+ {"vector", (void*(*)(void*))vector, -1},
+ {"vector?", (void*(*)(void*))vector_Q, 1},
+ {"atom", (void*(*)(void*))atom, 1},
+ {"atom?", (void*(*)(void*))atom_Q, 1},
+ {"deref", (void*(*)(void*))deref, 1},
+ {"reset!", (void*(*)(void*))reset_BANG, 2},
+ {"swap!", (void*(*)(void*))swap_BANG, -1},
+ {"sequential?", (void*(*)(void*))sequential_Q, 1},
+ {"cons", (void*(*)(void*))cons, 2},
+ {"count", (void*(*)(void*))count, 1},
+ {"empty?", (void*(*)(void*))empty_Q, 1},
+ {"concat", (void*(*)(void*))concat, -1},
+ {"conj", (void*(*)(void*))sconj, -1},
+ {"first", (void*(*)(void*))first, 1},
+ {"last", (void*(*)(void*))last, 1},
+ {"rest", (void*(*)(void*))rest, 1},
+ {"nth", (void*(*)(void*))nth, 2},
+ {"apply", (void*(*)(void*))apply, 2},
+ {"map", (void*(*)(void*))map, 2},
+ };
diff --git a/c/types.h b/c/types.h
new file mode 100644
index 0000000..271a899
--- /dev/null
+++ b/c/types.h
@@ -0,0 +1,162 @@
+#ifndef __MAL_TYPES__
+#define __MAL_TYPES__
+
+#include <glib.h>
+
+// State
+
+struct MalVal; // pre-declare
+extern struct MalVal *mal_error;
+
+#define abort(format, ...) \
+ { _error(format, ##__VA_ARGS__); return NULL; }
+
+#define assert(test, format, ...) \
+ if (!(test)) { \
+ _error(format, ##__VA_ARGS__); \
+ return NULL; \
+ }
+
+#define assert_type(mv, typ, format, ...) \
+ if (!(mv->type & (typ))) { \
+ _error(format, ##__VA_ARGS__); \
+ return NULL; \
+ }
+
+typedef enum {
+ MAL_NIL = 1,
+ MAL_TRUE = 2,
+ MAL_FALSE = 4,
+ MAL_INTEGER = 8,
+ MAL_FLOAT = 16,
+ MAL_SYMBOL = 32,
+ MAL_STRING = 64,
+ MAL_LIST = 128,
+ MAL_VECTOR = 256,
+ MAL_HASH_MAP = 512,
+ MAL_ATOM = 1024,
+ MAL_FUNCTION_C = 2048,
+ MAL_FUNCTION_MAL = 4096,
+} MalType;
+
+
+// Predeclare Env
+typedef struct Env Env;
+
+typedef struct MalVal {
+ MalType type;
+ struct MalVal *metadata;
+ union {
+ gint64 intnum;
+ gdouble floatnum;
+ char *string;
+ GArray *array;
+ GHashTable *hash_table;
+ struct MalVal *atom_val;
+ void *(*f0) ();
+ void *(*f1) (void*);
+ void *(*f2) (void*,void*);
+ void *(*f3) (void*,void*,void*);
+ void *(*f4) (void*,void*,void*,void*);
+ void *(*f5) (void*,void*,void*,void*,void*);
+ void *(*f6) (void*,void*,void*,void*,void*,void*);
+ void *(*f7) (void*,void*,void*,void*,void*,void*,void*);
+ void *(*f8) (void*,void*,void*,void*,void*,void*,void*,void*);
+ void *(*f9) (void*,void*,void*,void*,void*,void*,void*,void*,void*);
+ void *(*f10)(void*,void*,void*,void*,void*,void*,void*,void*,void*,void*);
+ void *(*f11)(void*,void*,void*,void*,void*,void*,void*,void*,void*,void*,
+ void*);
+ void *(*f12)(void*,void*,void*,void*,void*,void*,void*,void*,void*,void*,
+ void*,void*);
+ void *(*f13)(void*,void*,void*,void*,void*,void*,void*,void*,void*,void*,
+ void*,void*,void*);
+ void *(*f14)(void*,void*,void*,void*,void*,void*,void*,void*,void*,void*,
+ void*,void*,void*,void*);
+ void *(*f15)(void*,void*,void*,void*,void*,void*,void*,void*,void*,void*,
+ void*,void*,void*,void*,void*);
+ void *(*f16)(void*,void*,void*,void*,void*,void*,void*,void*,void*,void*,
+ void*,void*,void*,void*,void*,void*);
+ void *(*f17)(void*,void*,void*,void*,void*,void*,void*,void*,void*,void*,
+ void*,void*,void*,void*,void*,void*,void*);
+ void *(*f18)(void*,void*,void*,void*,void*,void*,void*,void*,void*,void*,
+ void*,void*,void*,void*,void*,void*,void*,void*);
+ void *(*f19)(void*,void*,void*,void*,void*,void*,void*,void*,void*,void*,
+ void*,void*,void*,void*,void*,void*,void*,void*,void*);
+ void *(*f20)(void*,void*,void*,void*,void*,void*,void*,void*,void*,void*,
+ void*,void*,void*,void*,void*,void*,void*,void*,void*,void*);
+ struct {
+ struct MalVal *(*evaluator)(struct MalVal *, Env *);
+ struct MalVal *args;
+ struct MalVal *body;
+ struct Env *env;
+ } func;
+ } val;
+ int func_arg_cnt;
+ int ismacro;
+} MalVal;
+
+// Constants
+
+extern MalVal mal_nil;
+extern MalVal mal_true;
+extern MalVal mal_false;
+
+
+// Declare functions used internally (by other C code).
+// Mal visible functions are "exported" in types_ns
+
+MalVal *malval_new(MalType type, MalVal *metadata);
+int malval_free(MalVal *mv);
+MalVal *malval_new_integer(gint64 val);
+MalVal *malval_new_float(gdouble val);
+MalVal *malval_new_string(char *val);
+MalVal *malval_new_symbol(char *val);
+MalVal *malval_new_list(MalType type, GArray *val);
+MalVal *malval_new_function(void *(*func)(void *), int arg_cnt, MalVal* metadata);
+
+MalVal *hash_map(MalVal *args);
+void _error(const char *fmt, ...);
+MalVal *_list(int count, ...);
+
+MalVal *apply(MalVal *f, MalVal *el);
+
+char *_pr_str(MalVal *args, int print_readably);
+
+MalVal *first(MalVal* seq);
+MalVal *last(MalVal* seq);
+MalVal *_slice(MalVal *seq, int start, int end);
+MalVal *_nth(MalVal *seq, int idx);
+MalVal *rest(MalVal *seq);
+
+MalVal *_map2(MalVal *(*func)(void*, void*), MalVal *lst, void *arg2);
+
+// These are just used by step2 and step3 before then type_ns environment is
+// imported
+
+MalVal *int_plus(MalVal *a, MalVal *b);
+MalVal *int_minus(MalVal *a, MalVal *b);
+MalVal *int_multiply(MalVal *a, MalVal *b);
+MalVal *int_divide(MalVal *a, MalVal *b);
+
+// Env
+
+typedef struct Env {
+ struct Env *outer;
+ GHashTable *table;
+} Env;
+
+Env *new_env(Env *outer, MalVal* binds, MalVal *exprs);
+Env *env_find(Env *env, char *key);
+MalVal *env_get(Env *env, char *key);
+Env *env_set(Env *env, char *key, MalVal *val);
+
+// namespace of type functions
+typedef struct {
+ char *name;
+ void *(*func)(void*);
+ int arg_cnt;
+} types_ns_entry;
+
+extern types_ns_entry types_ns[49];
+
+#endif
diff --git a/clojure/Makefile b/clojure/Makefile
new file mode 100644
index 0000000..d18eb50
--- /dev/null
+++ b/clojure/Makefile
@@ -0,0 +1,17 @@
+
+TESTS =
+
+SOURCES = src/types.clj src/readline.clj src/reader.clj src/stepA_more.clj
+
+all:
+
+.PHONY: stats tests $(TESTS)
+
+stats: $(SOURCES)
+ @wc $^
+
+tests: $(TESTS)
+
+$(TESTS):
+ @echo "Running $@"; \
+ lein with-profile XXX$@XXX trampoline run || exit 1; \
diff --git a/clojure/project.clj b/clojure/project.clj
new file mode 100644
index 0000000..4e7a15f
--- /dev/null
+++ b/clojure/project.clj
@@ -0,0 +1,25 @@
+(defproject mal "0.0.1-SNAPSHOT"
+ :description "Make-A-Lisp"
+
+ :dependencies [[org.clojure/clojure "1.5.1"]
+ [org.clojure/tools.reader "0.8.3"]
+ [net.n01se/clojure-jna "1.0.0"]]
+
+ ;; To run a step with correct readline behavior:
+ ;; lein trampoline with-profile stepX run
+ ;; To load step in repl:
+ ;; lein with-profile +stepX repl
+ :profiles {:step0 {:main step0-repl}
+ :step1 {:main step1-read-print}
+ :step2 {:main step2-eval}
+ :step3 {:main step3-env}
+ :step4 {:main step4-if-fn-do}
+ :step5 {:main step5-tco}
+ :step6 {:main step6-file}
+ :step7 {:main step7-quote}
+ :step8 {:main step8-macros}
+ :step9 {:main step9-interop}
+ :stepA {:main stepA-more}}
+
+ :main stepA-more)
+
diff --git a/clojure/src/reader.clj b/clojure/src/reader.clj
new file mode 100644
index 0000000..8f14767
--- /dev/null
+++ b/clojure/src/reader.clj
@@ -0,0 +1,32 @@
+(ns reader
+ (:refer-clojure :exclude [read-string])
+ (:require [clojure.tools.reader :as r]
+ [clojure.tools.reader.reader-types :as rt]))
+
+;; change tools.reader syntax-quote to quasiquote
+(defn- wrap [sym]
+ (fn [rdr _] (list sym (#'r/read rdr true nil true))))
+
+(defn- wrap-with [sym]
+ (fn [rdr arg _] (list sym (#'r/read rdr true nil true) arg)))
+
+;; Override some tools.reader reader macros so that we can do our own
+;; metadata and quasiquote handling
+(alter-var-root #'r/macros
+ (fn [f]
+ (fn [ch]
+ (case ch
+ \` (wrap 'quasiquote)
+ \~ (fn [rdr comma]
+ (if-let [ch (rt/peek-char rdr)]
+ (if (identical? \@ ch)
+ ((wrap 'splice-unquote) (doto rdr rt/read-char) \@)
+ ((wrap 'unquote) rdr \~))))
+ \^ (fn [rdr comma]
+ (let [m (#'r/read rdr)]
+ ((wrap-with 'with-meta) rdr m \^)))
+ \@ (wrap 'deref)
+ (f ch)))))
+
+(defn read-string [s]
+ (r/read-string s))
diff --git a/clojure/src/readline.clj b/clojure/src/readline.clj
new file mode 100644
index 0000000..dbd4872
--- /dev/null
+++ b/clojure/src/readline.clj
@@ -0,0 +1,36 @@
+(ns readline
+ (:require [clojure.string :refer [split]]
+ [net.n01se.clojure-jna :as jna]))
+
+(defonce history-loaded (atom nil))
+(def HISTORY-FILE "/home/joelm/.mal-history")
+
+;;
+;; Uncomment one of the following readline libraries
+;;
+
+;; editline (BSD)
+#_
+(do
+ (def readline-call (jna/to-fn String edit/readline))
+ (def add-history (jna/to-fn Void edit/add_history))
+ (def load-history #(doseq [line (split (slurp %) #"\n")]
+ (jna/invoke Void edit/add_history line))))
+
+;; GNU Readline (GPL)
+;; WARNING: distributing your code with GNU readline enabled means you
+;; must release your program as GPL
+;#_
+(do
+ (def readline-call (jna/to-fn String readline/readline))
+ (def add-history (jna/to-fn Void readline/add_history))
+ (def load-history (jna/to-fn Integer readline/read_history)))
+
+(defn readline [prompt & [lib]]
+ (if (not @history-loaded)
+ (load-history HISTORY-FILE))
+ (let [line (readline-call prompt)]
+ (when line
+ (add-history line)
+ (spit HISTORY-FILE (str line "\n") :append true))
+ line))
diff --git a/clojure/src/step0_repl.clj b/clojure/src/step0_repl.clj
new file mode 100644
index 0000000..7a050c7
--- /dev/null
+++ b/clojure/src/step0_repl.clj
@@ -0,0 +1,26 @@
+(ns step0-repl
+ (:require [readline]))
+
+
+;; read
+(defn READ [& [strng]]
+ (let [line (if strng strng (read-line))]
+ strng))
+
+;; eval
+(defn EVAL [ast env]
+ (eval (read-string ast)))
+
+;; print
+(defn PRINT [exp]
+ exp)
+
+;; repl
+(defn rep [strng] (PRINT (EVAL (READ strng), {})))
+
+(defn -main [& args]
+ (loop []
+ (let [line (readline/readline "user> ")]
+ (when line
+ (println (rep line))
+ (recur)))))
diff --git a/clojure/src/step1_read_print.clj b/clojure/src/step1_read_print.clj
new file mode 100644
index 0000000..a99a0ed
--- /dev/null
+++ b/clojure/src/step1_read_print.clj
@@ -0,0 +1,33 @@
+(ns step1-read-print
+ (:require [clojure.repl]
+ [types]
+ [readline]
+ [reader]))
+
+;; read
+(defn READ [& [strng]]
+ (let [line (if strng strng (read-line))]
+ (reader/read-string strng)))
+
+;; eval
+(defn EVAL [ast env]
+ ast)
+
+;; print
+(defn PRINT [exp] (pr-str exp))
+
+;; repl
+(defn rep
+ [strng]
+ (PRINT (EVAL (READ strng), {})))
+
+(defn -main [& args]
+ (loop []
+ (let [line (readline/readline "user> ")]
+ (when line
+ (when-not (re-seq #"^\s*$|^\s*;.*$" line) ; blank/comment
+ (try
+ (println (rep line))
+ (catch Throwable e
+ (clojure.repl/pst e))))
+ (recur)))))
diff --git a/clojure/src/step2_eval.clj b/clojure/src/step2_eval.clj
new file mode 100644
index 0000000..6ff9eb3
--- /dev/null
+++ b/clojure/src/step2_eval.clj
@@ -0,0 +1,61 @@
+(ns step2-eval
+ (:require [clojure.repl]
+ [types]
+ [readline]
+ [reader]))
+
+(declare EVAL)
+
+;; read
+(defn READ [& [strng]]
+ (let [line (if strng strng (read-line))]
+ (reader/read-string strng)))
+
+;; eval
+(defn eval-ast [ast env]
+ (cond
+ (symbol? ast) (or (get env ast)
+ (throw (Error. (str ast " not found"))))
+
+ (seq? ast) (doall (map #(EVAL % env) ast))
+
+ (vector? ast) (vec (doall (map #(EVAL % env) ast)))
+
+ (map? ast) (apply hash-map (doall (map #(EVAL % env)
+ (mapcat identity ast))))
+
+ :else ast))
+
+(defn EVAL [ast env]
+ ;;(prn "EVAL" ast (keys @env)) (flush)
+ (if (not (seq? ast))
+ (eval-ast ast env)
+
+ ;; apply list
+ (let [el (eval-ast ast env)
+ f (first el)
+ args (rest el)]
+ (apply f args))))
+
+;; print
+(defn PRINT [exp] (pr-str exp))
+
+;; repl
+(def repl-env {'+ +
+ '- -
+ '* *
+ '/ /})
+(defn rep
+ [strng]
+ (PRINT (EVAL (READ strng), repl-env)))
+
+(defn -main [& args]
+ (loop []
+ (let [line (readline/readline "user> ")]
+ (when line
+ (when-not (re-seq #"^\s*$|^\s*;.*$" line) ; blank/comment
+ (try
+ (println (rep line))
+ (catch Throwable e
+ (clojure.repl/pst e))))
+ (recur)))))
diff --git a/clojure/src/step3_env.clj b/clojure/src/step3_env.clj
new file mode 100644
index 0000000..c0c4e8e
--- /dev/null
+++ b/clojure/src/step3_env.clj
@@ -0,0 +1,76 @@
+(ns step3-env
+ (:require [clojure.repl]
+ [types]
+ [readline]
+ [reader]))
+
+(declare EVAL)
+
+;; read
+(defn READ [& [strng]]
+ (let [line (if strng strng (read-line))]
+ (reader/read-string strng)))
+
+;; eval
+(defn eval-ast [ast env]
+ (cond
+ (symbol? ast) (types/env-get env ast)
+
+ (seq? ast) (doall (map #(EVAL % env) ast))
+
+ (vector? ast) (vec (doall (map #(EVAL % env) ast)))
+
+ (map? ast) (apply hash-map (doall (map #(EVAL % env)
+ (mapcat identity ast))))
+
+ :else ast))
+
+(defn EVAL [ast env]
+ ;;(prn "EVAL" ast (keys @env)) (flush)
+ (if (not (seq? ast))
+ (eval-ast ast env)
+
+ ;; apply list
+ (let [[a0 a1 a2 a3] ast]
+ (condp = a0
+ 'def!
+ (types/env-set env a1 (EVAL a2 env))
+
+ 'let*
+ (let [let-env (types/env env)]
+ (doseq [[b e] (partition 2 a1)]
+ (types/env-set let-env b (EVAL e let-env)))
+ (EVAL a2 let-env))
+
+ ;; apply
+ (let [el (eval-ast ast env)
+ f (first el)
+ args (rest el)]
+ (apply f args))))))
+
+;; print
+(defn PRINT [exp] (pr-str exp))
+
+;; repl
+(def repl-env (types/env))
+(defn rep
+ [strng]
+ (PRINT (EVAL (READ strng), repl-env)))
+
+(defn _ref [k,v] (types/env-set repl-env k v))
+(_ref '+ +)
+(_ref '- -)
+(_ref '* *)
+(_ref '/ /)
+
+
+(defn -main [& args]
+ (loop []
+ (let [line (readline/readline "user> ")]
+ (when line
+ (when-not (re-seq #"^\s*$|^\s*;.*$" line) ; blank/comment
+ (try
+ (println (rep line))
+ (catch Throwable e
+ (clojure.repl/pst e))))
+ (recur)))))
diff --git a/clojure/src/step4_if_fn_do.clj b/clojure/src/step4_if_fn_do.clj
new file mode 100644
index 0000000..4171848
--- /dev/null
+++ b/clojure/src/step4_if_fn_do.clj
@@ -0,0 +1,92 @@
+(ns step4-if-fn-do
+ (:require [clojure.repl]
+ [types]
+ [readline]
+ [reader]))
+
+(declare EVAL)
+
+;; read
+(defn READ [& [strng]]
+ (let [line (if strng strng (read-line))]
+ (reader/read-string strng)))
+
+;; eval
+(defn eval-ast [ast env]
+ (cond
+ (symbol? ast) (types/env-get env ast)
+
+ (seq? ast) (doall (map #(EVAL % env) ast))
+
+ (vector? ast) (vec (doall (map #(EVAL % env) ast)))
+
+ (map? ast) (apply hash-map (doall (map #(EVAL % env)
+ (mapcat identity ast))))
+
+ :else ast))
+
+(defn EVAL [ast env]
+ ;;(prn "EVAL" ast (keys @env)) (flush)
+ (if (not (seq? ast))
+ (eval-ast ast env)
+
+ ;; apply list
+ (let [[a0 a1 a2 a3] ast]
+ (condp = a0
+ 'def!
+ (types/env-set env a1 (EVAL a2 env))
+
+ 'let*
+ (let [let-env (types/env env)]
+ (doseq [[b e] (partition 2 a1)]
+ (types/env-set let-env b (EVAL e let-env)))
+ (EVAL a2 let-env))
+
+ 'do
+ (last (eval-ast (rest ast) env))
+
+ 'if
+ (let [cond (EVAL a1 env)]
+ (if (or (= cond nil) (= cond false))
+ (if (> (count ast) 2)
+ (EVAL a3 env)
+ nil)
+ (EVAL a2 env)))
+
+ 'fn*
+ (fn [& args]
+ (EVAL a2 (types/env env a1 args)))
+
+ ;; apply
+ (let [el (eval-ast ast env)
+ f (first el)
+ args (rest el)]
+ (apply f args))))))
+
+;; print
+(defn PRINT [exp] (pr-str exp))
+
+;; repl
+(def repl-env (types/env))
+(defn rep
+ [strng]
+ (PRINT (EVAL (READ strng), repl-env)))
+
+(defn _ref [k,v] (types/env-set repl-env k v))
+
+;; Import types related functions
+(doseq [[k v] types/types_ns] (_ref k v))
+
+;; Defined using the language itself
+(rep "(def! not (fn* [a] (if a false true)))")
+
+(defn -main [& args]
+ (loop []
+ (let [line (readline/readline "user> ")]
+ (when line
+ (when-not (re-seq #"^\s*$|^\s*;.*$" line) ; blank/comment
+ (try
+ (println (rep line))
+ (catch Throwable e
+ (clojure.repl/pst e))))
+ (recur)))))
diff --git a/clojure/src/step5_tco.clj b/clojure/src/step5_tco.clj
new file mode 100644
index 0000000..2ed07b4
--- /dev/null
+++ b/clojure/src/step5_tco.clj
@@ -0,0 +1,101 @@
+(ns step5-tco
+ (:require [clojure.repl]
+ [types]
+ [readline]
+ [reader]))
+
+(declare EVAL)
+
+;; read
+(defn READ [& [strng]]
+ (let [line (if strng strng (read-line))]
+ (reader/read-string strng)))
+
+;; eval
+(defn eval-ast [ast env]
+ (cond
+ (symbol? ast) (types/env-get env ast)
+
+ (seq? ast) (doall (map #(EVAL % env) ast))
+
+ (vector? ast) (vec (doall (map #(EVAL % env) ast)))
+
+ (map? ast) (apply hash-map (doall (map #(EVAL % env)
+ (mapcat identity ast))))
+
+ :else ast))
+
+(defn EVAL [ast env]
+ (loop [ast ast
+ env env]
+ ;;(prn "EVAL" ast (keys @env)) (flush)
+ (if (not (seq? ast))
+ (eval-ast ast env)
+
+ ;; apply list
+ (let [[a0 a1 a2 a3] ast]
+ (condp = a0
+ 'def!
+ (types/env-set env a1 (EVAL a2 env))
+
+ 'let*
+ (let [let-env (types/env env)]
+ (doseq [[b e] (partition 2 a1)]
+ (types/env-set let-env b (EVAL e let-env)))
+ (EVAL a2 let-env))
+
+ 'do
+ (do (eval-ast (->> ast (drop-last) (drop 1)) env)
+ (recur (last ast) env))
+
+ 'if
+ (let [cond (EVAL a1 env)]
+ (if (or (= cond nil) (= cond false))
+ (if (> (count ast) 2)
+ (recur a3 env)
+ nil)
+ (recur a2 env)))
+
+ 'fn*
+ ^{:expression a2
+ :environment env
+ :parameters a1}
+ (fn [& args]
+ (EVAL a2 (types/env env a1 args)))
+
+ ;; apply
+ (let [el (eval-ast ast env)
+ f (first el)
+ args (rest el)
+ {:keys [expression environment parameters]} (meta f)]
+ (if expression
+ (recur expression (types/env environment parameters args))
+ (apply f args))))))))
+
+;; print
+(defn PRINT [exp] (pr-str exp))
+
+;; repl
+(def repl-env (types/env))
+(defn rep
+ [strng]
+ (PRINT (EVAL (READ strng), repl-env)))
+
+(defn _ref [k,v] (types/env-set repl-env k v))
+
+;; Import types related functions
+(doseq [[k v] types/types_ns] (_ref k v))
+
+;; Defined using the language itself
+(rep "(def! not (fn* [a] (if a false true)))")
+
+(defn -main [& args]
+ (loop []
+ (let [line (readline/readline "user> ")]
+ (when line
+ (when-not (re-seq #"^\s*$|^\s*;.*$" line) ; blank/comment
+ (try
+ (println (rep line))
+ (catch Throwable e
+ (clojure.repl/pst e))))
+ (recur)))))
diff --git a/clojure/src/step6_file.clj b/clojure/src/step6_file.clj
new file mode 100644
index 0000000..80eedef
--- /dev/null
+++ b/clojure/src/step6_file.clj
@@ -0,0 +1,109 @@
+(ns step6-file
+ (:require [clojure.repl]
+ [types]
+ [readline]
+ [reader]))
+
+(declare EVAL)
+
+;; read
+(defn READ [& [strng]]
+ (let [line (if strng strng (read-line))]
+ (reader/read-string strng)))
+
+;; eval
+(defn eval-ast [ast env]
+ (cond
+ (symbol? ast) (types/env-get env ast)
+
+ (seq? ast) (doall (map #(EVAL % env) ast))
+
+ (vector? ast) (vec (doall (map #(EVAL % env) ast)))
+
+ (map? ast) (apply hash-map (doall (map #(EVAL % env)
+ (mapcat identity ast))))
+
+ :else ast))
+
+(defn EVAL [ast env]
+ (loop [ast ast
+ env env]
+ ;;(prn "EVAL" ast (keys @env)) (flush)
+ (if (not (seq? ast))
+ (eval-ast ast env)
+
+ ;; apply list
+ (let [[a0 a1 a2 a3] ast]
+ (condp = a0
+ 'def!
+ (types/env-set env a1 (EVAL a2 env))
+
+ 'let*
+ (let [let-env (types/env env)]
+ (doseq [[b e] (partition 2 a1)]
+ (types/env-set let-env b (EVAL e let-env)))
+ (EVAL a2 let-env))
+
+ 'do
+ (do (eval-ast (->> ast (drop-last) (drop 1)) env)
+ (recur (last ast) env))
+
+ 'if
+ (let [cond (EVAL a1 env)]
+ (if (or (= cond nil) (= cond false))
+ (if (> (count ast) 2)
+ (recur a3 env)
+ nil)
+ (recur a2 env)))
+
+ 'fn*
+ ^{:expression a2
+ :environment env
+ :parameters a1}
+ (fn [& args]
+ (EVAL a2 (types/env env a1 args)))
+
+ ;; apply
+ (let [el (eval-ast ast env)
+ f (first el)
+ args (rest el)
+ {:keys [expression environment parameters]} (meta f)]
+ (if expression
+ (recur expression (types/env environment parameters args))
+ (apply f args))))))))
+
+;; print
+(defn PRINT [exp] (pr-str exp))
+
+;; repl
+(def repl-env (types/env))
+(defn rep
+ [strng]
+ (PRINT (EVAL (READ strng), repl-env)))
+
+(defn _ref [k,v] (types/env-set repl-env k v))
+
+;; Import types related functions
+(doseq [[k v] types/types_ns] (_ref k v))
+
+;; Defined using the language itself
+(_ref 'read-string reader/read-string)
+(_ref 'eval (fn [ast] (EVAL ast repl-env)))
+(_ref 'slurp slurp)
+(_ref 'slurp-do (fn [f] (str "(do " (slurp f) ")")))
+
+(rep "(def! not (fn* [a] (if a false true)))")
+(rep "(def! load-file (fn* [f] (eval (read-string (slurp-do f)))))")
+
+(defn -main [& args]
+ (if args
+ (rep (str "(load-file \"" (first args) "\")"))
+ (loop []
+ (let [line (readline/readline "user> ")]
+ (when line
+ (when-not (re-seq #"^\s*$|^\s*;.*$" line) ; blank/comment
+ (try
+ (println (rep line))
+ (catch Throwable e
+ (clojure.repl/pst e))))
+ (recur))))))
diff --git a/clojure/src/step7_quote.clj b/clojure/src/step7_quote.clj
new file mode 100644
index 0000000..8f190dd
--- /dev/null
+++ b/clojure/src/step7_quote.clj
@@ -0,0 +1,132 @@
+(ns step7-quote
+ (:require [clojure.repl]
+ [types]
+ [readline]
+ [reader]))
+
+(declare EVAL)
+
+;; read
+(defn READ [& [strng]]
+ (let [line (if strng strng (read-line))]
+ (reader/read-string strng)))
+
+;; eval
+(defn is-pair [x]
+ (and (sequential? x) (> (count x) 0)))
+
+(defn quasiquote [ast]
+ (cond
+ (not (is-pair ast))
+ (list 'quote ast)
+
+ (= 'unquote (first ast))
+ (second ast)
+
+ (and (is-pair (first ast)) (= 'splice-unquote (ffirst ast)))
+ (list 'concat (-> ast first second) (quasiquote (rest ast)))
+
+ :else
+ (list 'cons (quasiquote (first ast)) (quasiquote (rest ast)))))
+
+(defn eval-ast [ast env]
+ (cond
+ (symbol? ast) (types/env-get env ast)
+
+ (seq? ast) (doall (map #(EVAL % env) ast))
+
+ (vector? ast) (vec (doall (map #(EVAL % env) ast)))
+
+ (map? ast) (apply hash-map (doall (map #(EVAL % env)
+ (mapcat identity ast))))
+
+ :else ast))
+
+(defn EVAL [ast env]
+ (loop [ast ast
+ env env]
+ ;;(prn "EVAL" ast (keys @env)) (flush)
+ (if (not (seq? ast))
+ (eval-ast ast env)
+
+ ;; apply list
+ (let [[a0 a1 a2 a3] ast]
+ (condp = a0
+ 'def!
+ (types/env-set env a1 (EVAL a2 env))
+
+ 'let*
+ (let [let-env (types/env env)]
+ (doseq [[b e] (partition 2 a1)]
+ (types/env-set let-env b (EVAL e let-env)))
+ (EVAL a2 let-env))
+
+ 'quote
+ a1
+
+ 'quasiquote
+ (EVAL (quasiquote a1) env)
+
+ 'do
+ (do (eval-ast (->> ast (drop-last) (drop 1)) env)
+ (recur (last ast) env))
+
+ 'if
+ (let [cond (EVAL a1 env)]
+ (if (or (= cond nil) (= cond false))
+ (if (> (count ast) 2)
+ (recur a3 env)
+ nil)
+ (recur a2 env)))
+
+ 'fn*
+ ^{:expression a2
+ :environment env
+ :parameters a1}
+ (fn [& args]
+ (EVAL a2 (types/env env a1 args)))
+
+ ;; apply
+ (let [el (eval-ast ast env)
+ f (first el)
+ args (rest el)
+ {:keys [expression environment parameters]} (meta f)]
+ (if expression
+ (recur expression (types/env environment parameters args))
+ (apply f args))))))))
+
+;; print
+(defn PRINT [exp] (pr-str exp))
+
+;; repl
+(def repl-env (types/env))
+(defn rep
+ [strng]
+ (PRINT (EVAL (READ strng), repl-env)))
+
+(defn _ref [k,v] (types/env-set repl-env k v))
+
+;; Import types related functions
+(doseq [[k v] types/types_ns] (_ref k v))
+
+;; Defined using the language itself
+(_ref 'read-string reader/read-string)
+(_ref 'eval (fn [ast] (EVAL ast repl-env)))
+(_ref 'slurp slurp)
+(_ref 'slurp-do (fn [f] (str "(do " (slurp f) ")")))
+
+(rep "(def! not (fn* [a] (if a false true)))")
+(rep "(def! load-file (fn* [f] (eval (read-string (slurp-do f)))))")
+
+(defn -main [& args]
+ (if args
+ (rep (str "(load-file \"" (first args) "\")"))
+ (loop []
+ (let [line (readline/readline "user> ")]
+ (when line
+ (when-not (re-seq #"^\s*$|^\s*;.*$" line) ; blank/comment
+ (try
+ (println (rep line))
+ (catch Throwable e
+ (clojure.repl/pst e))))
+ (recur))))))
diff --git a/clojure/src/step8_macros.clj b/clojure/src/step8_macros.clj
new file mode 100644
index 0000000..8b95ba8
--- /dev/null
+++ b/clojure/src/step8_macros.clj
@@ -0,0 +1,158 @@
+(ns step8-macros
+ (:refer-clojure :exclude [macroexpand])
+ (:require [clojure.repl]
+ [types]
+ [readline]
+ [reader]))
+
+(declare EVAL)
+
+;; read
+(defn READ [& [strng]]
+ (let [line (if strng strng (read-line))]
+ (reader/read-string strng)))
+
+;; eval
+(defn is-pair [x]
+ (and (sequential? x) (> (count x) 0)))
+
+(defn quasiquote [ast]
+ (cond
+ (not (is-pair ast))
+ (list 'quote ast)
+
+ (= 'unquote (first ast))
+ (second ast)
+
+ (and (is-pair (first ast)) (= 'splice-unquote (ffirst ast)))
+ (list 'concat (-> ast first second) (quasiquote (rest ast)))
+
+ :else
+ (list 'cons (quasiquote (first ast)) (quasiquote (rest ast)))))
+
+(defn is-macro-call [ast env]
+ (and (seq? ast)
+ (symbol? (first ast))
+ (types/env-find env (first ast))
+ (:ismacro (meta (types/env-get env (first ast))))))
+
+(defn macroexpand [ast env]
+ (loop [ast ast]
+ (if (is-macro-call ast env)
+ (let [mac (types/env-get env (first ast))]
+ (recur (apply mac (rest ast))))
+ ast)))
+
+(defn eval-ast [ast env]
+ (cond
+ (symbol? ast) (types/env-get env ast)
+
+ (seq? ast) (doall (map #(EVAL % env) ast))
+
+ (vector? ast) (vec (doall (map #(EVAL % env) ast)))
+
+ (map? ast) (apply hash-map (doall (map #(EVAL % env)
+ (mapcat identity ast))))
+
+ :else ast))
+
+(defn EVAL [ast env]
+ (loop [ast ast
+ env env]
+ ;;(prn "EVAL" ast (keys @env)) (flush)
+ (if (not (seq? ast))
+ (eval-ast ast env)
+
+ ;; apply list
+ (let [ast (macroexpand ast env)]
+ (if (not (seq? ast))
+ ast
+
+ (let [[a0 a1 a2 a3] ast]
+ (condp = a0
+ 'def!
+ (types/env-set env a1 (EVAL a2 env))
+
+ 'let*
+ (let [let-env (types/env env)]
+ (doseq [[b e] (partition 2 a1)]
+ (types/env-set let-env b (EVAL e let-env)))
+ (EVAL a2 let-env))
+
+ 'quote
+ a1
+
+ 'quasiquote
+ (EVAL (quasiquote a1) env)
+
+ 'defmacro!
+ (let [func (with-meta (EVAL a2 env)
+ {:ismacro true})]
+ (types/env-set env a1 func))
+
+ 'macroexpand
+ (macroexpand a1 env)
+
+ 'do
+ (do (eval-ast (->> ast (drop-last) (drop 1)) env)
+ (recur (last ast) env))
+
+ 'if
+ (let [cond (EVAL a1 env)]
+ (if (or (= cond nil) (= cond false))
+ (if (> (count ast) 2)
+ (recur a3 env)
+ nil)
+ (recur a2 env)))
+
+ 'fn*
+ ^{:expression a2
+ :environment env
+ :parameters a1}
+ (fn [& args]
+ (EVAL a2 (types/env env a1 args)))
+
+ ;; apply
+ (let [el (eval-ast ast env)
+ f (first el)
+ args (rest el)
+ {:keys [expression environment parameters]} (meta f)]
+ (if expression
+ (recur expression (types/env environment parameters args))
+ (apply f args))))))))))
+
+;; print
+(defn PRINT [exp] (pr-str exp))
+
+;; repl
+(def repl-env (types/env))
+(defn rep
+ [strng]
+ (PRINT (EVAL (READ strng) repl-env)))
+
+(defn _ref [k,v] (types/env-set repl-env k v))
+
+;; Import types related functions
+(doseq [[k v] types/types_ns] (_ref k v))
+
+;; Defined using the language itself
+(_ref 'read-string reader/read-string)
+(_ref 'eval (fn [ast] (EVAL ast repl-env)))
+(_ref 'slurp slurp)
+(_ref 'slurp-do (fn [f] (str "(do " (slurp f) ")")))
+
+(rep "(def! not (fn* [a] (if a false true)))")
+(rep "(def! load-file (fn* [f] (eval (read-string (slurp-do f)))))")
+
+(defn -main [& args]
+ (if args
+ (rep (str "(load-file \"" (first args) "\")"))
+ (loop []
+ (let [line (readline/readline "user> ")]
+ (when line
+ (when-not (re-seq #"^\s*$|^\s*;.*$" line) ; blank/comment
+ (try
+ (println (rep line))
+ (catch Throwable e
+ (clojure.repl/pst e))))
+ (recur))))))
diff --git a/clojure/src/step9_interop.clj b/clojure/src/step9_interop.clj
new file mode 100644
index 0000000..48ae687
--- /dev/null
+++ b/clojure/src/step9_interop.clj
@@ -0,0 +1,161 @@
+(ns step9-interop
+ (:refer-clojure :exclude [macroexpand])
+ (:require [clojure.repl]
+ [types]
+ [readline]
+ [reader]))
+
+(declare EVAL)
+
+;; read
+(defn READ [& [strng]]
+ (let [line (if strng strng (read-line))]
+ (reader/read-string strng)))
+
+;; eval
+(defn is-pair [x]
+ (and (sequential? x) (> (count x) 0)))
+
+(defn quasiquote [ast]
+ (cond
+ (not (is-pair ast))
+ (list 'quote ast)
+
+ (= 'unquote (first ast))
+ (second ast)
+
+ (and (is-pair (first ast)) (= 'splice-unquote (ffirst ast)))
+ (list 'concat (-> ast first second) (quasiquote (rest ast)))
+
+ :else
+ (list 'cons (quasiquote (first ast)) (quasiquote (rest ast)))))
+
+(defn is-macro-call [ast env]
+ (and (seq? ast)
+ (symbol? (first ast))
+ (types/env-find env (first ast))
+ (:ismacro (meta (types/env-get env (first ast))))))
+
+(defn macroexpand [ast env]
+ (loop [ast ast]
+ (if (is-macro-call ast env)
+ (let [mac (types/env-get env (first ast))]
+ (recur (apply mac (rest ast))))
+ ast)))
+
+(defn eval-ast [ast env]
+ (cond
+ (symbol? ast) (types/env-get env ast)
+
+ (seq? ast) (doall (map #(EVAL % env) ast))
+
+ (vector? ast) (vec (doall (map #(EVAL % env) ast)))
+
+ (map? ast) (apply hash-map (doall (map #(EVAL % env)
+ (mapcat identity ast))))
+
+ :else ast))
+
+(defn EVAL [ast env]
+ (loop [ast ast
+ env env]
+ ;;(prn "EVAL" ast (keys @env)) (flush)
+ (if (not (seq? ast))
+ (eval-ast ast env)
+
+ ;; apply list
+ (let [ast (macroexpand ast env)]
+ (if (not (seq? ast))
+ ast
+
+ (let [[a0 a1 a2 a3] ast]
+ (condp = a0
+ 'def!
+ (types/env-set env a1 (EVAL a2 env))
+
+ 'let*
+ (let [let-env (types/env env)]
+ (doseq [[b e] (partition 2 a1)]
+ (types/env-set let-env b (EVAL e let-env)))
+ (EVAL a2 let-env))
+
+ 'quote
+ a1
+
+ 'quasiquote
+ (EVAL (quasiquote a1) env)
+
+ 'defmacro!
+ (let [func (with-meta (EVAL a2 env)
+ {:ismacro true})]
+ (types/env-set env a1 func))
+
+ 'macroexpand
+ (macroexpand a1 env)
+
+ 'clj*
+ (eval (reader/read-string a1))
+
+ 'do
+ (do (eval-ast (->> ast (drop-last) (drop 1)) env)
+ (recur (last ast) env))
+
+ 'if
+ (let [cond (EVAL a1 env)]
+ (if (or (= cond nil) (= cond false))
+ (if (> (count ast) 2)
+ (recur a3 env)
+ nil)
+ (recur a2 env)))
+
+ 'fn*
+ ^{:expression a2
+ :environment env
+ :parameters a1}
+ (fn [& args]
+ (EVAL a2 (types/env env a1 args)))
+
+ ;; apply
+ (let [el (eval-ast ast env)
+ f (first el)
+ args (rest el)
+ {:keys [expression environment parameters]} (meta f)]
+ (if expression
+ (recur expression (types/env environment parameters args))
+ (apply f args))))))))))
+
+;; print
+(defn PRINT [exp] (pr-str exp))
+
+;; repl
+(def repl-env (types/env))
+(defn rep
+ [strng]
+ (PRINT (EVAL (READ strng) repl-env)))
+
+(defn _ref [k,v] (types/env-set repl-env k v))
+
+;; Import types related functions
+(doseq [[k v] types/types_ns] (_ref k v))
+
+;; Defined using the language itself
+(_ref 'read-string reader/read-string)
+(_ref 'eval (fn [ast] (EVAL ast repl-env)))
+(_ref 'slurp slurp)
+(_ref 'slurp-do (fn [f] (str "(do " (slurp f) ")")))
+
+(rep "(def! not (fn* [a] (if a false true)))")
+(rep "(def! load-file (fn* [f] (eval (read-string (slurp-do f)))))")
+
+(defn -main [& args]
+ (if args
+ (rep (str "(load-file \"" (first args) "\")"))
+ (loop []
+ (let [line (readline/readline "user> ")]
+ (when line
+ (when-not (re-seq #"^\s*$|^\s*;.*$" line) ; blank/comment
+ (try
+ (println (rep line))
+ (catch Throwable e
+ (clojure.repl/pst e))))
+ (recur))))))
diff --git a/clojure/src/stepA_more.clj b/clojure/src/stepA_more.clj
new file mode 100644
index 0000000..19a0c36
--- /dev/null
+++ b/clojure/src/stepA_more.clj
@@ -0,0 +1,178 @@
+(ns stepA-more
+ (:refer-clojure :exclude [macroexpand])
+ (:require [clojure.repl]
+ [types]
+ [readline]
+ [reader]))
+
+(declare EVAL)
+
+;; read
+(defn READ [& [strng]]
+ (let [line (if strng strng (read-line))]
+ (reader/read-string strng)))
+
+;; eval
+(defn is-pair [x]
+ (and (sequential? x) (> (count x) 0)))
+
+(defn quasiquote [ast]
+ (cond
+ (not (is-pair ast))
+ (list 'quote ast)
+
+ (= 'unquote (first ast))
+ (second ast)
+
+ (and (is-pair (first ast)) (= 'splice-unquote (ffirst ast)))
+ (list 'concat (-> ast first second) (quasiquote (rest ast)))
+
+ :else
+ (list 'cons (quasiquote (first ast)) (quasiquote (rest ast)))))
+
+(defn is-macro-call [ast env]
+ (and (seq? ast)
+ (symbol? (first ast))
+ (types/env-find env (first ast))
+ (:ismacro (meta (types/env-get env (first ast))))))
+
+(defn macroexpand [ast env]
+ (loop [ast ast]
+ (if (is-macro-call ast env)
+ (let [mac (types/env-get env (first ast))]
+ (recur (apply mac (rest ast))))
+ ast)))
+
+(defn eval-ast [ast env]
+ (cond
+ (symbol? ast) (types/env-get env ast)
+
+ (seq? ast) (doall (map #(EVAL % env) ast))
+
+ (vector? ast) (vec (doall (map #(EVAL % env) ast)))
+
+ (map? ast) (apply hash-map (doall (map #(EVAL % env)
+ (mapcat identity ast))))
+
+ :else ast))
+
+(defn EVAL [ast env]
+ (loop [ast ast
+ env env]
+ ;;(prn "EVAL" ast (keys @env)) (flush)
+ (if (not (seq? ast))
+ (eval-ast ast env)
+
+ ;; apply list
+ (let [ast (macroexpand ast env)]
+ (if (not (seq? ast))
+ ast
+
+ (let [[a0 a1 a2 a3] ast]
+ (condp = a0
+ 'def!
+ (types/env-set env a1 (EVAL a2 env))
+
+ 'let*
+ (let [let-env (types/env env)]
+ (doseq [[b e] (partition 2 a1)]
+ (types/env-set let-env b (EVAL e let-env)))
+ (EVAL a2 let-env))
+
+ 'quote
+ a1
+
+ 'quasiquote
+ (EVAL (quasiquote a1) env)
+
+ 'defmacro!
+ (let [func (with-meta (EVAL a2 env)
+ {:ismacro true})]
+ (types/env-set env a1 func))
+
+ 'macroexpand
+ (macroexpand a1 env)
+
+ 'clj*
+ (eval (reader/read-string a1))
+
+ 'try*
+ (if (= 'catch* (nth a2 0))
+ (try
+ (EVAL a1 env)
+ (catch clojure.lang.ExceptionInfo ei
+ (EVAL (nth a2 2) (types/env env
+ [(nth a2 1)]
+ [(:data (ex-data ei))])))
+ (catch Throwable t
+ (EVAL (nth a2 2) (types/env env
+ [(nth a2 1)]
+ [(.getMessage t)]))))
+ (EVAL a1 env))
+
+ 'do
+ (do (eval-ast (->> ast (drop-last) (drop 1)) env)
+ (recur (last ast) env))
+
+ 'if
+ (let [cond (EVAL a1 env)]
+ (if (or (= cond nil) (= cond false))
+ (if (> (count ast) 2)
+ (recur a3 env)
+ nil)
+ (recur a2 env)))
+
+ 'fn*
+ ^{:expression a2
+ :environment env
+ :parameters a1}
+ (fn [& args]
+ (EVAL a2 (types/env env a1 args)))
+
+ ;; apply
+ (let [el (eval-ast ast env)
+ f (first el)
+ args (rest el)
+ {:keys [expression environment parameters]} (meta f)]
+ (if expression
+ (recur expression (types/env environment parameters args))
+ (apply f args))))))))))
+
+;; print
+(defn PRINT [exp] (pr-str exp))
+
+;; repl
+(def repl-env (types/env))
+(defn rep
+ [strng]
+ (PRINT (EVAL (READ strng) repl-env)))
+
+(defn _ref [k,v] (types/env-set repl-env k v))
+
+;; Import types related functions
+(doseq [[k v] types/types_ns] (_ref k v))
+
+;; Defined using the language itself
+(_ref 'readline readline/readline)
+(_ref 'read-string reader/read-string)
+(_ref 'eval (fn [ast] (EVAL ast repl-env)))
+(_ref 'slurp slurp)
+(_ref 'slurp-do (fn [f] (str "(do " (slurp f) ")")))
+
+(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)))))")
+
+(defn -main [& args]
+ (if args
+ (rep (str "(load-file \"" (first args) "\")"))
+ (loop []
+ (let [line (readline/readline "user> ")]
+ (when line
+ (when-not (re-seq #"^\s*$|^\s*;.*$" line) ; blank/comment
+ (try
+ (println (rep line))
+ (catch Throwable e
+ (clojure.repl/pst e))))
+ (recur))))))
diff --git a/clojure/src/types.clj b/clojure/src/types.clj
new file mode 100644
index 0000000..922cf79
--- /dev/null
+++ b/clojure/src/types.clj
@@ -0,0 +1,71 @@
+(ns types)
+
+;; Custom printing
+
+(defmethod clojure.core/print-method clojure.lang.Atom [a writer]
+ (.write writer "(atom ")
+ (.write writer (pr-str @a))
+ (.write writer ")"))
+
+;; Errors/exceptions
+(defn mal_throw [obj]
+ (throw (ex-info "mal exception" {:data obj})))
+
+
+;; Atoms
+(defn atom? [atm]
+ (= (type atm) clojure.lang.Atom))
+
+
+;; env
+
+(defn env [& [outer binds exprs]]
+ ;;(prn "env" binds exprs)
+ ;; (when (not= (count binds) (count exprs))
+ ;; (throw (Exception. "Arity mistmatch in env call")))
+ (atom
+ (loop [env {:outer outer}
+ b binds
+ e exprs]
+ (cond
+ (= nil b)
+ env
+
+ (= '& (first b))
+ (assoc env (nth b 1) e)
+
+ :else
+ (recur (assoc env (first b) (first e)) (next b) (next e))))))
+
+(defn env-find [env k]
+ (cond
+ (contains? @env k) env
+ (:outer @env) (env-find (:outer @env) k)
+ :else nil))
+
+(defn env-get [env k]
+ (let [e (env-find env k)]
+ (when-not e
+ (throw (Exception. (str "'" k "' not found"))))
+ (get @e k)))
+
+(defn env-set [env k v]
+ (swap! env assoc k v)
+ v)
+
+(def types_ns
+ [['pr-str pr-str] ['str str] ['prn prn] ['println println]
+ ['with-meta with-meta] ['meta meta] ['= =]
+ ['nil? nil?] ['true? true?] ['false? false?] ['symbol? symbol?]
+ ['> >] ['>= >=] ['< <] ['<= <=] ['+ +] ['- -] ['* *] ['/ /]
+ ['hash-map hash-map] ['map? map?]
+ ['assoc assoc] ['dissoc dissoc] ['get get]
+ ['contains? contains?] ['keys keys] ['vals vals]
+ ['throw mal_throw]
+ ['list list] ['list? seq?] ['vector vector] ['vector? vector?]
+ ['atom atom] ['atom? atom?] ['deref deref]
+ ['reset! reset!] ['swap! swap!]
+ ['sequential? sequential?] ['cons cons] ['nth nth]
+ ['empty? empty?] ['count count] ['concat concat]
+ ['conj conj] ['first first] ['rest rest]
+ ['apply apply] ['map #(doall (map %1 %2))]])
diff --git a/core.mal b/core.mal
new file mode 100644
index 0000000..2896dcc
--- /dev/null
+++ b/core.mal
@@ -0,0 +1,83 @@
+(def! inc (fn* (a) (+ a 1)))
+
+(def! dec (fn* (a) (- a 1)))
+
+(def! zero? (fn* (n) (= 0 n)))
+
+(def! reduce
+ (fn* (f init xs)
+ (if (> (count xs) 0)
+ (reduce f (f init (first xs)) (rest xs))
+ init)))
+
+(def! identity (fn* (x) x))
+
+(def! every?
+ (fn* (pred xs)
+ (if (> (count xs) 0)
+ (if (pred (first xs))
+ (every? pred (rest xs))
+ false)
+ true)))
+
+(def! not (fn* (x) (if x false true)))
+
+(def! some
+ (fn* (pred xs)
+ (if (> (count xs) 0)
+ (let* (res (pred (first xs)))
+ (if (pred (first xs))
+ res
+ (some pred (rest xs))))
+ nil)))
+
+(defmacro! and
+ (fn* (& xs)
+ (if (empty? xs)
+ true
+ (if (= 1 (count xs))
+ (first xs)
+ `(let* (and_FIXME ~(first xs))
+ (if and_FIXME (and ~@(rest xs)) and_FIXME))))))
+
+(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))))))))
+
+(defmacro! cond
+ (fn* (& clauses)
+ (if (> (count clauses) 0)
+ (list 'if (first clauses)
+ (if (> (count clauses) 1)
+ (nth clauses 1)
+ (throw "cond requires an even number of forms"))
+ (cons 'cond (rest (rest clauses)))))))
+
+(defmacro! ->
+ (fn* (x & xs)
+ (if (empty? xs)
+ x
+ (let* (form (first xs)
+ more (rest xs))
+ (if (empty? more)
+ (if (list? form)
+ `(~(first form) ~x ~@(rest form))
+ (list form x))
+ `(-> (-> ~x ~form) ~@more))))))
+
+(defmacro! ->>
+ (fn* (x & xs)
+ (if (empty? xs)
+ x
+ (let* (form (first xs)
+ more (rest xs))
+ (if (empty? more)
+ (if (list? form)
+ `(~(first form) ~@(rest form) ~x)
+ (list form x))
+ `(->> (->> ~x ~form) ~@more))))))
diff --git a/docs/TODO b/docs/TODO
new file mode 100644
index 0000000..d8f17b7
--- /dev/null
+++ b/docs/TODO
@@ -0,0 +1,95 @@
+All:
+ - multi-line read
+ - loop/recur ?
+ - hash-maps with non-string keys
+ - gensym reader inside quasiquote
+ - "intern" symbols, strings and numbers. Simplify equality
+ comparision.
+ - Contact Peter Norvig about license
+
+ - synchronize function/definitions order/names in files
+ - move Env into separate file (maybe)?
+ - more metadata tests
+ - more hash_map tests
+ - hash-map with space in key string (make)
+ - more interop tests
+ - support metadata on symbol, hash-map, list, vector, function, atom
+
+ - unindent tco while loop for step5-A
+
+---------------------------------------------
+
+JS:
+
+Python:
+
+Clojure:
+
+C:
+ - come up with better way to do 20 vararg code
+
+Bash:
+
+PHP:
+
+Make:
+ - Norvig2: TCO/recur?
+ - allow '_' in make variable names
+ - errors should propagate up from within load-file
+
+
+Mal:
+ - line numbers in errors
+ - step6: command line arguments
+ - step 5
+ - step 9
+
+Java:
+ - vectors, hash-maps, metadata
+ - step 9
+ - mvn exec:java -Dexec.mainClass="mal.step6_file" -Dexec.args="incC.mal"
+
+Rust:
+ - http://www.rustforrubyists.com/book/index.html
+ - http://static.rust-lang.org/doc/0.9/complement-cheatsheet.html
+ - http://pzol.github.io/getting_rusty/
+ - readline:
+ - http://redbrain.co.uk/2013/11/09/rust-and-readline-c-ffi/
+ - http://www.reddit.com/r/rust/comments/1q9pqc/rust_cffi_and_readline/
+ - https://github.com/dbp/rustrepl
+ - hash-map:
+ - http://static.rust-lang.org/doc/master/std/hashmap/index.html
+ - http://static.rust-lang.org/doc/master/std/hashmap/struct.HashMap.html
+ - vector/list:
+ - http://static.rust-lang.org/doc/master/std/vec/index.html
+ - steps 2-A
+
+
+Others (based on redmonk languages from Jan 2014): ?
+ http://sogrady-media.redmonk.com/sogrady/files/2014/01/lang-rank-114-wm.png
+
+ - Tier 1
+ * JavaScript
+ * Java
+ * PHP
+ ? C#
+ * Python
+ ? C++
+ - Ruby
+ * C
+ - Objective-C
+ - Perl
+ * Shell
+
+ - Tier 2
+ * Clojure
+ ? Go
+ ? Assembly
+ ? Fortan
+ ? Dart
+ ? D
+
+ - Tier 3
+ ? Pascal
+ - Rust
+ -
diff --git a/docs/step_notes.txt b/docs/step_notes.txt
new file mode 100644
index 0000000..768167d
--- /dev/null
+++ b/docs/step_notes.txt
@@ -0,0 +1,181 @@
+Step Notes:
+
+- step0_repl
+ - prompt, input, READ, EVAL, PRINT, output
+ - readline module
+ - display prompt, read line of input
+
+- use native eval in EVAL if available
+
+- libedit/GNU readline:
+ - use existing lib, wrap shell call or implement
+ - load history file on first call
+ - add non-blank lines to history
+ - append to history file
+
+- step1_read_print
+ - types module:
+ - add boxed types if no language equivalent:
+ - nil, true, false, symbol, integer, string, list
+ - pr_str:
+ - stringify boxed types to their Mal representations
+ - list/array is recursive
+ - reader module:
+ - stateful reader object
+ - alternative: mutate token list
+ - tokenize (use regex if available)
+ - standard regex pattern: "/[\s,]*(~@|[\[\]{}()'`~^@]|\"(?:\\.|[^\\\"])*\"|;.*|[^\s\[\]{}('\"`,;)]*)/"
+ - read_str
+ - read_form(new Reader(tokenize(str)))
+ - read_form
+ - detect errors
+ - call read_list or read_atom
+ - read_list
+ - read_form until ')'
+ - return array (boxed)
+ - read_atom
+ - return scalar boxed type:
+ - nil, true, false, symbol, integer, string
+
+- vectors
+ - Basically: two array types that retain their boxed types, can be
+ challenging depending on the language (e.g. JS, PHP: no clean
+ way to derive new array types).
+ - types module:
+ - add vector boxed type
+ - derived from array if possible
+ - pr_str:
+ - vector is recursive
+ - sequential?
+ - reader module:
+ - read_vector:
+ - re-use read_list but with different constructor, delims
+
+- hash-maps
+ - reader module:
+ - re-use read_list function and apply that using hash-map
+ constructor
+ - types module:
+ - pr_str addition
+ - hash-map, map?, assoc, dissoc, get, contains?, keys,
+ vals (probably assoc! and dissoc! for internal)
+ - eval_map: eval the keys and values of hash_maps
+ - EVAL:
+ - if hash_map, call eval_map on it
+
+- step2_eval
+ - types module:
+ - first, rest, nth on list
+ - eval_ast:
+ - if symbol, return value of looking up in env
+ - if list, eval each item, return new list
+ - if vector support, eval each item, return new vector
+ - if hash_map support, eval each value, return new hash_map
+ - otherwise, just return unchanged ast
+ - EVAL/apply:
+ - if not a list, call eval_ast
+ - otherwise, apply first item to eval_ast of (rest ast)
+ - repl_env as simple one level assoc. array (or hash_map)
+ - store function as hash_map value
+
+- step3_env
+ - types module:
+ - Env type:
+ - find, set, get (no binds/exprs in constructor yet)
+ - may need function type if HashMap is strongly typed (e.g. Java)
+ - EVAL/apply:
+ - def! - mutate current environment
+ - let* - create new environment with bindings
+ - _ref sugar
+
+- step4_if_fn_do
+ - types module:
+ - function type (closure)
+ - add function printing to pr_str
+ - add binds/exprs handling to Env constructor with variable arity
+ - functions (exported via types_ns):
+ - move arith operations here
+ - comparison operations (including =)
+ - prn, pr_str, = (recursive)
+ - list, list?, count, empty?
+ - EVAL:
+ - do:
+ - if:
+ - fn*:
+ - simple if language supports closures
+ - define not using rep()
+
+
+- metadata
+ -
+
+- step5_tco
+ - types module:
+ - function type:
+ - stores: func, exp, env, params
+ - func is EVAL in native mal case, otherwise reference to
+ platform function
+ - if metadata support, then store exp, env, params as
+ metadata
+ - update function printer to show function types
+ - EVAL:
+ - while loop around whole thing
+ - cases where we directly return result of EVAL, instead set
+ ast and env to what would be put in the EVAL, then loop.
+ - for apply case, set env to new Env based on properties
+ on the function
+
+- step6_file
+ - add read-string, eval, slurp, slurp-do platform wrappers
+ - define load-file function
+ - if files on command line, use load-file to run
+
+- step7_quote
+ - reader module:
+ - add reader macros to read_form for quote, unquote,
+ splice-unquote and quasiquote
+ - types module:
+ - add cons and concat functions
+ - add is_pair and quasiquote functions
+ - rewrite ast using cons/concat functions
+ - if vectors, use sequential? instead of list? in is_pair
+ - EVAL:
+ - add 'quote', 'quasiquote' cases
+
+- step8_macros
+ - types module:
+ - add first, rest functions
+ - add is_macro_call and macroexpand
+ - recursively macroexpand lists
+ - if applying a macro function, run it on the ast first before
+ continuing
+ - call macroexpand apply in EVAL
+ - EVAL:
+ - add 'defmacro!' and 'macroexpand'
+ - store ismacro property on function metadata
+
+- stepA_more
+ - types module:
+ - throw function
+ - map, apply functions
+ - symbol?, nil?, true?, false?
+ - conj, first, rest
+ - EVAL:
+ - try*/catch*: for normal exceptions, extracts string
+ otherwise extracts full value
+ - define cond and or macros using rep()
+
+- atoms
+ - reader module:
+ - @a reader macro -> (deref a)
+ - types module:
+ - pr_str case
+ - atom type, atom, atom?, deref, reset!, swap!
+
+- metadata
+ - types module:
+ - support meta property on symbols, hash-maps, lists, vectors,
+ functions, atoms
+ - add with-meta, meta functions
+ - reader module:
+ - ^ reader macro reads ^meta obj -> (with-meta obj meta)
diff --git a/java/Makefile b/java/Makefile
new file mode 100644
index 0000000..ccbd6c5
--- /dev/null
+++ b/java/Makefile
@@ -0,0 +1,19 @@
+
+TESTS =
+
+
+SOURCES = src/main/java/mal/types.java src/main/java/mal/readline.java \
+ src/main/java/mal/reader.java src/main/java/mal/Env.java \
+ src/main/java/mal/step5_tco.java
+
+#.PHONY: stats tests $(TESTS)
+.PHONY: stats
+
+stats: $(SOURCES)
+ @wc $^
+
+#tests: $(TESTS)
+#
+#$(TESTS):
+# @echo "Running $@"; \
+# python $@ || exit 1; \
diff --git a/java/pom.xml b/java/pom.xml
new file mode 100644
index 0000000..2f0a4fd
--- /dev/null
+++ b/java/pom.xml
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.martintribe</groupId>
+ <artifactId>mal</artifactId>
+ <packaging>jar</packaging>
+ <version>0.0.1</version>
+
+ <dependencies>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ <version>16.0.1</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-lang3</artifactId>
+ <version>3.3</version>
+ </dependency>
+ <dependency>
+ <groupId>net.java.dev.jna</groupId>
+ <artifactId>jna</artifactId>
+ <version>4.0.0</version>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>1.7</source>
+ <target>1.7</target>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>exec-maven-plugin</artifactId>
+ <version>1.2.1</version>
+ <executions>
+ <execution>
+ <goals>
+ <goal>java</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <!--
+ <mainClass>mal.stepA_more</mainClass>
+ <arguments>
+ <argument>foo</argument>
+ <argument>bar</argument>
+ </arguments>
+ -->
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-shade-plugin</artifactId>
+ <version>1.7.1</version>
+ <executions>
+ <execution>
+ <phase>package</phase>
+ <goals>
+ <goal>shade</goal>
+ </goals>
+ <configuration>
+ <transformers>
+ <transformer
+ implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
+ <mainClass>mal.stepA_more</mainClass>
+ </transformer>
+ </transformers>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/java/src/main/java/mal/reader.java b/java/src/main/java/mal/reader.java
new file mode 100644
index 0000000..6bae506
--- /dev/null
+++ b/java/src/main/java/mal/reader.java
@@ -0,0 +1,147 @@
+package mal;
+
+import java.util.ArrayList;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.apache.commons.lang3.StringEscapeUtils;
+import mal.types.*;
+
+public class reader {
+ public static class ParseError extends MalThrowable {
+ public ParseError(String msg) {
+ super(msg);
+ }
+ }
+
+ public static class Reader {
+ ArrayList<String> tokens;
+ Integer position;
+ public Reader(ArrayList<String> t) {
+ tokens = t;
+ position = 0;
+ }
+
+ public String peek() {
+ if (position >= tokens.size()) {
+ return null;
+ } else {
+ return tokens.get(position);
+ }
+ }
+ public String next() {
+ return tokens.get(position++);
+ }
+ }
+
+ public static ArrayList<String> tokenize(String str) {
+ ArrayList<String> tokens = new ArrayList<String>();
+ Pattern pattern = Pattern.compile("[\\s ,]*(~@|[\\[\\]{}()'`~@]|\"(?:[\\\\].|[^\\\\\"])*\"|;.*|[^\\s \\[\\]{}()'\"`~@,;]*)");
+ Matcher matcher = pattern.matcher(str);
+ while (matcher.find()) {
+ String token = matcher.group(1);
+ if (token != null &&
+ !token.equals("") &&
+ !(token.charAt(0) == ';')) {
+ tokens.add(token);
+ }
+ }
+ return tokens;
+ }
+
+ public static MalVal read_atom(Reader rdr)
+ throws ParseError {
+ String token = rdr.next();
+ Pattern pattern = Pattern.compile("(^-?[0-9]+$)|(^-?[0-9][0-9.]*$)|(^nil$)|(^true$)|(^false$)|^\"(.*)\"$|(^[^\"]*$)");
+ Matcher matcher = pattern.matcher(token);
+ if (!matcher.find()) {
+ throw new ParseError("unrecognized token '" + token + "'");
+ }
+ if (matcher.group(1) != null) {
+ return new MalInteger(Integer.parseInt(matcher.group(1)));
+ } else if (matcher.group(3) != null) {
+ return types.Nil;
+ } else if (matcher.group(4) != null) {
+ return types.True;
+ } else if (matcher.group(5) != null) {
+ return types.False;
+ } else if (matcher.group(6) != null) {
+ return new MalString(StringEscapeUtils.unescapeJson(matcher.group(6)));
+ } else if (matcher.group(7) != null) {
+ return new MalSymbol(matcher.group(7));
+ } else {
+ throw new ParseError("unrecognized '" + matcher.group(0) + "'");
+ }
+ }
+
+ public static MalVal read_list(Reader rdr, MalList lst, char start, char end)
+ throws MalContinue, ParseError {
+ String token = rdr.next();
+ if (token.charAt(0) != start) {
+ throw new ParseError("expected '" + start + "'");
+ }
+
+ while ((token = rdr.peek()) != null && token.charAt(0) != end) {
+ lst.conj_BANG(read_form(rdr));
+ }
+
+ if (token == null) {
+ throw new ParseError("expected '" + end + "', got EOF");
+ }
+ rdr.next();
+
+ return lst;
+ }
+
+ public static MalVal read_hash_map(Reader rdr)
+ throws MalContinue, ParseError {
+ MalList lst = (MalList)read_list(rdr, new MalList(), '{', '}');
+ return new MalHashMap(lst);
+ }
+
+ public static MalVal read_form(Reader rdr)
+ throws MalContinue, ParseError {
+ String token = rdr.peek();
+ if (token == null) { throw new MalContinue(); }
+ MalVal form;
+
+ switch (token.charAt(0)) {
+ case '\'': rdr.next();
+ return new MalList(new MalSymbol("quote"),
+ read_form(rdr));
+ case '`': rdr.next();
+ return new MalList(new MalSymbol("quasiquote"),
+ read_form(rdr));
+ case '~':
+ if (token.equals("~")) {
+ rdr.next();
+ return new MalList(new MalSymbol("unquote"),
+ read_form(rdr));
+ } else {
+ rdr.next();
+ return new MalList(new MalSymbol("splice-unquote"),
+ read_form(rdr));
+ }
+ case '^': rdr.next();
+ MalVal meta = read_form(rdr);
+ return new MalList(new MalSymbol("with-meta"),
+ read_form(rdr),
+ meta);
+ case '@': rdr.next();
+ return new MalList(new MalSymbol("deref"),
+ read_form(rdr));
+ case '(': form = read_list(rdr, new MalList(), '(' , ')'); break;
+ case ')': throw new ParseError("unexpected ')'");
+ case '[': form = read_list(rdr, new MalVector(), '[' , ']'); break;
+ case ']': throw new ParseError("unexpected ']'");
+ case '{': form = read_hash_map(rdr); break;
+ case '}': throw new ParseError("unexpected '}'");
+ default: form = read_atom(rdr);
+ }
+ return form;
+ }
+
+ public static MalVal read_str(String str)
+ throws MalContinue, ParseError {
+ return read_form(new Reader(tokenize(str)));
+ }
+}
diff --git a/java/src/main/java/mal/readline.java b/java/src/main/java/mal/readline.java
new file mode 100644
index 0000000..1705f39
--- /dev/null
+++ b/java/src/main/java/mal/readline.java
@@ -0,0 +1,101 @@
+package mal;
+
+import java.io.IOException;
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.io.BufferedWriter;
+import java.io.FileWriter;
+
+import java.io.File;
+import com.google.common.io.Files;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+
+import com.sun.jna.Library;
+import com.sun.jna.Native;
+import com.sun.jna.Platform;
+
+class readline {
+ public enum Mode { JNA, JAVA }
+ static Mode mode = Mode.JNA;
+
+ static String HISTORY_FILE = "/home/joelm/.mal-history";
+ static Boolean historyLoaded = false;
+
+ public static class EOFException extends Exception {
+ }
+
+ public interface RLLibrary extends Library {
+ // Select a library to use.
+ // WARNING: GNU readline is GPL.
+
+ // GNU readline (GPL)
+ RLLibrary INSTANCE = (RLLibrary)
+ Native.loadLibrary("readline", RLLibrary.class);
+ // Libedit (BSD)
+// RLLibrary INSTANCE = (RLLibrary)
+// Native.loadLibrary("edit", RLLibrary.class);
+
+ String readline(String prompt);
+ void add_history(String line);
+ }
+
+ public static void loadHistory(String filename) {
+ File file = new File(filename);
+ try {
+ List<String> lines = Files.readLines(file,
+ StandardCharsets.UTF_8);
+ for (String line : lines) {
+ RLLibrary.INSTANCE.add_history(line);
+ }
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+
+ public static void appendHistory(String filename, String line) {
+ try {
+ BufferedWriter w;
+ w = new BufferedWriter(new FileWriter(filename, true));
+ w.append(line + "\n");
+ w.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+
+ public static String jna_readline(String prompt)
+ throws EOFException, IOException {
+ if (!historyLoaded) {
+ loadHistory(HISTORY_FILE);
+ }
+ String line = RLLibrary.INSTANCE.readline(prompt);
+ if (line == null) {
+ throw new EOFException();
+ }
+ RLLibrary.INSTANCE.add_history(line);
+ appendHistory(HISTORY_FILE, line);
+ return line;
+ }
+
+ // Just java readline (no history, or line editing)
+ public static String java_readline(String prompt)
+ throws EOFException, IOException {
+ System.out.print(prompt);
+ BufferedReader buffer=new BufferedReader(new InputStreamReader(System.in));
+ String line=buffer.readLine();
+ if (line == null) {
+ throw new EOFException();
+ }
+ return line;
+ }
+
+ public static String readline(String prompt)
+ throws EOFException, IOException {
+ if (mode == Mode.JNA) {
+ return jna_readline(prompt);
+ } else {
+ return java_readline(prompt);
+ }
+ }
+}
diff --git a/java/src/main/java/mal/step0_repl.java b/java/src/main/java/mal/step0_repl.java
new file mode 100644
index 0000000..9095aca
--- /dev/null
+++ b/java/src/main/java/mal/step0_repl.java
@@ -0,0 +1,48 @@
+package mal;
+
+import java.io.IOException;
+
+import mal.readline;
+
+public class step0_repl {
+ // read
+ public static String READ(String str) {
+ return str;
+ }
+
+ // eval
+ public static String EVAL(String ast, String env) {
+ return ast;
+ }
+
+ // print
+ public static String PRINT(String exp) {
+ return exp;
+ }
+
+ // REPL
+ public static String RE(String env, String str) {
+ return EVAL(READ(str), env);
+ }
+
+ public static void main(String[] args) {
+ String prompt = "user> ";
+
+ if (args[0].equals("--raw")) {
+ readline.mode = readline.Mode.JAVA;
+ }
+ while (true) {
+ String line;
+ try {
+ line = readline.readline(prompt);
+ if (line == null) { continue; }
+ } catch (readline.EOFException e) {
+ break;
+ } catch (IOException e) {
+ System.out.println("IOException: " + e.getMessage());
+ break;
+ }
+ System.out.println(PRINT(RE(null, line)));
+ }
+ }
+}
diff --git a/java/src/main/java/mal/step1_read_print.java b/java/src/main/java/mal/step1_read_print.java
new file mode 100644
index 0000000..447afc5
--- /dev/null
+++ b/java/src/main/java/mal/step1_read_print.java
@@ -0,0 +1,60 @@
+package mal;
+
+import java.io.IOException;
+
+import mal.types.*;
+import mal.readline;
+import mal.reader;
+
+public class step1_read_print {
+ // read
+ public static MalVal READ(String str) throws MalThrowable {
+ return reader.read_str(str);
+ }
+
+ // eval
+ public static MalVal EVAL(MalVal ast, String env) {
+ return ast;
+ }
+
+ // print
+ public static String PRINT(MalVal exp) {
+ return types._pr_str(exp, true);
+ }
+
+ // REPL
+ public static MalVal RE(String env, String str) throws MalThrowable {
+ return EVAL(READ(str), env);
+ }
+
+ public static void main(String[] args) throws MalThrowable {
+ String prompt = "user> ";
+
+ if (args[0].equals("--raw")) {
+ readline.mode = readline.Mode.JAVA;
+ }
+ while (true) {
+ String line;
+ try {
+ line = readline.readline(prompt);
+ if (line == null) { continue; }
+ } catch (readline.EOFException e) {
+ break;
+ } catch (IOException e) {
+ System.out.println("IOException: " + e.getMessage());
+ break;
+ }
+ try {
+ System.out.println(PRINT(RE(null, line)));
+ } catch (MalContinue e) {
+ continue;
+ } catch (MalError e) {
+ System.out.println("Error: " + e.getMessage());
+ continue;
+ } catch (reader.ParseError e) {
+ System.out.println(e.getMessage());
+ continue;
+ }
+ }
+ }
+}
diff --git a/java/src/main/java/mal/step2_eval.java b/java/src/main/java/mal/step2_eval.java
new file mode 100644
index 0000000..e1b30a9
--- /dev/null
+++ b/java/src/main/java/mal/step2_eval.java
@@ -0,0 +1,140 @@
+package mal;
+
+import java.io.IOException;
+
+import java.util.List;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Iterator;
+import mal.types.*;
+import mal.readline;
+import mal.reader;
+
+public class step2_eval {
+ // read
+ public static MalVal READ(String str) throws MalThrowable {
+ return reader.read_str(str);
+ }
+
+ // eval
+ public static MalVal eval_ast(MalVal ast, HashMap env) throws MalThrowable {
+ if (ast instanceof MalSymbol) {
+ MalSymbol sym = (MalSymbol)ast;
+ return (MalVal)env.get(sym.getName());
+ } else if (ast instanceof MalList) {
+ MalList old_lst = (MalList)ast;
+ MalList new_lst = types._list_Q(ast) ? new MalList()
+ : (MalList)new MalVector();
+ for (MalVal mv : (List<MalVal>)old_lst.value) {
+ new_lst.conj_BANG(EVAL(mv, env));
+ }
+ return new_lst;
+ } else if (ast instanceof MalHashMap) {
+ MalHashMap new_hm = new MalHashMap();
+ Iterator it = ((MalHashMap)ast).value.entrySet().iterator();
+ while (it.hasNext()) {
+ Map.Entry entry = (Map.Entry)it.next();
+ new_hm.value.put(entry.getKey(), EVAL((MalVal)entry.getValue(), env));
+ }
+ return new_hm;
+ } else {
+ return ast;
+ }
+ }
+
+ public static MalVal EVAL(MalVal orig_ast, HashMap env) throws MalThrowable {
+ MalVal a0;
+ //System.out.println("EVAL: " + types._pr_str(orig_ast, true));
+ if (!(types._list_Q(orig_ast))) {
+ return eval_ast(orig_ast, env);
+ }
+
+ // apply list
+ MalList ast = (MalList)orig_ast;
+ if (ast.size() == 0) { return ast; }
+ a0 = ast.nth(0);
+ if (!(a0 instanceof MalSymbol)) {
+ throw new MalError("attempt to apply on non-symbol '"
+ + types._pr_str(a0,true) + "'");
+ }
+ MalVal args = eval_ast(types._rest(ast), env);
+ MalSymbol fsym = (MalSymbol)a0;
+ ILambda f = (ILambda)env.get(fsym.getName());
+ return f.apply((MalList)args);
+ }
+
+ // print
+ public static String PRINT(MalVal exp) {
+ return types._pr_str(exp, true);
+ }
+
+ // REPL
+ public static MalVal RE(HashMap env, String str) throws MalThrowable {
+ return EVAL(READ(str), env);
+ }
+
+ static interface ILambda {
+ public MalVal apply(MalList args);
+ }
+ static class plus implements ILambda {
+ public MalVal apply(MalList args) {
+ return ((MalInteger)args.nth(0)).add(
+ ((MalInteger)args.nth(1)));
+ }
+ }
+ static class minus implements ILambda {
+ public MalVal apply(MalList args) {
+ return ((MalInteger)args.nth(0)).subtract(
+ ((MalInteger)args.nth(1)));
+ }
+ }
+ static class multiply implements ILambda {
+ public MalVal apply(MalList args) {
+ return ((MalInteger)args.nth(0)).multiply(
+ ((MalInteger)args.nth(1)));
+ }
+ }
+ static class divide implements ILambda {
+ public MalVal apply(MalList args) {
+ return ((MalInteger)args.nth(0)).divide(
+ ((MalInteger)args.nth(1)));
+ }
+ }
+
+ public static void main(String[] args) throws MalThrowable {
+ String prompt = "user> ";
+
+ HashMap repl_env = new HashMap();
+ repl_env.put("+", new plus());
+ repl_env.put("-", new minus());
+ repl_env.put("*", new multiply());
+ repl_env.put("/", new divide());
+
+ if (args[0].equals("--raw")) {
+ readline.mode = readline.Mode.JAVA;
+ }
+ while (true) {
+ String line;
+ try {
+ line = readline.readline(prompt);
+ if (line == null) { continue; }
+ } catch (readline.EOFException e) {
+ break;
+ } catch (IOException e) {
+ System.out.println("IOException: " + e.getMessage());
+ break;
+ }
+ try {
+ System.out.println(PRINT(RE(repl_env, line)));
+ } catch (MalContinue e) {
+ continue;
+ } catch (MalError e) {
+ System.out.println("Error: " + e.getMessage());
+ continue;
+ } catch (reader.ParseError e) {
+ System.out.println(e.getMessage());
+ continue;
+ }
+ }
+ }
+}
diff --git a/java/src/main/java/mal/step3_env.java b/java/src/main/java/mal/step3_env.java
new file mode 100644
index 0000000..867dba1
--- /dev/null
+++ b/java/src/main/java/mal/step3_env.java
@@ -0,0 +1,137 @@
+package mal;
+
+import java.io.IOException;
+
+import java.util.List;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Iterator;
+import mal.types.*;
+import mal.readline;
+import mal.reader;
+
+public class step3_env {
+ // read
+ public static MalVal READ(String str) throws MalThrowable {
+ return reader.read_str(str);
+ }
+
+ // eval
+ public static MalVal eval_ast(MalVal ast, Env env) throws MalThrowable {
+ if (ast instanceof MalSymbol) {
+ MalSymbol sym = (MalSymbol)ast;
+ return env.get(sym.getName());
+ } else if (ast instanceof MalList) {
+ MalList old_lst = (MalList)ast;
+ MalList new_lst = types._list_Q(ast) ? new MalList()
+ : (MalList)new MalVector();
+ for (MalVal mv : (List<MalVal>)old_lst.value) {
+ new_lst.conj_BANG(EVAL(mv, env));
+ }
+ return new_lst;
+ } else if (ast instanceof MalHashMap) {
+ MalHashMap new_hm = new MalHashMap();
+ Iterator it = ((MalHashMap)ast).value.entrySet().iterator();
+ while (it.hasNext()) {
+ Map.Entry entry = (Map.Entry)it.next();
+ new_hm.value.put(entry.getKey(), EVAL((MalVal)entry.getValue(), env));
+ }
+ return new_hm;
+ } else {
+ return ast;
+ }
+ }
+
+ public static MalVal EVAL(MalVal orig_ast, Env env) throws MalThrowable {
+ MalVal a0, a1,a2, res;
+ //System.out.println("EVAL: " + types._pr_str(orig_ast, true));
+ if (!(types._list_Q(orig_ast))) {
+ return eval_ast(orig_ast, env);
+ }
+
+ // apply list
+ MalList ast = (MalList)orig_ast;
+ if (ast.size() == 0) { return ast; }
+ a0 = ast.nth(0);
+ if (!(a0 instanceof MalSymbol)) {
+ throw new MalError("attempt to apply on non-symbol '"
+ + types._pr_str(a0,true) + "'");
+ }
+
+ switch (((MalSymbol)a0).getName()) {
+ case "def!":
+ a1 = ast.nth(1);
+ a2 = ast.nth(2);
+ res = EVAL(a2, env);
+ env.set(((MalSymbol)a1).getName(), res);
+ return res;
+ case "let*":
+ a1 = ast.nth(1);
+ a2 = ast.nth(2);
+ MalSymbol key;
+ MalVal val;
+ Env let_env = new Env(env);
+ for(int i=0; i<((MalList)a1).size(); i+=2) {
+ key = (MalSymbol)((MalList)a1).nth(i);
+ val = ((MalList)a1).nth(i+1);
+ let_env.set(key.getName(), EVAL(val, let_env));
+ }
+ return EVAL(a2, let_env);
+ default:
+ MalVal args = eval_ast(types._rest(ast), env);
+ MalSymbol fsym = (MalSymbol)a0;
+ ILambda f = (ILambda)env.get(fsym.getName());
+ return f.apply((MalList)args);
+ }
+ }
+
+ // print
+ public static String PRINT(MalVal exp) {
+ return types._pr_str(exp, true);
+ }
+
+ // REPL
+ public static MalVal RE(Env env, String str) throws MalThrowable {
+ return EVAL(READ(str), env);
+ }
+ public static Env _ref(Env env, String name, MalVal mv) {
+ return env.set(name, mv);
+ }
+
+ public static void main(String[] args) throws MalThrowable {
+ String prompt = "user> ";
+
+ Env repl_env = new Env(null);
+ _ref(repl_env, "+", types.add);
+ _ref(repl_env, "-", types.subtract);
+ _ref(repl_env, "*", types.multiply);
+ _ref(repl_env, "/", types.divide);
+
+ if (args[0].equals("--raw")) {
+ readline.mode = readline.Mode.JAVA;
+ }
+ while (true) {
+ String line;
+ try {
+ line = readline.readline(prompt);
+ if (line == null) { continue; }
+ } catch (readline.EOFException e) {
+ break;
+ } catch (IOException e) {
+ System.out.println("IOException: " + e.getMessage());
+ break;
+ }
+ try {
+ System.out.println(PRINT(RE(repl_env, line)));
+ } catch (MalContinue e) {
+ continue;
+ } catch (MalError e) {
+ System.out.println("Error: " + e.getMessage());
+ continue;
+ } catch (reader.ParseError e) {
+ System.out.println(e.getMessage());
+ continue;
+ }
+ }
+ }
+}
diff --git a/java/src/main/java/mal/step4_if_fn_do.java b/java/src/main/java/mal/step4_if_fn_do.java
new file mode 100644
index 0000000..7501b50
--- /dev/null
+++ b/java/src/main/java/mal/step4_if_fn_do.java
@@ -0,0 +1,163 @@
+package mal;
+
+import java.io.IOException;
+
+import java.util.List;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Iterator;
+import mal.types.*;
+import mal.readline;
+import mal.reader;
+
+public class step4_if_fn_do {
+ // read
+ public static MalVal READ(String str) throws MalThrowable {
+ return reader.read_str(str);
+ }
+
+ // eval
+ public static MalVal eval_ast(MalVal ast, Env env) throws MalThrowable {
+ if (ast instanceof MalSymbol) {
+ MalSymbol sym = (MalSymbol)ast;
+ return env.get(sym.getName());
+ } else if (ast instanceof MalList) {
+ MalList old_lst = (MalList)ast;
+ MalList new_lst = types._list_Q(ast) ? new MalList()
+ : (MalList)new MalVector();
+ for (MalVal mv : (List<MalVal>)old_lst.value) {
+ new_lst.conj_BANG(EVAL(mv, env));
+ }
+ return new_lst;
+ } else if (ast instanceof MalHashMap) {
+ MalHashMap new_hm = new MalHashMap();
+ Iterator it = ((MalHashMap)ast).value.entrySet().iterator();
+ while (it.hasNext()) {
+ Map.Entry entry = (Map.Entry)it.next();
+ new_hm.value.put(entry.getKey(), EVAL((MalVal)entry.getValue(), env));
+ }
+ return new_hm;
+ } else {
+ return ast;
+ }
+ }
+
+ public static MalVal EVAL(MalVal orig_ast, Env env) throws MalThrowable {
+ MalVal a0, a1,a2, a3, res;
+ MalList el;
+ //System.out.println("EVAL: " + types._pr_str(orig_ast, true));
+ if (!(types._list_Q(orig_ast))) {
+ return eval_ast(orig_ast, env);
+ }
+
+ // apply list
+ MalList ast = (MalList)orig_ast;
+ if (ast.size() == 0) { return ast; }
+ a0 = ast.nth(0);
+ String a0sym = a0 instanceof MalSymbol ? ((MalSymbol)a0).getName()
+ : "__<*fn*>__";
+ switch (a0sym) {
+ case "def!":
+ a1 = ast.nth(1);
+ a2 = ast.nth(2);
+ res = EVAL(a2, env);
+ env.set(((MalSymbol)a1).getName(), res);
+ return res;
+ case "let*":
+ a1 = ast.nth(1);
+ a2 = ast.nth(2);
+ MalSymbol key;
+ MalVal val;
+ Env let_env = new Env(env);
+ for(int i=0; i<((MalList)a1).size(); i+=2) {
+ key = (MalSymbol)((MalList)a1).nth(i);
+ val = ((MalList)a1).nth(i+1);
+ let_env.set(key.getName(), EVAL(val, let_env));
+ }
+ return EVAL(a2, let_env);
+ case "do":
+ el = (MalList)eval_ast(types._rest(ast), env);
+ return el.nth(el.size()-1);
+ case "if":
+ a1 = ast.nth(1);
+ MalVal cond = EVAL(a1, env);
+ if (cond == types.Nil || cond == types.False) {
+ // eval false slot form
+ if (ast.size() > 3) {
+ a3 = ast.nth(3);
+ return EVAL(a3, env);
+ } else {
+ return types.Nil;
+ }
+ } else {
+ // eval true slot form
+ a2 = ast.nth(2);
+ return EVAL(a2, env);
+ }
+ case "fn*":
+ final MalList a1f = (MalList)ast.nth(1);
+ final MalVal a2f = ast.nth(2);
+ final Env cur_env = env;
+ return new MalFunction () {
+ public MalVal apply(MalList args) throws MalThrowable {
+ return EVAL(a2f, new Env(cur_env, a1f, args));
+ }
+ };
+ default:
+ el = (MalList)eval_ast(ast, env);
+ MalFunction f = (MalFunction)el.nth(0);
+ return f.apply(types._rest(el));
+ }
+ }
+
+ // print
+ public static String PRINT(MalVal exp) {
+ return types._pr_str(exp, true);
+ }
+
+ // REPL
+ public static MalVal RE(Env env, String str) throws MalThrowable {
+ return EVAL(READ(str), env);
+ }
+ public static Env _ref(Env env, String name, MalVal mv) {
+ return env.set(name, mv);
+ }
+
+ public static void main(String[] args) throws MalThrowable {
+ String prompt = "user> ";
+
+ Env repl_env = new Env(null);
+ for (String key : types.types_ns.keySet()) {
+ _ref(repl_env, key, types.types_ns.get(key));
+ }
+
+ RE(repl_env, "(def! not (fn* (a) (if a false true)))");
+
+ if (args[0].equals("--raw")) {
+ readline.mode = readline.Mode.JAVA;
+ }
+ while (true) {
+ String line;
+ try {
+ line = readline.readline(prompt);
+ if (line == null) { continue; }
+ } catch (readline.EOFException e) {
+ break;
+ } catch (IOException e) {
+ System.out.println("IOException: " + e.getMessage());
+ break;
+ }
+ try {
+ System.out.println(PRINT(RE(repl_env, line)));
+ } catch (MalContinue e) {
+ continue;
+ } catch (reader.ParseError e) {
+ System.out.println(e.getMessage());
+ continue;
+ } catch (MalThrowable t) {
+ System.out.println("Error: " + t.getMessage());
+ continue;
+ }
+ }
+ }
+}
diff --git a/java/src/main/java/mal/step5_tco.java b/java/src/main/java/mal/step5_tco.java
new file mode 100644
index 0000000..41b295b
--- /dev/null
+++ b/java/src/main/java/mal/step5_tco.java
@@ -0,0 +1,174 @@
+package mal;
+
+import java.io.IOException;
+
+import java.util.List;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Iterator;
+import mal.types.*;
+import mal.readline;
+import mal.reader;
+
+public class step5_tco {
+ // read
+ public static MalVal READ(String str) throws MalThrowable {
+ return reader.read_str(str);
+ }
+
+ // eval
+ public static MalVal eval_ast(MalVal ast, Env env) throws MalThrowable {
+ if (ast instanceof MalSymbol) {
+ MalSymbol sym = (MalSymbol)ast;
+ return env.get(sym.getName());
+ } else if (ast instanceof MalList) {
+ MalList old_lst = (MalList)ast;
+ MalList new_lst = types._list_Q(ast) ? new MalList()
+ : (MalList)new MalVector();
+ for (MalVal mv : (List<MalVal>)old_lst.value) {
+ new_lst.conj_BANG(EVAL(mv, env));
+ }
+ return new_lst;
+ } else if (ast instanceof MalHashMap) {
+ MalHashMap new_hm = new MalHashMap();
+ Iterator it = ((MalHashMap)ast).value.entrySet().iterator();
+ while (it.hasNext()) {
+ Map.Entry entry = (Map.Entry)it.next();
+ new_hm.value.put(entry.getKey(), EVAL((MalVal)entry.getValue(), env));
+ }
+ return new_hm;
+ } else {
+ return ast;
+ }
+ }
+
+ public static MalVal EVAL(MalVal orig_ast, Env env) throws MalThrowable {
+ MalVal a1,a2, a3, res;
+ MalList el;
+
+ while (true) {
+
+ //System.out.println("EVAL: " + types._pr_str(orig_ast, true));
+ if (!(types._list_Q(orig_ast))) {
+ return eval_ast(orig_ast, env);
+ }
+
+ // apply list
+ MalList ast = (MalList)orig_ast;
+ if (ast.size() == 0) { return ast; }
+ MalVal a0 = ast.nth(0);
+ String a0sym = a0 instanceof MalSymbol ? ((MalSymbol)a0).getName()
+ : "__<*fn*>__";
+ switch (a0sym) {
+ case "def!":
+ a1 = ast.nth(1);
+ a2 = ast.nth(2);
+ res = EVAL(a2, env);
+ env.set(((MalSymbol)a1).getName(), res);
+ return res;
+ case "let*":
+ a1 = ast.nth(1);
+ a2 = ast.nth(2);
+ MalSymbol key;
+ MalVal val;
+ Env let_env = new Env(env);
+ for(int i=0; i<((MalList)a1).size(); i+=2) {
+ key = (MalSymbol)((MalList)a1).nth(i);
+ val = ((MalList)a1).nth(i+1);
+ let_env.set(key.getName(), EVAL(val, let_env));
+ }
+ return EVAL(a2, let_env);
+ case "do":
+ eval_ast(ast.slice(1, ast.size()-1), env);
+ orig_ast = ast.nth(ast.size()-1);
+ break;
+ case "if":
+ a1 = ast.nth(1);
+ MalVal cond = EVAL(a1, env);
+ if (cond == types.Nil || cond == types.False) {
+ // eval false slot form
+ if (ast.size() > 3) {
+ orig_ast = ast.nth(3);
+ } else {
+ return types.Nil;
+ }
+ } else {
+ // eval true slot form
+ orig_ast = ast.nth(2);
+ }
+ break;
+ case "fn*":
+ final MalList a1f = (MalList)ast.nth(1);
+ final MalVal a2f = ast.nth(2);
+ final Env cur_env = env;
+ return new MalFunction (a2f, (mal.types.Env)env, a1f) {
+ public MalVal apply(MalList args) throws MalThrowable {
+ return EVAL(a2f, new Env(cur_env, a1f, args));
+ }
+ };
+ default:
+ el = (MalList)eval_ast(ast, env);
+ MalFunction f = (MalFunction)el.nth(0);
+ MalVal fnast = f.getAst();
+ if (fnast != null) {
+ orig_ast = fnast;
+ env = new Env(f.getEnv(), f.getParams(), el.slice(1));
+ } else {
+ return f.apply(types._rest(el));
+ }
+ }
+
+ }
+ }
+
+ // print
+ public static String PRINT(MalVal exp) {
+ return types._pr_str(exp, true);
+ }
+
+ // REPL
+ public static MalVal RE(Env env, String str) throws MalThrowable {
+ return EVAL(READ(str), env);
+ }
+ public static Env _ref(Env env, String name, MalVal mv) {
+ return env.set(name, mv);
+ }
+
+ public static void main(String[] args) throws MalThrowable {
+ String prompt = "user> ";
+
+ Env repl_env = new Env(null);
+ for (String key : types.types_ns.keySet()) {
+ _ref(repl_env, key, types.types_ns.get(key));
+ }
+
+ RE(repl_env, "(def! not (fn* (a) (if a false true)))");
+
+ if (args[0].equals("--raw")) {
+ readline.mode = readline.Mode.JAVA;
+ }
+ while (true) {
+ String line;
+ try {
+ line = readline.readline(prompt);
+ if (line == null) { continue; }
+ } catch (readline.EOFException e) {
+ break;
+ } catch (IOException e) {
+ System.out.println("IOException: " + e.getMessage());
+ break;
+ }
+ try {
+ System.out.println(PRINT(RE(repl_env, line)));
+ } catch (MalContinue e) {
+ continue;
+ } catch (reader.ParseError e) {
+ System.out.println(e.getMessage());
+ continue;
+ } catch (MalThrowable t) {
+ System.out.println("Error: " + t.getMessage());
+ continue;
+ }
+ }
+ }
+}
diff --git a/java/src/main/java/mal/step6_file.java b/java/src/main/java/mal/step6_file.java
new file mode 100644
index 0000000..95bfd1c
--- /dev/null
+++ b/java/src/main/java/mal/step6_file.java
@@ -0,0 +1,216 @@
+package mal;
+
+import java.io.IOException;
+import java.io.FileNotFoundException;
+
+import java.util.Scanner;
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Iterator;
+import mal.types.*;
+import mal.readline;
+import mal.reader;
+
+public class step6_file {
+ // read
+ public static MalVal READ(String str) throws MalThrowable {
+ return reader.read_str(str);
+ }
+
+ // eval
+ public static MalVal eval_ast(MalVal ast, Env env) throws MalThrowable {
+ if (ast instanceof MalSymbol) {
+ MalSymbol sym = (MalSymbol)ast;
+ return env.get(sym.getName());
+ } else if (ast instanceof MalList) {
+ MalList old_lst = (MalList)ast;
+ MalList new_lst = types._list_Q(ast) ? new MalList()
+ : (MalList)new MalVector();
+ for (MalVal mv : (List<MalVal>)old_lst.value) {
+ new_lst.conj_BANG(EVAL(mv, env));
+ }
+ return new_lst;
+ } else if (ast instanceof MalHashMap) {
+ MalHashMap new_hm = new MalHashMap();
+ Iterator it = ((MalHashMap)ast).value.entrySet().iterator();
+ while (it.hasNext()) {
+ Map.Entry entry = (Map.Entry)it.next();
+ new_hm.value.put(entry.getKey(), EVAL((MalVal)entry.getValue(), env));
+ }
+ return new_hm;
+ } else {
+ return ast;
+ }
+ }
+
+ public static MalVal EVAL(MalVal orig_ast, Env env) throws MalThrowable {
+ MalVal a1,a2, a3, res;
+ MalList el;
+
+ while (true) {
+
+ //System.out.println("EVAL: " + types._pr_str(orig_ast, true));
+ if (!(types._list_Q(orig_ast))) {
+ return eval_ast(orig_ast, env);
+ }
+
+ // apply list
+ MalList ast = (MalList)orig_ast;
+ if (ast.size() == 0) { return ast; }
+ MalVal a0 = ast.nth(0);
+ String a0sym = a0 instanceof MalSymbol ? ((MalSymbol)a0).getName()
+ : "__<*fn*>__";
+ switch (a0sym) {
+ case "def!":
+ a1 = ast.nth(1);
+ a2 = ast.nth(2);
+ res = EVAL(a2, env);
+ env.set(((MalSymbol)a1).getName(), res);
+ return res;
+ case "let*":
+ a1 = ast.nth(1);
+ a2 = ast.nth(2);
+ MalSymbol key;
+ MalVal val;
+ Env let_env = new Env(env);
+ for(int i=0; i<((MalList)a1).size(); i+=2) {
+ key = (MalSymbol)((MalList)a1).nth(i);
+ val = ((MalList)a1).nth(i+1);
+ let_env.set(key.getName(), EVAL(val, let_env));
+ }
+ return EVAL(a2, let_env);
+ case "do":
+ eval_ast(ast.slice(1, ast.size()-1), env);
+ orig_ast = ast.nth(ast.size()-1);
+ break;
+ case "if":
+ a1 = ast.nth(1);
+ MalVal cond = EVAL(a1, env);
+ if (cond == types.Nil || cond == types.False) {
+ // eval false slot form
+ if (ast.size() > 3) {
+ orig_ast = ast.nth(3);
+ } else {
+ return types.Nil;
+ }
+ } else {
+ // eval true slot form
+ orig_ast = ast.nth(2);
+ }
+ break;
+ case "fn*":
+ final MalList a1f = (MalList)ast.nth(1);
+ final MalVal a2f = ast.nth(2);
+ final Env cur_env = env;
+ return new MalFunction (a2f, (mal.types.Env)env, a1f) {
+ public MalVal apply(MalList args) throws MalThrowable {
+ return EVAL(a2f, new Env(cur_env, a1f, args));
+ }
+ };
+ default:
+ el = (MalList)eval_ast(ast, env);
+ MalFunction f = (MalFunction)el.nth(0);
+ MalVal fnast = f.getAst();
+ if (fnast != null) {
+ orig_ast = fnast;
+ env = new Env(f.getEnv(), f.getParams(), el.slice(1));
+ } else {
+ return f.apply(types._rest(el));
+ }
+ }
+
+ }
+ }
+
+ // print
+ public static String PRINT(MalVal exp) {
+ return types._pr_str(exp, true);
+ }
+
+ // REPL
+ public static MalVal RE(Env env, String str) throws MalThrowable {
+ return EVAL(READ(str), env);
+ }
+ public static Env _ref(Env env, String name, MalVal mv) {
+ return env.set(name, mv);
+ }
+ public static String slurp(String fname) throws MalThrowable {
+ try {
+ return new Scanner(new File(fname))
+ .useDelimiter("\\Z").next();
+ } catch (FileNotFoundException e) {
+ throw new MalError(e.getMessage());
+ }
+ }
+
+ public static void main(String[] args) throws MalThrowable {
+ String prompt = "user> ";
+
+ final Env repl_env = new Env(null);
+ for (String key : types.types_ns.keySet()) {
+ _ref(repl_env, key, types.types_ns.get(key));
+ }
+ _ref(repl_env, "read-string", new MalFunction() {
+ public MalVal apply(MalList args) throws MalThrowable {
+ return reader.read_str(((MalString)args.nth(0)).getValue());
+ }
+ });
+ _ref(repl_env, "eval", new MalFunction() {
+ public MalVal apply(MalList args) throws MalThrowable {
+ return EVAL(args.nth(0), repl_env);
+ }
+ });
+ _ref(repl_env, "slurp", new MalFunction() {
+ public MalVal apply(MalList args) throws MalThrowable {
+ String fname = ((MalString)args.nth(0)).getValue();
+ return new MalString(slurp(fname));
+ }
+ });
+ _ref(repl_env, "slurp-do", new MalFunction() {
+ public MalVal apply(MalList args) throws MalThrowable {
+ String fname = ((MalString)args.nth(0)).getValue();
+ return new MalString("(do " + slurp(fname) + ")");
+ }
+ });
+
+ RE(repl_env, "(def! not (fn* (a) (if a false true)))");
+ RE(repl_env, "(def! load-file (fn* (f) (eval (read-string (slurp-do f)))))");
+
+ Integer fileIdx = 0;
+ if (args.length > 0 && args[0].equals("--raw")) {
+ readline.mode = readline.Mode.JAVA;
+ fileIdx = 1;
+ }
+ if (args.length > fileIdx) {
+ for(Integer i=fileIdx; i<args.length; i++) {
+ RE(repl_env, "(load-file \"" + args[i] + "\")");
+ }
+ return;
+ }
+ while (true) {
+ String line;
+ try {
+ line = readline.readline(prompt);
+ if (line == null) { continue; }
+ } catch (readline.EOFException e) {
+ break;
+ } catch (IOException e) {
+ System.out.println("IOException: " + e.getMessage());
+ break;
+ }
+ try {
+ System.out.println(PRINT(RE(repl_env, line)));
+ } catch (MalContinue e) {
+ continue;
+ } catch (reader.ParseError e) {
+ System.out.println(e.getMessage());
+ continue;
+ } catch (MalThrowable t) {
+ System.out.println("Error: " + t.getMessage());
+ continue;
+ }
+ }
+ }
+}
diff --git a/java/src/main/java/mal/step7_quote.java b/java/src/main/java/mal/step7_quote.java
new file mode 100644
index 0000000..49f395e
--- /dev/null
+++ b/java/src/main/java/mal/step7_quote.java
@@ -0,0 +1,247 @@
+package mal;
+
+import java.io.IOException;
+import java.io.FileNotFoundException;
+
+import java.util.Scanner;
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Iterator;
+import mal.types.*;
+import mal.readline;
+import mal.reader;
+
+public class step7_quote {
+ // read
+ public static MalVal READ(String str) throws MalThrowable {
+ return reader.read_str(str);
+ }
+
+ // eval
+ public static Boolean is_pair(MalVal x) {
+ return x instanceof MalList && ((MalList)x).size() > 0;
+ }
+
+ public static MalVal quasiquote(MalVal ast) {
+ if (!is_pair(ast)) {
+ return new MalList(new MalSymbol("quote"), ast);
+ } else {
+ MalVal a0 = ((MalList)ast).nth(0);
+ if ((a0 instanceof MalSymbol) &&
+ (((MalSymbol)a0).getName() == "unquote")) {
+ return ((MalList)ast).nth(1);
+ } else if (is_pair(a0)) {
+ MalVal a00 = ((MalList)a0).nth(0);
+ if ((a00 instanceof MalSymbol) &&
+ (((MalSymbol)a00).getName() == "splice-unquote")) {
+ return new MalList(new MalSymbol("concat"),
+ ((MalList)a0).nth(1),
+ quasiquote(types._rest((MalList)ast)));
+ }
+ }
+ return new MalList(new MalSymbol("cons"),
+ quasiquote(a0),
+ quasiquote(types._rest((MalList)ast)));
+ }
+ }
+
+ public static MalVal eval_ast(MalVal ast, Env env) throws MalThrowable {
+ if (ast instanceof MalSymbol) {
+ MalSymbol sym = (MalSymbol)ast;
+ return env.get(sym.getName());
+ } else if (ast instanceof MalList) {
+ MalList old_lst = (MalList)ast;
+ MalList new_lst = types._list_Q(ast) ? new MalList()
+ : (MalList)new MalVector();
+ for (MalVal mv : (List<MalVal>)old_lst.value) {
+ new_lst.conj_BANG(EVAL(mv, env));
+ }
+ return new_lst;
+ } else if (ast instanceof MalHashMap) {
+ MalHashMap new_hm = new MalHashMap();
+ Iterator it = ((MalHashMap)ast).value.entrySet().iterator();
+ while (it.hasNext()) {
+ Map.Entry entry = (Map.Entry)it.next();
+ new_hm.value.put(entry.getKey(), EVAL((MalVal)entry.getValue(), env));
+ }
+ return new_hm;
+ } else {
+ return ast;
+ }
+ }
+
+ public static MalVal EVAL(MalVal orig_ast, Env env) throws MalThrowable {
+ MalVal a1,a2, a3, res;
+ MalList el;
+
+ while (true) {
+
+ //System.out.println("EVAL: " + types._pr_str(orig_ast, true));
+ if (!(types._list_Q(orig_ast))) {
+ return eval_ast(orig_ast, env);
+ }
+
+ // apply list
+ MalList ast = (MalList)orig_ast;
+ if (ast.size() == 0) { return ast; }
+ MalVal a0 = ast.nth(0);
+ String a0sym = a0 instanceof MalSymbol ? ((MalSymbol)a0).getName()
+ : "__<*fn*>__";
+ switch (a0sym) {
+ case "def!":
+ a1 = ast.nth(1);
+ a2 = ast.nth(2);
+ res = EVAL(a2, env);
+ env.set(((MalSymbol)a1).getName(), res);
+ return res;
+ case "let*":
+ a1 = ast.nth(1);
+ a2 = ast.nth(2);
+ MalSymbol key;
+ MalVal val;
+ Env let_env = new Env(env);
+ for(int i=0; i<((MalList)a1).size(); i+=2) {
+ key = (MalSymbol)((MalList)a1).nth(i);
+ val = ((MalList)a1).nth(i+1);
+ let_env.set(key.getName(), EVAL(val, let_env));
+ }
+ return EVAL(a2, let_env);
+ case "quote":
+ return ast.nth(1);
+ case "quasiquote":
+ return EVAL(quasiquote(ast.nth(1)), env);
+ case "do":
+ eval_ast(ast.slice(1, ast.size()-1), env);
+ orig_ast = ast.nth(ast.size()-1);
+ break;
+ case "if":
+ a1 = ast.nth(1);
+ MalVal cond = EVAL(a1, env);
+ if (cond == types.Nil || cond == types.False) {
+ // eval false slot form
+ if (ast.size() > 3) {
+ orig_ast = ast.nth(3);
+ } else {
+ return types.Nil;
+ }
+ } else {
+ // eval true slot form
+ orig_ast = ast.nth(2);
+ }
+ break;
+ case "fn*":
+ final MalList a1f = (MalList)ast.nth(1);
+ final MalVal a2f = ast.nth(2);
+ final Env cur_env = env;
+ return new MalFunction (a2f, (mal.types.Env)env, a1f) {
+ public MalVal apply(MalList args) throws MalThrowable {
+ return EVAL(a2f, new Env(cur_env, a1f, args));
+ }
+ };
+ default:
+ el = (MalList)eval_ast(ast, env);
+ MalFunction f = (MalFunction)el.nth(0);
+ MalVal fnast = f.getAst();
+ if (fnast != null) {
+ orig_ast = fnast;
+ env = new Env(f.getEnv(), f.getParams(), el.slice(1));
+ } else {
+ return f.apply(types._rest(el));
+ }
+ }
+
+ }
+ }
+
+ // print
+ public static String PRINT(MalVal exp) {
+ return types._pr_str(exp, true);
+ }
+
+ // REPL
+ public static MalVal RE(Env env, String str) throws MalThrowable {
+ return EVAL(READ(str), env);
+ }
+ public static Env _ref(Env env, String name, MalVal mv) {
+ return env.set(name, mv);
+ }
+ public static String slurp(String fname) throws MalThrowable {
+ try {
+ return new Scanner(new File(fname))
+ .useDelimiter("\\Z").next();
+ } catch (FileNotFoundException e) {
+ throw new MalError(e.getMessage());
+ }
+ }
+
+ public static void main(String[] args) throws MalThrowable {
+ String prompt = "user> ";
+
+ final Env repl_env = new Env(null);
+ for (String key : types.types_ns.keySet()) {
+ _ref(repl_env, key, types.types_ns.get(key));
+ }
+ _ref(repl_env, "read-string", new MalFunction() {
+ public MalVal apply(MalList args) throws MalThrowable {
+ return reader.read_str(((MalString)args.nth(0)).getValue());
+ }
+ });
+ _ref(repl_env, "eval", new MalFunction() {
+ public MalVal apply(MalList args) throws MalThrowable {
+ return EVAL(args.nth(0), repl_env);
+ }
+ });
+ _ref(repl_env, "slurp", new MalFunction() {
+ public MalVal apply(MalList args) throws MalThrowable {
+ String fname = ((MalString)args.nth(0)).getValue();
+ return new MalString(slurp(fname));
+ }
+ });
+ _ref(repl_env, "slurp-do", new MalFunction() {
+ public MalVal apply(MalList args) throws MalThrowable {
+ String fname = ((MalString)args.nth(0)).getValue();
+ return new MalString("(do " + slurp(fname) + ")");
+ }
+ });
+
+ RE(repl_env, "(def! not (fn* (a) (if a false true)))");
+ RE(repl_env, "(def! load-file (fn* (f) (eval (read-string (slurp-do f)))))");
+
+ Integer fileIdx = 0;
+ if (args.length > 0 && args[0].equals("--raw")) {
+ readline.mode = readline.Mode.JAVA;
+ fileIdx = 1;
+ }
+ if (args.length > fileIdx) {
+ for(Integer i=fileIdx; i<args.length; i++) {
+ RE(repl_env, "(load-file \"" + args[i] + "\")");
+ }
+ return;
+ }
+ while (true) {
+ String line;
+ try {
+ line = readline.readline(prompt);
+ if (line == null) { continue; }
+ } catch (readline.EOFException e) {
+ break;
+ } catch (IOException e) {
+ System.out.println("IOException: " + e.getMessage());
+ break;
+ }
+ try {
+ System.out.println(PRINT(RE(repl_env, line)));
+ } catch (MalContinue e) {
+ continue;
+ } catch (reader.ParseError e) {
+ System.out.println(e.getMessage());
+ continue;
+ } catch (MalThrowable t) {
+ System.out.println("Error: " + t.getMessage());
+ continue;
+ }
+ }
+ }
+}
diff --git a/java/src/main/java/mal/step8_macros.java b/java/src/main/java/mal/step8_macros.java
new file mode 100644
index 0000000..c632987
--- /dev/null
+++ b/java/src/main/java/mal/step8_macros.java
@@ -0,0 +1,285 @@
+package mal;
+
+import java.io.IOException;
+import java.io.FileNotFoundException;
+
+import java.util.Scanner;
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Iterator;
+import mal.types.*;
+import mal.readline;
+import mal.reader;
+
+public class step8_macros {
+ // read
+ public static MalVal READ(String str) throws MalThrowable {
+ return reader.read_str(str);
+ }
+
+ // eval
+ public static Boolean is_pair(MalVal x) {
+ return x instanceof MalList && ((MalList)x).size() > 0;
+ }
+
+ public static MalVal quasiquote(MalVal ast) {
+ if (!is_pair(ast)) {
+ return new MalList(new MalSymbol("quote"), ast);
+ } else {
+ MalVal a0 = ((MalList)ast).nth(0);
+ if ((a0 instanceof MalSymbol) &&
+ (((MalSymbol)a0).getName() == "unquote")) {
+ return ((MalList)ast).nth(1);
+ } else if (is_pair(a0)) {
+ MalVal a00 = ((MalList)a0).nth(0);
+ if ((a00 instanceof MalSymbol) &&
+ (((MalSymbol)a00).getName() == "splice-unquote")) {
+ return new MalList(new MalSymbol("concat"),
+ ((MalList)a0).nth(1),
+ quasiquote(types._rest((MalList)ast)));
+ }
+ }
+ return new MalList(new MalSymbol("cons"),
+ quasiquote(a0),
+ quasiquote(types._rest((MalList)ast)));
+ }
+ }
+
+ public static Boolean is_macro_call(MalVal ast, Env env)
+ throws MalThrowable {
+ if (ast instanceof MalList) {
+ MalVal a0 = ((MalList)ast).nth(0);
+ if (a0 instanceof MalSymbol &&
+ env.find(((MalSymbol)a0).getName()) != null) {
+ MalVal mac = env.get(((MalSymbol)a0).getName());
+ if (mac instanceof MalFunction &&
+ ((MalFunction)mac).isMacro()) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ public static MalVal macroexpand(MalVal ast, Env env)
+ throws MalThrowable {
+ while (is_macro_call(ast, env)) {
+ MalSymbol a0 = (MalSymbol)((MalList)ast).nth(0);
+ MalFunction mac = (MalFunction) env.get(a0.getName());
+ ast = mac.apply(types._rest((MalList)ast));
+ }
+ return ast;
+ }
+
+ public static MalVal eval_ast(MalVal ast, Env env) throws MalThrowable {
+ if (ast instanceof MalSymbol) {
+ MalSymbol sym = (MalSymbol)ast;
+ return env.get(sym.getName());
+ } else if (ast instanceof MalList) {
+ MalList old_lst = (MalList)ast;
+ MalList new_lst = types._list_Q(ast) ? new MalList()
+ : (MalList)new MalVector();
+ for (MalVal mv : (List<MalVal>)old_lst.value) {
+ new_lst.conj_BANG(EVAL(mv, env));
+ }
+ return new_lst;
+ } else if (ast instanceof MalHashMap) {
+ MalHashMap new_hm = new MalHashMap();
+ Iterator it = ((MalHashMap)ast).value.entrySet().iterator();
+ while (it.hasNext()) {
+ Map.Entry entry = (Map.Entry)it.next();
+ new_hm.value.put(entry.getKey(), EVAL((MalVal)entry.getValue(), env));
+ }
+ return new_hm;
+ } else {
+ return ast;
+ }
+ }
+
+ public static MalVal EVAL(MalVal orig_ast, Env env) throws MalThrowable {
+ MalVal a1,a2, a3, res;
+ MalList el;
+
+ while (true) {
+
+ //System.out.println("EVAL: " + types._pr_str(orig_ast, true));
+ if (!(types._list_Q(orig_ast))) {
+ return eval_ast(orig_ast, env);
+ }
+
+ // apply list
+ MalVal expanded = macroexpand(orig_ast, env);
+ if (!types._list_Q(expanded)) { return expanded; }
+ MalList ast = (MalList) expanded;
+ if (ast.size() == 0) { return ast; }
+ MalVal a0 = ast.nth(0);
+ String a0sym = a0 instanceof MalSymbol ? ((MalSymbol)a0).getName()
+ : "__<*fn*>__";
+ switch (a0sym) {
+ case "def!":
+ a1 = ast.nth(1);
+ a2 = ast.nth(2);
+ res = EVAL(a2, env);
+ env.set(((MalSymbol)a1).getName(), res);
+ return res;
+ case "let*":
+ a1 = ast.nth(1);
+ a2 = ast.nth(2);
+ MalSymbol key;
+ MalVal val;
+ Env let_env = new Env(env);
+ for(int i=0; i<((MalList)a1).size(); i+=2) {
+ key = (MalSymbol)((MalList)a1).nth(i);
+ val = ((MalList)a1).nth(i+1);
+ let_env.set(key.getName(), EVAL(val, let_env));
+ }
+ return EVAL(a2, let_env);
+ case "quote":
+ return ast.nth(1);
+ case "quasiquote":
+ return EVAL(quasiquote(ast.nth(1)), env);
+ case "defmacro!":
+ a1 = ast.nth(1);
+ a2 = ast.nth(2);
+ res = EVAL(a2, env);
+ ((MalFunction)res).setMacro();
+ env.set(((MalSymbol)a1).getName(), res);
+ return res;
+ case "macroexpand":
+ a1 = ast.nth(1);
+ return macroexpand(a1, env);
+ case "do":
+ eval_ast(ast.slice(1, ast.size()-1), env);
+ orig_ast = ast.nth(ast.size()-1);
+ break;
+ case "if":
+ a1 = ast.nth(1);
+ MalVal cond = EVAL(a1, env);
+ if (cond == types.Nil || cond == types.False) {
+ // eval false slot form
+ if (ast.size() > 3) {
+ orig_ast = ast.nth(3);
+ } else {
+ return types.Nil;
+ }
+ } else {
+ // eval true slot form
+ orig_ast = ast.nth(2);
+ }
+ break;
+ case "fn*":
+ final MalList a1f = (MalList)ast.nth(1);
+ final MalVal a2f = ast.nth(2);
+ final Env cur_env = env;
+ return new MalFunction (a2f, (mal.types.Env)env, a1f) {
+ public MalVal apply(MalList args) throws MalThrowable {
+ return EVAL(a2f, new Env(cur_env, a1f, args));
+ }
+ };
+ default:
+ el = (MalList)eval_ast(ast, env);
+ MalFunction f = (MalFunction)el.nth(0);
+ MalVal fnast = f.getAst();
+ if (fnast != null) {
+ orig_ast = fnast;
+ env = new Env(f.getEnv(), f.getParams(), el.slice(1));
+ } else {
+ return f.apply(types._rest(el));
+ }
+ }
+
+ }
+ }
+
+ // print
+ public static String PRINT(MalVal exp) {
+ return types._pr_str(exp, true);
+ }
+
+ // REPL
+ public static MalVal RE(Env env, String str) throws MalThrowable {
+ return EVAL(READ(str), env);
+ }
+ public static Env _ref(Env env, String name, MalVal mv) {
+ return env.set(name, mv);
+ }
+ public static String slurp(String fname) throws MalThrowable {
+ try {
+ return new Scanner(new File(fname))
+ .useDelimiter("\\Z").next();
+ } catch (FileNotFoundException e) {
+ throw new MalError(e.getMessage());
+ }
+ }
+
+ public static void main(String[] args) throws MalThrowable {
+ String prompt = "user> ";
+
+ final Env repl_env = new Env(null);
+ for (String key : types.types_ns.keySet()) {
+ _ref(repl_env, key, types.types_ns.get(key));
+ }
+ _ref(repl_env, "read-string", new MalFunction() {
+ public MalVal apply(MalList args) throws MalThrowable {
+ return reader.read_str(((MalString)args.nth(0)).getValue());
+ }
+ });
+ _ref(repl_env, "eval", new MalFunction() {
+ public MalVal apply(MalList args) throws MalThrowable {
+ return EVAL(args.nth(0), repl_env);
+ }
+ });
+ _ref(repl_env, "slurp", new MalFunction() {
+ public MalVal apply(MalList args) throws MalThrowable {
+ String fname = ((MalString)args.nth(0)).getValue();
+ return new MalString(slurp(fname));
+ }
+ });
+ _ref(repl_env, "slurp-do", new MalFunction() {
+ public MalVal apply(MalList args) throws MalThrowable {
+ String fname = ((MalString)args.nth(0)).getValue();
+ return new MalString("(do " + slurp(fname) + ")");
+ }
+ });
+
+ RE(repl_env, "(def! not (fn* (a) (if a false true)))");
+ RE(repl_env, "(def! load-file (fn* (f) (eval (read-string (slurp-do f)))))");
+
+ Integer fileIdx = 0;
+ if (args.length > 0 && args[0].equals("--raw")) {
+ readline.mode = readline.Mode.JAVA;
+ fileIdx = 1;
+ }
+ if (args.length > fileIdx) {
+ for(Integer i=fileIdx; i<args.length; i++) {
+ RE(repl_env, "(load-file \"" + args[i] + "\")");
+ }
+ return;
+ }
+ while (true) {
+ String line;
+ try {
+ line = readline.readline(prompt);
+ if (line == null) { continue; }
+ } catch (readline.EOFException e) {
+ break;
+ } catch (IOException e) {
+ System.out.println("IOException: " + e.getMessage());
+ break;
+ }
+ try {
+ System.out.println(PRINT(RE(repl_env, line)));
+ } catch (MalContinue e) {
+ continue;
+ } catch (reader.ParseError e) {
+ System.out.println(e.getMessage());
+ continue;
+ } catch (MalThrowable t) {
+ System.out.println("Error: " + t.getMessage());
+ continue;
+ }
+ }
+ }
+}
diff --git a/java/src/main/java/mal/stepA_more.java b/java/src/main/java/mal/stepA_more.java
new file mode 100644
index 0000000..ff09aff
--- /dev/null
+++ b/java/src/main/java/mal/stepA_more.java
@@ -0,0 +1,333 @@
+package mal;
+
+import java.io.IOException;
+import java.io.FileNotFoundException;
+
+import java.util.Scanner;
+import java.io.File;
+import java.io.StringWriter;
+import java.io.PrintWriter;
+import java.util.List;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Iterator;
+import mal.types.*;
+import mal.readline;
+import mal.reader;
+
+public class stepA_more {
+ // read
+ public static MalVal READ(String str) throws MalThrowable {
+ return reader.read_str(str);
+ }
+
+ // eval
+ public static Boolean is_pair(MalVal x) {
+ return x instanceof MalList && ((MalList)x).size() > 0;
+ }
+
+ public static MalVal quasiquote(MalVal ast) {
+ if (!is_pair(ast)) {
+ return new MalList(new MalSymbol("quote"), ast);
+ } else {
+ MalVal a0 = ((MalList)ast).nth(0);
+ if ((a0 instanceof MalSymbol) &&
+ (((MalSymbol)a0).getName() == "unquote")) {
+ return ((MalList)ast).nth(1);
+ } else if (is_pair(a0)) {
+ MalVal a00 = ((MalList)a0).nth(0);
+ if ((a00 instanceof MalSymbol) &&
+ (((MalSymbol)a00).getName() == "splice-unquote")) {
+ return new MalList(new MalSymbol("concat"),
+ ((MalList)a0).nth(1),
+ quasiquote(types._rest((MalList)ast)));
+ }
+ }
+ return new MalList(new MalSymbol("cons"),
+ quasiquote(a0),
+ quasiquote(types._rest((MalList)ast)));
+ }
+ }
+
+ public static Boolean is_macro_call(MalVal ast, Env env)
+ throws MalThrowable {
+ if (ast instanceof MalList) {
+ MalVal a0 = ((MalList)ast).nth(0);
+ if (a0 instanceof MalSymbol &&
+ env.find(((MalSymbol)a0).getName()) != null) {
+ MalVal mac = env.get(((MalSymbol)a0).getName());
+ if (mac instanceof MalFunction &&
+ ((MalFunction)mac).isMacro()) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ public static MalVal macroexpand(MalVal ast, Env env)
+ throws MalThrowable {
+ while (is_macro_call(ast, env)) {
+ MalSymbol a0 = (MalSymbol)((MalList)ast).nth(0);
+ MalFunction mac = (MalFunction) env.get(a0.getName());
+ ast = mac.apply(types._rest((MalList)ast));
+ }
+ return ast;
+ }
+
+ public static MalVal eval_ast(MalVal ast, Env env) throws MalThrowable {
+ if (ast instanceof MalSymbol) {
+ MalSymbol sym = (MalSymbol)ast;
+ return env.get(sym.getName());
+ } else if (ast instanceof MalList) {
+ MalList old_lst = (MalList)ast;
+ MalList new_lst = types._list_Q(ast) ? new MalList()
+ : (MalList)new MalVector();
+ for (MalVal mv : (List<MalVal>)old_lst.value) {
+ new_lst.conj_BANG(EVAL(mv, env));
+ }
+ return new_lst;
+ } else if (ast instanceof MalHashMap) {
+ MalHashMap new_hm = new MalHashMap();
+ Iterator it = ((MalHashMap)ast).value.entrySet().iterator();
+ while (it.hasNext()) {
+ Map.Entry entry = (Map.Entry)it.next();
+ new_hm.value.put(entry.getKey(), EVAL((MalVal)entry.getValue(), env));
+ }
+ return new_hm;
+ } else {
+ return ast;
+ }
+ }
+
+ public static MalVal EVAL(MalVal orig_ast, Env env) throws MalThrowable {
+ MalVal a1,a2, a3, res;
+ MalList el;
+
+ while (true) {
+
+ //System.out.println("EVAL: " + types._pr_str(orig_ast, true));
+ if (!(types._list_Q(orig_ast))) {
+ return eval_ast(orig_ast, env);
+ }
+
+ // apply list
+ MalVal expanded = macroexpand(orig_ast, env);
+ if (!types._list_Q(expanded)) { return expanded; }
+ MalList ast = (MalList) expanded;
+ if (ast.size() == 0) { return ast; }
+ MalVal a0 = ast.nth(0);
+ String a0sym = a0 instanceof MalSymbol ? ((MalSymbol)a0).getName()
+ : "__<*fn*>__";
+ switch (a0sym) {
+ case "def!":
+ a1 = ast.nth(1);
+ a2 = ast.nth(2);
+ res = EVAL(a2, env);
+ env.set(((MalSymbol)a1).getName(), res);
+ return res;
+ case "let*":
+ a1 = ast.nth(1);
+ a2 = ast.nth(2);
+ MalSymbol key;
+ MalVal val;
+ Env let_env = new Env(env);
+ for(int i=0; i<((MalList)a1).size(); i+=2) {
+ key = (MalSymbol)((MalList)a1).nth(i);
+ val = ((MalList)a1).nth(i+1);
+ let_env.set(key.getName(), EVAL(val, let_env));
+ }
+ return EVAL(a2, let_env);
+ case "quote":
+ return ast.nth(1);
+ case "quasiquote":
+ return EVAL(quasiquote(ast.nth(1)), env);
+ case "defmacro!":
+ a1 = ast.nth(1);
+ a2 = ast.nth(2);
+ res = EVAL(a2, env);
+ ((MalFunction)res).setMacro();
+ env.set(((MalSymbol)a1).getName(), res);
+ return res;
+ case "macroexpand":
+ a1 = ast.nth(1);
+ return macroexpand(a1, env);
+ case "try*":
+ try {
+ return EVAL(ast.nth(1), env);
+ } catch (Throwable t) {
+ if (ast.size() > 2) {
+ MalVal exc;
+ a2 = ast.nth(2);
+ MalVal a20 = ((MalList)a2).nth(0);
+ if (((MalSymbol)a20).getName().equals("catch*")) {
+ if (t instanceof MalException) {
+ exc = ((MalException)t).getValue();
+ } else {
+ StringWriter sw = new StringWriter();
+ t.printStackTrace(new PrintWriter(sw));
+ String tstr = sw.toString();
+ exc = new MalString(t.getMessage() + ": " + tstr);
+ }
+ return EVAL(((MalList)a2).nth(2),
+ new Env(env, ((MalList)a2).slice(1,2),
+ new MalList(exc)));
+ }
+ }
+ throw t;
+ }
+ case "do":
+ eval_ast(ast.slice(1, ast.size()-1), env);
+ orig_ast = ast.nth(ast.size()-1);
+ break;
+ case "if":
+ a1 = ast.nth(1);
+ MalVal cond = EVAL(a1, env);
+ if (cond == types.Nil || cond == types.False) {
+ // eval false slot form
+ if (ast.size() > 3) {
+ orig_ast = ast.nth(3);
+ } else {
+ return types.Nil;
+ }
+ } else {
+ // eval true slot form
+ orig_ast = ast.nth(2);
+ }
+ break;
+ case "fn*":
+ final MalList a1f = (MalList)ast.nth(1);
+ final MalVal a2f = ast.nth(2);
+ final Env cur_env = env;
+ return new MalFunction (a2f, (mal.types.Env)env, a1f) {
+ public MalVal apply(MalList args) throws MalThrowable {
+ return EVAL(a2f, new Env(cur_env, a1f, args));
+ }
+ };
+ default:
+ el = (MalList)eval_ast(ast, env);
+ MalFunction f = (MalFunction)el.nth(0);
+ MalVal fnast = f.getAst();
+ if (fnast != null) {
+ orig_ast = fnast;
+ env = new Env(f.getEnv(), f.getParams(), el.slice(1));
+ } else {
+ return f.apply(types._rest(el));
+ }
+ }
+
+ }
+ }
+
+ // print
+ public static String PRINT(MalVal exp) {
+ return types._pr_str(exp, true);
+ }
+
+ // REPL
+ public static MalVal RE(Env env, String str) throws MalThrowable {
+ return EVAL(READ(str), env);
+ }
+ public static Env _ref(Env env, String name, MalVal mv) {
+ return env.set(name, mv);
+ }
+ public static String slurp(String fname) throws MalThrowable {
+ try {
+ return new Scanner(new File(fname))
+ .useDelimiter("\\Z").next();
+ } catch (FileNotFoundException e) {
+ throw new MalError(e.getMessage());
+ }
+ }
+
+ public static void main(String[] args) throws MalThrowable {
+ String prompt = "user> ";
+
+ final Env repl_env = new Env(null);
+ for (String key : types.types_ns.keySet()) {
+ _ref(repl_env, key, types.types_ns.get(key));
+ }
+ _ref(repl_env, "readline", new MalFunction() {
+ public MalVal apply(MalList args) throws MalThrowable {
+ String prompt = ((MalString)args.nth(0)).getValue();
+ try {
+ return new MalString(readline.readline(prompt));
+ } catch (IOException e) {
+ throw new MalException(new MalString(e.getMessage()));
+ } catch (readline.EOFException e) {
+ throw new MalException(new MalString(e.getMessage()));
+ }
+ }
+ });
+ _ref(repl_env, "read-string", new MalFunction() {
+ public MalVal apply(MalList args) throws MalThrowable {
+ try {
+ return reader.read_str(((MalString)args.nth(0)).getValue());
+ } catch (MalContinue c) {
+ return types.Nil;
+ }
+ }
+ });
+ _ref(repl_env, "eval", new MalFunction() {
+ public MalVal apply(MalList args) throws MalThrowable {
+ return EVAL(args.nth(0), repl_env);
+ }
+ });
+ _ref(repl_env, "slurp", new MalFunction() {
+ public MalVal apply(MalList args) throws MalThrowable {
+ String fname = ((MalString)args.nth(0)).getValue();
+ return new MalString(slurp(fname));
+ }
+ });
+ _ref(repl_env, "slurp-do", new MalFunction() {
+ public MalVal apply(MalList args) throws MalThrowable {
+ String fname = ((MalString)args.nth(0)).getValue();
+ return new MalString("(do " + slurp(fname) + ")");
+ }
+ });
+
+ RE(repl_env, "(def! not (fn* (a) (if a false true)))");
+ RE(repl_env, "(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)))))))");
+ RE(repl_env, "(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))))))))");
+
+ RE(repl_env, "(def! load-file (fn* (f) (eval (read-string (slurp-do f)))))");
+
+ Integer fileIdx = 0;
+ if (args.length > 0 && args[0].equals("--raw")) {
+ readline.mode = readline.Mode.JAVA;
+ fileIdx = 1;
+ }
+ if (args.length > fileIdx) {
+ for(Integer i=fileIdx; i<args.length; i++) {
+ RE(repl_env, "(load-file \"" + args[i] + "\")");
+ }
+ return;
+ }
+ while (true) {
+ String line;
+ try {
+ line = readline.readline(prompt);
+ if (line == null) { continue; }
+ } catch (readline.EOFException e) {
+ break;
+ } catch (IOException e) {
+ System.out.println("IOException: " + e.getMessage());
+ break;
+ }
+ try {
+ System.out.println(PRINT(RE(repl_env, line)));
+ } catch (MalContinue e) {
+ continue;
+ } catch (reader.ParseError e) {
+ System.out.println(e.getMessage());
+ continue;
+ } catch (MalException e) {
+ System.out.println("Error: " + types._pr_str(e.getValue(), false));
+ continue;
+ } catch (MalThrowable t) {
+ System.out.println("Error: " + t.getMessage());
+ continue;
+ }
+ }
+ }
+}
diff --git a/java/src/main/java/mal/types.java b/java/src/main/java/mal/types.java
new file mode 100644
index 0000000..1e9bb34
--- /dev/null
+++ b/java/src/main/java/mal/types.java
@@ -0,0 +1,882 @@
+package mal;
+
+import java.util.List;
+import java.util.ArrayList;
+import com.google.common.base.Joiner;
+import java.util.Set;
+import java.util.Map;
+import java.util.HashMap;
+import com.google.common.collect.ImmutableMap;
+import org.apache.commons.lang3.StringEscapeUtils;
+
+public class types {
+ //
+ // Exceptions/Errors
+ //
+ public static class MalThrowable extends Exception {
+ public MalThrowable() { }
+ public MalThrowable(String msg) { super(msg); }
+ }
+ public static class MalError extends MalThrowable {
+ public MalError(String msg) { super(msg); }
+ }
+ public static class MalContinue extends MalThrowable { }
+
+ // Thrown by throw function
+ public static class MalException extends MalThrowable {
+ MalVal value;
+ public MalException(MalVal value) {
+ this.value = value;
+ }
+ public MalException(String value) {
+ this.value = new MalString(value);
+ }
+ public MalVal getValue() { return value; }
+ }
+
+
+ //
+ // Mal boxed types
+ //
+ abstract public static class MalVal {
+ MalVal meta = Nil;
+ abstract public MalVal copy() throws MalThrowable;
+
+ // Default is just to call regular toString()
+ public String toString(Boolean print_readably) {
+ return this.toString();
+ }
+ public MalVal getMeta() { return meta; }
+ public void setMeta(MalVal m) { meta = m; }
+ }
+ public static class MalConstant extends MalVal {
+ String value;
+ public MalConstant(String name) { value = name; }
+ public MalConstant copy() throws MalThrowable { return this; }
+
+ public String toString() { return value; }
+ }
+ static MalConstant Nil = new MalConstant("nil");
+ static MalConstant True = new MalConstant("true");
+ static MalConstant False = new MalConstant("false");
+
+ public static class MalInteger extends MalVal {
+ Integer value;
+ public MalInteger(Integer v) { value = v; }
+ public MalInteger copy() throws MalThrowable { return this; }
+
+ public Integer getValue() { return value; }
+ @Override public String toString() {
+ return value.toString();
+ }
+ public MalInteger add(MalInteger other) {
+ return new MalInteger(value + other.getValue());
+ }
+ public MalInteger subtract(MalInteger other) {
+ return new MalInteger(value - other.getValue());
+ }
+ public MalInteger multiply(MalInteger other) {
+ return new MalInteger(value * other.getValue());
+ }
+ public MalInteger divide(MalInteger other) {
+ return new MalInteger(value / other.getValue());
+ }
+ public MalConstant lt(MalInteger other) {
+ return (value < other.getValue()) ? True : False;
+ }
+ public MalConstant lte(MalInteger other) {
+ return (value <= other.getValue()) ? True : False;
+ }
+ public MalConstant gt(MalInteger other) {
+ return (value > other.getValue()) ? True : False;
+ }
+ public MalConstant gte(MalInteger other) {
+ return (value >= other.getValue()) ? True : False;
+ }
+ }
+
+ public static class MalSymbol extends MalVal {
+ String value;
+ public MalSymbol(String v) { value = v; }
+ public MalSymbol copy() throws MalThrowable { return this; }
+
+ public String getName() { return value; }
+ @Override public String toString() {
+ return value;
+ }
+ }
+
+ public static class MalString extends MalVal {
+ String value;
+ public MalString(String v) { value = v; }
+ public MalString copy() throws MalThrowable { return this; }
+
+ public String getValue() { return value; }
+ @Override public String toString() {
+ return "\"" + value + "\"";
+ }
+ public String toString(Boolean print_readably) {
+ if (print_readably) {
+ return "\"" + StringEscapeUtils.escapeJson(value) + "\"";
+ } else {
+ return value;
+ }
+ }
+ }
+
+ public static class MalList extends MalVal {
+ String start = "(", end = ")";
+ List value;
+ public MalList(List val) {
+ value = val;
+ }
+ public MalList(MalVal... mvs) {
+ value = new ArrayList<MalVal>();
+ conj_BANG(mvs);
+ }
+ public MalList copy() throws MalThrowable {
+ MalList new_ml = new MalList();
+ new_ml.value.addAll(value);
+ new_ml.meta = meta;
+ return new_ml;
+ }
+
+ String _join(String delim, Boolean print_readably) {
+ ArrayList<String> strs = new ArrayList<String>();
+ for (MalVal mv : (List<MalVal>)value) {
+ strs.add(mv.toString(print_readably));
+ }
+ return Joiner.on(delim).join(strs);
+ }
+ @Override public String toString() {
+ return start + _join(" ", true) + end;
+ }
+ public String toString(Boolean print_readably) {
+ return start + _join(" ", print_readably) + end;
+ }
+
+ public MalList conj_BANG(MalVal... mvs) {
+ for (MalVal mv : mvs) {
+ value.add(mv);
+ }
+ return this;
+ }
+
+ public Integer size() {
+ return value.size();
+ }
+
+ public MalVal nth(Integer idx) {
+ return (MalVal)value.get(idx);
+ }
+
+ public MalList slice(Integer start, Integer end) {
+ return new MalList(value.subList(start, end));
+ }
+ public MalList slice(Integer start) {
+ return slice(start, value.size());
+ }
+ }
+
+ public static class MalVector extends MalList {
+ // Same implementation except for instantiation methods
+ public MalVector(List val) {
+ value = val;
+ start = "[";
+ end = "]";
+ }
+ public MalVector(MalVal... mvs) {
+ super(mvs);
+ start = "[";
+ end = "]";
+ }
+ public MalVector copy() throws MalThrowable {
+ MalVector new_mv = new MalVector();
+ new_mv.value.addAll(value);
+ new_mv.meta = meta;
+ return new_mv;
+ }
+
+ public MalVector slice(Integer start, Integer end) {
+ return new MalVector(value.subList(start, end));
+ }
+ }
+
+ public static class MalHashMap extends MalVal {
+ Map value;
+ public MalHashMap(Map val) {
+ value = val;
+ }
+ public MalHashMap(MalList lst) {
+ value = new HashMap<String, MalVal>();
+ assoc_BANG(lst);
+ }
+ public MalHashMap(MalVal... mvs) {
+ value = new HashMap<String, MalVal>();
+ assoc_BANG(mvs);
+ }
+ public MalHashMap copy() throws MalThrowable {
+ Map<String,MalVal> shallowCopy = new HashMap<String,MalVal>();
+ shallowCopy.putAll(value);
+ MalHashMap new_hm = new MalHashMap(shallowCopy);
+ new_hm.meta = meta;
+ return new_hm;
+ }
+
+ String _join(Boolean print_readably) {
+ ArrayList<String> strs = new ArrayList<String>();
+ for (Map.Entry<String, MalVal> entry :
+ ((Map<String,MalVal>)value).entrySet()) {
+ if (print_readably) {
+ strs.add("\"" + entry.getKey().toString() + "\"");
+ } else {
+ strs.add(entry.getKey().toString());
+ }
+ strs.add(entry.getValue().toString(print_readably));
+ }
+ return Joiner.on(" ").join(strs);
+ }
+ @Override public String toString() {
+ return "{" + _join(true) + "}";
+ }
+ public String toString(Boolean print_readably) {
+ return "{" + _join(print_readably) + "}";
+ }
+
+ public Set _entries() {
+ return value.entrySet();
+ }
+
+ public MalHashMap assoc_BANG(MalVal... mvs) {
+ for (Integer i=0; i<mvs.length; i+=2) {
+ value.put(((MalSymbol)mvs[i]).getName(),
+ mvs[i+1]);
+ }
+ return this;
+ }
+
+ public MalHashMap assoc_BANG(MalList lst) {
+ for (Integer i=0; i<lst.value.size(); i+=2) {
+ value.put(((MalString)lst.nth(i)).getValue(),
+ lst.nth(i+1));
+ }
+ return this;
+ }
+
+ public MalHashMap dissoc_BANG(MalList lst) {
+ for (Integer i=0; i<lst.value.size(); i++) {
+ value.remove(((MalString)lst.nth(i)).getValue());
+ }
+ return this;
+ }
+
+ public Integer size() {
+ return value.size();
+ }
+ }
+
+ public static class MalAtom extends MalVal {
+ MalVal value;
+ public MalAtom(MalVal value) { this.value = value; }
+ public MalAtom copy() throws MalThrowable { return new MalAtom(value); }
+ @Override public String toString() {
+ return "(atom " + _pr_str(value, true) + ")";
+ }
+ public String toString(Boolean print_readably) {
+ return "(atom " + _pr_str(value, print_readably) + ")";
+ }
+ }
+
+ public static interface ILambda {
+ public MalVal apply(MalList args) throws MalThrowable;
+ }
+
+ public static abstract class MalFunction extends MalVal
+ implements ILambda, java.lang.Cloneable {
+ public MalVal ast = null;
+ public Env env = null;
+ public MalList params = null;
+ public Boolean macro = false;
+ public MalFunction() { }
+ public MalFunction(MalVal ast, Env env, MalList params) {
+ this.ast = ast;
+ this.env = env;
+ this.params = params;
+ }
+ public MalFunction copy() throws MalThrowable {
+ try {
+ // WARNING: clone() is broken:
+ // http://www.artima.com/intv/bloch13.html
+ // However, this doesn't work:
+ // MalFunction new_mf = this.getClass().newInstance();
+ // So for now it's clone.
+ MalFunction new_mf = (MalFunction) this.clone();
+ new_mf.ast = ast;
+ new_mf.env = env;
+ new_mf.params = params;
+ new_mf.macro = macro;
+ return new_mf;
+ } catch (Throwable t) {
+ // not much we can do
+ t.printStackTrace();
+ throw new MalError("Could not copy MalFunction: " + this);
+ }
+ }
+
+ public MalVal getAst() { return ast; }
+ public Env getEnv() { return env; }
+ public MalList getParams() { return params; }
+ public Boolean isMacro() { return macro; }
+ public void setMacro() { macro = true; }
+ }
+
+
+ //
+ // General functions
+ //
+ public static String _pr_str(MalVal mv, Boolean print_readably) {
+ return mv.toString(print_readably);
+ }
+
+ public static String _pr_str_args(MalList args, String sep, Boolean print_readably) {
+ return args._join(sep, print_readably);
+ }
+
+ static MalFunction pr_str = new MalFunction() {
+ public MalVal apply(MalList args) throws MalThrowable {
+ return new MalString(_pr_str_args(args, " ", true));
+ }
+ };
+
+ static MalFunction str = new MalFunction() {
+ public MalVal apply(MalList args) throws MalThrowable {
+ return new MalString(_pr_str_args(args, "", false));
+ }
+ };
+
+ static MalFunction prn = new MalFunction() {
+ public MalVal apply(MalList args) throws MalThrowable {
+ System.out.println(_pr_str_args(args, " ", true));
+ return Nil;
+ }
+ };
+
+ static MalFunction println = new MalFunction() {
+ public MalVal apply(MalList args) throws MalThrowable {
+ System.out.println(_pr_str_args(args, " ", false));
+ return Nil;
+ }
+ };
+
+
+ static MalFunction meta = new MalFunction() {
+ public MalVal apply(MalList args) throws MalThrowable {
+ return args.nth(0).getMeta();
+ }
+ };
+
+ static MalFunction with_meta = new MalFunction() {
+ public MalVal apply(MalList args) throws MalThrowable {
+ MalVal new_mv = ((MalVal)args.nth(0)).copy();
+ new_mv.setMeta(args.nth(1));
+ return new_mv;
+ }
+ };
+
+
+ public static Boolean _equal_Q(MalVal a, MalVal b) {
+ Class ota = a.getClass(), otb = b.getClass();
+ if (!((ota == otb) ||
+ (a instanceof MalList && b instanceof MalList))) {
+ return false;
+ } else {
+ if (a instanceof MalInteger) {
+ return ((MalInteger)a).getValue() ==
+ ((MalInteger)b).getValue();
+ } else if (a instanceof MalSymbol) {
+ return ((MalSymbol)a).getName().equals(
+ ((MalSymbol)b).getName());
+ } else if (a instanceof MalString) {
+ return ((MalString)a).getValue().equals(
+ ((MalString)b).getValue());
+ } else if (a instanceof MalList) {
+ if (((MalList)a).size() != ((MalList)b).size()) {
+ return false;
+ }
+ for (Integer i=0; i<((MalList)a).size(); i++) {
+ if (! _equal_Q(((MalList)a).nth(i),
+ ((MalList)b).nth(i))) {
+ return false;
+ }
+ }
+ return true;
+ } else {
+ return a == b;
+ }
+ }
+ }
+
+ static MalFunction equal_Q = new MalFunction() {
+ public MalVal apply(MalList args) throws MalThrowable {
+ return _equal_Q(args.nth(0), args.nth(1)) ? True : False;
+ }
+ };
+
+
+ //
+ // Constants operations
+ //
+ static MalFunction symbol_Q = new MalFunction() {
+ public MalVal apply(MalList args) throws MalThrowable {
+ return args.nth(0) instanceof MalSymbol ? True : False;
+ }
+ };
+
+ static MalFunction nil_Q = new MalFunction() {
+ public MalVal apply(MalList args) throws MalThrowable {
+ return args.nth(0) == Nil ? True : False;
+ }
+ };
+
+ static MalFunction true_Q = new MalFunction() {
+ public MalVal apply(MalList args) throws MalThrowable {
+ return args.nth(0) == True ? True : False;
+ }
+ };
+
+ static MalFunction false_Q = new MalFunction() {
+ public MalVal apply(MalList args) throws MalThrowable {
+ return args.nth(0) == False ? True : False;
+ }
+ };
+
+
+ //
+ // Number operations
+ //
+ static MalFunction add = new MalFunction() {
+ public MalVal apply(MalList a) throws MalThrowable {
+ return ((MalInteger)a.nth(0)).add((MalInteger)a.nth(1));
+ }
+ };
+ static MalFunction subtract = new MalFunction() {
+ public MalVal apply(MalList a) throws MalThrowable {
+ return ((MalInteger)a.nth(0)).subtract((MalInteger)a.nth(1));
+ }
+ };
+ static MalFunction multiply = new MalFunction() {
+ public MalVal apply(MalList a) throws MalThrowable {
+ return ((MalInteger)a.nth(0)).multiply((MalInteger)a.nth(1));
+ }
+ };
+ static MalFunction divide = new MalFunction() {
+ public MalVal apply(MalList a) throws MalThrowable {
+ return ((MalInteger)a.nth(0)).divide((MalInteger)a.nth(1));
+ }
+ };
+
+ static MalFunction lt = new MalFunction() {
+ public MalVal apply(MalList a) throws MalThrowable {
+ return ((MalInteger)a.nth(0)).lt((MalInteger)a.nth(1));
+ }
+ };
+ static MalFunction lte = new MalFunction() {
+ public MalVal apply(MalList a) throws MalThrowable {
+ return ((MalInteger)a.nth(0)).lte((MalInteger)a.nth(1));
+ }
+ };
+ static MalFunction gt = new MalFunction() {
+ public MalVal apply(MalList a) throws MalThrowable {
+ return ((MalInteger)a.nth(0)).gt((MalInteger)a.nth(1));
+ }
+ };
+ static MalFunction gte = new MalFunction() {
+ public MalVal apply(MalList a) throws MalThrowable {
+ return ((MalInteger)a.nth(0)).gte((MalInteger)a.nth(1));
+ }
+ };
+
+ //
+ // Errors/Exceptions
+ //
+ static MalFunction mal_throw = new MalFunction() {
+ public MalVal apply(MalList a) throws MalThrowable {
+ throw new MalException(a.nth(0));
+ }
+ };
+
+ //
+ // List operations
+ //
+ static MalFunction new_list = new MalFunction() {
+ public MalVal apply(MalList a) throws MalThrowable {
+ return new MalList(a.value);
+ }
+ };
+
+ static public Boolean _list_Q(MalVal mv) {
+ return mv.getClass().equals(MalList.class);
+ }
+ static MalFunction list_Q = new MalFunction() {
+ public MalVal apply(MalList a) throws MalThrowable {
+ return _list_Q(a.nth(0)) ? True : False;
+ }
+ };
+
+ //
+ // Vector operations
+ //
+ static MalFunction new_vector = new MalFunction() {
+ public MalVal apply(MalList a) throws MalThrowable {
+ return new MalVector(a.value);
+ }
+ };
+
+ static public Boolean _vector_Q(MalVal mv) {
+ return mv.getClass().equals(MalVector.class);
+ }
+ static MalFunction vector_Q = new MalFunction() {
+ public MalVal apply(MalList a) throws MalThrowable {
+ return _vector_Q(a.nth(0)) ? True : False;
+ }
+ };
+
+ //
+ // Hash map operations
+ //
+ static MalFunction new_hash_map = new MalFunction() {
+ public MalVal apply(MalList a) throws MalThrowable {
+ return new MalHashMap(a);
+ }
+ };
+
+ static MalFunction hash_map_Q = new MalFunction() {
+ public MalVal apply(MalList a) throws MalThrowable {
+ return a.nth(0) instanceof MalHashMap ? True : False;
+ }
+ };
+
+ static MalFunction contains_Q = new MalFunction() {
+ public MalVal apply(MalList a) throws MalThrowable {
+ String key = ((MalString)a.nth(1)).getValue();
+ MalHashMap mhm = (MalHashMap)a.nth(0);
+ HashMap<String,MalVal> hm = (HashMap<String,MalVal>)mhm.value;
+ return hm.containsKey(key) ? True : False;
+ }
+ };
+
+ static MalFunction assoc = new MalFunction() {
+ public MalVal apply(MalList a) throws MalThrowable {
+ MalHashMap mhm = (MalHashMap)a.nth(0);
+ HashMap<String,MalVal> hm = (HashMap<String,MalVal>)mhm.value;
+ MalHashMap new_mhm = new MalHashMap((Map)hm.clone());
+ new_mhm.assoc_BANG((MalList)a.slice(1));
+ return new_mhm;
+ }
+ };
+
+ static MalFunction dissoc = new MalFunction() {
+ public MalVal apply(MalList a) throws MalThrowable {
+ MalHashMap mhm = (MalHashMap)a.nth(0);
+ HashMap<String,MalVal> hm = (HashMap<String,MalVal>)mhm.value;
+ MalHashMap new_mhm = new MalHashMap((Map)hm.clone());
+ new_mhm.dissoc_BANG((MalList)a.slice(1));
+ return new_mhm;
+ }
+ };
+
+ static MalFunction get = new MalFunction() {
+ public MalVal apply(MalList a) throws MalThrowable {
+ String key = ((MalString)a.nth(1)).getValue();
+ MalHashMap mhm = (MalHashMap)a.nth(0);
+ HashMap<String,MalVal> hm = (HashMap<String,MalVal>)mhm.value;
+ if (hm.containsKey(key)) {
+ return hm.get(key);
+ } else {
+ return Nil;
+ }
+ }
+ };
+
+ static MalFunction keys = new MalFunction() {
+ public MalVal apply(MalList a) throws MalThrowable {
+ MalHashMap mhm = (MalHashMap)a.nth(0);
+ HashMap<String,MalVal> hm = (HashMap<String,MalVal>)mhm.value;
+ MalList key_lst = new MalList();
+ for (String key : hm.keySet()) {
+ key_lst.conj_BANG(new MalString(key));
+ }
+ return key_lst;
+ }
+ };
+
+ static MalFunction vals = new MalFunction() {
+ public MalVal apply(MalList a) throws MalThrowable {
+ MalHashMap mhm = (MalHashMap)a.nth(0);
+ HashMap<String,MalVal> hm = (HashMap<String,MalVal>)mhm.value;
+ //return new ArrayList<MalVal>(((HashMap<String,MalVal>)hm).values());
+ MalList val_lst = new MalList();
+ for (MalVal val : hm.values()) {
+ val_lst.conj_BANG(val);
+ }
+ return val_lst;
+ }
+ };
+
+
+ //
+ // Atoms
+ //
+ static MalFunction new_atom = new MalFunction() {
+ public MalVal apply(MalList a) throws MalThrowable {
+ return new MalAtom(a.nth(0));
+ }
+ };
+
+ static MalFunction atom_Q = new MalFunction() {
+ public MalVal apply(MalList a) throws MalThrowable {
+ return a.nth(0) instanceof MalAtom ? True : False;
+ }
+ };
+
+ static MalFunction deref = new MalFunction() {
+ public MalVal apply(MalList a) throws MalThrowable {
+ return ((MalAtom)a.nth(0)).value;
+ }
+ };
+
+ static MalFunction reset_BANG = new MalFunction() {
+ public MalVal apply(MalList a) throws MalThrowable {
+ return ((MalAtom)a.nth(0)).value = a.nth(1);
+ }
+ };
+
+ static MalFunction swap_BANG = new MalFunction() {
+ public MalVal apply(MalList a) throws MalThrowable {
+ MalAtom atm = (MalAtom)a.nth(0);
+ MalFunction f = (MalFunction)a.nth(1);
+ MalList new_args = new MalList();
+ new_args.value.addAll(((MalList)a.slice(2)).value);
+ new_args.value.add(0, atm.value);
+ atm.value = f.apply(new_args);
+ return atm.value;
+ }
+ };
+
+
+
+
+ //
+ // Sequence operations
+ //
+ static MalFunction sequential_Q = new MalFunction() {
+ public MalVal apply(MalList a) throws MalThrowable {
+ return a.nth(0) instanceof MalList ? True : False;
+ }
+ };
+
+ static MalFunction count = new MalFunction() {
+ public MalVal apply(MalList a) throws MalThrowable {
+ return new MalInteger(((MalList)a.nth(0)).size());
+ }
+ };
+
+ static MalFunction empty_Q = new MalFunction() {
+ public MalVal apply(MalList a) throws MalThrowable {
+ MalVal exp = a.nth(0);
+ if (exp == Nil || (exp instanceof MalList &&
+ ((MalList)exp).size() == 0)) {
+ return True;
+ } else {
+ return False;
+ }
+ }
+ };
+
+ static MalFunction cons = new MalFunction() {
+ public MalVal apply(MalList a) throws MalThrowable {
+ MalList lst = new MalList();
+ lst.value.addAll(((MalList)a.nth(1)).value);
+ lst.value.add(0, a.nth(0));
+ return (MalVal) lst;
+ }
+ };
+
+ static MalFunction concat = new MalFunction() {
+ public MalVal apply(MalList a) throws MalThrowable {
+ if (a.size() == 0) { return new MalList(); }
+ MalList lst = new MalList();
+ lst.value.addAll(((MalList)a.nth(0)).value);
+ for(Integer i=1; i<a.size(); i++) {
+ lst.value.addAll(((MalList)a.nth(i)).value);
+ }
+ return (MalVal) lst;
+ }
+ };
+
+ static MalFunction conj = new MalFunction() {
+ public MalVal apply(MalList a) throws MalThrowable {
+ MalList lst = new MalList();
+ lst.value.addAll(((MalList)a.nth(0)).value);
+ for(Integer i=1; i<a.size(); i++) {
+ lst.value.add(a.nth(i));
+ }
+ return (MalVal) lst;
+ }
+ };
+
+ static MalFunction first = new MalFunction() {
+ public MalVal apply(MalList a) throws MalThrowable {
+ MalList ml = ((MalList)a.nth(0));
+ return ml.size() > 0 ? ml.nth(0) : Nil;
+ }
+ };
+
+ static MalList _rest (MalList ml) {
+ if (ml.size() > 0) {
+ return new MalList(ml.value.subList(1, ml.value.size()));
+ } else {
+ return new MalList();
+ }
+ }
+
+ static MalFunction rest = new MalFunction() {
+ public MalVal apply(MalList a) throws MalThrowable {
+ MalList ml = ((MalList)a.nth(0));
+ return _rest(ml);
+ }
+ };
+
+ static MalFunction nth = new MalFunction() {
+ public MalVal apply(MalList a) throws MalThrowable {
+ Integer idx = ((MalInteger)a.nth(1)).getValue();
+ return ((MalList)a.nth(0)).nth(idx);
+ }
+ };
+
+ // General list related functions
+ static MalFunction apply = new MalFunction() {
+ public MalVal apply(MalList a) throws MalThrowable {
+ MalFunction f = (MalFunction)a.nth(0);
+ MalList args = a.slice(1,a.size()-1);
+ args.value.addAll( ((MalList)a.nth(a.size()-1)).value);
+ return f.apply(args);
+ }
+ };
+
+ static MalFunction map = new MalFunction() {
+ public MalVal apply(MalList a) throws MalThrowable {
+ MalFunction f = (MalFunction) a.nth(0);
+ MalList src_lst = (MalList) a.nth(1);
+ MalList new_lst = new MalList();
+ for(Integer i=0; i<src_lst.size(); i++) {
+ new_lst.value.add(
+ f.apply(new MalList(src_lst.nth(i))));
+ }
+ return new_lst;
+ }
+ };
+
+
+
+ //
+ // Env implementation
+ //
+ public static class Env {
+ Env outer = null;
+ HashMap<String,MalVal> data = new HashMap<String,MalVal>();
+
+ public Env(Env outer) {
+ this.outer = outer;
+ }
+ public Env(Env outer, MalList binds, MalList exprs) {
+ this.outer = outer;
+ for (Integer i=0; i<binds.size(); i++) {
+ String sym = ((MalSymbol)binds.nth(i)).getName();
+ if (sym.equals("&")) {
+ data.put(((MalSymbol)binds.nth(i+1)).getName(),
+ exprs.slice(i));
+ break;
+ } else {
+ data.put(sym, exprs.nth(i));
+ }
+ }
+ }
+
+ public Env find(String key) {
+ if (data.containsKey(key)) {
+ return this;
+ } else if (outer != null) {
+ return outer.find(key);
+ } else {
+ return null;
+ }
+ }
+
+ public MalVal get(String key) throws MalThrowable {
+ Env e = find(key);
+ if (e == null) {
+ throw new MalException("'" + key + "' not found");
+ } else {
+ return e.data.get(key);
+ }
+ }
+
+ public Env set(String key, MalVal value) {
+ data.put(key, value);
+ return this;
+ }
+ }
+
+ // types_ns is namespace of type functions
+ static Map<String, MalVal> types_ns = ImmutableMap.<String, MalVal>builder()
+ .put("pr-str", pr_str)
+ .put("str", str)
+ .put("prn", prn)
+ .put("println", println)
+ .put("meta", meta)
+ .put("with-meta", with_meta)
+ .put("=", equal_Q)
+ .put("symbol?", symbol_Q)
+ .put("nil?", nil_Q)
+ .put("true?", true_Q)
+ .put("false?", false_Q)
+ .put("<", lt)
+ .put("<=", lte)
+ .put(">", gt)
+ .put(">=", gte)
+ .put("+", add)
+ .put("-", subtract)
+ .put("*", multiply)
+ .put("/", divide)
+ .put("throw", mal_throw)
+ .put("list", new_list)
+ .put("list?", list_Q)
+ .put("vector", new_vector)
+ .put("vector?", vector_Q)
+ .put("hash-map", new_hash_map)
+ .put("map?", hash_map_Q)
+ .put("assoc", assoc)
+ .put("dissoc", dissoc)
+ .put("contains?", contains_Q)
+ .put("get", get)
+ .put("keys", keys)
+ .put("vals", vals)
+ .put("atom", new_atom)
+ .put("atom?", atom_Q)
+ .put("deref", deref)
+ .put("reset!", reset_BANG)
+ .put("swap!", swap_BANG)
+ .put("sequential?", sequential_Q)
+ .put("cons", cons)
+ .put("concat", concat)
+ .put("conj", conj)
+ .put("first", first)
+ .put("rest", rest)
+ .put("nth", nth)
+ .put("count", count)
+ .put("empty?", empty_Q)
+ .put("apply", apply)
+ .put("map", map)
+ .build();
+}
diff --git a/js/Makefile b/js/Makefile
new file mode 100644
index 0000000..cb57644
--- /dev/null
+++ b/js/Makefile
@@ -0,0 +1,29 @@
+
+TESTS = tests/types.js tests/reader.js tests/step5_tco.js
+
+SOURCES = node_readline.js types.js reader.js stepA_more.js
+WEB_SOURCES = $(SOURCES:node_readline.js=josh_readline.js)
+
+all: mal.js mal_web.js
+
+mal.js: $(SOURCES)
+ echo "#!/usr/bin/env node" > $@
+ cat $+ | grep -v "= *require('./" >> $@
+ chmod +x $@
+
+mal_web.js: $(WEB_SOURCES)
+ cat $+ | grep -v "= *require('./" > $@
+
+clean:
+ rm -f mal.js mal_web.js
+
+.PHONY: stats tests $(TESTS)
+
+stats: $(SOURCES)
+ @wc $^
+
+tests: $(TESTS)
+
+$(TESTS):
+ @echo "Running $@"; \
+ node $@ || exit 1; \
diff --git a/js/josh_readline.js b/js/josh_readline.js
new file mode 100644
index 0000000..ff4d201
--- /dev/null
+++ b/js/josh_readline.js
@@ -0,0 +1,402 @@
+/* ------------------------------------------------------------------------*
+ * Copyright 2013 Arne F. Claassen
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *-------------------------------------------------------------------------*/
+
+var Josh = Josh || {};
+(function(root, $, _) {
+ Josh.Shell = function(config) {
+ config = config || {};
+
+ // instance fields
+ var _console = config.console || (Josh.Debug && root.console ? root.console : {
+ log: function() {
+ }
+ });
+ var _prompt = config.prompt || 'jsh$';
+ var _action = config.action || function(str) {
+ return "<div>No action defined for: " + str + "</div>";
+ };
+ var _shell_view_id = config.shell_view_id || 'shell-view';
+ var _shell_panel_id = config.shell_panel_id || 'shell-panel';
+ var _input_id = config.input_id || 'shell-cli';
+ var _blinktime = config.blinktime || 500;
+ var _history = config.history || new Josh.History();
+ var _readline = config.readline || new Josh.ReadLine({history: _history, console: _console});
+ var _active = false;
+ var _cursor_visible = false;
+ var _activationHandler;
+ var _deactivationHandler;
+ var _cmdHandlers = {
+ clear: {
+ exec: function(cmd, args, callback) {
+ $(id(_input_id)).parent().empty();
+ callback();
+ }
+ },
+ help: {
+ exec: function(cmd, args, callback) {
+ callback(self.templates.help({commands: commands()}));
+ }
+ },
+ history: {
+ exec: function(cmd, args, callback) {
+ if(args[0] == "-c") {
+ _history.clear();
+ callback();
+ return;
+ }
+ callback(self.templates.history({items: _history.items()}));
+ }
+ },
+ _default: {
+ exec: function(cmd, args, callback) {
+ callback(self.templates.bad_command({cmd: cmd}));
+ },
+ completion: function(cmd, arg, line, callback) {
+ if(!arg) {
+ arg = cmd;
+ }
+ return callback(self.bestMatch(arg, self.commands()))
+ }
+ }
+ };
+ var _line = {
+ text: '',
+ cursor: 0
+ };
+ var _searchMatch = '';
+ var _view, _panel;
+ var _promptHandler;
+ var _initializationHandler;
+ var _initialized;
+
+ // public methods
+ var self = {
+ commands: commands,
+ templates: {
+ history: _.template("<div><% _.each(items, function(cmd, i) { %><div><%- i %>&nbsp;<%- cmd %></div><% }); %></div>"),
+ help: _.template("<div><div><strong>Commands:</strong></div><% _.each(commands, function(cmd) { %><div>&nbsp;<%- cmd %></div><% }); %></div>"),
+ bad_command: _.template('<div><strong>Unrecognized command:&nbsp;</strong><%=cmd%></div>'),
+ input_cmd: _.template('<div id="<%- id %>"><span class="prompt"></span>&nbsp;<span class="input"><span class="left"/><span class="cursor"/><span class="right"/></span></div>'),
+ input_search: _.template('<div id="<%- id %>">(reverse-i-search)`<span class="searchterm"></span>\':&nbsp;<span class="input"><span class="left"/><span class="cursor"/><span class="right"/></span></div>'),
+ suggest: _.template("<div><% _.each(suggestions, function(suggestion) { %><div><%- suggestion %></div><% }); %></div>")
+ },
+ isActive: function() {
+ return _readline.isActive();
+ },
+ activate: function() {
+ if($(id(_shell_view_id)).length == 0) {
+ _active = false;
+ return;
+ }
+ _readline.activate();
+ },
+ deactivate: function() {
+ _console.log("deactivating");
+ _active = false;
+ _readline.deactivate();
+ },
+ setCommandHandler: function(cmd, cmdHandler) {
+ _cmdHandlers[cmd] = cmdHandler;
+ },
+ getCommandHandler: function(cmd) {
+ return _cmdHandlers[cmd];
+ },
+ setPrompt: function(prompt) {
+ _prompt = prompt;
+ if(!_active) {
+ return;
+ }
+ self.refresh();
+ },
+ onEOT: function(completionHandler) {
+ _readline.onEOT(completionHandler);
+ },
+ onCancel: function(completionHandler) {
+ _readline.onCancel(completionHandler);
+ },
+ onInitialize: function(completionHandler) {
+ _initializationHandler = completionHandler;
+ },
+ onActivate: function(completionHandler) {
+ _activationHandler = completionHandler;
+ },
+ onDeactivate: function(completionHandler) {
+ _deactivationHandler = completionHandler;
+ },
+ onNewPrompt: function(completionHandler) {
+ _promptHandler = completionHandler;
+ },
+ render: function() {
+ var text = _line.text || '';
+ var cursorIdx = _line.cursor || 0;
+ if(_searchMatch) {
+ cursorIdx = _searchMatch.cursoridx || 0;
+ text = _searchMatch.text || '';
+ $(id(_input_id) + ' .searchterm').text(_searchMatch.term);
+ }
+ var left = _.escape(text.substr(0, cursorIdx)).replace(/ /g, '&nbsp;');
+ var cursor = text.substr(cursorIdx, 1);
+ var right = _.escape(text.substr(cursorIdx + 1)).replace(/ /g, '&nbsp;');
+ $(id(_input_id) + ' .prompt').html(_prompt);
+ $(id(_input_id) + ' .input .left').html(left);
+ if(!cursor) {
+ $(id(_input_id) + ' .input .cursor').html('&nbsp;').css('textDecoration', 'underline');
+ } else {
+ $(id(_input_id) + ' .input .cursor').text(cursor).css('textDecoration', 'underline');
+ }
+ $(id(_input_id) + ' .input .right').html(right);
+ _cursor_visible = true;
+ self.scrollToBottom();
+ _console.log('rendered "' + text + '" w/ cursor at ' + cursorIdx);
+ },
+ refresh: function() {
+ $(id(_input_id)).replaceWith(self.templates.input_cmd({id:_input_id}));
+ self.render();
+ _console.log('refreshed ' + _input_id);
+
+ },
+ scrollToBottom: function() {
+ _panel.animate({scrollTop: _view.height()}, 0);
+ },
+ bestMatch: function(partial, possible) {
+ _console.log("bestMatch on partial '" + partial + "'");
+ var result = {
+ completion: null,
+ suggestions: []
+ };
+ if(!possible || possible.length == 0) {
+ return result;
+ }
+ var common = '';
+ if(!partial) {
+ if(possible.length == 1) {
+ result.completion = possible[0];
+ result.suggestions = possible;
+ return result;
+ }
+ if(!_.every(possible, function(x) {
+ return possible[0][0] == x[0]
+ })) {
+ result.suggestions = possible;
+ return result;
+ }
+ }
+ for(var i = 0; i < possible.length; i++) {
+ var option = possible[i];
+ if(option.slice(0, partial.length) == partial) {
+ result.suggestions.push(option);
+ if(!common) {
+ common = option;
+ _console.log("initial common:" + common);
+ } else if(option.slice(0, common.length) != common) {
+ _console.log("find common stem for '" + common + "' and '" + option + "'");
+ var j = partial.length;
+ while(j < common.length && j < option.length) {
+ if(common[j] != option[j]) {
+ common = common.substr(0, j);
+ break;
+ }
+ j++;
+ }
+ }
+ }
+ }
+ result.completion = common.substr(partial.length);
+ return result;
+ }
+ };
+
+ function id(id) {
+ return "#"+id;
+ }
+
+ function commands() {
+ return _.chain(_cmdHandlers).keys().filter(function(x) {
+ return x[0] != "_"
+ }).value();
+ }
+
+ function blinkCursor() {
+ if(!_active) {
+ return;
+ }
+ root.setTimeout(function() {
+ if(!_active) {
+ return;
+ }
+ _cursor_visible = !_cursor_visible;
+ if(_cursor_visible) {
+ $(id(_input_id) + ' .input .cursor').css('textDecoration', 'underline');
+ } else {
+ $(id(_input_id) + ' .input .cursor').css('textDecoration', '');
+ }
+ blinkCursor();
+ }, _blinktime);
+ }
+
+ function split(str) {
+ return _.filter(str.split(/\s+/), function(x) {
+ return x;
+ });
+ }
+
+ function getHandler(cmd) {
+ return _cmdHandlers[cmd] || _cmdHandlers._default;
+ }
+
+ function renderOutput(output, callback) {
+ if(output) {
+ $(id(_input_id)).after(output);
+ }
+ $(id(_input_id) + ' .input .cursor').css('textDecoration', '');
+ $(id(_input_id)).removeAttr('id');
+ $(id(_shell_view_id)).append(self.templates.input_cmd({id:_input_id}));
+ if(_promptHandler) {
+ return _promptHandler(function(prompt) {
+ self.setPrompt(prompt);
+ return callback();
+ });
+ }
+ return callback();
+ }
+
+ function activate() {
+ _console.log("activating shell");
+ if(!_view) {
+ _view = $(id(_shell_view_id));
+ }
+ if(!_panel) {
+ _panel = $(id(_shell_panel_id));
+ }
+ if($(id(_input_id)).length == 0) {
+ _view.append(self.templates.input_cmd({id:_input_id}));
+ }
+ self.refresh();
+ _active = true;
+ blinkCursor();
+ if(_promptHandler) {
+ _promptHandler(function(prompt) {
+ self.setPrompt(prompt);
+ })
+ }
+ if(_activationHandler) {
+ _activationHandler();
+ }
+ }
+
+ // init
+ _readline.onActivate(function() {
+ if(!_initialized) {
+ _initialized = true;
+ if(_initializationHandler) {
+ return _initializationHandler(activate);
+ }
+ }
+ return activate();
+ });
+ _readline.onDeactivate(function() {
+ if(_deactivationHandler) {
+ _deactivationHandler();
+ }
+ });
+ _readline.onChange(function(line) {
+ _line = line;
+ self.render();
+ });
+ _readline.onClear(function() {
+ _cmdHandlers.clear.exec(null, null, function() {
+ renderOutput(null, function() {
+ });
+ });
+ });
+ _readline.onSearchStart(function() {
+ $(id(_input_id)).replaceWith(self.templates.input_search({id:_input_id}));
+ _console.log('started search');
+ });
+ _readline.onSearchEnd(function() {
+ $(id(_input_id)).replaceWith(self.templates.input_cmd({id:_input_id}));
+ _searchMatch = null;
+ self.render();
+ _console.log("ended search");
+ });
+ _readline.onSearchChange(function(match) {
+ _searchMatch = match;
+ self.render();
+ });
+ _readline.onEnter(function(cmdtext, callback) {
+ _console.log("got command: " + cmdtext);
+ var result;
+ try {
+ result = "<div>" + _action(cmdtext) + "</div>";
+ } catch (e) {
+ result = "<div>" + e.stack + "</div>";
+ }
+ renderOutput(result, function() {
+ callback("");
+ });
+ });
+ _readline.onCompletion(function(line, callback) {
+ if(!line) {
+ return callback();
+ }
+ var text = line.text.substr(0, line.cursor);
+ var parts = split(text);
+
+ var cmd = parts.shift() || '';
+ var arg = parts.pop() || '';
+ _console.log("getting completion handler for " + cmd);
+ var handler = getHandler(cmd);
+ if(handler != _cmdHandlers._default && cmd && cmd == text) {
+
+ _console.log("valid cmd, no args: append space");
+ // the text to complete is just a valid command, append a space
+ return callback(' ');
+ }
+ if(!handler.completion) {
+ // handler has no completion function, so we can't complete
+ return callback();
+ }
+ _console.log("calling completion handler for " + cmd);
+ return handler.completion(cmd, arg, line, function(match) {
+ _console.log("completion: " + JSON.stringify(match));
+ if(!match) {
+ return callback();
+ }
+ if(match.suggestions && match.suggestions.length > 1) {
+ return renderOutput(self.templates.suggest({suggestions: match.suggestions}), function() {
+ callback(match.completion);
+ });
+ }
+ return callback(match.completion);
+ });
+ });
+ return self;
+ }
+})(this, $, _);
+
+var readline = {};
+readline.rlwrap = function(action) {
+ var history = new Josh.History({ key: 'josh.helloworld'});
+ var shell = Josh.Shell({history: history,
+ action: action});
+ var promptCounter = 0;
+ shell.onNewPrompt(function(callback) {
+ promptCounter++;
+ //callback("[" + promptCounter + "] $");
+ callback("user>");
+ });
+ shell.activate();
+}
diff --git a/js/node_readline.js b/js/node_readline.js
new file mode 100644
index 0000000..bfd1982
--- /dev/null
+++ b/js/node_readline.js
@@ -0,0 +1,38 @@
+// IMPORTANT: choose one
+var RL_LIB = "libreadline"; // NOTE: libreadline is GPL
+//var RL_LIB = "libedit";
+
+var HISTORY_FILE = require('path').join(process.env.HOME, '.mal-history');
+
+var rlwrap = {}; // namespace for this module in web context
+
+var ffi = require('ffi'),
+ fs = require('fs');
+
+var rllib = ffi.Library(RL_LIB, {
+ 'readline': [ 'string', [ 'string' ] ],
+ 'add_history': [ 'int', [ 'string' ] ]});
+
+var rl_history_loaded = false;
+
+exports.readline = rlwrap.readline = function(prompt) {
+ prompt = prompt || "user> ";
+
+ if (!rl_history_loaded) {
+ rl_history_loaded = true;
+ var lines = fs.readFileSync(HISTORY_FILE).toString().split("\n");
+ // Max of 2000 lines
+ lines = lines.slice(Math.max(lines.length - 2000, 0));
+ for (var i=0; i<lines.length; i++) {
+ if (lines[i]) { rllib.add_history(lines[i]); }
+ }
+ }
+
+ var line = rllib.readline(prompt);
+ if (line) {
+ rllib.add_history(line);
+ fs.appendFileSync(HISTORY_FILE, line + "\n");
+ }
+
+ return line;
+}
diff --git a/js/package.json b/js/package.json
new file mode 100644
index 0000000..976e6ae
--- /dev/null
+++ b/js/package.json
@@ -0,0 +1,8 @@
+{
+ "name": "mal",
+ "version": "0.0.1",
+ "description": "Make a Lisp (mal) language implemented in Javascript",
+ "dependencies": {
+ "ffi": "1.2.x"
+ }
+}
diff --git a/js/reader.js b/js/reader.js
new file mode 100644
index 0000000..da51088
--- /dev/null
+++ b/js/reader.js
@@ -0,0 +1,127 @@
+// Node vs browser behavior
+var reader = {};
+if (typeof module !== 'undefined') {
+ var types = require('./types');
+} else {
+ var exports = reader;
+}
+
+function Reader(tokens) {
+ // copy
+ this.tokens = tokens.map(function (a) { return a; });
+ this.position = 0;
+}
+Reader.prototype.next = function() { return this.tokens[this.position++]; }
+Reader.prototype.peek = function() { return this.tokens[this.position]; }
+
+function tokenize(str) {
+ var re = /[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"`,;)]*)/g;
+ var results = [];
+ while ((match = re.exec(str)[1]) != '') {
+ if (match[0] === ';') { continue; }
+ results.push(match);
+ }
+ return results;
+}
+
+function read_atom (reader) {
+ var token = reader.next();
+ //console.log("read_atom:", token);
+ if (token.match(/^-?[0-9]+$/)) {
+ return parseInt(token,10) // integer
+ } else if (token.match(/^-?[0-9][0-9.]*$/)) {
+ return parseFloat(token,10); // float
+ } else if (token[0] === "\"") {
+ return token.slice(1,token.length-1).replace(/\\"/g, '"'); // string
+ } else if (token === "nil") {
+ return null;
+ } else if (token === "true") {
+ return true;
+ } else if (token === "false") {
+ return false;
+ } else {
+ return types.symbol(token); // symbol
+ }
+}
+
+// read list of tokens
+function read_list(reader, start, end) {
+ start = start || '(';
+ end = end || ')';
+ var ast = [];
+ var token = reader.next();
+ if (token !== start) {
+ throw new Error("expected '" + start + "'");
+ }
+ while ((token = reader.peek()) !== end) {
+ if (!token) {
+ throw new Error("expected '" + end + "', got EOF");
+ }
+ ast.push(read_form(reader));
+ }
+ reader.next();
+ return ast;
+}
+
+// read vector of tokens
+function read_vector(reader) {
+ var lst = read_list(reader, '[', ']');
+ return types.vector.apply(types.vector, lst);
+}
+
+// read hash-map key/value pairs
+function read_hash_map(reader) {
+ var lst = read_list(reader, '{', '}');
+ return types.hash_map.apply(types.hash_map, lst);
+}
+
+function read_form(reader) {
+ var token = reader.peek();
+ switch (token) {
+ // reader macros/transforms
+ case ';': return null; // Ignore comments
+ case '\'': reader.next();
+ return [types.symbol('quote'), read_form(reader)];
+ case '`': reader.next();
+ return [types.symbol('quasiquote'), read_form(reader)];
+ case '~': reader.next();
+ return [types.symbol('unquote'), read_form(reader)];
+ case '~@': reader.next();
+ return [types.symbol('splice-unquote'), read_form(reader)];
+ case '^': reader.next();
+ var meta = read_form(reader);
+ return [types.symbol('with-meta'), read_form(reader), meta];
+ case '@': reader.next();
+ return [types.symbol('deref'), read_form(reader)];
+
+ // list
+ case ')': throw new Error("unexpected ')'");
+ case '(': return read_list(reader);
+
+ // vector
+ case ']': throw new Error("unexpected ']'");
+ case '[': return read_vector(reader);
+
+ // hash-map
+ case '}': throw new Error("unexpected '}'");
+ case '{': return read_hash_map(reader);
+
+ // atom
+ default: return read_atom(reader);
+ }
+}
+
+function BlankException(msg) {
+}
+
+function read_str(str) {
+ var tokens = tokenize(str);
+ if (tokens.length === 0) { throw new BlankException(); }
+ return read_form(new Reader(tokens))
+}
+
+exports.Reader = reader.Reader = Reader;
+exports.BlankException = reader.BlankException = BlankException;
+exports.tokenize = reader.tokenize = tokenize;
+exports.read_form = reader.read_form = read_form;
+exports.read_str = reader.read_str = read_str;
diff --git a/js/step0_repl.js b/js/step0_repl.js
new file mode 100644
index 0000000..5fa10f2
--- /dev/null
+++ b/js/step0_repl.js
@@ -0,0 +1,42 @@
+if (typeof module !== 'undefined') {
+ var readline = require('./node_readline');
+}
+
+// read
+function READ(str) {
+ return str;
+}
+
+// eval
+function EVAL(ast, env) {
+ return eval(ast);
+}
+
+// print
+function PRINT(exp) {
+ return exp;
+}
+
+// repl
+var rep = function(str) { return PRINT(EVAL(READ(str), {})); };
+
+if (typeof require === 'undefined') {
+ // Asynchronous browser mode
+ readline.rlwrap(function(line) { return rep(line); },
+ function(exc) {
+ if (exc.stack) { console.log(exc.stack); } else { console.log(exc); }
+ });
+} else if (require.main === module) {
+ // Synchronous node.js commandline mode
+ while (true) {
+ var line = readline.readline("user> ");
+ if (line === null) { break; }
+ try {
+ if (line) { console.log(rep(line)); }
+ } catch (exc) {
+ if (exc.stack) { console.log(exc.stack); } else { console.log(exc); }
+ }
+ }
+} else {
+ exports.rep = rep;
+}
diff --git a/js/step1_read_print.js b/js/step1_read_print.js
new file mode 100644
index 0000000..ee027d7
--- /dev/null
+++ b/js/step1_read_print.js
@@ -0,0 +1,47 @@
+var types = require('./types');
+var reader = require('./reader');
+if (typeof module !== 'undefined') {
+ var readline = require('./node_readline');
+}
+
+// read
+function READ(str) {
+ return reader.read_str(str);
+}
+
+// eval
+function EVAL(ast, env) {
+ return ast;
+}
+
+// print
+function PRINT(exp) {
+ return types._pr_str(exp, true);
+}
+
+// repl
+var re = function(str) { return EVAL(READ(str), {}); };
+var rep = function(str) { return PRINT(EVAL(READ(str), {})); };
+
+if (typeof require === 'undefined') {
+ // Asynchronous browser mode
+ readline.rlwrap(function(line) { return rep(line); },
+ function(exc) {
+ if (exc instanceof reader.BlankException) { return; }
+ if (exc.stack) { console.log(exc.stack); } else { console.log(exc); }
+ });
+} else if (require.main === module) {
+ // Synchronous node.js commandline mode
+ while (true) {
+ var line = readline.readline("user> ");
+ if (line === null) { break; }
+ try {
+ if (line) { console.log(rep(line)); }
+ } catch (exc) {
+ if (exc instanceof reader.BlankException) { continue; }
+ if (exc.stack) { console.log(exc.stack); } else { console.log(exc); }
+ }
+ }
+} else {
+ exports.rep = rep;
+}
diff --git a/js/step2_eval.js b/js/step2_eval.js
new file mode 100644
index 0000000..f2cb8b1
--- /dev/null
+++ b/js/step2_eval.js
@@ -0,0 +1,83 @@
+var types = require('./types');
+var reader = require('./reader');
+if (typeof module !== 'undefined') {
+ var readline = require('./node_readline');
+}
+
+// read
+function READ(str) {
+ return reader.read_str(str);
+}
+
+// eval
+function eval_ast(ast, env) {
+ if (types.symbol_Q(ast)) {
+ return env[ast];
+ } else if (types.list_Q(ast)) {
+ return ast.map(function(a) { return EVAL(a, env); });
+ } else if (types.vector_Q(ast)) {
+ var v = ast.map(function(a) { return EVAL(a, env); });
+ v.__isvector__ = true;
+ return v;
+ } else if (types.hash_map_Q(ast)) {
+ var new_hm = {};
+ for (k in ast) {
+ new_hm[EVAL(k, env)] = EVAL(ast[k], env);
+ }
+ return new_hm;
+ } else {
+ return ast;
+ }
+}
+
+function _EVAL(ast, env) {
+ if (!types.list_Q(ast)) {
+ return eval_ast(ast, env);
+ }
+
+ // apply list
+ var el = eval_ast(ast, env), f = el[0];
+ return f.apply(f, el.slice(1));
+}
+
+function EVAL(ast, env) {
+ var result = _EVAL(ast, env);
+ return (typeof result !== "undefined") ? result : null;
+}
+
+// print
+function PRINT(exp) {
+ return types._pr_str(exp, true);
+}
+
+// repl
+repl_env = {};
+var rep = function(str) { return PRINT(EVAL(READ(str), repl_env)); };
+
+repl_env['+'] = function(a,b){return a+b;};
+repl_env['-'] = function(a,b){return a-b;};
+repl_env['*'] = function(a,b){return a*b;};
+repl_env['/'] = function(a,b){return a/b;};
+
+if (typeof require === 'undefined') {
+ // Asynchronous browser mode
+ readline.rlwrap(function(line) { return rep(line); },
+ function(exc) {
+ if (exc instanceof reader.BlankException) { return; }
+ if (exc.stack) { console.log(exc.stack); } else { console.log(exc); }
+ });
+} else if (require.main === module) {
+ // Synchronous node.js commandline mode
+ while (true) {
+ var line = readline.readline("user> ");
+ if (line === null) { break; }
+ try {
+ if (line) { console.log(rep(line)); }
+ } catch (exc) {
+ if (exc instanceof reader.BlankException) { continue; }
+ if (exc.stack) { console.log(exc.stack); } else { console.log(exc); }
+ }
+ }
+} else {
+ exports.rep = rep;
+}
diff --git a/js/step3_env.js b/js/step3_env.js
new file mode 100644
index 0000000..5b6e802
--- /dev/null
+++ b/js/step3_env.js
@@ -0,0 +1,97 @@
+var types = require('./types');
+var reader = require('./reader');
+if (typeof module !== 'undefined') {
+ var readline = require('./node_readline');
+}
+
+// read
+function READ(str) {
+ return reader.read_str(str);
+}
+
+// eval
+function eval_ast(ast, env) {
+ if (types.symbol_Q(ast)) {
+ return env.get(ast);
+ } else if (types.list_Q(ast)) {
+ return ast.map(function(a) { return EVAL(a, env); });
+ } else if (types.vector_Q(ast)) {
+ var v = ast.map(function(a) { return EVAL(a, env); });
+ v.__isvector__ = true;
+ return v;
+ } else if (types.hash_map_Q(ast)) {
+ var new_hm = {};
+ for (k in ast) {
+ new_hm[EVAL(k, env)] = EVAL(ast[k], env);
+ }
+ return new_hm;
+ } else {
+ return ast;
+ }
+}
+
+function _EVAL(ast, env) {
+ if (!types.list_Q(ast)) {
+ return eval_ast(ast, env);
+ }
+
+ // apply list
+ var a0 = ast[0], a1 = ast[1], a2 = ast[2], a3 = ast[3];
+ switch (a0.value) {
+ case "def!":
+ var res = EVAL(a2, env);
+ return env.set(a1, res);
+ case "let*":
+ var let_env = new types.Env(env);
+ for (var i=0; i < a1.length; i+=2) {
+ let_env.set(a1[i].value, EVAL(a1[i+1], let_env));
+ }
+ return EVAL(a2, let_env);
+ default:
+ var el = eval_ast(ast, env), f = el[0];
+ return f.apply(f, el.slice(1));
+ }
+}
+
+function EVAL(ast, env) {
+ var result = _EVAL(ast, env);
+ return (typeof result !== "undefined") ? result : null;
+}
+
+// print
+function PRINT(exp) {
+ return types._pr_str(exp, true);
+}
+
+// repl
+var repl_env = new types.Env();
+var rep = function(str) { return PRINT(EVAL(READ(str), repl_env)); };
+_ref = function (k,v) { repl_env.set(k, v); }
+
+_ref('+', function(a,b){return a+b;});
+_ref('-', function(a,b){return a-b;});
+_ref('*', function(a,b){return a*b;});
+_ref('/', function(a,b){return a/b;});
+
+if (typeof require === 'undefined') {
+ // Asynchronous browser mode
+ readline.rlwrap(function(line) { return rep(line); },
+ function(exc) {
+ if (exc instanceof reader.BlankException) { return; }
+ if (exc.stack) { console.log(exc.stack); } else { console.log(exc); }
+ });
+} else if (require.main === module) {
+ // Synchronous node.js commandline mode
+ while (true) {
+ var line = readline.readline("user> ");
+ if (line === null) { break; }
+ try {
+ if (line) { console.log(rep(line)); }
+ } catch (exc) {
+ if (exc instanceof reader.BlankException) { continue; }
+ if (exc.stack) { console.log(exc.stack); } else { console.log(exc); }
+ }
+ }
+} else {
+ exports.rep = rep;
+}
diff --git a/js/step4_if_fn_do.js b/js/step4_if_fn_do.js
new file mode 100644
index 0000000..d33ec04
--- /dev/null
+++ b/js/step4_if_fn_do.js
@@ -0,0 +1,112 @@
+var types = require('./types');
+var reader = require('./reader');
+if (typeof module !== 'undefined') {
+ var readline = require('./node_readline');
+}
+
+// read
+function READ(str) {
+ return reader.read_str(str);
+}
+
+// eval
+function eval_ast(ast, env) {
+ if (types.symbol_Q(ast)) {
+ return env.get(ast);
+ } else if (types.list_Q(ast)) {
+ return ast.map(function(a) { return EVAL(a, env); });
+ } else if (types.vector_Q(ast)) {
+ var v = ast.map(function(a) { return EVAL(a, env); });
+ v.__isvector__ = true;
+ return v;
+ } else if (types.hash_map_Q(ast)) {
+ var new_hm = {};
+ for (k in ast) {
+ new_hm[EVAL(k, env)] = EVAL(ast[k], env);
+ }
+ return new_hm;
+ } else {
+ return ast;
+ }
+}
+
+function _EVAL(ast, env) {
+ if (!types.list_Q(ast)) {
+ return eval_ast(ast, env);
+ }
+
+ // apply list
+ var a0 = ast[0], a1 = ast[1], a2 = ast[2], a3 = ast[3];
+ switch (a0.value) {
+ case "def!":
+ var res = EVAL(a2, env);
+ return env.set(a1, res);
+ case "let*":
+ var let_env = new types.Env(env);
+ for (var i=0; i < a1.length; i+=2) {
+ let_env.set(a1[i].value, EVAL(a1[i+1], let_env));
+ }
+ return EVAL(a2, let_env);
+ case "do":
+ var el = eval_ast(ast.slice(1), env);
+ return el[el.length-1];
+ case "if":
+ var cond = EVAL(a1, env);
+ if (cond === null || cond === false) {
+ return typeof a3 !== "undefined" ? EVAL(a3, env) : null;
+ } else {
+ return EVAL(a2, env);
+ }
+ case "fn*":
+ return function() {
+ return EVAL(a2, new types.Env(env, a1, arguments));
+ };
+ default:
+ var el = eval_ast(ast, env), f = el[0];
+ return f.apply(f, el.slice(1));
+ }
+}
+
+function EVAL(ast, env) {
+ var result = _EVAL(ast, env);
+ return (typeof result !== "undefined") ? result : null;
+}
+
+// print
+function PRINT(exp) {
+ return types._pr_str(exp, true);
+}
+
+// repl
+var repl_env = new types.Env();
+var rep = function(str) { return PRINT(EVAL(READ(str), repl_env)); };
+_ref = function (k,v) { repl_env.set(k, v); }
+
+// Import types functions
+for (var n in types.ns) { repl_env.set(n, types.ns[n]); }
+
+// Defined using the language itself
+rep("(def! not (fn* (a) (if a false true)))");
+
+if (typeof require === 'undefined') {
+ // Asynchronous browser mode
+ readline.rlwrap(function(line) { return rep(line); },
+ function(exc) {
+ if (exc instanceof reader.BlankException) { return; }
+ if (exc.stack) { console.log(exc.stack); } else { console.log(exc); }
+ });
+} else if (require.main === module) {
+ // Synchronous node.js commandline mode
+ while (true) {
+ var line = readline.readline("user> ");
+ if (line === null) { break; }
+ try {
+ if (line) { console.log(rep(line)); }
+ } catch (exc) {
+ if (exc instanceof reader.BlankException) { continue; }
+ if (exc.stack) { console.log(exc.stack); } else { console.log(exc); }
+ }
+ }
+} else {
+ exports.rep = rep;
+}
diff --git a/js/step5_tco.js b/js/step5_tco.js
new file mode 100644
index 0000000..20a9583
--- /dev/null
+++ b/js/step5_tco.js
@@ -0,0 +1,119 @@
+var types = require('./types');
+var reader = require('./reader');
+if (typeof module !== 'undefined') {
+ var readline = require('./node_readline');
+}
+
+// read
+function READ(str) {
+ return reader.read_str(str);
+}
+
+// eval
+function eval_ast(ast, env) {
+ if (types.symbol_Q(ast)) {
+ return env.get(ast);
+ } else if (types.list_Q(ast)) {
+ return ast.map(function(a) { return EVAL(a, env); });
+ } else if (types.vector_Q(ast)) {
+ var v = ast.map(function(a) { return EVAL(a, env); });
+ v.__isvector__ = true;
+ return v;
+ } else if (types.hash_map_Q(ast)) {
+ var new_hm = {};
+ for (k in ast) {
+ new_hm[EVAL(k, env)] = EVAL(ast[k], env);
+ }
+ return new_hm;
+ } else {
+ return ast;
+ }
+}
+
+function _EVAL(ast, env) {
+ while (true) {
+ if (!types.list_Q(ast)) {
+ return eval_ast(ast, env);
+ }
+
+ // apply list
+ var a0 = ast[0], a1 = ast[1], a2 = ast[2], a3 = ast[3];
+ switch (a0.value) {
+ case "def!":
+ var res = EVAL(a2, env);
+ return env.set(a1, res);
+ case "let*":
+ var let_env = new types.Env(env);
+ for (var i=0; i < a1.length; i+=2) {
+ let_env.set(a1[i].value, EVAL(a1[i+1], let_env));
+ }
+ return EVAL(a2, let_env);
+ case "do":
+ eval_ast(ast.slice(1, -1), env);
+ ast = ast[ast.length-1];
+ break;
+ case "if":
+ var cond = EVAL(a1, env);
+ if (cond === null || cond === false) {
+ ast = (typeof a3 !== "undefined") ? a3 : null;
+ } else {
+ ast = a2;
+ }
+ break;
+ case "fn*":
+ return types.new_function(EVAL, a2, env, a1);
+ default:
+ var el = eval_ast(ast, env), f = el[0], meta = f.__meta__;
+ if (meta && meta.exp) {
+ ast = meta.exp;
+ env = new types.Env(meta.env, meta.params, el.slice(1));
+ } else {
+ return f.apply(f, el.slice(1));
+ }
+ }
+ }
+}
+
+function EVAL(ast, env) {
+ var result = _EVAL(ast, env);
+ return (typeof result !== "undefined") ? result : null;
+}
+
+// print
+function PRINT(exp) {
+ return types._pr_str(exp, true);
+}
+
+// repl
+var repl_env = new types.Env();
+var rep = function(str) { return PRINT(EVAL(READ(str), repl_env)); };
+_ref = function (k,v) { repl_env.set(k, v); }
+
+// Import types functions
+for (var n in types.ns) { repl_env.set(n, types.ns[n]); }
+
+// Defined using the language itself
+rep("(def! not (fn* (a) (if a false true)))");
+
+if (typeof require === 'undefined') {
+ // Asynchronous browser mode
+ readline.rlwrap(function(line) { return rep(line); },
+ function(exc) {
+ if (exc instanceof reader.BlankException) { return; }
+ if (exc.stack) { console.log(exc.stack); } else { console.log(exc); }
+ });
+} else if (require.main === module) {
+ // Synchronous node.js commandline mode
+ while (true) {
+ var line = readline.readline("user> ");
+ if (line === null) { break; }
+ try {
+ if (line) { console.log(rep(line)); }
+ } catch (exc) {
+ if (exc instanceof reader.BlankException) { continue; }
+ if (exc.stack) { console.log(exc.stack); } else { console.log(exc); }
+ }
+ }
+} else {
+ exports.rep = rep;
+}
diff --git a/js/step6_file.js b/js/step6_file.js
new file mode 100644
index 0000000..b9ec187
--- /dev/null
+++ b/js/step6_file.js
@@ -0,0 +1,133 @@
+var types = require('./types');
+var reader = require('./reader');
+if (typeof module !== 'undefined') {
+ var readline = require('./node_readline');
+}
+
+// read
+function READ(str) {
+ return reader.read_str(str);
+}
+
+// eval
+function eval_ast(ast, env) {
+ if (types.symbol_Q(ast)) {
+ return env.get(ast);
+ } else if (types.list_Q(ast)) {
+ return ast.map(function(a) { return EVAL(a, env); });
+ } else if (types.vector_Q(ast)) {
+ var v = ast.map(function(a) { return EVAL(a, env); });
+ v.__isvector__ = true;
+ return v;
+ } else if (types.hash_map_Q(ast)) {
+ var new_hm = {};
+ for (k in ast) {
+ new_hm[EVAL(k, env)] = EVAL(ast[k], env);
+ }
+ return new_hm;
+ } else {
+ return ast;
+ }
+}
+
+function _EVAL(ast, env) {
+ while (true) {
+ if (!types.list_Q(ast)) {
+ return eval_ast(ast, env);
+ }
+
+ // apply list
+ var a0 = ast[0], a1 = ast[1], a2 = ast[2], a3 = ast[3];
+ switch (a0.value) {
+ case "def!":
+ var res = EVAL(a2, env);
+ return env.set(a1, res);
+ case "let*":
+ var let_env = new types.Env(env);
+ for (var i=0; i < a1.length; i+=2) {
+ let_env.set(a1[i].value, EVAL(a1[i+1], let_env));
+ }
+ return EVAL(a2, let_env);
+ case "do":
+ eval_ast(ast.slice(1, -1), env);
+ ast = ast[ast.length-1];
+ break;
+ case "if":
+ var cond = EVAL(a1, env);
+ if (cond === null || cond === false) {
+ ast = (typeof a3 !== "undefined") ? a3 : null;
+ } else {
+ ast = a2;
+ }
+ break;
+ case "fn*":
+ return types.new_function(EVAL, a2, env, a1);
+ default:
+ var el = eval_ast(ast, env), f = el[0], meta = f.__meta__;
+ if (meta && meta.exp) {
+ ast = meta.exp;
+ env = new types.Env(meta.env, meta.params, el.slice(1));
+ } else {
+ return f.apply(f, el.slice(1));
+ }
+ }
+ }
+}
+
+function EVAL(ast, env) {
+ var result = _EVAL(ast, env);
+ return (typeof result !== "undefined") ? result : null;
+}
+
+// print
+function PRINT(exp) {
+ return types._pr_str(exp, true);
+}
+
+// repl
+var repl_env = new types.Env();
+var rep = function(str) { return PRINT(EVAL(READ(str), repl_env)); };
+_ref = function (k,v) { repl_env.set(k, v); }
+
+// Import types functions
+for (var n in types.ns) { repl_env.set(n, types.ns[n]); }
+
+_ref('read-string', reader.read_str);
+_ref('eval', function(ast) { return EVAL(ast, repl_env); });
+_ref('slurp', function(f) {
+ return require('fs').readFileSync(f, 'utf-8');
+});
+_ref('slurp-do', function(f) {
+ return '(do ' + require('fs').readFileSync(f, 'utf-8') + ')';
+});
+
+// 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 (typeof process !== 'undefined' && process.argv.length > 2) {
+ for (var i=2; i < process.argv.length; i++) {
+ rep('(load-file "' + process.argv[i] + '")');
+ }
+} else if (typeof require === 'undefined') {
+ // Asynchronous browser mode
+ readline.rlwrap(function(line) { return rep(line); },
+ function(exc) {
+ if (exc instanceof reader.BlankException) { return; }
+ if (exc.stack) { console.log(exc.stack); } else { console.log(exc); }
+ });
+} else if (require.main === module) {
+ // Synchronous node.js commandline mode
+ while (true) {
+ var line = readline.readline("user> ");
+ if (line === null) { break; }
+ try {
+ if (line) { console.log(rep(line)); }
+ } catch (exc) {
+ if (exc instanceof reader.BlankException) { continue; }
+ if (exc.stack) { console.log(exc.stack); } else { console.log(exc); }
+ }
+ }
+} else {
+ exports.rep = rep;
+}
diff --git a/js/step7_quote.js b/js/step7_quote.js
new file mode 100644
index 0000000..832e47b
--- /dev/null
+++ b/js/step7_quote.js
@@ -0,0 +1,154 @@
+var types = require('./types');
+var reader = require('./reader');
+if (typeof module !== 'undefined') {
+ var readline = require('./node_readline');
+}
+
+// read
+function READ(str) {
+ return reader.read_str(str);
+}
+
+// eval
+function is_pair(x) {
+ return types.sequential_Q(x) && x.length > 0;
+}
+
+function quasiquote(ast) {
+ if (!is_pair(ast)) {
+ return [types.symbol("quote"), ast];
+ } else if (ast[0].value === 'unquote') {
+ return ast[1];
+ } else if (is_pair(ast[0]) && ast[0][0].value === 'splice-unquote') {
+ return [types.symbol("concat"), ast[0][1], quasiquote(ast.slice(1))];
+ } else {
+ return [types.symbol("cons"), quasiquote(ast[0]), quasiquote(ast.slice(1))];
+ }
+}
+
+function eval_ast(ast, env) {
+ if (types.symbol_Q(ast)) {
+ return env.get(ast);
+ } else if (types.list_Q(ast)) {
+ return ast.map(function(a) { return EVAL(a, env); });
+ } else if (types.vector_Q(ast)) {
+ var v = ast.map(function(a) { return EVAL(a, env); });
+ v.__isvector__ = true;
+ return v;
+ } else if (types.hash_map_Q(ast)) {
+ var new_hm = {};
+ for (k in ast) {
+ new_hm[EVAL(k, env)] = EVAL(ast[k], env);
+ }
+ return new_hm;
+ } else {
+ return ast;
+ }
+}
+
+function _EVAL(ast, env) {
+ while (true) {
+ //console.log("EVAL:", types._pr_str(ast, true));
+ if (!types.list_Q(ast)) {
+ return eval_ast(ast, env);
+ }
+
+ // apply list
+ var a0 = ast[0], a1 = ast[1], a2 = ast[2], a3 = ast[3];
+ switch (a0.value) {
+ case "def!":
+ var res = EVAL(a2, env);
+ return env.set(a1, res);
+ case "let*":
+ var let_env = new types.Env(env);
+ for (var i=0; i < a1.length; i+=2) {
+ let_env.set(a1[i].value, EVAL(a1[i+1], let_env));
+ }
+ return EVAL(a2, let_env);
+ case "quote":
+ return a1;
+ case "quasiquote":
+ return EVAL(quasiquote(a1), env);
+ case "do":
+ eval_ast(ast.slice(1, -1), env);
+ ast = ast[ast.length-1];
+ break;
+ case "if":
+ var cond = EVAL(a1, env);
+ if (cond === null || cond === false) {
+ ast = (typeof a3 !== "undefined") ? a3 : null;
+ } else {
+ ast = a2;
+ }
+ break;
+ case "fn*":
+ return types.new_function(EVAL, a2, env, a1);
+ default:
+ var el = eval_ast(ast, env), f = el[0], meta = f.__meta__;
+ if (meta && meta.exp) {
+ ast = meta.exp;
+ env = new types.Env(meta.env, meta.params, el.slice(1));
+ } else {
+ return f.apply(f, el.slice(1));
+ }
+ }
+ }
+}
+
+function EVAL(ast, env) {
+ var result = _EVAL(ast, env);
+ return (typeof result !== "undefined") ? result : null;
+}
+
+// print
+function PRINT(exp) {
+ return types._pr_str(exp, true);
+}
+
+// repl
+var repl_env = new types.Env();
+var rep = function(str) { return PRINT(EVAL(READ(str), repl_env)); };
+_ref = function (k,v) { repl_env.set(k, v); }
+
+// Import types functions
+for (var n in types.ns) { repl_env.set(n, types.ns[n]); }
+
+_ref('read-string', reader.read_str);
+_ref('eval', function(ast) { return EVAL(ast, repl_env); });
+_ref('slurp', function(f) {
+ return require('fs').readFileSync(f, 'utf-8');
+});
+_ref('slurp-do', function(f) {
+ return '(do ' + require('fs').readFileSync(f, 'utf-8') + ')';
+});
+
+// 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 (typeof process !== 'undefined' && process.argv.length > 2) {
+ for (var i=2; i < process.argv.length; i++) {
+ rep('(load-file "' + process.argv[i] + '")');
+ }
+} else if (typeof require === 'undefined') {
+ // Asynchronous browser mode
+ readline.rlwrap(function(line) { return rep(line); },
+ function(exc) {
+ if (exc instanceof reader.BlankException) { return; }
+ if (exc.stack) { console.log(exc.stack); } else { console.log(exc); }
+ });
+} else if (require.main === module) {
+ // Synchronous node.js commandline mode
+ while (true) {
+ var line = readline.readline("user> ");
+ if (line === null) { break; }
+ try {
+ if (line) { console.log(rep(line)); }
+ } catch (exc) {
+ if (exc instanceof reader.BlankException) { continue; }
+ if (exc.stack) { console.log(exc.stack); } else { console.log(exc); }
+ }
+ }
+} else {
+ exports.rep = rep;
+}
diff --git a/js/step8_macros.js b/js/step8_macros.js
new file mode 100644
index 0000000..766f750
--- /dev/null
+++ b/js/step8_macros.js
@@ -0,0 +1,178 @@
+var types = require('./types');
+var reader = require('./reader');
+if (typeof module !== 'undefined') {
+ var readline = require('./node_readline');
+}
+
+// read
+function READ(str) {
+ return reader.read_str(str);
+}
+
+// eval
+function is_pair(x) {
+ return types.sequential_Q(x) && x.length > 0;
+}
+
+function quasiquote(ast) {
+ if (!is_pair(ast)) {
+ return [types.symbol("quote"), ast];
+ } else if (ast[0].value === 'unquote') {
+ return ast[1];
+ } else if (is_pair(ast[0]) && ast[0][0].value === 'splice-unquote') {
+ return [types.symbol("concat"), ast[0][1], quasiquote(ast.slice(1))];
+ } else {
+ return [types.symbol("cons"), quasiquote(ast[0]), quasiquote(ast.slice(1))];
+ }
+}
+
+function is_macro_call(ast, env) {
+ return types.list_Q(ast) &&
+ types.symbol_Q(ast[0]) &&
+ env.find(ast[0].value) &&
+ env.get(ast[0].value)._ismacro_;
+}
+
+function macroexpand(ast, env) {
+ while (is_macro_call(ast, env)) {
+ var mac = env.get(ast[0]);
+ ast = mac.apply(mac, ast.slice(1));
+ }
+ return ast;
+}
+
+function eval_ast(ast, env) {
+ if (types.symbol_Q(ast)) {
+ return env.get(ast);
+ } else if (types.list_Q(ast)) {
+ return ast.map(function(a) { return EVAL(a, env); });
+ } else if (types.vector_Q(ast)) {
+ var v = ast.map(function(a) { return EVAL(a, env); });
+ v.__isvector__ = true;
+ return v;
+ } else if (types.hash_map_Q(ast)) {
+ var new_hm = {};
+ for (k in ast) {
+ new_hm[EVAL(k, env)] = EVAL(ast[k], env);
+ }
+ return new_hm;
+ } else {
+ return ast;
+ }
+}
+
+function _EVAL(ast, env) {
+ while (true) {
+ //console.log("EVAL:", types._pr_str(ast, true));
+ if (!types.list_Q(ast)) {
+ return eval_ast(ast, env);
+ }
+
+ // apply list
+ ast = macroexpand(ast, env);
+ if (!types.list_Q(ast)) { return ast; }
+
+ var a0 = ast[0], a1 = ast[1], a2 = ast[2], a3 = ast[3];
+ switch (a0.value) {
+ case "def!":
+ var res = EVAL(a2, env);
+ return env.set(a1, res);
+ case "let*":
+ var let_env = new types.Env(env);
+ for (var i=0; i < a1.length; i+=2) {
+ let_env.set(a1[i].value, EVAL(a1[i+1], let_env));
+ }
+ return EVAL(a2, let_env);
+ case "quote":
+ return a1;
+ case "quasiquote":
+ return EVAL(quasiquote(a1), env);
+ case 'defmacro!':
+ var func = EVAL(a2, env);
+ func._ismacro_ = true;
+ return env.set(a1, func);
+ case 'macroexpand':
+ return macroexpand(a1, env);
+ case "do":
+ eval_ast(ast.slice(1, -1), env);
+ ast = ast[ast.length-1];
+ break;
+ case "if":
+ var cond = EVAL(a1, env);
+ if (cond === null || cond === false) {
+ ast = (typeof a3 !== "undefined") ? a3 : null;
+ } else {
+ ast = a2;
+ }
+ break;
+ case "fn*":
+ return types.new_function(EVAL, a2, env, a1);
+ default:
+ var el = eval_ast(ast, env), f = el[0], meta = f.__meta__;
+ if (meta && meta.exp) {
+ ast = meta.exp;
+ env = new types.Env(meta.env, meta.params, el.slice(1));
+ } else {
+ return f.apply(f, el.slice(1));
+ }
+ }
+ }
+}
+
+function EVAL(ast, env) {
+ var result = _EVAL(ast, env);
+ return (typeof result !== "undefined") ? result : null;
+}
+
+// print
+function PRINT(exp) {
+ return types._pr_str(exp, true);
+}
+
+// repl
+var repl_env = new types.Env();
+var rep = function(str) { return PRINT(EVAL(READ(str), repl_env)); };
+_ref = function (k,v) { repl_env.set(k, v); }
+
+// Import types functions
+for (var n in types.ns) { repl_env.set(n, types.ns[n]); }
+
+_ref('read-string', reader.read_str);
+_ref('eval', function(ast) { return EVAL(ast, repl_env); });
+_ref('slurp', function(f) {
+ return require('fs').readFileSync(f, 'utf-8');
+});
+_ref('slurp-do', function(f) {
+ return '(do ' + require('fs').readFileSync(f, 'utf-8') + ')';
+});
+
+// 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 (typeof process !== 'undefined' && process.argv.length > 2) {
+ for (var i=2; i < process.argv.length; i++) {
+ rep('(load-file "' + process.argv[i] + '")');
+ }
+} else if (typeof require === 'undefined') {
+ // Asynchronous browser mode
+ readline.rlwrap(function(line) { return rep(line); },
+ function(exc) {
+ if (exc instanceof reader.BlankException) { return; }
+ if (exc.stack) { console.log(exc.stack); } else { console.log(exc); }
+ });
+} else if (require.main === module) {
+ // Synchronous node.js commandline mode
+ while (true) {
+ var line = readline.readline("user> ");
+ if (line === null) { break; }
+ try {
+ if (line) { console.log(rep(line)); }
+ } catch (exc) {
+ if (exc instanceof reader.BlankException) { continue; }
+ if (exc.stack) { console.log(exc.stack); } else { console.log(exc); }
+ }
+ }
+} else {
+ exports.rep = rep;
+}
diff --git a/js/step9_interop.js b/js/step9_interop.js
new file mode 100644
index 0000000..a811c52
--- /dev/null
+++ b/js/step9_interop.js
@@ -0,0 +1,184 @@
+var types = require('./types');
+var reader = require('./reader');
+if (typeof module !== 'undefined') {
+ var readline = require('./node_readline');
+}
+
+// read
+function READ(str) {
+ return reader.read_str(str);
+}
+
+// eval
+function is_pair(x) {
+ return types.sequential_Q(x) && x.length > 0;
+}
+
+function quasiquote(ast) {
+ if (!is_pair(ast)) {
+ return [types.symbol("quote"), ast];
+ } else if (ast[0].value === 'unquote') {
+ return ast[1];
+ } else if (is_pair(ast[0]) && ast[0][0].value === 'splice-unquote') {
+ return [types.symbol("concat"), ast[0][1], quasiquote(ast.slice(1))];
+ } else {
+ return [types.symbol("cons"), quasiquote(ast[0]), quasiquote(ast.slice(1))];
+ }
+}
+
+function is_macro_call(ast, env) {
+ return types.list_Q(ast) &&
+ types.symbol_Q(ast[0]) &&
+ env.find(ast[0].value) &&
+ env.get(ast[0].value)._ismacro_;
+}
+
+function macroexpand(ast, env) {
+ while (is_macro_call(ast, env)) {
+ var mac = env.get(ast[0]);
+ ast = mac.apply(mac, ast.slice(1));
+ }
+ return ast;
+}
+
+function eval_ast(ast, env) {
+ if (types.symbol_Q(ast)) {
+ return env.get(ast);
+ } else if (types.list_Q(ast)) {
+ return ast.map(function(a) { return EVAL(a, env); });
+ } else if (types.vector_Q(ast)) {
+ var v = ast.map(function(a) { return EVAL(a, env); });
+ v.__isvector__ = true;
+ return v;
+ } else if (types.hash_map_Q(ast)) {
+ var new_hm = {};
+ for (k in ast) {
+ new_hm[EVAL(k, env)] = EVAL(ast[k], env);
+ }
+ return new_hm;
+ } else {
+ return ast;
+ }
+}
+
+function _EVAL(ast, env) {
+ while (true) {
+ //console.log("EVAL:", types._pr_str(ast, true));
+ if (!types.list_Q(ast)) {
+ return eval_ast(ast, env);
+ }
+
+ // apply list
+ ast = macroexpand(ast, env);
+ if (!types.list_Q(ast)) { return ast; }
+
+ var a0 = ast[0], a1 = ast[1], a2 = ast[2], a3 = ast[3];
+ switch (a0.value) {
+ case "def!":
+ var res = EVAL(a2, env);
+ return env.set(a1, res);
+ case "let*":
+ var let_env = new types.Env(env);
+ for (var i=0; i < a1.length; i+=2) {
+ let_env.set(a1[i].value, EVAL(a1[i+1], let_env));
+ }
+ return EVAL(a2, let_env);
+ case "quote":
+ return a1;
+ case "quasiquote":
+ return EVAL(quasiquote(a1), env);
+ case 'defmacro!':
+ var func = EVAL(a2, env);
+ func._ismacro_ = true;
+ return env.set(a1, func);
+ case 'macroexpand':
+ return macroexpand(a1, env);
+ case "js*":
+ return eval(a1.toString());
+ case ".":
+ var el = eval_ast(ast.slice(2), env),
+ f = eval(a1.toString());
+ return f.apply(f, el);
+ case "do":
+ eval_ast(ast.slice(1, -1), env);
+ ast = ast[ast.length-1];
+ break;
+ case "if":
+ var cond = EVAL(a1, env);
+ if (cond === null || cond === false) {
+ ast = (typeof a3 !== "undefined") ? a3 : null;
+ } else {
+ ast = a2;
+ }
+ break;
+ case "fn*":
+ return types.new_function(EVAL, a2, env, a1);
+ default:
+ var el = eval_ast(ast, env), f = el[0], meta = f.__meta__;
+ if (meta && meta.exp) {
+ ast = meta.exp;
+ env = new types.Env(meta.env, meta.params, el.slice(1));
+ } else {
+ return f.apply(f, el.slice(1));
+ }
+ }
+ }
+}
+
+function EVAL(ast, env) {
+ var result = _EVAL(ast, env);
+ return (typeof result !== "undefined") ? result : null;
+}
+
+// print
+function PRINT(exp) {
+ return types._pr_str(exp, true);
+}
+
+// repl
+var repl_env = new types.Env();
+var rep = function(str) { return PRINT(EVAL(READ(str), repl_env)); };
+_ref = function (k,v) { repl_env.set(k, v); }
+
+// Import types functions
+for (var n in types.ns) { repl_env.set(n, types.ns[n]); }
+
+_ref('read-string', reader.read_str);
+_ref('eval', function(ast) { return EVAL(ast, repl_env); });
+_ref('slurp', function(f) {
+ return require('fs').readFileSync(f, 'utf-8');
+});
+_ref('slurp-do', function(f) {
+ return '(do ' + require('fs').readFileSync(f, 'utf-8') + ')';
+});
+
+// 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 (typeof process !== 'undefined' && process.argv.length > 2) {
+ for (var i=2; i < process.argv.length; i++) {
+ rep('(load-file "' + process.argv[i] + '")');
+ }
+} else if (typeof require === 'undefined') {
+ // Asynchronous browser mode
+ readline.rlwrap(function(line) { return rep(line); },
+ function(exc) {
+ if (exc instanceof reader.BlankException) { return; }
+ if (exc.stack) { console.log(exc.stack); } else { console.log(exc); }
+ });
+} else if (require.main === module) {
+ // Synchronous node.js commandline mode
+ while (true) {
+ var line = readline.readline("user> ");
+ if (line === null) { break; }
+ try {
+ if (line) { console.log(rep(line)); }
+ } catch (exc) {
+ if (exc instanceof reader.BlankException) { continue; }
+ if (exc.stack) { console.log(exc.stack); } else { console.log(exc); }
+ }
+ }
+} else {
+ exports.rep = rep;
+}
diff --git a/js/stepA_more.js b/js/stepA_more.js
new file mode 100644
index 0000000..fca3744
--- /dev/null
+++ b/js/stepA_more.js
@@ -0,0 +1,198 @@
+var types = require('./types');
+var reader = require('./reader');
+if (typeof module !== 'undefined') {
+ var readline = require('./node_readline');
+}
+
+// read
+function READ(str) {
+ return reader.read_str(str);
+}
+
+// eval
+function is_pair(x) {
+ return types.sequential_Q(x) && x.length > 0;
+}
+
+function quasiquote(ast) {
+ if (!is_pair(ast)) {
+ return [types.symbol("quote"), ast];
+ } else if (ast[0].value === 'unquote') {
+ return ast[1];
+ } else if (is_pair(ast[0]) && ast[0][0].value === 'splice-unquote') {
+ return [types.symbol("concat"), ast[0][1], quasiquote(ast.slice(1))];
+ } else {
+ return [types.symbol("cons"), quasiquote(ast[0]), quasiquote(ast.slice(1))];
+ }
+}
+
+function is_macro_call(ast, env) {
+ return types.list_Q(ast) &&
+ types.symbol_Q(ast[0]) &&
+ env.find(ast[0].value) &&
+ env.get(ast[0].value)._ismacro_;
+}
+
+function macroexpand(ast, env) {
+ while (is_macro_call(ast, env)) {
+ var mac = env.get(ast[0]);
+ ast = mac.apply(mac, ast.slice(1));
+ }
+ return ast;
+}
+
+function eval_ast(ast, env) {
+ if (types.symbol_Q(ast)) {
+ return env.get(ast);
+ } else if (types.list_Q(ast)) {
+ return ast.map(function(a) { return EVAL(a, env); });
+ } else if (types.vector_Q(ast)) {
+ var v = ast.map(function(a) { return EVAL(a, env); });
+ v.__isvector__ = true;
+ return v;
+ } else if (types.hash_map_Q(ast)) {
+ var new_hm = {};
+ for (k in ast) {
+ new_hm[EVAL(k, env)] = EVAL(ast[k], env);
+ }
+ return new_hm;
+ } else {
+ return ast;
+ }
+}
+
+function _EVAL(ast, env) {
+ while (true) {
+ //console.log("EVAL:", types._pr_str(ast, true));
+ if (!types.list_Q(ast)) {
+ return eval_ast(ast, env);
+ }
+
+ // apply list
+ ast = macroexpand(ast, env);
+ if (!types.list_Q(ast)) { return ast; }
+
+ var a0 = ast[0], a1 = ast[1], a2 = ast[2], a3 = ast[3];
+ switch (a0.value) {
+ case "def!":
+ var res = EVAL(a2, env);
+ return env.set(a1, res);
+ case "let*":
+ var let_env = new types.Env(env);
+ for (var i=0; i < a1.length; i+=2) {
+ let_env.set(a1[i].value, EVAL(a1[i+1], let_env));
+ }
+ return EVAL(a2, let_env);
+ case "quote":
+ return a1;
+ case "quasiquote":
+ return EVAL(quasiquote(a1), env);
+ case 'defmacro!':
+ var func = EVAL(a2, env);
+ func._ismacro_ = true;
+ return env.set(a1, func);
+ case 'macroexpand':
+ return macroexpand(a1, env);
+ case "js*":
+ return eval(a1.toString());
+ case ".":
+ var el = eval_ast(ast.slice(2), env),
+ f = eval(a1.toString());
+ return f.apply(f, el);
+ case "try*":
+ try {
+ return EVAL(a1, env);
+ } catch (exc) {
+ if (a2 && a2[0].value === "catch*") {
+ if (exc instanceof Error) { exc = exc.message; }
+ return EVAL(a2[2], new types.Env(env, [a2[1]], [exc]));
+ } else {
+ throw exc;
+ }
+ }
+ case "do":
+ eval_ast(ast.slice(1, -1), env);
+ ast = ast[ast.length-1];
+ break;
+ case "if":
+ var cond = EVAL(a1, env);
+ if (cond === null || cond === false) {
+ ast = (typeof a3 !== "undefined") ? a3 : null;
+ } else {
+ ast = a2;
+ }
+ break;
+ case "fn*":
+ return types.new_function(EVAL, a2, env, a1);
+ default:
+ var el = eval_ast(ast, env), f = el[0], meta = f.__meta__;
+ if (meta && meta.exp) {
+ ast = meta.exp;
+ env = new types.Env(meta.env, meta.params, el.slice(1));
+ } else {
+ return f.apply(f, el.slice(1));
+ }
+ }
+ }
+}
+
+function EVAL(ast, env) {
+ var result = _EVAL(ast, env);
+ return (typeof result !== "undefined") ? result : null;
+}
+
+// print
+function PRINT(exp) {
+ return types._pr_str(exp, true);
+}
+
+// repl
+var repl_env = new types.Env();
+var rep = function(str) { return PRINT(EVAL(READ(str), repl_env)); };
+_ref = function (k,v) { repl_env.set(k, v); }
+
+// Import types functions
+for (var n in types.ns) { repl_env.set(n, types.ns[n]); }
+
+_ref('readline', readline.readline)
+_ref('read-string', reader.read_str);
+_ref('eval', function(ast) { return EVAL(ast, repl_env); });
+_ref('slurp', function(f) {
+ return require('fs').readFileSync(f, 'utf-8');
+});
+_ref('slurp-do', function(f) {
+ return '(do ' + require('fs').readFileSync(f, 'utf-8') + ')';
+});
+
+// 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 (typeof process !== 'undefined' && process.argv.length > 2) {
+ for (var i=2; i < process.argv.length; i++) {
+ rep('(load-file "' + process.argv[i] + '")');
+ }
+} else if (typeof require === 'undefined') {
+ // Asynchronous browser mode
+ readline.rlwrap(function(line) { return rep(line); },
+ function(exc) {
+ if (exc instanceof reader.BlankException) { return; }
+ if (exc.stack) { console.log(exc.stack); } else { console.log(exc); }
+ });
+} else if (require.main === module) {
+ // Synchronous node.js commandline mode
+ while (true) {
+ var line = readline.readline("user> ");
+ if (line === null) { break; }
+ try {
+ if (line) { console.log(rep(line)); }
+ } catch (exc) {
+ if (exc instanceof reader.BlankException) { continue; }
+ if (exc.stack) { console.log(exc.stack); } else { console.log(exc); }
+ }
+ }
+} else {
+ exports.rep = rep;
+}
diff --git a/js/tests/common.js b/js/tests/common.js
new file mode 100644
index 0000000..a95d79b
--- /dev/null
+++ b/js/tests/common.js
@@ -0,0 +1,15 @@
+fs = require('fs');
+assert = require('assert');
+
+function assert_eq(a, b) {
+ GLOBAL.assert.deepEqual(a, b, a + " !== " + b);
+}
+
+function load(file) {
+ console.log(process.cwd());
+ //process.chdir('../');
+ eval(fs.readFileSync(file,'utf8'));
+}
+
+exports.assert_eq = assert_eq;
+exports.load = load;
diff --git a/js/tests/node_modules b/js/tests/node_modules
new file mode 120000
index 0000000..b870225
--- /dev/null
+++ b/js/tests/node_modules
@@ -0,0 +1 @@
+../ \ No newline at end of file
diff --git a/js/tests/reader.js b/js/tests/reader.js
new file mode 100644
index 0000000..2aa81c6
--- /dev/null
+++ b/js/tests/reader.js
@@ -0,0 +1,68 @@
+common = require('./common.js');
+types = require('../types');
+reader = require('../reader');
+var assert_eq = common.assert_eq,
+ read_str = reader.read_str,
+ nth = types.ns.nth;
+
+console.log("Testing read of constants/strings");
+assert_eq(2,read_str('2'));
+assert_eq(12345,read_str('12345'));
+assert_eq(12345,read_str('12345 "abc"'));
+assert_eq('abc',read_str('"abc"'));
+assert_eq('a string (with parens)',read_str('"a string (with parens)"'));
+
+console.log("Testing read of symbols");
+assert(types.symbol_Q(read_str('abc')));
+assert_eq('abc',read_str('abc').value);
+assert_eq('.',read_str('.').value);
+
+console.log("Testing READ_STR of strings");
+assert_eq('a string',read_str('"a string"'));
+assert_eq('a string (with parens)',read_str('"a string (with parens)"'));
+assert_eq('a string',read_str('"a string"()'));
+assert_eq('a string',read_str('"a string"123'));
+assert_eq('a string',read_str('"a string"abc'));
+assert_eq('',read_str('""'));
+assert_eq('abc ',read_str('"abc "'));
+assert_eq(' abc',read_str('" abc"'));
+assert_eq('$abc',read_str('"$abc"'));
+assert_eq('abc$()',read_str('"abc$()"'));
+assert_eq('"xyz"',read_str('"\\"xyz\\""'));
+
+
+console.log("Testing READ_STR of lists");
+assert_eq(2,types.ns.count(read_str('(2 3)')));
+assert_eq(2,types.ns.first(read_str('(2 3)')));
+assert_eq(3,types.ns.first(types.ns.rest(read_str('(2 3)'))));
+L = read_str('(+ 1 2 "str1" "string (with parens) and \'single quotes\'")');
+assert_eq(5,types.ns.count(L));
+assert_eq('str1',nth(L,3));
+assert_eq('string (with parens) and \'single quotes\'',nth(L,4));
+assert_eq([2,3],read_str('(2 3)'));
+assert_eq([2,3, 'string (with parens)'],read_str('(2 3 "string (with parens)")'));
+
+
+console.log("Testing READ_STR of quote/quasiquote");
+assert_eq('quote',nth(read_str('\'1'),0).value);
+assert_eq(1,nth(read_str('\'1'),1));
+assert_eq('quote',nth(read_str('\'(1 2 3)'),0).value);
+assert_eq(3,nth(nth(read_str('\'(1 2 3)'),1),2));
+
+assert_eq('quasiquote',nth(read_str('`1'),0).value);
+assert_eq(1,nth(read_str('`1'),1));
+assert_eq('quasiquote',nth(read_str('`(1 2 3)'),0).value);
+assert_eq(3,nth(nth(read_str('`(1 2 3)'),1),2));
+
+assert_eq('unquote',nth(read_str('~1'),0).value);
+assert_eq(1,nth(read_str('~1'),1));
+assert_eq('unquote',nth(read_str('~(1 2 3)'),0).value);
+assert_eq(3,nth(nth(read_str('~(1 2 3)'),1),2));
+
+assert_eq('splice-unquote',nth(read_str('~@1'),0).value);
+assert_eq(1,nth(read_str('~@1'),1));
+assert_eq('splice-unquote',nth(read_str('~@(1 2 3)'),0).value);
+assert_eq(3,nth(nth(read_str('~@(1 2 3)'),1),2));
+
+
+console.log("All tests completed");
diff --git a/js/tests/step5_tco.js b/js/tests/step5_tco.js
new file mode 100644
index 0000000..60c0576
--- /dev/null
+++ b/js/tests/step5_tco.js
@@ -0,0 +1,22 @@
+common = require('./common.js');
+var assert_eq = common.assert_eq;
+var rep = require('../step5_tco.js').rep;
+
+console.log("Testing Stack Exhaustion Function");
+rep('(def! sum-to (fn* (n) (if (= n 0) 0 (+ n (sum-to (- n 1))))))');
+try {
+ rep('(sum-to 10000)');
+ throw new Error("Did not get expected stack exhaustion");
+} catch (e) {
+ if (e.toString().match(/RangeError/)) {
+ console.log("Got expected stack exhaustion");
+ } else {
+ throw new Error("Unexpected error: " + e);
+ }
+}
+
+console.log("Testing Tail Call Optimization/Elimination");
+rep('(def! sum2 (fn* (n acc) (if (= n 0) acc (sum2 (- n 1) (+ n acc)))))');
+rep('(sum2 10000 0)');
+
+console.log("All tests completed");
diff --git a/js/tests/types.js b/js/tests/types.js
new file mode 100644
index 0000000..71b276f
--- /dev/null
+++ b/js/tests/types.js
@@ -0,0 +1,94 @@
+common = require('./common.js');
+var assert_eq = common.assert_eq;
+var types = require('../types.js');
+var symbol = types.symbol,
+ hash_map = types.ns['hash-map'],
+ hash_map_Q = types.ns['map?'],
+ assoc = types.ns['assoc'],
+ dissoc = types.ns['dissoc'],
+ get = types.ns['get'],
+ contains_Q = types.ns['contains?'],
+ count = types.ns['count'],
+ equal_Q = types.ns['='];
+
+
+console.log("Testing hash_maps");
+X = hash_map();
+assert_eq(true, hash_map_Q(X));
+
+assert_eq(null, get(X,'a'));
+assert_eq(false, contains_Q(X, 'a'));
+X1 = assoc(X, 'a', "value of X a");
+assert_eq(null, get(X,'a'));
+assert_eq(false, contains_Q(X, 'a'));
+assert_eq("value of X a", get(X1, 'a'));
+assert_eq(true, contains_Q(X1, 'a'));
+
+Y = hash_map();
+assert_eq(0, count(Y));
+Y1 = assoc(Y, 'a', "value of Y a");
+assert_eq(1, count(Y1));
+Y2 = assoc(Y1, 'b', "value of Y b");
+assert_eq(2, count(Y2));
+assert_eq("value of Y a", get(Y2, 'a'));
+assert_eq("value of Y b", get(Y2, 'b'));
+
+X2 = assoc(X1, 'b', Y2);
+assert_eq(2, count(Y2));
+
+assert_eq(true, hash_map_Q(get(X2,'b')));
+
+assert_eq('value of Y a', get(get(X2,'b'),'a'));
+assert_eq('value of Y b', get(get(X2,'b'),'b'));
+
+Y3 = dissoc(Y2, 'a');
+assert_eq(2, count(Y2));
+assert_eq(1, count(Y3));
+assert_eq(null, get(Y3, 'a'));
+Y4 = dissoc(Y3, 'b');
+assert_eq(0, count(Y4));
+assert_eq(null, get(Y4, 'b'));
+
+
+console.log("Testing equal? function");
+assert_eq(true, equal_Q(2,2));
+assert_eq(false, equal_Q(2,3));
+assert_eq(false, equal_Q(2,3));
+assert_eq(true, equal_Q("abc","abc"));
+assert_eq(false, equal_Q("abc","abz"));
+assert_eq(false, equal_Q("zbc","abc"));
+assert_eq(true, equal_Q(symbol("abc"),symbol("abc")));
+assert_eq(false, equal_Q(symbol("abc"),symbol("abz")));
+assert_eq(false, equal_Q(symbol("zbc"),symbol("abc")));
+L6 = [1, 2, 3];
+L7 = [1, 2, 3];
+L8 = [1, 2, "Z"];
+L9 = ["Z", 2, 3];
+L10 = [1, 2];
+assert_eq(true, equal_Q(L6, L7));
+assert_eq(false, equal_Q(L6, L8));
+assert_eq(false, equal_Q(L6, L9));
+assert_eq(false, equal_Q(L6, L10));
+assert_eq(false, equal_Q(L10, L6));
+
+
+console.log("Testing ENV (1 level)")
+env1 = new types.Env();
+assert_eq('val_a',env1.set('a','val_a'));
+assert_eq('val_b',env1.set('b','val_b'));
+assert_eq('val_eq',env1.set('=','val_eq'));
+assert_eq('val_a',env1.get('a'));
+assert_eq('val_b',env1.get('b'));
+assert_eq('val_eq',env1.get('='));
+
+console.log("Testing ENV (2 levels)");
+env2 = new types.Env(env1);
+assert_eq('val_b2',env2.set('b','val_b2'));
+assert_eq('val_c',env2.set('c','val_c'));
+assert_eq(env1,env2.find('a'));
+assert_eq(env2,env2.find('b'));
+assert_eq(env2,env2.find('c'));
+assert_eq('val_a', env2.get('a'));
+assert_eq('val_b2',env2.get('b'));
+assert_eq('val_c', env2.get('c'));
+
diff --git a/js/types.js b/js/types.js
new file mode 100644
index 0000000..062b0dd
--- /dev/null
+++ b/js/types.js
@@ -0,0 +1,429 @@
+// Node vs browser behavior
+var types = {};
+if (typeof module === 'undefined') {
+ var exports = types;
+}
+
+// General utility functions
+
+// Clone a function
+Function.prototype.clone = function() {
+ var that = this;
+ var temp = function () { return that.apply(this, arguments); };
+ for( key in this ) {
+ temp[key] = this[key];
+ }
+ return temp;
+};
+
+function _clone (obj) {
+ var new_obj;
+ switch (obj_type(obj)) {
+ case 'list':
+ new_obj = obj.slice(0);
+ break;
+ case 'vector':
+ new_obj = obj.slice(0);
+ new_obj.__isvector__ = true;
+ break;
+ case 'hash-map':
+ new_obj = {};
+ for (var k in obj) {
+ if (obj.hasOwnProperty(k)) { new_obj[k] = obj[k]; }
+ }
+ break;
+ case 'function':
+ new_obj = obj.clone();
+ break;
+ default:
+ throw new Error("clone of non-collection: " + obj_type(obj));
+ }
+ return new_obj;
+}
+
+
+
+
+function nil_Q(a) { return a === null ? true : false; }
+function true_Q(a) { return a === true ? true : false; }
+function false_Q(a) { return a === false ? true : false; }
+
+function obj_type(obj) {
+ if (symbol_Q(obj)) { return 'symbol'; }
+ else if (list_Q(obj)) { return 'list'; }
+ else if (vector_Q(obj)) { return 'vector'; }
+ else if (hash_map_Q(obj)) { return 'hash-map'; }
+ else if (nil_Q(obj)) { return 'nil'; }
+ else if (true_Q(obj)) { return 'true'; }
+ else if (false_Q(obj)) { return 'false'; }
+ else if (atom_Q(obj)) { return 'atom'; }
+ else {
+ switch (typeof(obj)) {
+ case 'number': return 'number';
+ case 'function': return 'function';
+ case 'string': return 'string';
+ default: throw new Error("Unknown type '" + typeof(obj) + "'");
+ }
+ }
+}
+
+function _pr_str(obj, print_readably) {
+ if (typeof print_readably === 'undefined') { print_readably = true; }
+ var _r = print_readably;
+ var ot = obj_type(obj);
+ switch (ot) {
+ case 'list':
+ var ret = obj.map(function(e) { return _pr_str(e,_r); });
+ return "(" + ret.join(' ') + ")";
+ case 'vector':
+ var ret = obj.map(function(e) { return _pr_str(e,_r); });
+ return "[" + ret.join(' ') + "]";
+ case 'hash-map':
+ var ret = [];
+ for (var k in obj) {
+ ret.push(_pr_str(k,_r), _pr_str(obj[k],_r));
+ }
+ return "{" + ret.join(' ') + "}";
+ case 'string':
+ if (print_readably) {
+ return '"' + obj.replace(/\\/, "\\\\").replace(/"/g, '\\"') + '"';
+ } else {
+ return obj;
+ }
+ case 'nil':
+ return "nil";
+ case 'atom':
+ return "(atom " + _pr_str(obj.val,_r) + ")";
+ default:
+ return obj.toString();
+ }
+}
+
+function pr_str() {
+ return Array.prototype.map.call(arguments,function(exp) {
+ return _pr_str(exp, true);
+ }).join(" ");
+}
+
+function str() {
+ return Array.prototype.map.call(arguments,function(exp) {
+ return _pr_str(exp, false);
+ }).join("");
+}
+
+function prn() {
+ console.log.apply(console, Array.prototype.map.call(arguments,function(exp) {
+ return _pr_str(exp, true);
+ }));
+}
+
+function println() {
+ console.log.apply(console, Array.prototype.map.call(arguments,function(exp) {
+ return _pr_str(exp, false);
+ }));
+}
+
+function with_meta(obj, m) {
+ var new_obj = _clone(obj);
+ new_obj.__meta__ = m;
+ return new_obj;
+}
+
+function meta(obj) {
+ // TODO: support symbols and atoms
+ if ((!sequential_Q(obj)) &&
+ (!(hash_map_Q(obj))) &&
+ (!(function_Q(obj)))) {
+ throw new Error("attempt to get metadata from: " + obj_type(obj));
+ }
+ return obj.__meta__;
+}
+
+
+function equal_Q (a, b) {
+ var ota = obj_type(a), otb = obj_type(b);
+ if (!(ota === otb || (sequential_Q(a) && sequential_Q(b)))) {
+ return false;
+ }
+ switch (ota) {
+ case 'symbol': return a.value === b.value;
+ case 'list':
+ case 'vector':
+ if (a.length !== b.length) { return false; }
+ for (var i=0; i<a.length; i++) {
+ if (! equal_Q(a[i], b[i])) { return false; }
+ }
+ return true;
+ case 'hash-map':
+ var akeys = Object.keys(a).sort(),
+ bkeys = Object.keys(b).sort();
+ if (akeys.length !== bkeys.length) { return false; }
+ for (var i=0; i<akeys.length; i++) {
+ if (akeys[i] !== bkeys[i]) { return false; }
+ if (! equal_Q(a[akeys[i]], b[bkeys[i]])) { return false; }
+ }
+ return true;
+ default:
+ return a === b;
+ }
+}
+
+
+
+// Symbols
+function Symbol(name) {
+ this.value = name;
+ return this;
+}
+Symbol.prototype.toString = function() { return this.value; }
+
+function symbol(name) { return new Symbol(name); }
+
+function symbol_Q(obj) { return obj instanceof Symbol; }
+
+
+// Functions
+function new_function(func, exp, env, params) {
+ var f = function() {
+ // TODO: figure out why this throws with 'and' macro
+ //throw new Error("Attempt to invoke mal function directly");
+ return func(exp, new Env(env, params, arguments));
+ };
+ f.__meta__ = {exp: exp, env: env, params: params};
+ return f;
+
+}
+function function_Q(f) { return typeof f == "function"; }
+
+
+
+// Errors/Exceptions
+function mal_throw(exc) { throw exc; }
+
+
+// Vectors
+function vector() {
+ var v = Array.prototype.slice.call(arguments, 0);
+ v.__isvector__ = true;
+ return v;
+}
+
+function vector_Q(v) { return Array.isArray(v) && v.__isvector__; }
+
+
+// Lists
+
+function list() {
+ return Array.prototype.slice.call(arguments, 0);
+}
+
+function list_Q(lst) { return Array.isArray(lst) && !lst.__isvector__; }
+
+
+// Hash Maps
+
+function hash_map() {
+ if (arguments.length % 2 === 1) {
+ throw new Error("Odd number of hash map arguments");
+ }
+ var args = [{}].concat(Array.prototype.slice.call(arguments, 0));
+ return assoc_BANG.apply(null, args);
+}
+
+function hash_map_Q(hm) {
+ return typeof hm === "object" &&
+ !Array.isArray(hm) &&
+ !(hm === null) &&
+ !(hm instanceof Atom);
+}
+
+function assoc_BANG(hm) {
+ if (arguments.length % 2 !== 1) {
+ throw new Error("Odd number of assoc arguments");
+ }
+ for (var i=1; i<arguments.length; i+=2) {
+ var ktoken = arguments[i],
+ vtoken = arguments[i+1];
+ // TODO: support more than string keys
+ //if (list_Q(ktoken) && hash_map_Q(ktoken)) {
+ // throw new Error("expected hash-map key atom, got collection");
+ //}
+ if (typeof ktoken !== "string") {
+ throw new Error("expected hash-map key string, got: " + (typeof ktoken));
+ }
+ hm[ktoken] = vtoken;
+ }
+ return hm;
+}
+
+function assoc(src_hm) {
+ var hm = _clone(src_hm);
+ var args = [hm].concat(Array.prototype.slice.call(arguments, 1));
+ return assoc_BANG.apply(null, args);
+}
+
+function dissoc_BANG(hm) {
+ for (var i=1; i<arguments.length; i++) {
+ var ktoken = arguments[i];
+ delete hm[ktoken];
+ }
+ return hm;
+}
+
+function dissoc(src_hm) {
+ var hm = _clone(src_hm);
+ var args = [hm].concat(Array.prototype.slice.call(arguments, 1));
+ return dissoc_BANG.apply(null, args);
+}
+
+function get(hm, key) {
+ if (key in hm) {
+ return hm[key];
+ } else {
+ return null;
+ }
+}
+
+function contains_Q(hm, key) {
+ if (key in hm) { return true; } else { return false; }
+}
+
+function keys(hm) { return Object.keys(hm); }
+function vals(hm) { return Object.keys(hm).map(function(k) { return hm[k]; }); }
+
+
+// Atoms
+function Atom(val) { this.val = val; }
+function atom(val) { return new Atom(val); }
+function atom_Q(atm) { return atm instanceof Atom; }
+function deref(atm) { return atm.val; }
+function reset_BANG(atm, val) { return atm.val = val; }
+function swap_BANG(atm, f) {
+ var args = [atm.val].concat(Array.prototype.slice.call(arguments, 2));
+ atm.val = f.apply(f, args);
+ return atm.val;
+}
+
+
+// Sequence operations
+function sequential_Q(lst) { return list_Q(lst) || vector_Q(lst); }
+
+function nth(lst, idx) { return lst[idx]; }
+
+function count(s) {
+ if (Array.isArray(s)) { return s.length; }
+ else { return Object.keys(s).length; }
+}
+
+function empty_Q(lst) { return lst.length === 0; }
+
+function cons(a, b) { return [a].concat(b); }
+
+function concat(lst) {
+ lst = lst || [];
+ return lst.concat.apply(lst, Array.prototype.slice.call(arguments, 1));
+}
+
+function conj(lst) {
+ return lst.concat(Array.prototype.slice.call(arguments, 1));
+}
+
+function first(lst) { return lst[0]; }
+
+function rest(lst) { return lst.slice(1); }
+
+
+
+// General list related functions
+function apply(f) {
+ var args = Array.prototype.slice.call(arguments, 1);
+ return f.apply(f, args.slice(0, args.length-1).concat(args[args.length-1]));
+}
+
+function map(f, lst) {
+ return lst.map(function(el){ return f(el); });
+}
+
+
+// Env implementation
+function Env(outer, binds, exprs) {
+ this.data = {};
+ this.outer = outer || null;
+
+ if (binds && exprs) {
+ // Returns a new Env with symbols in binds bound to
+ // corresponding values in exprs
+ // TODO: check types of binds and exprs and compare lengths
+ for (var i=0; i<binds.length;i++) {
+ if (binds[i].value === "&") {
+ // variable length arguments
+ this.data[binds[i+1].value] = Array.prototype.slice.call(exprs, i);
+ break;
+ } else {
+ this.data[binds[i].value] = exprs[i];
+ }
+ }
+ }
+ return this;
+}
+Env.prototype.find = function (key) {
+ if (key in this.data) { return this; }
+ else if (this.outer) { return this.outer.find(key); }
+ else { return null; }
+};
+Env.prototype.set = function(key, value) { this.data[key] = value; return value; },
+Env.prototype.get = function(key) {
+ var env = this.find(key);
+ if (!env) { throw new Error("'" + key + "' not found"); }
+ return env.data[key];
+};
+
+// types.ns is namespace of type functions
+var ns = {'pr-str': pr_str, 'str': str, 'prn': prn, 'println': println,
+ 'with-meta': with_meta, 'meta': meta,
+ type: obj_type, '=': equal_Q,
+ symbol: symbol, 'symbol?': symbol_Q,
+ 'nil?': nil_Q, 'true?': true_Q, 'false?': false_Q,
+ '<' : function(a,b){return a<b;},
+ '<=' : function(a,b){return a<=b;},
+ '>' : function(a,b){return a>b;},
+ '>=' : function(a,b){return a>=b;},
+ '+' : function(a,b){return a+b;},
+ '-' : function(a,b){return a-b;},
+ '*' : function(a,b){return a*b;},
+ '/' : function(a,b){return a/b;},
+ 'throw': mal_throw,
+ 'list': list, 'list?': list_Q,
+ 'vector': vector, 'vector?': vector_Q,
+ 'hash-map': hash_map, 'map?': hash_map_Q,
+ 'assoc': assoc, 'dissoc': dissoc, 'get': get,
+ 'contains?': contains_Q, 'keys': keys, 'vals': vals,
+ 'atom': atom, 'atom?': atom_Q,
+ "deref": deref, "reset!": reset_BANG, "swap!": swap_BANG,
+ 'sequential?': sequential_Q, 'cons': cons, 'nth': nth,
+ 'empty?': empty_Q, 'count': count, 'concat': concat,
+ 'conj': conj, 'first': first, 'rest': rest,
+ 'apply': apply, 'map': map};
+
+exports.ns = types.ns = ns;
+exports._pr_str = types._pr_str = _pr_str;
+exports.prn = types.prn = prn;
+exports.Env = types.Env = Env;
+
+exports.symbol = types.symbol = symbol;
+exports.symbol_Q = types.symbol_Q = symbol_Q;
+exports.hash_map = types.hash_map = hash_map;
+exports.hash_map_Q = types.hash_map_Q = hash_map_Q;
+exports.new_function = types.new_function = new_function;
+exports.list = types.list = list;
+exports.list_Q = types.list_Q = list_Q;
+exports.vector = types.vector = vector;
+exports.vector_Q = types.vector_Q = vector_Q;
+
+exports.sequential_Q = types.sequential_Q = sequential_Q;
+exports.cons = types.cons = cons;
+exports.concat = types.concat = concat;
+exports.first = types.first = first;
+exports.rest = types.rest = rest;
+exports.apply = types.apply = apply;
+exports.map = types.map = map;
diff --git a/make/Makefile b/make/Makefile
new file mode 100644
index 0000000..1110397
--- /dev/null
+++ b/make/Makefile
@@ -0,0 +1,23 @@
+
+TESTS = tests/types.mk tests/reader.mk tests/step9_interop.mk
+
+SOURCES = util.mk readline.mk gmsl.mk types.mk reader.mk stepA_more.mk
+
+mal.mk: $(SOURCES)
+ echo "#!/usr/bin/make -f" > $@
+ cat $+ | grep -v "^include " >> $@
+ chmod +x $@
+
+clean:
+ rm -f mal.mk
+
+.PHONY: stats tests $(TESTS)
+
+stats: $(SOURCES)
+ @wc $^
+
+tests: $(TESTS)
+
+$(TESTS):
+ @echo "Running $@"; \
+ make -f $@ || exit 1; \
diff --git a/make/gmsl.mk b/make/gmsl.mk
new file mode 100644
index 0000000..e988d2c
--- /dev/null
+++ b/make/gmsl.mk
@@ -0,0 +1,115 @@
+#
+# mal (Make Lisp) trimmed and namespaced GMSL functions/definitions
+# - derived from the GMSL 1.1.3
+#
+
+ifndef __mal_gmsl_included
+__mal_gmsl_included := true
+
+# ----------------------------------------------------------------------------
+#
+# GNU Make Standard Library (GMSL)
+#
+# A library of functions to be used with GNU Make's $(call) that
+# provides functionality not available in standard GNU Make.
+#
+# Copyright (c) 2005-2013 John Graham-Cumming
+#
+# This file is part of GMSL
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#
+# Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# Neither the name of the John Graham-Cumming nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+# ----------------------------------------------------------------------------
+
+
+# Numbers
+__gmsl_sixteen := x x x x x x x x x x x x x x x x
+__gmsl_input_int := $(foreach a,$(__gmsl_sixteen), \
+ $(foreach b,$(__gmsl_sixteen), \
+ $(foreach c,$(__gmsl_sixteen), \
+ $(__gmsl_sixteen)))))
+
+int_decode = $(words $1)
+int_encode = $(wordlist 1,$1,$(__gmsl_input_int))
+
+__gmsl_int_wrap = $(call int_decode,$(call $1,$(call int_encode,$2),$(call int_encode,$3)))
+
+int_plus = $(strip $1 $2)
+int_subtract = $(strip $(if $(call int_gte,$1,$2), \
+ $(filter-out xx,$(join $1,$2)), \
+ $(warning Subtraction underflow)))
+int_multiply = $(strip $(foreach a,$1,$2))
+# _error function must be provided to report/catch division by zero
+int_divide = $(strip $(if $2, \
+ $(if $(call int_gte,$1,$2), \
+ x $(call int_divide,$(call int_subtract,$1,$2),$2),), \
+ $(call _error,Division by zero)))
+
+int_max = $(subst xx,x,$(join $1,$2))
+int_min = $(subst xx,x,$(filter xx,$(join $1,$2)))
+int_gt = $(strip $(filter-out $(words $2),$(words $(call int_max,$1,$2))))
+int_gte = $(strip $(call int_gt,$1,$2)$(call int_eq,$1,$2))
+int_lt = $(strip $(filter-out $(words $1),$(words $(call int_max,$1,$2))))
+int_lte = $(strip $(call int_lt,$1,$2)$(call int_eq,$1,$2))
+int_eq = $(strip $(filter $(words $1),$(words $2)))
+int_ne = $(strip $(filter-out $(words $1),$(words $2)))
+
+gmsl_plus = $(call __gmsl_int_wrap,int_plus,$1,$2)
+gmsl_subtract = $(call __gmsl_int_wrap,int_subtract,$1,$2)
+gmsl_multiply = $(call __gmsl_int_wrap,int_multiply,$1,$2)
+gmsl_divide = $(call __gmsl_int_wrap,int_divide,$1,$2)
+
+
+# Strings
+
+__gmsl_characters := A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
+__gmsl_characters += a b c d e f g h i j k l m n o p q r s t u v w x y z
+__gmsl_characters += 0 1 2 3 4 5 6 7 8 9
+__gmsl_characters += ` ~ ! @ \# $$ % ^ & * ( ) - _ = +
+__gmsl_characters += { } [ ] \ : ; ' " < > , . / ? |
+__syntax_highlight_protect = #"'`
+
+
+__gmsl_space :=
+__gmsl_space +=
+
+gmsl_strlen = $(strip $(eval __temp := $(subst $(__gmsl_space),x,$1)) \
+ $(foreach a,$(__gmsl_characters),$(eval __temp := $$(subst $$a,x,$(__temp)))) \
+ $(eval __temp := $(subst x,x ,$(__temp))) \
+ $(words $(__temp)))
+
+gmsl_merge = $(strip $(if $2, \
+ $(if $(call _EQ,1,$(words $2)), \
+ $2,$(firstword $2)$1$(call gmsl_merge,$1,$(wordlist 2,$(words $2),$2)))))
+
+gmsl_pairmap = $(strip \
+ $(if $2$3,$(call $1,$(word 1,$2),$(word 1,$3)) \
+ $(call gmsl_pairmap,$1,$(wordlist 2,$(words $2),$2),$(wordlist 2,$(words $3),$3))))
+
+endif
diff --git a/make/reader.mk b/make/reader.mk
new file mode 100755
index 0000000..ce3b078
--- /dev/null
+++ b/make/reader.mk
@@ -0,0 +1,170 @@
+#
+# mal (Make Lisp) Parser/Reader
+#
+
+ifndef __mal_reader_included
+__mal_reader_included := true
+
+_TOP_DIR := $(dir $(lastword $(MAKEFILE_LIST)))
+include $(_TOP_DIR)util.mk
+include $(_TOP_DIR)types.mk
+include $(_TOP_DIR)readline.mk
+
+READER_DEBUG ?=
+
+_TOKEN_DELIMS := $(SEMI) $(COMMA) $(DQUOTE) $(QQUOTE) $(_SP) $(_NL) $(_LC) $(_RC) $(_LP) $(_RP) $(LBRACKET) $(RBRACKET)
+
+define READ_NUMBER
+$(foreach ch,$(word 1,$($(1))),\
+ $(if $(ch),\
+ $(if $(filter $(_TOKEN_DELIMS),$(ch)),\
+ ,\
+ $(if $(filter-out $(NUMBERS),$(ch)),\
+ $(call _error,Invalid number character '$(ch)'),\
+ $(eval $(1) := $(wordlist 2,$(words $($(1))),$($(1))))\
+ $(and $(READER_DEBUG),$(info READ_NUMBER ch: $(ch) | $($(1))))\
+ $(ch)$(strip $(call READ_NUMBER,$(1))))),\
+ ))
+endef
+
+define READ_STRING
+$(foreach ch,$(word 1,$($(1))),\
+ $(if $(ch),\
+ $(if $(and $(filter \,$(ch)),$(filter $(DQUOTE),$(word 2,$($(1))))),\
+ $(eval $(1) := $(wordlist 3,$(words $($(1))),$($(1))))\
+ $(and $(READER_DEBUG),$(info READ_STRING ch: \$(word 1,$($(1))) | $($(1))))\
+ $(DQUOTE) $(strip $(call READ_STRING,$(1))),\
+ $(if $(filter $(DQUOTE),$(ch)),\
+ ,\
+ $(eval $(1) := $(wordlist 2,$(words $($(1))),$($(1))))\
+ $(and $(READER_DEBUG),$(info READ_STRING ch: $(ch) | $($(1))))\
+ $(ch) $(strip $(call READ_STRING,$(1))))),))
+endef
+
+define READ_SYMBOL
+$(foreach ch,$(word 1,$($(1))),\
+ $(if $(ch),\
+ $(if $(filter $(_TOKEN_DELIMS),$(ch)),\
+ ,\
+ $(eval $(1) := $(wordlist 2,$(words $($(1))),$($(1))))\
+ $(and $(READER_DEBUG),$(info READ_SYMBOL ch: $(ch) | $($(1))))\
+ $(ch)$(strip $(call READ_SYMBOL,$(1)))),\
+ ))
+endef
+
+define READ_ATOM
+$(foreach ch,$(word 1,$($(1))),\
+ $(if $(filter $(NUMBERS),$(ch)),\
+ $(call number,$(call READ_NUMBER,$(1))),\
+ $(if $(filter $(DQUOTE),$(ch)),\
+ $(eval $(1) := $(wordlist 2,$(words $($(1))),$($(1))))\
+ $(call _string,$(strip $(call READ_STRING,$(1))))\
+ $(eval $(if $(filter $(DQUOTE),$(word 1,$($(1)))),\
+ $(eval $(1) := $(wordlist 2,$(words $($(1))),$($(1)))),\
+ $(call _error,Expected '$(DQUOTE)' in; $($(1))))),\
+ $(foreach sym,$(call READ_SYMBOL,$(1)),\
+ $(if $(call _EQ,nil,$(sym)),\
+ $(__nil),\
+ $(if $(call _EQ,true,$(sym)),\
+ $(__true),\
+ $(if $(call _EQ,false,$(sym)),\
+ $(__false),\
+ $(call symbol,$(sym)))))))))
+endef
+
+# read and return tokens until $(2) found
+define READ_UNTIL
+$(and $(READER_DEBUG),$(info READ_UNTIL: $($(1)) [$(2) $(3)]))
+$(foreach ch,$(word 1,$($(1))),\
+ $(if $(ch),\
+ $(if $(filter $(2),$(ch)),\
+ ,\
+ $(call READ_FORM,$(1))\
+ $(call READ_UNTIL,$(1),$(2),$(3))),\
+ $(call _error,Expected '$(3)')))
+endef
+
+define DROP_UNTIL
+$(and $(READER_DEBUG),$(info DROP_UNTIL: $($(1)) [$(2)]))
+$(foreach ch,$(word 1,$($(1))),\
+ $(if $(ch),\
+ $(if $(filter $(2),$(ch)),\
+ ,\
+ $(eval $(1) := $(wordlist 2,$(words $($(1))),$($(1))))\
+ $(call DROP_UNTIL,$(1),$(2),$(3))),\
+ ))
+endef
+
+define READ_SPACES
+$(and $(READER_DEBUG),$(info READ_SPACES: $($(1))))
+$(foreach ch,$(word 1,$($(1))),\
+ $(if $(filter $(_SP) $(_NL) $(COMMA),$(ch)),\
+ $(eval $(1) := $(wordlist 2,$(words $($(1))),$($(1))))\
+ $(call READ_SPACES,$(1)),))
+endef
+
+define READ_FORM
+$(and $(READER_DEBUG),$(info READ_FORM: $($(1))))
+$(call READ_SPACES,$(1))
+$(foreach ch,$(word 1,$($(1))),\
+ $(if $(filter $(SEMI),$(ch)),\
+ $(call DROP_UNTIL,$(1),$(_NL)),\
+ $(if $(filter $(SQUOTE),$(ch)),\
+ $(eval $(1) := $(wordlist 2,$(words $($(1))),$($(1))))\
+ $(call list,$(call symbol,quote) $(strip $(call READ_FORM,$(1)))),\
+ $(if $(filter $(QQUOTE),$(ch)),\
+ $(eval $(1) := $(wordlist 2,$(words $($(1))),$($(1))))\
+ $(call list,$(call symbol,quasiquote) $(strip $(call READ_FORM,$(1)))),\
+ $(if $(filter $(UNQUOTE),$(ch)),\
+ $(eval $(1) := $(wordlist 2,$(words $($(1))),$($(1))))\
+ $(call list,$(call symbol,unquote) $(strip $(call READ_FORM,$(1)))),\
+ $(if $(filter $(_SUQ),$(ch)),\
+ $(eval $(1) := $(wordlist 2,$(words $($(1))),$($(1))))\
+ $(call list,$(call symbol,splice-unquote) $(strip $(call READ_FORM,$(1)))),\
+ $(if $(filter $(CARET),$(ch)),\
+ $(eval $(1) := $(wordlist 2,$(words $($(1))),$($(1))))\
+ $(foreach meta,$(strip $(call READ_FORM,$(1))),\
+ $(call list,$(call symbol,with-meta) $(strip $(call READ_FORM,$(1))) $(meta))),\
+ $(if $(filter $(ATSIGN),$(ch)),\
+ $(eval $(1) := $(wordlist 2,$(words $($(1))),$($(1))))\
+ $(call list,$(call symbol,deref) $(strip $(call READ_FORM,$(1)))),\
+ $(if $(filter $(_RC),$(ch)),\
+ $(call _error,Unexpected '$(RCURLY)'),\
+ $(if $(filter $(_LC),$(ch)),\
+ $(eval $(1) := $(wordlist 2,$(words $($(1))),$($(1))))\
+ $(foreach thm,$(call hash_map),\
+ $(call do,$(call _assoc_seq!,$(thm),$(strip $(call READ_UNTIL,$(1),$(_RC),$(RCURLY)))))\
+ $(eval $(if $(filter $(_RC),$(word 1,$($(1)))),\
+ $(eval $(1) := $(wordlist 2,$(words $($(1))),$($(1)))),\
+ $(call _error,Expected '$(RCURLY)')))\
+ $(thm)),\
+ $(if $(filter $(_RP),$(ch)),\
+ $(call _error,Unexpected '$(RPAREN)'),\
+ $(if $(filter $(_LP),$(ch)),\
+ $(eval $(1) := $(wordlist 2,$(words $($(1))),$($(1))))\
+ $(foreach tlist,$(call _list),\
+ $(eval $(foreach item,$(strip $(call READ_UNTIL,$(1),$(_RP),$(RPAREN))),\
+ $(call do,$(call _conj!,$(tlist),$(item)))))\
+ $(eval $(if $(filter $(_RP),$(word 1,$($(1)))),\
+ $(eval $(1) := $(wordlist 2,$(words $($(1))),$($(1)))),\
+ $(call _error,Expected '$(RPAREN)')))\
+ $(tlist)),\
+ $(if $(filter $(RBRACKET),$(ch)),\
+ $(call _error,Unexpected '$(RBRACKET)'),\
+ $(if $(filter $(LBRACKET),$(ch)),\
+ $(eval $(1) := $(wordlist 2,$(words $($(1))),$($(1))))\
+ $(foreach tvec,$(call _vector),\
+ $(eval $(foreach item,$(strip $(call READ_UNTIL,$(1),$(RBRACKET),$(RBRACKET))),\
+ $(call do,$(call _conj!,$(tvec),$(item)))))\
+ $(eval $(if $(filter $(RBRACKET),$(word 1,$($(1)))),\
+ $(eval $(1) := $(wordlist 2,$(words $($(1))),$($(1)))),\
+ $(call _error,Expected '$(RBRACKET)')))\
+ $(tvec)),\
+ $(call READ_ATOM,$(1))))))))))))))))
+$(call READ_SPACES,$(1))
+endef
+
+# read-str from a raw "string" or from a string object
+READ_STR = $(strip $(eval __reader_temp := $(call str_encode,$(if $(call _string?,$(1)),$(call str_decode,$($(1)_value)),$(1))))$(call READ_FORM,__reader_temp))
+
+endif
diff --git a/make/readline.mk b/make/readline.mk
new file mode 100644
index 0000000..1208f5c
--- /dev/null
+++ b/make/readline.mk
@@ -0,0 +1,15 @@
+#
+# mal (Make Lisp) shell readline wrapper
+#
+
+ifndef __mal_readline_included
+__mal_readline_included := true
+
+# Call bash read/readline. Since each call is in a separate shell
+# instance we need to restore and save after each call in order to
+# have readline history.
+READLINE_EOF :=
+READLINE_HISTORY_FILE := $${HOME}/.mal-history
+READLINE = $(eval __readline_temp := $(shell history -r $(READLINE_HISTORY_FILE); read -u 0 -r -e -p $(if $(1),$(1),"user> ") line && history -s -- "$${line}" && history -a $(READLINE_HISTORY_FILE) && echo "$${line}" || echo "__||EOF||__"))$(if $(filter __||EOF||__,$(__readline_temp)),$(eval READLINE_EOF := yes),$(__readline_temp))
+
+endif
diff --git a/make/step0_repl.mk b/make/step0_repl.mk
new file mode 100644
index 0000000..b8b1309
--- /dev/null
+++ b/make/step0_repl.mk
@@ -0,0 +1,26 @@
+#
+# mal (Make Lisp)
+#
+_TOP_DIR := $(dir $(lastword $(MAKEFILE_LIST)))
+include $(_TOP_DIR)readline.mk
+
+SHELL := /bin/bash
+
+define READ
+$(call READLINE)
+endef
+
+define EVAL
+$(if $(READLINE_EOF),,\
+ $(if $(findstring =,$(1)),$(eval $(1))$($(word 1,$(1))),$(eval __return := $(1))$(__return)))
+endef
+
+define PRINT
+$(1)
+endef
+
+REP = $(call PRINT,$(strip $(call EVAL,$(strip $(call READ)))))
+REPL = $(info $(call REP))$(if $(READLINE_EOF),,$(call REPL))
+
+# Call the read-eval-print loop
+$(call REPL)
diff --git a/make/step1_read_print.mk b/make/step1_read_print.mk
new file mode 100644
index 0000000..710cd1d
--- /dev/null
+++ b/make/step1_read_print.mk
@@ -0,0 +1,31 @@
+#
+# mal (Make Lisp)
+#
+_TOP_DIR := $(dir $(lastword $(MAKEFILE_LIST)))
+include $(_TOP_DIR)types.mk
+include $(_TOP_DIR)reader.mk
+
+SHELL := /bin/bash
+INTERACTIVE ?= yes
+
+# READ: read and parse input
+define READ
+$(if $(READLINE_EOF)$(__ERROR),,$(call READ_STR,$(if $(1),$(1),$(call READLINE,"user> "))))
+endef
+
+# EVAL: just return the input
+define EVAL
+$(if $(READLINE_EOF)$(__ERROR),,$(1))
+endef
+
+# PRINT:
+define PRINT
+$(if $(__ERROR),Error: $(call _pr_str,$(__ERROR),yes),$(if $(1),$(call _pr_str,$(1),yes)))$(if $(__ERROR),$(eval __ERROR :=),)
+endef
+
+# REPL: read, eval, print, loop
+REP = $(call PRINT,$(strip $(call EVAL,$(strip $(call READ,$(1))),$(REPL_ENV))))
+REPL = $(info $(call REP,$(call READLINE,"user> ")))$(if $(READLINE_EOF),,$(call REPL))
+
+# Call the read-eval-print loop
+$(if $(strip $(INTERACTIVE)),$(call REPL))
diff --git a/make/step2_eval.mk b/make/step2_eval.mk
new file mode 100644
index 0000000..62cd415
--- /dev/null
+++ b/make/step2_eval.mk
@@ -0,0 +1,71 @@
+#
+# mal (Make Lisp)
+#
+_TOP_DIR := $(dir $(lastword $(MAKEFILE_LIST)))
+include $(_TOP_DIR)types.mk
+include $(_TOP_DIR)reader.mk
+
+SHELL := /bin/bash
+INTERACTIVE ?= yes
+EVAL_DEBUG ?=
+
+# READ: read and parse input
+define READ
+$(if $(READLINE_EOF)$(__ERROR),,$(call READ_STR,$(if $(1),$(1),$(call READLINE,"user> "))))
+endef
+
+# EVAL: evaluate the parameter
+define EVAL_AST
+$(strip \
+ $(and $(EVAL_DEBUG),$(info EVAL_AST: $(call _pr_str,$(1))))\
+ $(if $(call _symbol?,$(1)),\
+ $(foreach key,$($(1)_value),\
+ $(if $(call _contains?,$(2),$(key)),\
+ $(call _get,$(2),$(key)),\
+ $(call _error,'$(key)' not found in REPL_ENV ($(2))))),\
+ $(if $(call _list?,$(1)),\
+ $(call _smap,EVAL,$(1),$(2)),\
+ $(if $(call _vector?,$(1)),\
+ $(call _smap_vec,EVAL,$(1),$(2)),\
+ $(if $(call _hash_map?,$(1)),\
+ $(foreach new_hmap,$(call __new_obj,hmap),\
+ $(foreach v,$(call __get_obj_values,$(1)),\
+ $(eval $(v:$(1)_%=$(new_hmap)_%) := $(call EVAL,$($(v)),$(2))))\
+ $(eval $(new_hmap)_size := $($(1)_size))\
+ $(new_hmap)),\
+ $(1))))))
+endef
+
+define EVAL_INVOKE
+$(if $(__ERROR),,\
+ $(and $(EVAL_DEBUG),$(info EVAL_INVOKE: $(call _pr_str,$(1))))\
+ $(foreach el,$(call EVAL_AST,$(1),$(2)),\
+ $(call _apply,$(call sfirst,$(el)),$(call srest,$(el)))))
+endef
+
+define EVAL
+$(strip $(if $(__ERROR),,\
+ $(and $(EVAL_DEBUG),$(info EVAL: $(call _pr_str,$(1))))\
+ $(if $(call _list?,$(1)),\
+ $(strip $(call EVAL_INVOKE,$(1),$(2))),\
+ $(call EVAL_AST,$(1),$(2)))))
+endef
+
+
+# PRINT:
+define PRINT
+$(if $(__ERROR),Error: $(call _pr_str,$(__ERROR),yes),$(if $(1),$(call _pr_str,$(1),yes)))$(if $(__ERROR),$(eval __ERROR :=),)
+endef
+
+# REPL:
+REPL_ENV := $(call _hash_map)
+REP = $(call PRINT,$(strip $(call EVAL,$(strip $(call READ,$(1))),$(REPL_ENV))))
+REPL = $(info $(call REP,$(call READLINE,"user> ")))$(if $(READLINE_EOF),,$(call REPL))
+
+$(call do,$(call _assoc!,$(REPL_ENV),+,number_plus))
+$(call do,$(call _assoc!,$(REPL_ENV),-,number_subtract))
+$(call do,$(call _assoc!,$(REPL_ENV),*,number_multiply))
+$(call do,$(call _assoc!,$(REPL_ENV),/,number_divide))
+
+# Call the read-eval-print loop
+$(if $(strip $(INTERACTIVE)),$(call REPL))
diff --git a/make/step3_env.mk b/make/step3_env.mk
new file mode 100644
index 0000000..4f3f070
--- /dev/null
+++ b/make/step3_env.mk
@@ -0,0 +1,93 @@
+#
+# mal (Make Lisp)
+#
+_TOP_DIR := $(dir $(lastword $(MAKEFILE_LIST)))
+include $(_TOP_DIR)types.mk
+include $(_TOP_DIR)reader.mk
+
+SHELL := /bin/bash
+INTERACTIVE ?= yes
+EVAL_DEBUG ?=
+
+# READ: read and parse input
+define READ
+$(if $(READLINE_EOF)$(__ERROR),,$(call READ_STR,$(if $(1),$(1),$(call READLINE,"user> "))))
+endef
+
+# EVAL: evaluate the parameter
+define LET
+$(strip \
+ $(word 1,$(2) \
+ $(foreach var,$(call _nth,$(1),0),\
+ $(foreach val,$(call _nth,$(1),1),\
+ $(call ENV_SET,$(2),$($(var)_value),$(call EVAL,$(val),$(2)))\
+ $(foreach left,$(call srest,$(call srest,$(1))),
+ $(if $(call _EQ,0,$(call _count,$(left))),\
+ ,\
+ $(call LET,$(left),$(2))))))))
+endef
+
+define EVAL_AST
+$(strip \
+ $(and $(EVAL_DEBUG),$(info EVAL_AST: $(call _pr_str,$(1))))\
+ $(if $(call _symbol?,$(1)),\
+ $(foreach key,$($(1)_value),\
+ $(call ENV_GET,$(2),$(key))),\
+ $(if $(call _list?,$(1)),\
+ $(call _smap,EVAL,$(1),$(2)),\
+ $(if $(call _vector?,$(1)),\
+ $(call _smap_vec,EVAL,$(1),$(2)),\
+ $(if $(call _hash_map?,$(1)),\
+ $(foreach new_hmap,$(call __new_obj,hmap),\
+ $(foreach v,$(call __get_obj_values,$(1)),\
+ $(eval $(v:$(1)_%=$(new_hmap)_%) := $(call EVAL,$($(v)),$(2))))\
+ $(eval $(new_hmap)_size := $($(1)_size))\
+ $(new_hmap)),\
+ $(1))))))
+endef
+
+define EVAL_INVOKE
+$(if $(__ERROR),,\
+ $(and $(EVAL_DEBUG),$(info EVAL_INVOKE: $(call _pr_str,$(1))))
+ $(foreach a0,$(call _nth,$(1),0),\
+ $(if $(call _EQ,def!,$($(a0)_value)),\
+ $(foreach a1,$(call _nth,$(1),1),\
+ $(foreach a2,$(call _nth,$(1),2),\
+ $(foreach res,$(call EVAL,$(a2),$(2)),\
+ $(if $(call ENV_SET,$(2),$($(a1)_value),$(res)),$(res),)))),\
+ $(if $(call _EQ,let*,$($(a0)_value)),\
+ $(foreach a1,$(call _nth,$(1),1),\
+ $(foreach a2,$(call _nth,$(1),2),\
+ $(call EVAL,$(a2),$(call LET,$(a1),$(call ENV,$(2)))))),\
+ $(foreach el,$(call EVAL_AST,$(1),$(2)),\
+ $(call _apply,$(call sfirst,$(el)),$(call srest,$(el))))))))
+endef
+
+define EVAL
+$(strip $(if $(__ERROR),,\
+ $(and $(EVAL_DEBUG),$(info EVAL: $(call _pr_str,$(1))))\
+ $(if $(call _list?,$(1)),\
+ $(strip $(call EVAL_INVOKE,$(1),$(2))),\
+ $(call EVAL_AST,$(1),$(2)))))
+endef
+
+
+# PRINT:
+define PRINT
+$(if $(__ERROR),Error: $(call _pr_str,$(__ERROR),yes),$(if $(1),$(call _pr_str,$(1),yes)))$(if $(__ERROR),$(eval __ERROR :=),)
+endef
+
+# REPL:
+REPL_ENV := $(call ENV)
+REP = $(call PRINT,$(strip $(call EVAL,$(strip $(call READ,$(1))),$(REPL_ENV))))
+REPL = $(info $(call REP,$(call READLINE,"user> ")))$(if $(READLINE_EOF),,$(call REPL))
+
+# Setup the environment
+_ref = $(eval REPL_ENV := $(call ENV_SET,$(REPL_ENV),$(1),$(if $(2),$(2),$(1))))
+$(call _ref,+,number_plus)
+$(call _ref,-,number_subtract)
+$(call _ref,*,number_multiply)
+$(call _ref,/,number_divide)
+
+# Call the read-eval-print loop
+$(if $(strip $(INTERACTIVE)),$(call REPL))
diff --git a/make/step4_if_fn_do.mk b/make/step4_if_fn_do.mk
new file mode 100644
index 0000000..d08998d
--- /dev/null
+++ b/make/step4_if_fn_do.mk
@@ -0,0 +1,112 @@
+#
+# mal (Make Lisp)
+#
+_TOP_DIR := $(dir $(lastword $(MAKEFILE_LIST)))
+include $(_TOP_DIR)types.mk
+include $(_TOP_DIR)reader.mk
+
+SHELL := /bin/bash
+INTERACTIVE ?= yes
+EVAL_DEBUG ?=
+
+# READ: read and parse input
+define READ
+$(if $(READLINE_EOF)$(__ERROR),,$(call READ_STR,$(if $(1),$(1),$(call READLINE,"user> "))))
+endef
+
+# EVAL: evaluate the parameter
+define LET
+$(strip \
+ $(word 1,$(2) \
+ $(foreach var,$(call _nth,$(1),0),\
+ $(foreach val,$(call _nth,$(1),1),\
+ $(call ENV_SET,$(2),$($(var)_value),$(call EVAL,$(val),$(2)))\
+ $(foreach left,$(call srest,$(call srest,$(1))),
+ $(if $(call _EQ,0,$(call _count,$(left))),\
+ ,\
+ $(call LET,$(left),$(2))))))))
+endef
+
+define EVAL_AST
+$(strip \
+ $(and $(EVAL_DEBUG),$(info EVAL_AST: $(call _pr_str,$(1))))\
+ $(if $(call _symbol?,$(1)),\
+ $(foreach key,$($(1)_value),\
+ $(call ENV_GET,$(2),$(key))),\
+ $(if $(call _list?,$(1)),\
+ $(call _smap,EVAL,$(1),$(2)),\
+ $(if $(call _vector?,$(1)),\
+ $(call _smap_vec,EVAL,$(1),$(2)),\
+ $(if $(call _hash_map?,$(1)),\
+ $(foreach new_hmap,$(call __new_obj,hmap),\
+ $(foreach v,$(call __get_obj_values,$(1)),\
+ $(eval $(v:$(1)_%=$(new_hmap)_%) := $(call EVAL,$($(v)),$(2))))\
+ $(eval $(new_hmap)_size := $($(1)_size))\
+ $(new_hmap)),\
+ $(1))))))
+endef
+
+define EVAL_INVOKE
+$(if $(__ERROR),,\
+ $(and $(EVAL_DEBUG),$(info EVAL_INVOKE: $(call _pr_str,$(1))))
+ $(foreach a0,$(call _nth,$(1),0),\
+ $(if $(call _EQ,def!,$($(a0)_value)),\
+ $(foreach a1,$(call _nth,$(1),1),\
+ $(foreach a2,$(call _nth,$(1),2),\
+ $(foreach res,$(call EVAL,$(a2),$(2)),\
+ $(if $(call ENV_SET,$(2),$($(a1)_value),$(res)),$(res),)))),\
+ $(if $(call _EQ,let*,$($(a0)_value)),\
+ $(foreach a1,$(call _nth,$(1),1),\
+ $(foreach a2,$(call _nth,$(1),2),\
+ $(call EVAL,$(a2),$(call LET,$(a1),$(call ENV,$(2)))))),\
+ $(if $(call _EQ,do,$($(a0)_value)),\
+ $(call slast,$(call EVAL_AST,$(call srest,$(1)),$(2))),\
+ $(if $(call _EQ,if,$($(a0)_value)),\
+ $(foreach a1,$(call _nth,$(1),1),\
+ $(foreach a2,$(call _nth,$(1),2),\
+ $(foreach cond,$(call EVAL,$(a1),$(2)),\
+ $(if $(or $(call _EQ,$(__nil),$(cond)),$(call _EQ,$(__false),$(cond))),\
+ $(foreach a3,$(call _nth,$(1),3),$(call EVAL,$(a3),$(2))),\
+ $(call EVAL,$(a2),$(2)))))),\
+ $(if $(call _EQ,fn*,$($(a0)_value)),\
+ $(foreach a1,$(call _nth,$(1),1),\
+ $(foreach a2,$(call _nth,$(1),2),\
+ $(call function,$$(call EVAL,$(a2),$$(call ENV,$(2),$(a1),$$1))))),\
+ $(foreach el,$(call EVAL_AST,$(1),$(2)),\
+ $(and $(EVAL_DEBUG),$(info invoke: $(call _pr_str,$(el))))\
+ $(foreach f,$(call sfirst,$(el)),\
+ $(foreach args,$(call srest,$(el)),\
+ $(call apply,$(f),$(args))))))))))))
+endef
+
+define EVAL
+$(strip $(if $(__ERROR),,\
+ $(and $(EVAL_DEBUG),$(info EVAL: $(call _pr_str,$(1))))\
+ $(if $(call _list?,$(1)),\
+ $(word 1,$(strip $(call EVAL_INVOKE,$(1),$(2)) $(__nil))),\
+ $(call EVAL_AST,$(1),$(2)))))
+endef
+
+
+# PRINT:
+define PRINT
+$(if $(__ERROR),Error: $(call _pr_str,$(__ERROR),yes),$(if $(1),$(call _pr_str,$(1),yes)))$(if $(__ERROR),$(eval __ERROR :=),)
+endef
+
+# REPL:
+REPL_ENV := $(call ENV)
+REP = $(call PRINT,$(strip $(call EVAL,$(strip $(call READ,$(1))),$(REPL_ENV))))
+REPL = $(info $(call REP,$(call READLINE,"user> ")))$(if $(READLINE_EOF),,$(call REPL))
+
+# Setup the environment
+_fref = $(eval REPL_ENV := $(call ENV_SET,$(REPL_ENV),$(1),$(call function,$$(call $(2),$$1))))
+
+# Import types functions
+_import_types = $(if $(strip $(1)),$(call _fref,$(word 1,$(1)),$(word 2,$(1)))$(call _import_types,$(wordlist 3,$(words $(1)),$(1))),)
+$(call _import_types,$(types_ns))
+
+# Defined in terms of the language itself
+$(call do,$(call REP, (def! not (fn* (a) (if a false true))) ))
+
+# Call the read-eval-print loop
+$(if $(strip $(INTERACTIVE)),$(call REPL))
diff --git a/make/step6_file.mk b/make/step6_file.mk
new file mode 100644
index 0000000..da6de04
--- /dev/null
+++ b/make/step6_file.mk
@@ -0,0 +1,130 @@
+#
+# mal (Make Lisp)
+#
+_TOP_DIR := $(dir $(lastword $(MAKEFILE_LIST)))
+include $(_TOP_DIR)types.mk
+include $(_TOP_DIR)reader.mk
+
+SHELL := /bin/bash
+INTERACTIVE ?= yes
+EVAL_DEBUG ?=
+
+# READ: read and parse input
+define READ
+$(if $(READLINE_EOF)$(__ERROR),,$(call READ_STR,$(if $(1),$(1),$(call READLINE,"user> "))))
+endef
+
+# EVAL: evaluate the parameter
+define LET
+$(strip \
+ $(word 1,$(2) \
+ $(foreach var,$(call _nth,$(1),0),\
+ $(foreach val,$(call _nth,$(1),1),\
+ $(call ENV_SET,$(2),$($(var)_value),$(call EVAL,$(val),$(2)))\
+ $(foreach left,$(call srest,$(call srest,$(1))),
+ $(if $(call _EQ,0,$(call _count,$(left))),\
+ ,\
+ $(call LET,$(left),$(2))))))))
+endef
+
+define EVAL_AST
+$(strip \
+ $(and $(EVAL_DEBUG),$(info EVAL_AST: $(call _pr_str,$(1))))\
+ $(if $(call _symbol?,$(1)),\
+ $(foreach key,$($(1)_value),\
+ $(call ENV_GET,$(2),$(key))),\
+ $(if $(call _list?,$(1)),\
+ $(call _smap,EVAL,$(1),$(2)),\
+ $(if $(call _vector?,$(1)),\
+ $(call _smap_vec,EVAL,$(1),$(2)),\
+ $(if $(call _hash_map?,$(1)),\
+ $(foreach new_hmap,$(call __new_obj,hmap),\
+ $(foreach v,$(call __get_obj_values,$(1)),\
+ $(eval $(v:$(1)_%=$(new_hmap)_%) := $(call EVAL,$($(v)),$(2))))\
+ $(eval $(new_hmap)_size := $($(1)_size))\
+ $(new_hmap)),\
+ $(1))))))
+endef
+
+define EVAL_INVOKE
+$(if $(__ERROR),,\
+ $(and $(EVAL_DEBUG),$(info EVAL_INVOKE: $(call _pr_str,$(1))))
+ $(foreach a0,$(call _nth,$(1),0),\
+ $(if $(call _EQ,def!,$($(a0)_value)),\
+ $(foreach a1,$(call _nth,$(1),1),\
+ $(foreach a2,$(call _nth,$(1),2),\
+ $(foreach res,$(call EVAL,$(a2),$(2)),\
+ $(if $(call ENV_SET,$(2),$($(a1)_value),$(res)),$(res),)))),\
+ $(if $(call _EQ,let*,$($(a0)_value)),\
+ $(foreach a1,$(call _nth,$(1),1),\
+ $(foreach a2,$(call _nth,$(1),2),\
+ $(call EVAL,$(a2),$(call LET,$(a1),$(call ENV,$(2)))))),\
+ $(if $(call _EQ,do,$($(a0)_value)),\
+ $(call slast,$(call EVAL_AST,$(call srest,$(1)),$(2))),\
+ $(if $(call _EQ,if,$($(a0)_value)),\
+ $(foreach a1,$(call _nth,$(1),1),\
+ $(foreach a2,$(call _nth,$(1),2),\
+ $(foreach cond,$(call EVAL,$(a1),$(2)),\
+ $(if $(or $(call _EQ,$(__nil),$(cond)),$(call _EQ,$(__false),$(cond))),\
+ $(foreach a3,$(call _nth,$(1),3),$(call EVAL,$(a3),$(2))),\
+ $(call EVAL,$(a2),$(2)))))),\
+ $(if $(call _EQ,fn*,$($(a0)_value)),\
+ $(foreach a1,$(call _nth,$(1),1),\
+ $(foreach a2,$(call _nth,$(1),2),\
+ $(call function,$$(call EVAL,$(a2),$$(call ENV,$(2),$(a1),$$1))))),\
+ $(foreach el,$(call EVAL_AST,$(1),$(2)),\
+ $(and $(EVAL_DEBUG),$(info invoke: $(call _pr_str,$(el))))\
+ $(foreach f,$(call sfirst,$(el)),\
+ $(foreach args,$(call srest,$(el)),\
+ $(call apply,$(f),$(args))))))))))))
+endef
+
+define EVAL
+$(strip $(if $(__ERROR),,\
+ $(and $(EVAL_DEBUG),$(info EVAL: $(call _pr_str,$(1))))\
+ $(if $(call _list?,$(1)),\
+ $(word 1,$(strip $(call EVAL_INVOKE,$(1),$(2)) $(__nil))),\
+ $(call EVAL_AST,$(1),$(2)))))
+endef
+
+
+# PRINT:
+define PRINT
+$(if $(__ERROR),Error: $(call _pr_str,$(__ERROR),yes),$(if $(1),$(call _pr_str,$(1),yes)))$(if $(__ERROR),$(eval __ERROR :=),)
+endef
+
+# REPL:
+REPL_ENV := $(call ENV)
+REP = $(call PRINT,$(strip $(call EVAL,$(strip $(call READ,$(1))),$(REPL_ENV))))
+REPL = $(info $(call REP,$(call READLINE,"user> ")))$(if $(READLINE_EOF),,$(call REPL))
+
+# Setup the environment
+_ref = $(eval REPL_ENV := $(call ENV_SET,$(REPL_ENV),$(1),$(if $(2),$(2),$(1))))
+_fref = $(eval REPL_ENV := $(call ENV_SET,$(REPL_ENV),$(1),$(call function,$$(call $(2),$$1))))
+
+# Import types functions
+_import_types = $(if $(strip $(1)),$(call _fref,$(word 1,$(1)),$(word 2,$(1)))$(call _import_types,$(wordlist 3,$(words $(1)),$(1))),)
+$(call _import_types,$(types_ns))
+
+$(call _ref,read-string,$(call function,$$(call READ_STR,$$(1))))
+$(call _ref,eval,$(call function,$$(call EVAL,$$(1),$$(REPL_ENV))))
+
+_slurp = $(call string,$(call _read_file,$(1)))
+_slurp_do = $(call string,(do $(call _read_file,$(1))))
+$(call _ref,slurp,$(call function,$$(call _slurp,$$(call str_decode,$$($$(1)_value)))))
+$(call _ref,slurp-do,$(call function,$$(call _slurp_do,$$(call str_decode,$$($$(1)_value)))))
+
+# Defined in terms of the language itself
+$(call do,$(call REP, (def! not (fn* (a) (if a false true))) ))
+$(call do,$(call REP, (def! load-file (fn* (f) (eval (read-string (slurp-do f))))) ))
+
+# Load and eval any files specified on the command line
+$(if $(MAKECMDGOALS),\
+ $(foreach file,$(MAKECMDGOALS),$(call do,$(call REP, (load-file "$(file)") )))\
+ $(eval INTERACTIVE :=),)
+.PHONY: none $(MAKECMDGOALS)
+none $(MAKECMDGOALS):
+ @true
+
+# Call the read-eval-print loop
+$(if $(strip $(INTERACTIVE)),$(call REPL))
diff --git a/make/step7_quote.mk b/make/step7_quote.mk
new file mode 100644
index 0000000..a8695da
--- /dev/null
+++ b/make/step7_quote.mk
@@ -0,0 +1,147 @@
+#
+# mal (Make Lisp)
+#
+_TOP_DIR := $(dir $(lastword $(MAKEFILE_LIST)))
+include $(_TOP_DIR)types.mk
+include $(_TOP_DIR)reader.mk
+
+SHELL := /bin/bash
+INTERACTIVE ?= yes
+EVAL_DEBUG ?=
+
+# READ: read and parse input
+define READ
+$(if $(READLINE_EOF)$(__ERROR),,$(call READ_STR,$(if $(1),$(1),$(call READLINE,"user> "))))
+endef
+
+# EVAL: evaluate the parameter
+IS_PAIR = $(if $(call _EQ,list,$(call _obj_type,$(1))),$(if $(call _EQ,0,$(call _count,$(1))),,true),)
+
+define QUASIQUOTE
+$(strip \
+ $(if $(call _NOT,$(call IS_PAIR,$(1))),\
+ $(call list,$(call symbol,quote) $(1)),\
+ $(if $(call _EQ,unquote,$($(call _nth,$(1),0)_value)),\
+ $(call _nth,$(1),1),\
+ $(if $(and $(call IS_PAIR,$(call _nth,$(1),0)),$(call _EQ,splice-unquote,$($(call _nth,$(call _nth,$(1),0),0)_value))),\
+ $(call list,$(call symbol,concat) $(call _nth,$(call _nth,$(1),0),1) $(call QUASIQUOTE,$(call srest,$(1)))),\
+ $(call list,$(call symbol,cons) $(call QUASIQUOTE,$(call _nth,$(1),0)) $(call QUASIQUOTE,$(call srest,$(1))))))))
+endef
+
+define LET
+$(strip \
+ $(word 1,$(2) \
+ $(foreach var,$(call _nth,$(1),0),\
+ $(foreach val,$(call _nth,$(1),1),\
+ $(call ENV_SET,$(2),$($(var)_value),$(call EVAL,$(val),$(2)))\
+ $(foreach left,$(call srest,$(call srest,$(1))),
+ $(if $(call _EQ,0,$(call _count,$(left))),\
+ ,\
+ $(call LET,$(left),$(2))))))))
+endef
+
+define EVAL_AST
+$(strip \
+ $(and $(EVAL_DEBUG),$(info EVAL_AST: $(call _pr_str,$(1))))\
+ $(if $(call _symbol?,$(1)),\
+ $(foreach key,$($(1)_value),\
+ $(call ENV_GET,$(2),$(key))),\
+ $(if $(call _list?,$(1)),\
+ $(call _smap,EVAL,$(1),$(2)),\
+ $(if $(call _vector?,$(1)),\
+ $(call _smap_vec,EVAL,$(1),$(2)),\
+ $(if $(call _hash_map?,$(1)),\
+ $(foreach new_hmap,$(call __new_obj,hmap),\
+ $(foreach v,$(call __get_obj_values,$(1)),\
+ $(eval $(v:$(1)_%=$(new_hmap)_%) := $(call EVAL,$($(v)),$(2))))\
+ $(eval $(new_hmap)_size := $($(1)_size))\
+ $(new_hmap)),\
+ $(1))))))
+endef
+
+define EVAL_INVOKE
+$(if $(__ERROR),,\
+ $(and $(EVAL_DEBUG),$(info EVAL_INVOKE: $(call _pr_str,$(1))))
+ $(foreach a0,$(call _nth,$(1),0),\
+ $(if $(call _EQ,def!,$($(a0)_value)),\
+ $(foreach a1,$(call _nth,$(1),1),\
+ $(foreach a2,$(call _nth,$(1),2),\
+ $(foreach res,$(call EVAL,$(a2),$(2)),\
+ $(if $(call ENV_SET,$(2),$($(a1)_value),$(res)),$(res),)))),\
+ $(if $(call _EQ,let*,$($(a0)_value)),\
+ $(foreach a1,$(call _nth,$(1),1),\
+ $(foreach a2,$(call _nth,$(1),2),\
+ $(call EVAL,$(a2),$(call LET,$(a1),$(call ENV,$(2)))))),\
+ $(if $(call _EQ,quote,$($(a0)_value)),\
+ $(call _nth,$(1),1),\
+ $(if $(call _EQ,quasiquote,$($(a0)_value)),\
+ $(call EVAL,$(call QUASIQUOTE,$(call _nth,$(1),1)),$(2)),\
+ $(if $(call _EQ,do,$($(a0)_value)),\
+ $(call slast,$(call EVAL_AST,$(call srest,$(1)),$(2))),\
+ $(if $(call _EQ,if,$($(a0)_value)),\
+ $(foreach a1,$(call _nth,$(1),1),\
+ $(foreach a2,$(call _nth,$(1),2),\
+ $(foreach cond,$(call EVAL,$(a1),$(2)),\
+ $(if $(or $(call _EQ,$(__nil),$(cond)),$(call _EQ,$(__false),$(cond))),\
+ $(foreach a3,$(call _nth,$(1),3),$(call EVAL,$(a3),$(2))),\
+ $(call EVAL,$(a2),$(2)))))),\
+ $(if $(call _EQ,fn*,$($(a0)_value)),\
+ $(foreach a1,$(call _nth,$(1),1),\
+ $(foreach a2,$(call _nth,$(1),2),\
+ $(call function,$$(call EVAL,$(a2),$$(call ENV,$(2),$(a1),$$1))))),\
+ $(foreach el,$(call EVAL_AST,$(1),$(2)),\
+ $(and $(EVAL_DEBUG),$(info invoke: $(call _pr_str,$(el))))\
+ $(foreach f,$(call sfirst,$(el)),\
+ $(foreach args,$(call srest,$(el)),\
+ $(call apply,$(f),$(args))))))))))))))
+endef
+
+define EVAL
+$(strip $(if $(__ERROR),,\
+ $(and $(EVAL_DEBUG),$(info EVAL: $(call _pr_str,$(1))))\
+ $(if $(call _list?,$(1)),\
+ $(word 1,$(strip $(call EVAL_INVOKE,$(1),$(2)) $(__nil))),\
+ $(call EVAL_AST,$(1),$(2)))))
+endef
+
+
+# PRINT:
+define PRINT
+$(if $(__ERROR),Error: $(call _pr_str,$(__ERROR),yes),$(if $(1),$(call _pr_str,$(1),yes)))$(if $(__ERROR),$(eval __ERROR :=),)
+endef
+
+# REPL:
+REPL_ENV := $(call ENV)
+REP = $(call PRINT,$(strip $(call EVAL,$(strip $(call READ,$(1))),$(REPL_ENV))))
+REPL = $(info $(call REP,$(call READLINE,"user> ")))$(if $(READLINE_EOF),,$(call REPL))
+
+# Setup the environment
+_ref = $(eval REPL_ENV := $(call ENV_SET,$(REPL_ENV),$(1),$(if $(2),$(2),$(1))))
+_fref = $(eval REPL_ENV := $(call ENV_SET,$(REPL_ENV),$(1),$(call function,$$(call $(2),$$1))))
+
+# Import types functions
+_import_types = $(if $(strip $(1)),$(call _fref,$(word 1,$(1)),$(word 2,$(1)))$(call _import_types,$(wordlist 3,$(words $(1)),$(1))),)
+$(call _import_types,$(types_ns))
+
+$(call _ref,read-string,$(call function,$$(call READ_STR,$$(1))))
+$(call _ref,eval,$(call function,$$(call EVAL,$$(1),$$(REPL_ENV))))
+
+_slurp = $(call string,$(call _read_file,$(1)))
+_slurp_do = $(call string,(do $(call _read_file,$(1))))
+$(call _ref,slurp,$(call function,$$(call _slurp,$$(call str_decode,$$($$(1)_value)))))
+$(call _ref,slurp-do,$(call function,$$(call _slurp_do,$$(call str_decode,$$($$(1)_value)))))
+
+# Defined in terms of the language itself
+$(call do,$(call REP, (def! not (fn* (a) (if a false true))) ))
+$(call do,$(call REP, (def! load-file (fn* (f) (eval (read-string (slurp-do f))))) ))
+
+# Load and eval any files specified on the command line
+$(if $(MAKECMDGOALS),\
+ $(foreach file,$(MAKECMDGOALS),$(call do,$(call REP, (load-file "$(file)") )))\
+ $(eval INTERACTIVE :=),)
+.PHONY: none $(MAKECMDGOALS)
+none $(MAKECMDGOALS):
+ @true
+
+# Call the read-eval-print loop
+$(if $(strip $(INTERACTIVE)),$(call REPL))
diff --git a/make/step8_macros.mk b/make/step8_macros.mk
new file mode 100644
index 0000000..2b4e33b
--- /dev/null
+++ b/make/step8_macros.mk
@@ -0,0 +1,170 @@
+#
+# mal (Make Lisp)
+#
+_TOP_DIR := $(dir $(lastword $(MAKEFILE_LIST)))
+include $(_TOP_DIR)types.mk
+include $(_TOP_DIR)reader.mk
+
+SHELL := /bin/bash
+INTERACTIVE ?= yes
+EVAL_DEBUG ?=
+
+# READ: read and parse input
+define READ
+$(if $(READLINE_EOF)$(__ERROR),,$(call READ_STR,$(if $(1),$(1),$(call READLINE,"user> "))))
+endef
+
+# EVAL: evaluate the parameter
+IS_PAIR = $(if $(call _EQ,list,$(call _obj_type,$(1))),$(if $(call _EQ,0,$(call _count,$(1))),,true),)
+
+define QUASIQUOTE
+$(strip \
+ $(if $(call _NOT,$(call IS_PAIR,$(1))),\
+ $(call list,$(call symbol,quote) $(1)),\
+ $(if $(call _EQ,unquote,$($(call _nth,$(1),0)_value)),\
+ $(call _nth,$(1),1),\
+ $(if $(and $(call IS_PAIR,$(call _nth,$(1),0)),$(call _EQ,splice-unquote,$($(call _nth,$(call _nth,$(1),0),0)_value))),\
+ $(call list,$(call symbol,concat) $(call _nth,$(call _nth,$(1),0),1) $(call QUASIQUOTE,$(call srest,$(1)))),\
+ $(call list,$(call symbol,cons) $(call QUASIQUOTE,$(call _nth,$(1),0)) $(call QUASIQUOTE,$(call srest,$(1))))))))
+endef
+
+define IS_MACRO_CALL
+$(if $(call _list?,$(1)),$(call ENV_FIND,$(2),_macro_$($(call _nth,$(1),0)_value)),)
+endef
+
+define MACROEXPAND
+$(strip $(if $(__ERROR),,\
+ $(if $(call IS_MACRO_CALL,$(1),$(2)),\
+ $(foreach mac,$(call ENV_GET,$(2),$($(call _nth,$(1),0)_value)),\
+ $(call MACROEXPAND,$(call apply,$(mac),$(call srest,$(1))),$(2))),\
+ $(1))))
+endef
+
+define LET
+$(strip \
+ $(word 1,$(2) \
+ $(foreach var,$(call _nth,$(1),0),\
+ $(foreach val,$(call _nth,$(1),1),\
+ $(call ENV_SET,$(2),$($(var)_value),$(call EVAL,$(val),$(2)))\
+ $(foreach left,$(call srest,$(call srest,$(1))),
+ $(if $(call _EQ,0,$(call _count,$(left))),\
+ ,\
+ $(call LET,$(left),$(2))))))))
+endef
+
+define EVAL_AST
+$(strip \
+ $(and $(EVAL_DEBUG),$(info EVAL_AST: $(call _pr_str,$(1))))\
+ $(if $(call _symbol?,$(1)),\
+ $(foreach key,$($(1)_value),\
+ $(call ENV_GET,$(2),$(key))),\
+ $(if $(call _list?,$(1)),\
+ $(call _smap,EVAL,$(1),$(2)),\
+ $(if $(call _vector?,$(1)),\
+ $(call _smap_vec,EVAL,$(1),$(2)),\
+ $(if $(call _hash_map?,$(1)),\
+ $(foreach new_hmap,$(call __new_obj,hmap),\
+ $(foreach v,$(call __get_obj_values,$(1)),\
+ $(eval $(v:$(1)_%=$(new_hmap)_%) := $(call EVAL,$($(v)),$(2))))\
+ $(eval $(new_hmap)_size := $($(1)_size))\
+ $(new_hmap)),\
+ $(1))))))
+endef
+
+define EVAL_INVOKE
+$(if $(__ERROR),,\
+ $(and $(EVAL_DEBUG),$(info EVAL_INVOKE: $(call _pr_str,$(1))))
+ $(foreach a0,$(call _nth,$(1),0),\
+ $(if $(call _EQ,def!,$($(a0)_value)),\
+ $(foreach a1,$(call _nth,$(1),1),\
+ $(foreach a2,$(call _nth,$(1),2),\
+ $(foreach res,$(call EVAL,$(a2),$(2)),\
+ $(if $(call ENV_SET,$(2),$($(a1)_value),$(res)),$(res),)))),\
+ $(if $(call _EQ,let*,$($(a0)_value)),\
+ $(foreach a1,$(call _nth,$(1),1),\
+ $(foreach a2,$(call _nth,$(1),2),\
+ $(call EVAL,$(a2),$(call LET,$(a1),$(call ENV,$(2)))))),\
+ $(if $(call _EQ,quote,$($(a0)_value)),\
+ $(call _nth,$(1),1),\
+ $(if $(call _EQ,quasiquote,$($(a0)_value)),\
+ $(call EVAL,$(call QUASIQUOTE,$(call _nth,$(1),1)),$(2)),\
+ $(if $(call _EQ,defmacro!,$($(a0)_value)),\
+ $(foreach a1,$(call _nth,$(1),1),\
+ $(foreach a2,$(call _nth,$(1),2),\
+ $(foreach res,$(call EVAL,$(a2),$(2)),\
+ $(if $(call ENV_SET,$(2),_macro_$($(a1)_value),true),,)\
+ $(if $(call ENV_SET,$(2),$($(a1)_value),$(res)),$(res),)))),\
+ $(if $(call _EQ,macroexpand,$($(a0)_value)),\
+ $(call MACROEXPAND,$(call _nth,$(1),1),$(2)),\
+ $(if $(call _EQ,do,$($(a0)_value)),\
+ $(call slast,$(call EVAL_AST,$(call srest,$(1)),$(2))),\
+ $(if $(call _EQ,if,$($(a0)_value)),\
+ $(foreach a1,$(call _nth,$(1),1),\
+ $(foreach a2,$(call _nth,$(1),2),\
+ $(foreach cond,$(call EVAL,$(a1),$(2)),\
+ $(if $(or $(call _EQ,$(__nil),$(cond)),$(call _EQ,$(__false),$(cond))),\
+ $(foreach a3,$(call _nth,$(1),3),$(call EVAL,$(a3),$(2))),\
+ $(call EVAL,$(a2),$(2)))))),\
+ $(if $(call _EQ,fn*,$($(a0)_value)),\
+ $(foreach a1,$(call _nth,$(1),1),\
+ $(foreach a2,$(call _nth,$(1),2),\
+ $(call function,$$(call EVAL,$(a2),$$(call ENV,$(2),$(a1),$$1))))),\
+ $(foreach el,$(call EVAL_AST,$(1),$(2)),\
+ $(and $(EVAL_DEBUG),$(info invoke: $(call _pr_str,$(el))))\
+ $(foreach f,$(call sfirst,$(el)),\
+ $(foreach args,$(call srest,$(el)),\
+ $(call apply,$(f),$(args))))))))))))))))
+endef
+
+define EVAL
+$(strip $(if $(__ERROR),,\
+ $(and $(EVAL_DEBUG),$(info EVAL: $(call _pr_str,$(1))))\
+ $(if $(call _list?,$(1)),\
+ $(foreach ast,$(call MACROEXPAND,$(1),$(2)),
+ $(if $(call _list?,$(ast)),\
+ $(word 1,$(strip $(call EVAL_INVOKE,$(ast),$(2)) $(__nil))),\
+ $(ast))),\
+ $(call EVAL_AST,$(1),$(2)))))
+endef
+
+
+# PRINT:
+define PRINT
+$(if $(__ERROR),Error: $(call _pr_str,$(__ERROR),yes),$(if $(1),$(call _pr_str,$(1),yes)))$(if $(__ERROR),$(eval __ERROR :=),)
+endef
+
+# REPL:
+REPL_ENV := $(call ENV)
+REP = $(call PRINT,$(strip $(call EVAL,$(strip $(call READ,$(1))),$(REPL_ENV))))
+REPL = $(info $(call REP,$(call READLINE,"user> ")))$(if $(READLINE_EOF),,$(call REPL))
+
+# Setup the environment
+_ref = $(eval REPL_ENV := $(call ENV_SET,$(REPL_ENV),$(1),$(if $(2),$(2),$(1))))
+_fref = $(eval REPL_ENV := $(call ENV_SET,$(REPL_ENV),$(1),$(call function,$$(call $(2),$$1))))
+
+# Import types functions
+_import_types = $(if $(strip $(1)),$(call _fref,$(word 1,$(1)),$(word 2,$(1)))$(call _import_types,$(wordlist 3,$(words $(1)),$(1))),)
+$(call _import_types,$(types_ns))
+
+$(call _ref,read-string,$(call function,$$(call READ_STR,$$(1))))
+$(call _ref,eval,$(call function,$$(call EVAL,$$(1),$$(REPL_ENV))))
+
+_slurp = $(call string,$(call _read_file,$(1)))
+_slurp_do = $(call string,(do $(call _read_file,$(1))))
+$(call _ref,slurp,$(call function,$$(call _slurp,$$(call str_decode,$$($$(1)_value)))))
+$(call _ref,slurp-do,$(call function,$$(call _slurp_do,$$(call str_decode,$$($$(1)_value)))))
+
+# Defined in terms of the language itself
+$(call do,$(call REP, (def! not (fn* (a) (if a false true))) ))
+$(call do,$(call REP, (def! load-file (fn* (f) (eval (read-string (slurp-do f))))) ))
+
+# Load and eval any files specified on the command line
+$(if $(MAKECMDGOALS),\
+ $(foreach file,$(MAKECMDGOALS),$(call do,$(call REP, (load-file "$(file)") )))\
+ $(eval INTERACTIVE :=),)
+.PHONY: none $(MAKECMDGOALS)
+none $(MAKECMDGOALS):
+ @true
+
+# Call the read-eval-print loop
+$(if $(strip $(INTERACTIVE)),$(call REPL))
diff --git a/make/step9_interop.mk b/make/step9_interop.mk
new file mode 100644
index 0000000..a3d2b5e
--- /dev/null
+++ b/make/step9_interop.mk
@@ -0,0 +1,174 @@
+#
+# mal (Make Lisp)
+#
+_TOP_DIR := $(dir $(lastword $(MAKEFILE_LIST)))
+include $(_TOP_DIR)types.mk
+include $(_TOP_DIR)reader.mk
+
+SHELL := /bin/bash
+INTERACTIVE ?= yes
+EVAL_DEBUG ?=
+
+# READ: read and parse input
+define READ
+$(if $(READLINE_EOF)$(__ERROR),,$(call READ_STR,$(if $(1),$(1),$(call READLINE,"user> "))))
+endef
+
+# EVAL: evaluate the parameter
+IS_PAIR = $(if $(call _EQ,list,$(call _obj_type,$(1))),$(if $(call _EQ,0,$(call _count,$(1))),,true),)
+
+define QUASIQUOTE
+$(strip \
+ $(if $(call _NOT,$(call IS_PAIR,$(1))),\
+ $(call list,$(call symbol,quote) $(1)),\
+ $(if $(call _EQ,unquote,$($(call _nth,$(1),0)_value)),\
+ $(call _nth,$(1),1),\
+ $(if $(and $(call IS_PAIR,$(call _nth,$(1),0)),$(call _EQ,splice-unquote,$($(call _nth,$(call _nth,$(1),0),0)_value))),\
+ $(call list,$(call symbol,concat) $(call _nth,$(call _nth,$(1),0),1) $(call QUASIQUOTE,$(call srest,$(1)))),\
+ $(call list,$(call symbol,cons) $(call QUASIQUOTE,$(call _nth,$(1),0)) $(call QUASIQUOTE,$(call srest,$(1))))))))
+endef
+
+define IS_MACRO_CALL
+$(if $(call _list?,$(1)),$(call ENV_FIND,$(2),_macro_$($(call _nth,$(1),0)_value)),)
+endef
+
+define MACROEXPAND
+$(strip $(if $(__ERROR),,\
+ $(if $(call IS_MACRO_CALL,$(1),$(2)),\
+ $(foreach mac,$(call ENV_GET,$(2),$($(call _nth,$(1),0)_value)),\
+ $(call MACROEXPAND,$(call apply,$(mac),$(call srest,$(1))),$(2))),\
+ $(1))))
+endef
+
+define LET
+$(strip \
+ $(word 1,$(2) \
+ $(foreach var,$(call _nth,$(1),0),\
+ $(foreach val,$(call _nth,$(1),1),\
+ $(call ENV_SET,$(2),$($(var)_value),$(call EVAL,$(val),$(2)))\
+ $(foreach left,$(call srest,$(call srest,$(1))),
+ $(if $(call _EQ,0,$(call _count,$(left))),\
+ ,\
+ $(call LET,$(left),$(2))))))))
+endef
+
+define EVAL_AST
+$(strip \
+ $(and $(EVAL_DEBUG),$(info EVAL_AST: $(call _pr_str,$(1))))\
+ $(if $(call _symbol?,$(1)),\
+ $(foreach key,$($(1)_value),\
+ $(call ENV_GET,$(2),$(key))),\
+ $(if $(call _list?,$(1)),\
+ $(call _smap,EVAL,$(1),$(2)),\
+ $(if $(call _vector?,$(1)),\
+ $(call _smap_vec,EVAL,$(1),$(2)),\
+ $(if $(call _hash_map?,$(1)),\
+ $(foreach new_hmap,$(call __new_obj,hmap),\
+ $(foreach v,$(call __get_obj_values,$(1)),\
+ $(eval $(v:$(1)_%=$(new_hmap)_%) := $(call EVAL,$($(v)),$(2))))\
+ $(eval $(new_hmap)_size := $($(1)_size))\
+ $(new_hmap)),\
+ $(1))))))
+endef
+
+define EVAL_INVOKE
+$(if $(__ERROR),,\
+ $(and $(EVAL_DEBUG),$(info EVAL_INVOKE: $(call _pr_str,$(1))))
+ $(foreach a0,$(call _nth,$(1),0),\
+ $(if $(call _EQ,def!,$($(a0)_value)),\
+ $(foreach a1,$(call _nth,$(1),1),\
+ $(foreach a2,$(call _nth,$(1),2),\
+ $(foreach res,$(call EVAL,$(a2),$(2)),\
+ $(if $(call ENV_SET,$(2),$($(a1)_value),$(res)),$(res),)))),\
+ $(if $(call _EQ,let*,$($(a0)_value)),\
+ $(foreach a1,$(call _nth,$(1),1),\
+ $(foreach a2,$(call _nth,$(1),2),\
+ $(call EVAL,$(a2),$(call LET,$(a1),$(call ENV,$(2)))))),\
+ $(if $(call _EQ,quote,$($(a0)_value)),\
+ $(call _nth,$(1),1),\
+ $(if $(call _EQ,quasiquote,$($(a0)_value)),\
+ $(call EVAL,$(call QUASIQUOTE,$(call _nth,$(1),1)),$(2)),\
+ $(if $(call _EQ,defmacro!,$($(a0)_value)),\
+ $(foreach a1,$(call _nth,$(1),1),\
+ $(foreach a2,$(call _nth,$(1),2),\
+ $(foreach res,$(call EVAL,$(a2),$(2)),\
+ $(if $(call ENV_SET,$(2),_macro_$($(a1)_value),true),,)\
+ $(if $(call ENV_SET,$(2),$($(a1)_value),$(res)),$(res),)))),\
+ $(if $(call _EQ,macroexpand,$($(a0)_value)),\
+ $(call MACROEXPAND,$(call _nth,$(1),1),$(2)),\
+ $(if $(call _EQ,make*,$($(a0)_value)),\
+ $(foreach a1,$(call _nth,$(1),1),\
+ $(and $(EVAL_DEBUG),$(info make*: $$(eval __result := $(call str_decode,$(value $(a1)_value)))))\
+ $(eval __result := $(call str_decode,$(value $(a1)_value)))$(call READ_STR,$(__result))),\
+ $(if $(call _EQ,do,$($(a0)_value)),\
+ $(call slast,$(call EVAL_AST,$(call srest,$(1)),$(2))),\
+ $(if $(call _EQ,if,$($(a0)_value)),\
+ $(foreach a1,$(call _nth,$(1),1),\
+ $(foreach a2,$(call _nth,$(1),2),\
+ $(foreach cond,$(call EVAL,$(a1),$(2)),\
+ $(if $(or $(call _EQ,$(__nil),$(cond)),$(call _EQ,$(__false),$(cond))),\
+ $(foreach a3,$(call _nth,$(1),3),$(call EVAL,$(a3),$(2))),\
+ $(call EVAL,$(a2),$(2)))))),\
+ $(if $(call _EQ,fn*,$($(a0)_value)),\
+ $(foreach a1,$(call _nth,$(1),1),\
+ $(foreach a2,$(call _nth,$(1),2),\
+ $(call function,$$(call EVAL,$(a2),$$(call ENV,$(2),$(a1),$$1))))),\
+ $(foreach el,$(call EVAL_AST,$(1),$(2)),\
+ $(and $(EVAL_DEBUG),$(info invoke: $(call _pr_str,$(el))))\
+ $(foreach f,$(call sfirst,$(el)),\
+ $(foreach args,$(call srest,$(el)),\
+ $(call apply,$(f),$(args)))))))))))))))))
+endef
+
+define EVAL
+$(strip $(if $(__ERROR),,\
+ $(and $(EVAL_DEBUG),$(info EVAL: $(call _pr_str,$(1))))\
+ $(if $(call _list?,$(1)),\
+ $(foreach ast,$(call MACROEXPAND,$(1),$(2)),
+ $(if $(call _list?,$(ast)),\
+ $(word 1,$(strip $(call EVAL_INVOKE,$(ast),$(2)) $(__nil))),\
+ $(ast))),\
+ $(call EVAL_AST,$(1),$(2)))))
+endef
+
+
+# PRINT:
+define PRINT
+$(if $(__ERROR),Error: $(call _pr_str,$(__ERROR),yes),$(if $(1),$(call _pr_str,$(1),yes)))$(if $(__ERROR),$(eval __ERROR :=),)
+endef
+
+# REPL:
+REPL_ENV := $(call ENV)
+REP = $(call PRINT,$(strip $(call EVAL,$(strip $(call READ,$(1))),$(REPL_ENV))))
+REPL = $(info $(call REP,$(call READLINE,"user> ")))$(if $(READLINE_EOF),,$(call REPL))
+
+# Setup the environment
+_ref = $(eval REPL_ENV := $(call ENV_SET,$(REPL_ENV),$(1),$(if $(2),$(2),$(1))))
+_fref = $(eval REPL_ENV := $(call ENV_SET,$(REPL_ENV),$(1),$(call function,$$(call $(2),$$1))))
+
+# Import types functions
+_import_types = $(if $(strip $(1)),$(call _fref,$(word 1,$(1)),$(word 2,$(1)))$(call _import_types,$(wordlist 3,$(words $(1)),$(1))),)
+$(call _import_types,$(types_ns))
+
+$(call _ref,read-string,$(call function,$$(call READ_STR,$$(1))))
+$(call _ref,eval,$(call function,$$(call EVAL,$$(1),$$(REPL_ENV))))
+
+_slurp = $(call string,$(call _read_file,$(1)))
+_slurp_do = $(call string,(do $(call _read_file,$(1))))
+$(call _ref,slurp,$(call function,$$(call _slurp,$$(call str_decode,$$($$(1)_value)))))
+$(call _ref,slurp-do,$(call function,$$(call _slurp_do,$$(call str_decode,$$($$(1)_value)))))
+
+# Defined in terms of the language itself
+$(call do,$(call REP, (def! not (fn* (a) (if a false true))) ))
+$(call do,$(call REP, (def! load-file (fn* (f) (eval (read-string (slurp-do f))))) ))
+
+# Load and eval any files specified on the command line
+$(if $(MAKECMDGOALS),\
+ $(foreach file,$(MAKECMDGOALS),$(call do,$(call REP, (load-file "$(file)") )))\
+ $(eval INTERACTIVE :=),)
+.PHONY: none $(MAKECMDGOALS)
+none $(MAKECMDGOALS):
+ @true
+
+# Call the read-eval-print loop
+$(if $(strip $(INTERACTIVE)),$(call REPL))
diff --git a/make/stepA_more.mk b/make/stepA_more.mk
new file mode 100644
index 0000000..ec32d85
--- /dev/null
+++ b/make/stepA_more.mk
@@ -0,0 +1,192 @@
+#
+# mal (Make Lisp)
+#
+_TOP_DIR := $(dir $(lastword $(MAKEFILE_LIST)))
+include $(_TOP_DIR)types.mk
+include $(_TOP_DIR)reader.mk
+
+SHELL := /bin/bash
+INTERACTIVE ?= yes
+EVAL_DEBUG ?=
+
+# READ: read and parse input
+define READ
+$(if $(READLINE_EOF)$(__ERROR),,$(call READ_STR,$(if $(1),$(1),$(call READLINE,"user> "))))
+endef
+
+# EVAL: evaluate the parameter
+IS_PAIR = $(if $(call _EQ,list,$(call _obj_type,$(1))),$(if $(call _EQ,0,$(call _count,$(1))),,true),)
+
+define QUASIQUOTE
+$(strip \
+ $(if $(call _NOT,$(call IS_PAIR,$(1))),\
+ $(call list,$(call symbol,quote) $(1)),\
+ $(if $(call _EQ,unquote,$($(call _nth,$(1),0)_value)),\
+ $(call _nth,$(1),1),\
+ $(if $(and $(call IS_PAIR,$(call _nth,$(1),0)),$(call _EQ,splice-unquote,$($(call _nth,$(call _nth,$(1),0),0)_value))),\
+ $(call list,$(call symbol,concat) $(call _nth,$(call _nth,$(1),0),1) $(call QUASIQUOTE,$(call srest,$(1)))),\
+ $(call list,$(call symbol,cons) $(call QUASIQUOTE,$(call _nth,$(1),0)) $(call QUASIQUOTE,$(call srest,$(1))))))))
+endef
+
+define IS_MACRO_CALL
+$(if $(call _list?,$(1)),$(call ENV_FIND,$(2),_macro_$($(call _nth,$(1),0)_value)),)
+endef
+
+define MACROEXPAND
+$(strip $(if $(__ERROR),,\
+ $(if $(call IS_MACRO_CALL,$(1),$(2)),\
+ $(foreach mac,$(call ENV_GET,$(2),$($(call _nth,$(1),0)_value)),\
+ $(call MACROEXPAND,$(call apply,$(mac),$(call srest,$(1))),$(2))),\
+ $(1))))
+endef
+
+define LET
+$(strip \
+ $(word 1,$(2) \
+ $(foreach var,$(call _nth,$(1),0),\
+ $(foreach val,$(call _nth,$(1),1),\
+ $(call ENV_SET,$(2),$($(var)_value),$(call EVAL,$(val),$(2)))\
+ $(foreach left,$(call srest,$(call srest,$(1))),
+ $(if $(call _EQ,0,$(call _count,$(left))),\
+ ,\
+ $(call LET,$(left),$(2))))))))
+endef
+
+define EVAL_AST
+$(strip \
+ $(and $(EVAL_DEBUG),$(info EVAL_AST: $(call _pr_str,$(1))))\
+ $(if $(call _symbol?,$(1)),\
+ $(foreach key,$($(1)_value),\
+ $(call ENV_GET,$(2),$(key))),\
+ $(if $(call _list?,$(1)),\
+ $(call _smap,EVAL,$(1),$(2)),\
+ $(if $(call _vector?,$(1)),\
+ $(call _smap_vec,EVAL,$(1),$(2)),\
+ $(if $(call _hash_map?,$(1)),\
+ $(foreach new_hmap,$(call __new_obj,hmap),\
+ $(foreach v,$(call __get_obj_values,$(1)),\
+ $(eval $(v:$(1)_%=$(new_hmap)_%) := $(call EVAL,$($(v)),$(2))))\
+ $(eval $(new_hmap)_size := $($(1)_size))\
+ $(new_hmap)),\
+ $(1))))))
+endef
+
+define EVAL_INVOKE
+$(if $(__ERROR),,\
+ $(and $(EVAL_DEBUG),$(info EVAL_INVOKE: $(call _pr_str,$(1))))
+ $(foreach a0,$(call _nth,$(1),0),\
+ $(if $(call _EQ,def!,$($(a0)_value)),\
+ $(foreach a1,$(call _nth,$(1),1),\
+ $(foreach a2,$(call _nth,$(1),2),\
+ $(foreach res,$(call EVAL,$(a2),$(2)),\
+ $(if $(call ENV_SET,$(2),$($(a1)_value),$(res)),$(res),)))),\
+ $(if $(call _EQ,let*,$($(a0)_value)),\
+ $(foreach a1,$(call _nth,$(1),1),\
+ $(foreach a2,$(call _nth,$(1),2),\
+ $(call EVAL,$(a2),$(call LET,$(a1),$(call ENV,$(2)))))),\
+ $(if $(call _EQ,quote,$($(a0)_value)),\
+ $(call _nth,$(1),1),\
+ $(if $(call _EQ,quasiquote,$($(a0)_value)),\
+ $(call EVAL,$(call QUASIQUOTE,$(call _nth,$(1),1)),$(2)),\
+ $(if $(call _EQ,defmacro!,$($(a0)_value)),\
+ $(foreach a1,$(call _nth,$(1),1),\
+ $(foreach a2,$(call _nth,$(1),2),\
+ $(foreach res,$(call EVAL,$(a2),$(2)),\
+ $(if $(call ENV_SET,$(2),_macro_$($(a1)_value),true),,)\
+ $(if $(call ENV_SET,$(2),$($(a1)_value),$(res)),$(res),)))),\
+ $(if $(call _EQ,macroexpand,$($(a0)_value)),\
+ $(call MACROEXPAND,$(call _nth,$(1),1),$(2)),\
+ $(if $(call _EQ,make*,$($(a0)_value)),\
+ $(foreach a1,$(call _nth,$(1),1),\
+ $(and $(EVAL_DEBUG),$(info make*: $$(eval __result := $(call str_decode,$(value $(a1)_value)))))\
+ $(eval __result := $(call str_decode,$(value $(a1)_value)))$(call READ_STR,$(__result))),\
+ $(if $(call _EQ,try*,$($(a0)_value)),\
+ $(foreach a1,$(call _nth,$(1),1),\
+ $(foreach res,$(call EVAL,$(a1),$(2)),\
+ $(if $(__ERROR),\
+ $(foreach a2,$(call _nth,$(1),2),\
+ $(foreach a20,$(call _nth,$(a2),0),\
+ $(if $(call _EQ,catch*,$($(a20)_value)),\
+ $(foreach a21,$(call _nth,$(a2),1),\
+ $(foreach a22,$(call _nth,$(a2),2),\
+ $(foreach binds,$(call list,$(a21)),\
+ $(foreach catch_env,$(call ENV,$(2),$(binds),$(__ERROR)),\
+ $(eval __ERROR :=)\
+ $(call EVAL,$(a22),$(catch_env)))))),\
+ $(res)))),\
+ $(res)))),\
+ $(if $(call _EQ,do,$($(a0)_value)),\
+ $(call slast,$(call EVAL_AST,$(call srest,$(1)),$(2))),\
+ $(if $(call _EQ,if,$($(a0)_value)),\
+ $(foreach a1,$(call _nth,$(1),1),\
+ $(foreach a2,$(call _nth,$(1),2),\
+ $(foreach cond,$(call EVAL,$(a1),$(2)),\
+ $(if $(or $(call _EQ,$(__nil),$(cond)),$(call _EQ,$(__false),$(cond))),\
+ $(foreach a3,$(call _nth,$(1),3),$(call EVAL,$(a3),$(2))),\
+ $(call EVAL,$(a2),$(2)))))),\
+ $(if $(call _EQ,fn*,$($(a0)_value)),\
+ $(foreach a1,$(call _nth,$(1),1),\
+ $(foreach a2,$(call _nth,$(1),2),\
+ $(call function,$$(call EVAL,$(a2),$$(call ENV,$(2),$(a1),$$1))))),\
+ $(foreach el,$(call EVAL_AST,$(1),$(2)),\
+ $(and $(EVAL_DEBUG),$(info invoke: $(call _pr_str,$(el))))\
+ $(foreach f,$(call sfirst,$(el)),\
+ $(foreach args,$(call srest,$(el)),\
+ $(call apply,$(f),$(args))))))))))))))))))
+endef
+
+define EVAL
+$(strip $(if $(__ERROR),,\
+ $(and $(EVAL_DEBUG),$(info EVAL: $(call _pr_str,$(1))))\
+ $(if $(call _list?,$(1)),\
+ $(foreach ast,$(call MACROEXPAND,$(1),$(2)),
+ $(if $(call _list?,$(ast)),\
+ $(word 1,$(strip $(call EVAL_INVOKE,$(ast),$(2)) $(__nil))),\
+ $(ast))),\
+ $(call EVAL_AST,$(1),$(2)))))
+endef
+
+
+# PRINT:
+define PRINT
+$(if $(__ERROR),Error: $(call _pr_str,$(__ERROR),yes),$(if $(1),$(call _pr_str,$(1),yes)))$(if $(__ERROR),$(eval __ERROR :=),)
+endef
+
+# REPL:
+REPL_ENV := $(call ENV)
+REP = $(call PRINT,$(strip $(call EVAL,$(strip $(call READ,$(1))),$(REPL_ENV))))
+REPL = $(info $(call REP,$(call READLINE,"user> ")))$(if $(READLINE_EOF),,$(call REPL))
+
+# Setup the environment
+_ref = $(eval REPL_ENV := $(call ENV_SET,$(REPL_ENV),$(1),$(if $(2),$(2),$(1))))
+_fref = $(eval REPL_ENV := $(call ENV_SET,$(REPL_ENV),$(1),$(call function,$$(call $(2),$$1))))
+
+# Import types functions
+_import_types = $(if $(strip $(1)),$(call _fref,$(word 1,$(1)),$(word 2,$(1)))$(call _import_types,$(wordlist 3,$(words $(1)),$(1))),)
+$(call _import_types,$(types_ns))
+
+$(call _ref,readline,$(call function,$$(foreach res,$$(call string,$$(call READLINE,"$$(call str_decode,$$($$(1)_value))")),$$(if $$(READLINE_EOF),$$(__nil),$$(res)))))
+$(call _ref,read-string,$(call function,$$(call READ_STR,$$(1))))
+$(call _ref,eval,$(call function,$$(call EVAL,$$(1),$$(REPL_ENV))))
+
+_slurp = $(call string,$(call _read_file,$(1)))
+_slurp_do = $(call string,(do $(call _read_file,$(1))))
+$(call _ref,slurp,$(call function,$$(call _slurp,$$(call str_decode,$$($$(1)_value)))))
+$(call _ref,slurp-do,$(call function,$$(call _slurp_do,$$(call str_decode,$$($$(1)_value)))))
+
+# Defined in terms of the language itself
+$(call do,$(call REP, (def! not (fn* (a) (if a false true))) ))
+$(call do,$(call 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))))))) ))
+$(call do,$(call 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)))))))) ))
+$(call do,$(call REP, (def! load-file (fn* (f) (eval (read-string (slurp-do f))))) ))
+
+# Load and eval any files specified on the command line
+$(if $(MAKECMDGOALS),\
+ $(foreach file,$(MAKECMDGOALS),$(call do,$(call REP, (load-file "$(file)") )))\
+ $(eval INTERACTIVE :=),)
+.PHONY: none $(MAKECMDGOALS)
+none $(MAKECMDGOALS):
+ @true
+
+# Call the read-eval-print loop
+$(if $(strip $(INTERACTIVE)),$(call REPL))
diff --git a/make/tests/common.mk b/make/tests/common.mk
new file mode 100644
index 0000000..55b931d
--- /dev/null
+++ b/make/tests/common.mk
@@ -0,0 +1,18 @@
+
+# assert macros
+assert = $(if $1,,$(error assert failure: $2))
+assert_not = $(if $1,$(error assert_not: $2),)
+assert_eq = $(if $(call _EQ,$(1),$(2)),,$(error assert_eq failure: $(1) != $(2): $(3)))
+# With debug:
+#assert_eq = $(info 1: $(1))$(info 2: $(2))$(info 3: $(3))$(if $(call _EQ,$(1),$(2)),,$(error assert_eq failure: $(3)))
+
+
+# REPL related wrappers
+test_read = $(call READ_STR,$(1))
+ifndef MACROEXPAND
+define MACROEXPAND
+$(1)
+endef
+endif
+test_re = $(strip $(call EVAL,$(call MACROEXPAND,$(strip $(call test_read,$(1))),$(REPL_ENV)),$(REPL_ENV)))
+test_rep = $(call PRINT,$(strip $(call EVAL,$(call MACROEXPAND,$(strip $(call test_read,$(1))),$(REPL_ENV)),$(REPL_ENV))))
diff --git a/make/tests/reader.mk b/make/tests/reader.mk
new file mode 100644
index 0000000..672d27b
--- /dev/null
+++ b/make/tests/reader.mk
@@ -0,0 +1,76 @@
+INTERACTIVE = no
+
+include tests/common.mk
+include reader.mk
+
+_tonum = $(call int_decode,$($(1)_value))
+
+$(info Testing READ_STR of numbers)
+$(call assert_eq,2,$(call _tonum,$(call READ_STR,2)))
+$(call assert_eq,12345,$(call _tonum,$(call READ_STR,12345)))
+$(call assert_eq,12345,$(call _tonum,$(call READ_STR,12345 "abc")))
+$(call assert_eq,12345,$(call _tonum,$(call READ_STR,12345"abc")))
+$(call assert_eq,12345,$(call _tonum,$(call READ_STR,12345(1))))
+
+$(info Testing READ_STR of symbols)
+$(call assert_eq,abc,$($(call READ_STR,abc)_value))
+$(call assert_eq,abc,$($(call READ_STR,abc )_value))
+$(call assert_eq,abc,$($(call READ_STR,abc"a str")_value))
+$(call assert_eq,abc,$($(call READ_STR,abc(2 3))_value))
+
+$(info Testing READ_STR of strings)
+$(call assert_eq,a string,$(call str_decode,$($(call READ_STR,"a string")_value)))
+$(call assert_eq,a string (with parens),$(call str_decode,$($(call READ_STR,"a string (with parens)")_value)))
+$(call assert_eq,a string,$(call str_decode,$($(call READ_STR,"a string"())_value)))
+$(call assert_eq,a string,$(call str_decode,$($(call READ_STR,"a string"123)_value)))
+$(call assert_eq,a string,$(call str_decode,$($(call READ_STR,"a string"abc)_value)))
+$(call assert_eq,,$(call str_decode,$($(call READ_STR,"")_value)))
+$(call assert_eq,abc ,$(call str_decode,$($(call READ_STR,"abc ")_value)))
+$(call assert_eq, abc,$(call str_decode,$($(call READ_STR," abc")_value)))
+$(call assert_eq,$$abc,$(call str_decode,$($(call READ_STR,"$$abc")_value)))
+$(call assert_eq,abc$$(),$(call str_decode,$($(call READ_STR,"abc$$()")_value)))
+$(call assert_eq,"xyz",$(call str_decode,$($(call READ_STR,"\"xyz\"")_value)))
+
+$(info Testing READ_STR of lists)
+$(call assert_eq,2,$(call _count,$(call READ_STR,(2 3))))
+$(call assert_eq,2,$(call _tonum,$(call sfirst,$(call READ_STR,(2 3)))))
+$(call assert_eq,3,$(call _tonum,$(call sfirst,$(call srest,$(call READ_STR,(2 3))))))
+L := $(strip $(call READ_STR,(+ 1 2 "str1" "string (with parens) and 'single quotes'")))
+$(call assert_eq,5,$(call _count,$(L)))
+$(call assert_eq,str1,$(call str_decode,$($(call _nth,$(L),3)_value)))
+$(call assert_eq,string (with parens) and 'single quotes',$(call str_decode,$($(call _nth,$(L),4)_value)))
+
+$(info Testing READ_STR of vectors)
+$(call assert_eq,2,$(call _count,$(call READ_STR,[2 3])))
+$(call assert_eq,2,$(call _tonum,$(call sfirst,$(call READ_STR,[2 3]))))
+$(call assert_eq,3,$(call _tonum,$(call sfirst,$(call srest,$(call READ_STR,[2 3])))))
+L := $(strip $(call READ_STR,[+ 1 2 "str1" "string (with parens) and 'single quotes'"]))
+$(call assert_eq,5,$(call _count,$(L)))
+$(call assert_eq,str1,$(call str_decode,$($(call _nth,$(L),3)_value)))
+$(call assert_eq,string (with parens) and 'single quotes',$(call str_decode,$($(call _nth,$(L),4)_value)))
+
+$(info Testing READ_STR of quote/quasiquote)
+$(call assert_eq,quote,$($(call _nth,$(call READ_STR,'1),0)_value)) #'
+$(call assert_eq,1,$(call _tonum,$(call _nth,$(call READ_STR,'1),1))) #'
+$(call assert_eq,quote,$($(call _nth,$(call READ_STR,'(1 2 3)),0)_value)) #'
+$(call assert_eq,3,$(call _tonum,$(call _nth,$(call _nth,$(call READ_STR,'(1 2 3)),1),2))) #'
+
+$(call assert_eq,quasiquote,$($(call _nth,$(call READ_STR,`1),0)_value))
+$(call assert_eq,1,$(call _tonum,$(call _nth,$(call READ_STR,`1),1)))
+$(call assert_eq,quasiquote,$($(call _nth,$(call READ_STR,`(1 2 3)),0)_value))
+$(call assert_eq,3,$(call _tonum,$(call _nth,$(call _nth,$(call READ_STR,`(1 2 3)),1),2)))
+
+$(call assert_eq,unquote,$($(call _nth,$(call READ_STR,~1),0)_value))
+$(call assert_eq,1,$(call _tonum,$(call _nth,$(call READ_STR,~1),1)))
+$(call assert_eq,unquote,$($(call _nth,$(call READ_STR,~(1 2 3)),0)_value))
+$(call assert_eq,3,$(call _tonum,$(call _nth,$(call _nth,$(call READ_STR,~(1 2 3)),1),2)))
+
+$(call assert_eq,splice-unquote,$($(call _nth,$(call READ_STR,~@1),0)_value))
+$(call assert_eq,1,$(call _tonum,$(call _nth,$(call READ_STR,~@1),1)))
+$(call assert_eq,splice-unquote,$($(call _nth,$(call READ_STR,~@(1 2 3)),0)_value))
+$(call assert_eq,3,$(call _tonum,$(call _nth,$(call _nth,$(call READ_STR,~@(1 2 3)),1),2)))
+
+
+.PHONY: all
+all:
+ @echo "All tests completed"
diff --git a/make/tests/step9_interop.mk b/make/tests/step9_interop.mk
new file mode 100644
index 0000000..0e44a88
--- /dev/null
+++ b/make/tests/step9_interop.mk
@@ -0,0 +1,14 @@
+INTERACTIVE =
+
+include tests/common.mk
+include step9_interop.mk
+
+$(info Testing trivial macros)
+$(call assert_eq,7,$(call test_rep, (make* "7") ))
+$(call assert_eq,"XaY XbY XcY",$(call test_rep, (make* "\"$(foreach v,a b c,X$(v)Y)\"") ))
+$(call assert_eq,(2 3 4),$(call test_rep, (make* "($(foreach v,1 2 3,$(call gmsl_plus,1,$(v))))") ))
+
+
+.PHONY: all
+all:
+ @echo "All tests completed"
diff --git a/make/tests/types.mk b/make/tests/types.mk
new file mode 100644
index 0000000..c1c1849
--- /dev/null
+++ b/make/tests/types.mk
@@ -0,0 +1,304 @@
+include tests/common.mk
+include types.mk
+
+# treat an expression as a statement
+do = $(eval __tmp := $(1))
+
+
+$(info Testing foreach as a let form)
+
+$(call assert_eq,XbX,$(foreach local_var,b,X$(local_var)X),\
+ Using foreach as 'let' failed)
+
+
+$(info Testing type function)
+$(call assert_eq,make,$(call _obj_type,xyz),\
+ (type xyz) is not 'make')
+$(call assert_eq,nil,$(call _obj_type,$(__nil)),\
+ (type $$(__nil)) is not 'nil')
+$(call assert_eq,true,$(call _obj_type,$(__true)),\
+ (type $$(__true)) is not 'true')
+$(call assert_eq,false,$(call _obj_type,$(__false)),\
+ (type $$(__false)) is not 'false')
+
+
+$(info Testing number? function)
+
+$(call assert_eq,number,$(call _obj_type,$(call number,1)))
+$(call assert_eq,number,$(call _obj_type,$(call number,10)))
+$(call assert_eq,number,$(call _obj_type,$(call number,12345)))
+
+
+$(info Testing symbols)
+
+$(call assert_eq,symbol,$(call _obj_type,$(call symbol,abc)),\
+ (type (symbol abc)) is not 'symbol')
+SYM1 := $(call symbol,a sym value)
+$(call assert_eq,a sym value,$($(SYM1)_value))
+$(call assert_eq,$(__true),$(call symbol?,$(SYM1)))
+
+
+$(info Testing strings)
+
+$(call assert_eq,string,$(call _obj_type,$(call string,abc)),\
+ (type (string abc)) is not string)
+
+STR1 := $(call string,a string value)
+$(call assert_eq,a string value,$(call str_decode,$($(STR1)_value)))
+$(call assert_eq,$(__true),$(call string?,$(STR1)))
+$(call assert_eq,14,$(call _count,$(STR1)))
+
+STR2 := $(call string,a string (with parens))
+$(call assert_eq,a string (with parens),$(call str_decode,$($(STR2)_value)))
+$(call assert_eq,$(__true),$(call string?,$(STR2)))
+$(call assert_eq,22,$(call _count,$(STR2)))
+
+$(info Testing strings (subs))
+$(call assert_eq,a string (with parens),$(call str_decode,$($(call subs,$(STR2),$(call number,2))_value)))
+$(call assert_eq,a string,$(call str_decode,$($(call subs,$(STR2),$(call number,0),$(call number,8))_value)))
+
+$(info Testing strings (str))
+$(call assert_eq,a string value - a string (with parens),$(call str_decode,$($(call str,$(STR1) $(call string, - ) $(STR2))_value)))
+
+
+$(info Testing function objects)
+
+$(call assert_eq,function,$(call _obj_type,$(call function,abc)),\
+ (type (function abc)) is not 'function')
+FN1 := $(call function,arg1:'$$(word 1,$$(1))' arg2:'$$(word 2,$$(1))')
+$(call assert_eq,$(__true),$(call function?,$(FN1)))
+$(call assert_eq,arg1:'A' arg2:'B',$(call apply,$(FN1),$(call list,A B)))
+
+
+$(info Testing lists)
+
+$(call assert_eq,list,$(call _obj_type,$(call list)),\
+ (type (list)) is not 'list')
+
+$(info Testing lists (cons))
+L1 := $(call cons,P $(call list))
+L2 := $(call cons,Q $(L1))
+$(call assert_eq,$(__true),$(call list?,$(L1)))
+$(call assert_eq,$(__true),$(call list?,$(L2)))
+$(call assert_eq,P,$(call sfirst,$(L1)))
+$(call assert_eq,2,$(call _count,$(L2)))
+$(call assert_eq,Q,$(call sfirst,$(L2)))
+$(call assert_eq,P,$(call _nth,$(L2),1))
+$(call assert_eq,$(__true),$(call equal?,$(L1) $(call srest,$(L2))))
+
+$(info Testing lists (concat))
+L1_2 := $(call concat,$(L1) $(L2))
+$(call assert_eq,3,$(call _count,$(L1_2)))
+$(call assert_eq,P,$(call sfirst,$(L1_2)))
+$(call assert_eq,Q,$(call _nth,$(L1_2),1))
+$(call assert_eq,P,$(call _nth,$(L1_2),2))
+$(call assert_eq,$(__true),$(call equal?,$(L2) $(call srest,$(L1_2))))
+
+$(info Testing lists (conj))
+L3 := $(call _conj!,$(call list),A B)
+L4 := $(call _conj!,$(call list),X $(L3))
+$(call assert_eq,$(__true),$(call list?,$(L3)),\
+ (list? $$(L3)))
+$(call assert_eq,$(__true),$(call list?,$(L4)),\
+ (list? $$(L3)))
+$(call assert_eq,A,$(call sfirst,$(L3)),\
+ (sfirst $$(L3)) is not 'A')
+$(call assert_eq,X,$(call sfirst,$(L4)),\
+ (sfirst $$(L4)) is not 'X')
+$(call assert_eq,$(__true),$(call list?,$(call _nth,$(L4),1)),\
+ (_nth $$(L4),1) is not a list)
+$(call assert_eq,A,$(call sfirst,$(call _nth,$(L4),1)),\
+ (first (_nth $$(L4),1)) is not 'A')
+
+
+$(info Testing hash_maps)
+
+X := $(call hash_map)
+$(call assert_eq,$(__true),$(call hash_map?,$(X)),\
+ (hash_map? $$(X)))
+$(call assert_eq,$(__false),$(call vector?,$(X)),\
+ (vector? $$(X)))
+
+mykey := $(call _string,a)
+$(call assert_not,$(call _get,$(X),a),\
+ (get $$(X),a))
+$(call assert_eq,$(__false),$(call contains?,$(X),$(mykey)),\
+ (contains? $$(X),a))
+$(call do,$(call _assoc!,$(X),a,value of X a))
+$(call assert_eq,value of X a,$(call _get,$(X),a),\
+ (get $$(X),a) is not 'value of Xa')
+$(call assert_eq,$(__true),$(call contains?,$(X) $(mykey)),\
+ (contains? $$(X),a))
+
+Y := $(call hash_map)
+$(call assert_eq,0,$(call _count,$(Y)),\
+ (_count $$(Y)))
+$(call do,$(call _assoc!,$(Y),a,value of Y a))
+$(call assert_eq,1,$(call _count,$(Y)),\
+ (_count $$(Y)))
+$(call do,$(call _assoc!,$(Y),b,value of Y b))
+$(call assert_eq,2,$(call _count,$(Y)),\
+ (_count $$(Y)))
+$(call assert_eq,value of Y a,$(call _get,$(Y),a),\
+ (get $$(Y),a) is not 'value of Y a')
+$(call assert_eq,value of Y b,$(call _get,$(Y),b),\
+ (get $$(Y),b) is not 'value of Y b')
+$(call assert_eq,value of Y a value of Y b,$(call raw_flat,$(Y),b),\
+ (raw_flat $(Y)) is not 'value of Y a value of Y b')
+
+$(call do,$(call _assoc!,$(X),b,$(Y)))
+$(call assert_eq,2,$(call _count,$(Y),a),\
+ (_count $$(Y)) should still be 2)
+
+$(call assert_eq,$(__true),$(call hash_map?,$(call _get,$(X),b)),\
+ (hash_map? (get $$(X),b)))
+
+$(call assert_eq,$(call _get,$(call _get,$(X),b),a),value of Y a,\
+ (get (get $(X),b),a) is not 'value of Y a')
+$(call assert_eq,$(call _get,$(call _get,$(X),b),b),value of Y b,\
+ (get (get $(X),b),b) is not 'value of Y b')
+
+$(call do,$(call _dissoc!,$(Y),a))
+$(call assert_eq,1,$(call _count,$(Y)),\
+ (_count $$(Y)) should now be 1)
+$(call assert_not,$(call _get,$(Y),a),\
+ (get $$(Y),a))
+$(call do,$(call _dissoc!,$(Y),b))
+$(call assert_eq,0,$(call _count,$(Y)),\
+ (_count $$(Y)) should now be 0)
+$(call assert_not,$(call _get,$(Y),b),\
+ (get $$(Y),b))
+
+
+$(info Testing vectors)
+
+V1 := $(call _conj!,$(call vector),first.vector.value second.vector.value third.vector.value)
+$(call assert_eq,$(__true),$(call vector?,$(V1)),\
+ (vector? $$(V1)))
+$(call assert_eq,first.vector.value,$(call _nth,$(V1),0))
+$(call assert_eq,second.vector.value,$(call _nth,$(V1),1))
+$(call assert_eq,third.vector.value,$(call _nth,$(V1),2))
+$(call assert_eq,third.vector.value,$(call slast,$(V1)))
+$(call assert_eq,3,$(call _count,$(V1)))
+
+V2 := $(call _conj!,$(call vector),A B C)
+$(call assert_eq,3,$(call _count,$(V2)),\
+ (_count $$(V2)) is not 3)
+$(call assert_eq,A B C,$($(V2)_value))
+$(call assert_eq,A,$(call sfirst,$(V2)),\
+ (first $$(V2)) is not 'A')
+$(call assert_eq,$(__true),$(call list?,$(call srest,$(V2))),\
+ (rest $$(V2)) is not a vector)
+$(call assert_eq,B C,$($(call srest,$(V2))_value))
+$(call assert_eq,B,$(call sfirst,$(call srest,$(V2))),\
+ (first (rest $$(V2))) is not 'B')
+$(call assert_eq,C,$(call sfirst,$(call srest,$(call srest,$(V2)))),\
+ (first (rest (rest $$(V2)))) is not 'C')
+$(call assert_eq,C,$(call _nth,$(V2),2),\
+ (_nth $$(V2),2) is not 'C')
+
+V2_1 := $(call _conj!,$(V2),$(V1))
+$(call assert_eq,4,$(call _count,$(V2_1)),\
+ (_count $$(V2_1)) is not 4)
+$(call assert_eq,C,$(call _nth,$(V2_1),2),\
+ (_nth $$(V2_1),2) is no longer 'C')
+$(call assert_eq,$(__true),$(call vector?,$(call _nth,$(V2_1),3)),\
+ (_nth $$(V2_1),3) is not a vector)
+$(call assert_eq,second.vector.value,$(call _nth,$(call _nth,$(V2_1),3),1),\
+ (_nth (_nth $$(V2_1),3),1) is not 'second.vector.value')
+
+$(info Testing vectors (rest))
+
+V3 := $(call srest,$(V2_1))
+$(call assert_eq,3,$(call _count,$(V3)),\
+ (_count $$(V3)) is not 3)
+$(call assert_eq,B,$(call sfirst,$(V3)),\
+ (first $$(V3)) is not 'B')
+$(call assert_eq,$(__true),$(call vector?,$(call _nth,$(V3),2)),\
+ (_nth $$(V3),2) is not a vector)
+$(call assert_eq,second.vector.value,$(call _nth,$(call _nth,$(V3),2),1),\
+ (_nth (_nth $$(V3),2),1) is not 'second.vector.value')
+
+$(info Testing vectors (contains?))
+
+$(call assert_eq,$(__true),$(call _contains?,$(V2_1),0),\
+ (contains? $$(V2_1),0))
+$(call assert_eq,,$(call _contains?,$(V2_1),7),\
+ (contains? $$(V2_1),7))
+
+
+$(info Testing _apply function)
+
+label_args = $(word 1,$(1))$(word 2,$(1))$(word 3,$(1))$(word 4,$(1))
+$(call assert_eq,,$(call _apply,label_args,$(call list)))
+$(call assert_eq,A,$(call _apply,label_args,$(call list,A)))
+$(call assert_eq,AB,$(call _apply,label_args,$(call list,A B)))
+$(call assert_eq,ABCD,$(call _apply,label_args,$(call list,A B C D)))
+
+
+$(info Testing smap function)
+
+L5 := $(call _conj!,$(call list),$(call number,1) $(call number,2) $(call number,3))
+inc = $(call number_plus,$(call number,1) $(1))
+$(call assert_eq,(2 3 4),$(call _pr_str,$(call _smap,inc,$(L5))))
+inc_func := $(call function,$$(call number_plus,$$(call number,1) $$(1)))
+$(call assert_eq,(2 3 4),$(call _pr_str,$(call smap,$(inc_func) $(L5))))
+
+
+$(info Testing equal? function)
+$(call assert_eq,$(__true),$(call equal?,2 2))
+$(call assert_eq,$(__false),$(call equal?,2 3))
+$(call assert_eq,$(__false),$(call equal?,2 3))
+$(call assert_eq,$(__true),$(call equal?,abc abc))
+$(call assert_eq,$(__false),$(call equal?,abc abz))
+$(call assert_eq,$(__false),$(call equal?,zbc abc))
+$(call assert_eq,$(__true),$(call equal?,$(call string,abc) $(call string,abc)))
+$(call assert_eq,$(__false),$(call equal?,$(call string,abc) $(call string,abz)))
+$(call assert_eq,$(__false),$(call equal?,$(call string,zbc) $(call string,abc)))
+$(call assert_eq,$(__true),$(call equal?,$(call symbol,abc) $(call symbol,abc)))
+$(call assert_eq,$(__false),$(call equal?,$(call symbol,abc) $(call symbol,abz)))
+$(call assert_eq,$(__false),$(call equal?,$(call symbol,zbc) $(call symbol,abc)))
+L6 := $(call _conj!,$(call list),1 2 3)
+L7 := $(call _conj!,$(call list),1 2 3)
+L8 := $(call _conj!,$(call list),1 2 Z)
+L9 := $(call _conj!,$(call list),Z 2 3)
+L10 := $(call _conj!,$(call list),1 2)
+$(call assert_eq,$(__true),$(call equal?,$(L6) $(L7)))
+$(call assert_eq,$(__false),$(call equal?,$(L6) $(L8)))
+$(call assert_eq,$(__false),$(call equal?,$(L6) $(L9)))
+$(call assert_eq,$(__false),$(call equal?,$(L6) $(L10)))
+$(call assert_eq,$(__false),$(call equal?,$(L10) $(L6)))
+
+
+$(info Testing empty? function)
+$(call assert_eq,$(__true),$(call empty?,$(call list)))
+$(call assert_eq,$(__false),$(call empty?,$(call list,1)))
+
+
+$(info Testing ENV (1 level))
+env1 := $(call ENV)
+$(call assert_eq,,$(call ENV_GET,$(env1),a))
+$(call assert_eq,$(env1),$(call ENV_SET,$(env1),a,val_a))
+$(call assert_eq,$(env1),$(call ENV_SET,$(env1),b,val_b))
+$(call assert_eq,$(env1),$(call ENV_SET,$(env1),=,val_eq))
+$(call assert_eq,val_a,$(call ENV_GET,$(env1),a))
+$(call assert_eq,val_b,$(call ENV_GET,$(env1),b))
+$(call assert_eq,val_eq,$(call ENV_GET,$(env1),=))
+$(call assert_eq,hash_map,$(call _obj_type,$(call ENV_FIND,$(env1),a)))
+$(call assert_eq,val_a,$(call _get,$(call ENV_FIND,$(env1),a),a))
+
+$(info Testing ENV (2 levels))
+env2 := $(call ENV,$(env1))
+$(call assert_eq,$(env2),$(call ENV_SET,$(env2),b,val_b2))
+$(call assert_eq,$(env2),$(call ENV_SET,$(env2),c,val_c))
+$(call assert_eq,$(env1),$(call ENV_FIND,$(env2),a))
+$(call assert_eq,$(env2),$(call ENV_FIND,$(env2),b))
+$(call assert_eq,$(env2),$(call ENV_FIND,$(env2),c))
+$(call assert_eq,val_a,$(call ENV_GET,$(env2),a))
+$(call assert_eq,val_b2,$(call ENV_GET,$(env2),b))
+$(call assert_eq,val_c,$(call ENV_GET,$(env2),c))
+
+
+.PHONY: all
+all:
+ @echo "All tests completed"
diff --git a/make/types.mk b/make/types.mk
new file mode 100644
index 0000000..234ca51
--- /dev/null
+++ b/make/types.mk
@@ -0,0 +1,484 @@
+#
+# mal (Make Lisp) Object Types and Functions
+#
+
+ifndef __mal_types_included
+__mal_types_included := true
+
+_TOP_DIR := $(dir $(lastword $(MAKEFILE_LIST)))
+include $(_TOP_DIR)util.mk
+
+# magic is \u2344 \u204a
+__obj_magic = ⍄⁊
+# \u2256
+__equal = ≛
+__obj_hash_code = 0
+
+__new_obj_hash_code = $(eval __obj_hash_code := $(call gmsl_plus,1,$(__obj_hash_code)))$(__obj_hash_code)
+
+__new_obj = $(__obj_magic)_$(1)_$(call __new_obj_hash_code)
+
+__new_obj_like = $(foreach type,$(word 2,$(subst _, ,$(1))),$(__obj_magic)_$(type)_$(__new_obj_hash_code))
+
+__get_obj_values = $(strip \
+ $(if $(filter $(__obj_magic)_hmap_%,$(1)),\
+ $(sort $(foreach v,$(filter %_value,$(filter $(1)_%,$(.VARIABLES))),$(if $(call _undefined?,$(v)),,$(v)))),\
+ $($(1)_value)))
+
+__ERROR :=
+
+#
+# General functions
+#
+
+# Return the type of the object (or "make" if it's not a object
+_obj_type = $(strip \
+ $(if $(filter $(__obj_magic)_symb_%,$(1)),symbol,\
+ $(if $(filter $(__obj_magic)_list_%,$(1)),list,\
+ $(if $(filter $(__obj_magic)_numb_%,$(1)),number,\
+ $(if $(filter $(__obj_magic)_func_%,$(1)),function,\
+ $(if $(filter $(__obj_magic)_strn_%,$(1)),string,\
+ $(if $(filter $(__obj_magic)__nil_%,$(1)),nil,\
+ $(if $(filter $(__obj_magic)_true_%,$(1)),true,\
+ $(if $(filter $(__obj_magic)_fals_%,$(1)),false,\
+ $(if $(filter $(__obj_magic)_vect_%,$(1)),vector,\
+ $(if $(filter $(__obj_magic)_atom_%,$(1)),atom,\
+ $(if $(filter $(__obj_magic)_hmap_%,$(1)),hash_map,\
+ $(if $(filter $(__obj_magic)_undf_%,$(1)),undefined,\
+ make)))))))))))))
+obj_type = $(call string,$(call _obj_type,$(1)))
+
+# return a printable form of the argument, the second parameter is
+# 'print_readably' which backslashes quotes in string values
+_pr_str = $(if $(1),$(foreach ot,$(call _obj_type,$(1)),$(if $(call _EQ,make,$(ot)),$(call _error,_pr_str failed on $(1)),$(call $(ot)_pr_str,$(1),$(2)))),)
+
+# Like _pr_str but takes multiple values in first argument, the second
+# parameter is 'print_readably' which backslashes quotes in string
+# values, the third parameter is the delimeter to use between each
+# _pr_str'd value
+_pr_str_mult = $(call _pr_str,$(word 1,$(1)),$(2))$(if $(word 2,$(1)),$(3)$(call _pr_str_mult,$(wordlist 2,$(words $(1)),$(1)),$(2),$(3)),)
+
+pr_str = $(call string,$(call _pr_str_mult,$(1),yes, ))
+str = $(call string,$(call _pr_str_mult,$(1),,))
+prn = $(info $(call _pr_str_mult,$(1),yes, ))
+println = $(info $(subst \n,$(NEWLINE),$(call _pr_str_mult,$(1),, )))
+
+_clone_obj = $(strip \
+ $(foreach new_hcode,$(call __new_obj_hash_code),\
+ $(foreach new_obj,$(foreach type,$(word 2,$(subst _, ,$(1))),$(__obj_magic)_$(type)_$(new_hcode)),\
+ $(if $(filter $(__obj_magic)_hmap_%,$(1)),\
+ $(foreach v,$(call __get_obj_values,$(1)),\
+ $(eval $(v:$(1)_%=$(new_obj)_%) := $($(v))))\
+ $(eval $(new_obj)_size := $($(1)_size)),\
+ $(if $(filter $(__obj_magic)_func_%,$(1)),\
+ $(eval $(new_obj)_value = $(value $(1)_value)),\
+ $(eval $(new_obj)_value := $(strip $($(1)_value)))))\
+ $(new_obj))))
+
+with_meta = $(strip \
+ $(foreach new_obj,$(call _clone_obj,$(word 1,$(1))),\
+ $(eval $(new_obj)_meta := $(strip $(word 2,$(1))))\
+ $(new_obj)))
+
+meta = $(strip $($(1)_meta))
+
+
+#
+# Special atomic values
+#
+__undefined = $(__obj_magic)_undf_0
+__nil = $(__obj_magic)__nil_0
+__true = $(__obj_magic)_true_0
+__false = $(__obj_magic)_fals_0
+
+_undefined? = $(or $(call _EQ,undefined,$(origin $(1))),$(filter $(__undefined),$($(1))))
+undefined? = $(if $(call _undefined?,$(1)),$(__true),$(__false))
+
+_nil? = $(if $(filter $(__obj_magic)__nil_%,$(1)),$(__true),)
+nil? = $(if $(call _nil?,$(1)),$(__true),$(__false))
+nil_pr_str = nil
+
+_true? = $(if $(filter $(__obj_magic)_true_%,$(1)),$(__true),)
+true? = $(if $(call _true?,$(1)),$(__true),$(__false))
+true_pr_str = true
+
+_false? = $(if $(filter $(__obj_magic)_fals_%,$(1)),$(__true),)
+false? = $(if $(call _false?,$(1)),$(__true),$(__false))
+false_pr_str = false
+
+
+#
+# Numbers
+#
+
+_pnumber = $(foreach hcode,$(call __new_obj_hash_code),$(__obj_magic)_numb_$(hcode)$(eval $(__obj_magic)_numb_$(hcode)_value := $(1)))
+number = $(call _pnumber,$(call int_encode,$(1)))
+
+_number? = $(if $(filter $(__obj_magic)_numb_%,$(1)),$(__true),)
+number? = $(if $(call _number?,$(1)),$(__true),$(__false))
+
+number_pr_str = $(call int_decode,$($(1)_value))
+
+number_plus = $(call _pnumber,$(call int_plus,$($(word 1,$(1))_value),$($(word 2,$(1))_value)))
+number_subtract = $(call _pnumber,$(call int_subtract,$($(word 1,$(1))_value),$($(word 2,$(1))_value)))
+number_multiply = $(call _pnumber,$(call int_multiply,$($(word 1,$(1))_value),$($(word 2,$(1))_value)))
+number_divide = $(call _pnumber,$(call int_divide,$($(word 1,$(1))_value),$($(word 2,$(1))_value)))
+
+number_gt = $(if $(call int_gt,$($(word 1,$(1))_value),$($(word 2,$(1))_value)),$(__true),$(__false))
+number_gte = $(if $(call int_gte,$($(word 1,$(1))_value),$($(word 2,$(1))_value)),$(__true),$(__false))
+number_lt = $(if $(call int_lt,$($(word 1,$(1))_value),$($(word 2,$(1))_value)),$(__true),$(__false))
+number_lte = $(if $(call int_lte,$($(word 1,$(1))_value),$($(word 2,$(1))_value)),$(__true),$(__false))
+
+
+#
+# Symbols
+#
+symbol = $(foreach hcode,$(call __new_obj_hash_code),$(__obj_magic)_symb_$(hcode)$(eval $(__obj_magic)_symb_$(hcode)_value := $(1)))
+
+_symbol? = $(if $(filter $(__obj_magic)_symb_%,$(1)),$(__true),)
+symbol? = $(if $(call _symbol?,$(1)),$(__true),$(__false))
+
+symbol_pr_str = $($(1)_value)
+
+#
+# Strings
+#
+_string = $(foreach hcode,$(call __new_obj_hash_code),$(__obj_magic)_strn_$(hcode)$(eval $(__obj_magic)_strn_$(hcode)_value := $(1)))
+string = $(foreach hcode,$(call __new_obj_hash_code),$(__obj_magic)_strn_$(hcode)$(eval $(__obj_magic)_strn_$(hcode)_value := $(call str_encode,$(1))))
+
+_string? = $(if $(filter $(__obj_magic)_strn_%,$(1)),$(__true),)
+string? = $(if $(call _string?,$(1)),$(__true),$(__false))
+
+string_pr_str = $(if $(2),"$(subst $(DQUOTE),$(ESC_DQUOTE),$(subst $(SLASH),$(SLASH)$(SLASH),$(call str_decode,$($(1)_value))))",$(call str_decode,$($(1)_value)))
+
+subs = $(strip \
+ $(foreach start,$(call gmsl_plus,1,$(call int_decode,$($(word 2,$(1))_value))),\
+ $(foreach end,$(if $(3),$(call int_decode,$($(3)_value)),$(words $($(word 1,$(1))_value))),\
+ $(call _string,$(wordlist $(start),$(end),$($(word 1,$(1))_value))))))
+
+#
+# Function objects
+#
+
+# Return a function object. The first parameter is the
+# function/macro 'source'. Note that any $ must be escaped as $$ to be
+# preserved and become positional arguments for when the
+# function/macro is later invoked.
+function = $(foreach hcode,$(call __new_obj_hash_code),$(__obj_magic)_func_$(hcode)$(eval $(__obj_magic)_func_$(hcode)_value = $(1)))
+
+_function? = $(if $(filter $(__obj_magic)_func_%,$(1)),$(__true),)
+function? = $(if $(call _function?,$(1)),$(__true),$(__false))
+
+function_pr_str = <$(if $(word 6,$(value $(1)_value)),$(wordlist 1,5,$(value $(1)_value))...,$(value $(1)_value))>
+
+# Takes a function name and a list object of arguments and invokes
+# the function with space separated arguments
+_apply = $(call $(1),$($(2)_value))
+
+# Takes a function object and a list object of arguments and invokes
+# the function with space separated arguments
+apply = $(call $(1)_value,$($(2)_value))
+
+# Takes a space separated arguments and invokes the first argument
+# (function object) using the remaining arguments.
+sapply = $(call $(word 1,$(1))_value,$($(word 2,$(1))_value))
+
+#
+# hash maps (associative arrays)
+#
+
+# create a new anonymous empty hash map
+_hash_map = $(foreach hcode,$(call __new_obj_hash_code),$(__obj_magic)_hmap_$(hcode)$(eval $(__obj_magic)_hmap_$(hcode)_size := 0))
+hash_map = $(word 1,$(foreach new_hmap,$(call _hash_map),$(new_hmap) $(if $(1),$(call _assoc_seq!,$(new_hmap),$(1)))))
+
+_hash_map? = $(if $(filter $(__obj_magic)_hmap_%,$(1)),$(__true),)
+hash_map? = $(if $(call _hash_map?,$(1)),$(__true),$(__false))
+
+hash_map_pr_str = {$(foreach v,$(call __get_obj_values,$(1)),"$(foreach hcode,$(word 3,$(subst _, ,$(1))),$(patsubst $(1)_%,%,$(v:%_value=%)))" $(call _pr_str,$($(v)),$(2)))}
+
+# Set multiple key/values in a map
+_assoc_seq! = $(call _assoc!,$(1),$(call str_decode,$($(word 1,$(2))_value)),$(word 2,$(2)))$(if $(word 3,$(2)),$(call _assoc_seq!,$(1),$(wordlist 3,$(words $(2)),$(2))),)
+
+# set a key/value in the hash map
+_assoc! = $(foreach k,$(subst =,$(__equal),$(2)),$(if $(call _undefined?,$(1)_$(k)_value),$(eval $(1)_size := $(call gmsl_plus,$($(1)_size),1)),)$(eval $(1)_$(k)_value := $(3))$(1))
+
+# set a key/value in a copy of the hash map
+# TODO: multiple arguments
+assoc = $(foreach hm,$(call _clone_obj,$(word 1,$(1))),$(call _assoc!,$(hm),$(call str_decode,$($(word 2,$(1))_value)),$(word 3,$(1))))
+
+# unset a key in the hash map
+_dissoc! = $(foreach k,$(subst =,$(__equal),$(2)),$(if $(call _undefined?,$(1)_$(k)_value),,$(eval $(1)_$(k)_value := $(__undefined))$(eval $(1)_size := $(call gmsl_subtract,$($(1)_size),1))))$(1)
+
+# unset a key in a copy of the hash map
+# TODO: this could be made more efficient by not copying the key in
+# the first place
+# TODO: multiple arguments
+dissoc = $(foreach hm,$(call _clone_obj,$(word 1,$(1))),$(call _dissoc!,$(hm),$(call str_decode,$($(word 2,$(1))_value))))
+
+keys = $(foreach new_list,$(call _list),$(new_list)$(eval $(new_list)_value := $(foreach v,$(call __get_obj_values,$(1)),$(call string,$(word 4,$(subst _, ,$(v)))))))
+
+vals = $(foreach new_list,$(call _list),$(new_list)$(eval $(new_list)_value := $(foreach v,$(call __get_obj_values,$(1)),$($(v)))))
+
+
+
+# Hash map and vector functions
+
+# retrieve the value of a plain string key from the hash map, or
+# retrive a vector by plain index
+_get = $(strip \
+ $(if $(call _hash_map?,$(1)),\
+ $(foreach k,$(subst =,$(__equal),$(2)),$(if $(call _undefined?,$(1)_$(k)_value),,$($(1)_$(k)_value))),\
+ $(if $(call _vector?,$(1)),\
+ $(word $(call gmsl_plus,1,$(2)),$($(1)_value)),\
+ ,)))
+
+# retrieve the value of a string key object from the hash map, or
+# retrive a vector by number object index
+get = $(strip \
+ $(if $(call _hash_map?,$(word 1,$(1))),\
+ $(call _get,$(word 1,$(1)),$(call str_decode,$($(word 2,$(1))_value))),\
+ $(call _get,$(word 1,$(1)),$(call number_pr_str,$(word 2,$(1))))))
+
+_contains? = $(strip \
+ $(if $(call _hash_map?,$(1)),\
+ $(foreach k,$(subst =,$(__equal),$(2)),$(if $(call _undefined?,$(1)_$(k)_value),,$(__true))),\
+ $(if $(call _vector?,$(1)),\
+ $(if $(word $(call gmsl_plus,1,$(2)),$($(1)_value)),$(__true),),\
+ ,)))
+contains? = $(if $(call _contains?,$(word 1,$(1)),$(call str_decode,$($(word 2,$(1))_value))),$(__true),$(__false))
+
+
+#
+# Errors/Exceptions
+#
+_error = $(eval __ERROR := $(call string,$(1)))
+throw = $(eval __ERROR := $(1))
+
+
+#
+# vectors
+#
+
+_vector = $(foreach hcode,$(call __new_obj_hash_code),$(__obj_magic)_vect_$(hcode))
+vector = $(word 1,$(foreach new_vect,$(call _vector),$(new_vect) $(eval $(new_vect)_value := $1)))
+
+_vector? = $(if $(filter $(__obj_magic)_vect_%,$(1)),$(__true),)
+vector? = $(if $(call _vector?,$(1)),$(__true),$(__false))
+
+vector_pr_str = [$(foreach v,$(call __get_obj_values,$(1)),$(call _pr_str,$(v),$(2)))]
+
+#
+# list (same as vectors for now)
+#
+
+_list = $(foreach hcode,$(call __new_obj_hash_code),$(__obj_magic)_list_$(hcode))
+list = $(word 1,$(foreach new_list,$(call _list),$(new_list) $(eval $(new_list)_value := $1)))
+
+_list? = $(if $(filter $(__obj_magic)_list_%,$(1)),$(__true),)
+list? = $(if $(call _list?,$(1)),$(__true),$(__false))
+
+list_pr_str = ($(foreach v,$(call __get_obj_values,$(1)),$(call _pr_str,$(v),$(2))))
+
+cons = $(word 1,$(foreach new_list,$(call _list),$(new_list) $(eval $(new_list)_value := $(strip $(word 1,$(1)) $(call __get_obj_values,$(word 2,$(1)))))))
+
+
+#
+# atoms
+#
+atom = $(strip \
+ $(foreach hcode,$(call __new_obj_hash_code),\
+ $(foreach new_atom,$(__obj_magic)_atom_$(hcode),\
+ $(new_atom)\
+ $(eval $(new_atom)_value := $(1)))))
+
+_atom? = $(if $(filter $(__obj_magic)_atom_%,$(1)),$(__true),)
+atom? = $(if $(call _atom?,$(1)),$(__true),$(__false))
+
+atom_pr_str = (atom $(call _pr_str,$($(1)_value),$(2)))
+
+deref = $($(1)_value)
+
+reset! = $(eval $(word 1,$(1))_value := $(word 2,$(1)))$(word 2,$(1))
+
+swap! = $(foreach resp,$(call $(word 2,$(1))_value,$($(word 1,$(1))_value) $(wordlist 3,$(words $(1)),$(1))),\
+ $(eval $(word 1,$(1))_value := $(resp))\
+ $(resp))
+
+
+#
+# sequence operations
+#
+
+_sequential? = $(if $(filter $(__obj_magic)_list_% $(__obj_magic)_vect_%,$(1)),$(__true),)
+sequential? = $(if $(call _sequential?,$(1)),$(__true),$(__false))
+
+raw_flat = $(foreach val,$(call __get_obj_values,$(1)),$($(val)))
+
+_nth = $(word $(call gmsl_plus,1,$(2)),$($(1)_value))
+
+nth = $(word $(call gmsl_plus,1,$(call int_decode,$($(word 2,$(1))_value))),$($(word 1,$(1))_value))
+
+empty? = $(if $(call _EQ,0,$(if $(call _hash_map?,$(1)),$($(1)_size),$(words $($(1)_value)))),$(__true),$(__false))
+
+concat = $(word 1,$(foreach new_list,$(call _list),$(new_list) $(eval $(new_list)_value := $(strip $(foreach lst,$1,$(call __get_obj_values,$(lst)))))))
+
+conj = $(word 1,$(foreach new_list,$(call __new_obj_like,$(word 1,$(1))),$(new_list) $(eval $(new_list)_value := $(strip $($(word 1,$(1))_value) $(wordlist 2,$(words $(1)),$(1))))))
+
+# conj that mutates a sequence in-place to append the call arguments
+_conj! = $(eval $(1)_value := $(strip $($(1)_value) $2 $3 $4 $5 $6 $7 $8 $9 $(10) $(11) $(12) $(13) $(14) $(15) $(16) $(17) $(18) $(19) $(20)))$(1)
+
+_count = $(strip \
+ $(if $(call _hash_map?,$(1)),\
+ $($(1)_size),\
+ $(words $($(1)_value))))
+count = $(call number,$(call _count,$(1)))
+
+sfirst = $(word 1,$($(1)_value))
+
+slast = $(word $(words $($(1)_value)),$($(1)_value))
+
+# Creates a new vector/list of the everything after but the first
+# element
+srest = $(word 1,$(foreach new_list,$(call _list),$(new_list) $(eval $(new_list)_value := $(wordlist 2,$(words $($(1)_value)),$($(1)_value)))))
+
+# maps a make function over a list object, using mutating _conj!
+_smap = $(word 1,\
+ $(foreach new_list,$(call _list),\
+ $(new_list)\
+ $(foreach v,$(call __get_obj_values,$(2)),\
+ $(call _conj!,$(new_list),$(call $(1),$(v),$(3),$(4))))))
+
+# Same as _smap but returns a vector
+_smap_vec = $(word 1,\
+ $(foreach new_vector,$(call vector),\
+ $(new_vector)\
+ $(foreach v,$(call __get_obj_values,$(2)),\
+ $(call _conj!,$(new_vector),$(call $(1),$(v),$(3),$(4))))))
+
+# Map a function object over a list object
+smap = $(strip\
+ $(foreach func,$(word 1,$(1)),\
+ $(foreach lst,$(word 2,$(1)),\
+ $(foreach type,$(word 2,$(subst _, ,$(lst))),\
+ $(foreach new_hcode,$(call __new_obj_hash_code),\
+ $(foreach sz,$(words $(call __get_obj_values,$(lst))),\
+ $(eval $(__obj_magic)_$(type)_$(new_hcode)_value := $(strip \
+ $(foreach val,$(call __get_obj_values,$(lst)),\
+ $(call $(func)_value,$(val))))))\
+ $(__obj_magic)_$(type)_$(new_hcode))))))
+
+
+
+_equal? = $(strip \
+ $(foreach ot1,$(call _obj_type,$(1)),$(foreach ot2,$(call _obj_type,$(2)),\
+ $(if $(or $(call _EQ,$(ot1),$(ot2)),\
+ $(and $(call _sequential?,$(1)),$(call _sequential?,$(2)))),\
+ $(if $(or $(call _string?,$(1)),$(call _symbol?,$(1)),$(call _number?,$(1))),\
+ $(call _EQ,$($(1)_value),$($(2)_value)),\
+ $(if $(or $(call _vector?,$(1)),$(call _list?,$(1)),$(call _hash_map?,$(1))),\
+ $(if $(and $(call _EQ,$(call _count,$(1)),$(call _count,$(2))),\
+ $(call _EQ,$(call _count,$(1)),$(words $(call gmsl_pairmap,_equal?,$(call __get_obj_values,$(1)),$(call __get_obj_values,$(2)))))),$(__true),),\
+ $(call _EQ,$(1),$(2))))))))
+
+equal? = $(if $(call _equal?,$(word 1,$(1)),$(word 2,$(1))),$(__true),$(__false))
+
+#
+# ENV
+#
+
+# An ENV environment is a hash-map with an __outer__ reference to an
+# outer environment
+define BIND_ARGS
+$(strip \
+ $(word 1,$(1) \
+ $(foreach fparam,$(call _nth,$(2),0),\
+ $(if $(call _EQ,&,$($(fparam)_value)),
+ $(call ENV_SET,$(1),$($(call _nth,$(2),1)_value),$(strip \
+ $(foreach new_list,$(call _list),
+ $(word 1,$(new_list) \
+ $(foreach val,$(3),$(call _conj!,$(new_list),$(val))))))),\
+ $(foreach val,$(word 1,$(3)),\
+ $(call ENV_SET,$(1),$($(fparam)_value),$(val))\
+ $(foreach left,$(call srest,$(2)),\
+ $(if $(call _EQ,0,$(call _count,$(left))),\
+ ,\
+ $(call BIND_ARGS,$(1),$(left),$(wordlist 2,$(words $(3)),$(3))))))))))
+endef
+
+# Create a new ENV and optional bind values in it
+# $(1): outer environment (set as a key named __outer__)
+# $(2): list/vector object of bind forms
+# $(3): space separated list of expressions to bind
+ENV = $(strip $(foreach new_env,$(call _assoc!,$(call _hash_map),__outer__,$(if $(1),$(1),$(__nil))),$(if $(2),$(call BIND_ARGS,$(new_env),$(2),$(3)),$(new_env))))
+ENV_FIND = $(strip \
+ $(if $(call _contains?,$(1),$(subst =,$(__equal),$(2))),\
+ $(1),\
+ $(if $(call _EQ,$(__nil),$(call _get,$(1),__outer__)),\
+ ,\
+ $(call ENV_FIND,$(call _get,$(1),__outer__),$(2)))))
+
+ENV_GET = $(foreach env,|$(call ENV_FIND,$(1),$(2))|,$(if $(call _EQ,||,$(env)),$(call _error,'$(2)' not found),$(call _get,$(strip $(subst |,,$(env))),$(subst =,$(__equal),$(2)))))
+
+ENV_SET = $(if $(call _assoc!,$(1),$(subst =,$(__equal),$(2)),$(3)),$(1),)
+
+#
+# Visualize Objects in memory
+#
+
+__var_name = $(word 2,$(subst _, ,$(1)))_$(word 3,$(subst _, ,$(1)))
+__var_idx := 0
+__var_print = $(foreach v,$(1),\
+ $(foreach var,$(call __var_name,$(v)),\
+ $(if $(or $(call _list?,$(v)),$(call _vector?,$(v))),\
+ $(info $(2)$(var):)\
+ $(eval __var_idx := $(call gmsl_plus,1,$(__var_idx)))\
+ $(foreach lidx,__lidx_$(__var_idx),\
+ $(eval $(lidx) := 0)\
+ $(foreach val,$($(v)_value),\
+ $(call __var_print,$(val),$(2)$(SPACE)$(SPACE)$($(lidx)): )\
+ $(eval $(lidx) := $(call gmsl_plus,1,$($(lidx)))))),\
+ $(if $(call _hash_map?,$(v)),\
+ $(info $(2)$(var):)\
+ $(foreach vkey,$(filter $(v)_%,$(.VARIABLES)),\
+ $(foreach key,$(word 4,$(subst _, ,$(vkey))),\
+ $(info $(2)$(SPACE)$(SPACE)$(subst $(__equal),=,$(key)): )\
+ $(call __var_print,$($(vkey)),$(2)$(SPACE)$(SPACE)$(SPACE)$(SPACE)))),\
+ $(if $(call _symbol?,$(v)),\
+ $(info $(2)$(var): $($(v)_value)),\
+ $(if $(call _number?,$(v)),\
+ $(info $(2)$(var): $(call int_decode,$($(v)_value))),\
+ $(if $(call _nil?,$(v)),\
+ $(info $(2)nil),\
+ $(if $(call _function?,$(v)),\
+ $(if $(word 6,$(value $(v)_value)),\
+ $(info $(2)$(var): $(wordlist 1,5,$(value $(v)_value))...),\
+ $(info $(2)$(var): $(value $(v)_value))),\
+ $(info $(2)$(var): ...)))))))))
+
+
+visualize_memory = $(foreach var,$(sort $(foreach vv,$(filter $(__obj_magic)_%,$(.VARIABLES)),$(call __var_name,$(vv)))),$(call __var_print,$(__obj_magic)_$(var)))
+
+#
+# Namespace of type functions
+#
+types_ns = pr-str pr_str str str prn prn println println \
+ with-meta with_meta meta meta \
+ type obj_type = equal? \
+ nil? nil? true? true? false? false? \
+ number? number? \
+ > number_gt >= number_gte < number_lt <= number_lte \
+ + number_plus - number_subtract * number_multiply / number_divide \
+ symbol? symbol? function? function? \
+ string? string? subs subs \
+ 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! swap! swap! \
+ sequential? sequential? \
+ cons cons nth nth empty? empty? count count concat concat \
+ conj conj first sfirst last slast rest srest \
+ apply sapply map smap \
+
+endif
diff --git a/make/util.mk b/make/util.mk
new file mode 100644
index 0000000..43923fc
--- /dev/null
+++ b/make/util.mk
@@ -0,0 +1,72 @@
+#
+# mal (Make Lisp) utility functions/definitions
+#
+
+ifndef __mal_util_included
+__mal_util_included := true
+
+_TOP_DIR := $(dir $(lastword $(MAKEFILE_LIST)))
+include $(_TOP_DIR)gmsl.mk
+
+SEMI := ;
+COMMA := ,
+LCURLY := {
+RCURLY := }
+LPAREN := (
+RPAREN := )
+LBRACKET := [
+RBRACKET := ]
+DQUOTE := "# "
+SLASH := $(strip \ )
+ESC_DQUOTE := $(SLASH)$(DQUOTE)
+SQUOTE := '# '
+QQUOTE := `# `
+SPACE :=
+SPACE +=
+NUMBERS := 0 1 2 3 4 5 6 7 8 9
+UNQUOTE := ~
+SPLICE_UNQUOTE := ~@
+define NEWLINE
+
+
+endef
+CARET := ^
+ATSIGN := @
+
+# \u00ab
+_LP := «
+# \u00bb
+_RP := »
+# \u00ed
+_LC := í
+# \u00ec
+_RC := ì
+## \u00a7
+_SP := §
+## \u00ae
+_SUQ := ®
+## \u015e
+_DOL := Ş
+## \u00b6
+_NL := ¶
+## \u00a8
+###_EDQ := ¨
+
+
+#
+# Utility functions
+#
+
+_EQ = $(if $(subst x$1,,x$2)$(subst x$2,,x$1),,true)
+
+_NOT = $(if $1,,true)
+
+# READ: read and parse input
+str_encode = $(strip $(eval __temp := $$(subst $$$$,$(_DOL) ,$$(subst $(SPLICE_UNQUOTE),$(_SUQ) ,$$(subst $$(LPAREN),$$(_LP) ,$$(subst $$(RPAREN),$$(_RP) ,$$(subst $$(LCURLY),$$(_LC) ,$$(subst $$(RCURLY),$$(_RC) ,$$(subst $$(NEWLINE),$$(_NL) ,$$(subst $$(SPACE),$(_SP) ,$$1)))))))))$(foreach a,$(__gmsl_characters),$(eval __temp := $$(subst $$a,$$a$$(SPACE),$(__temp))))$(__temp))
+
+str_decode = $(subst $(_SP),$(SPACE),$(subst $(_NL),$(NEWLINE),$(subst $(_LC),$(LCURLY),$(subst $(_RC),$(RCURLY),$(subst $(_LP),$(LPAREN),$(subst $(_RP),$(RPAREN),$(subst $(_SUQ),$(SPLICE_UNQUOTE),$(subst $(_DOL),$$,$(strip $(call gmsl_merge,,$(1)))))))))))
+
+# Read a whole file substituting newlines with $(_NL)
+_read_file = $(subst $(_NL),$(NEWLINE),$(shell out=""; while read -r l; do out="$${out}$${l}$(_NL)"; done < $(1); echo "$$out"))
+
+endif
diff --git a/mal.html b/mal.html
new file mode 100644
index 0000000..de3a51d
--- /dev/null
+++ b/mal.html
@@ -0,0 +1,52 @@
+<!doctype html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="chrome=1">
+ <title>Building a Lisp</title>
+ <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
+ <link href='http://fonts.googleapis.com/css?family=Source+Code+Pro' rel='stylesheet' type='text/css'>
+ <link rel="stylesheet" href="http://code.jquery.com/ui/1.9.2/themes/base/jquery-ui.css">
+ <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"></script>
+ <script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.9.2/jquery-ui.min.js"></script>
+ <script src="http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.4.2/underscore-min.js"></script>
+ <!--<script>Josh = {Debug: true };</script>-->
+ <script src="js/josh.js/js/killring.js"></script>
+ <script src="js/josh.js/js/history.js"></script>
+ <script src="js/josh.js/js/readline.js"></script>
+ <script src="js/josh.js/js/shell.js"></script>
+ <script src="js/josh_readline.js"></script>
+ <style type="text/css">
+ #shell-panel {
+ height: 400px;
+ width: 100%;
+ background-color: #002f05;
+ color: #00fe00;
+ padding: 20px 20px 20px 20px;
+ font-family: 'Source Code Pro';
+ overflow: scroll;
+ overflow-x: hidden;
+ overflow-y: scroll;
+ border: 1px dashed #E6EBE0;
+ }
+
+ #shell-cli .prompt {
+ font-weight: bold;
+ }</style>
+</head>
+<body>
+ <div class="wrapper">
+
+ <section>
+ <h1>Building a Lisp</h1>
+
+ <div id="shell-panel">
+ <div>Lisp REPL</div>
+ <div id="shell-view"></div>
+ </div>
+ </section>
+ </div>
+
+ <script src="js/mal_web.js"></script>
+</body>
+</html>
diff --git a/mal/Makefile b/mal/Makefile
new file mode 100644
index 0000000..f90f574
--- /dev/null
+++ b/mal/Makefile
@@ -0,0 +1,17 @@
+
+TESTS =
+
+
+SOURCES = types.mal env.mal stepA_more.mal
+
+#.PHONY: stats tests $(TESTS)
+.PHONY: stats
+
+stats: $(SOURCES)
+ @wc $^
+
+#tests: $(TESTS)
+#
+#$(TESTS):
+# @echo "Running $@"; \
+# python $@ || exit 1; \
diff --git a/mal/env.mal b/mal/env.mal
new file mode 100644
index 0000000..40937c5
--- /dev/null
+++ b/mal/env.mal
@@ -0,0 +1,40 @@
+;; env
+
+(def! bind-env (fn* [env b e]
+ (if (empty? b)
+ env
+
+ (if (= "&" (str (first b)))
+ (assoc env (str (nth b 1)) e)
+
+ (bind-env (assoc env (str (first b)) (first e))
+ (rest b) (rest e))))))
+
+(def! new-env (fn* [& args]
+ (if (<= (count args) 1)
+ (atom {"--outer--" (first args)})
+ (atom (bind-env {"--outer--" (first args)}
+ (nth args 1) (nth args 2))))))
+
+(def! env-find (fn* [env k]
+ (let* [ks (str k)
+ data @env]
+ (if (contains? data ks)
+ env
+ (if (get data "--outer--")
+ (env-find (get data "--outer--") ks)
+ nil)))))
+
+(def! env-get (fn* [env k]
+ (let* [ks (str k)
+ e (env-find env ks)]
+ (if e
+ (get @e ks)
+ (throw (str "'" ks "' not found"))))))
+
+(def! env-set (fn* [env k v]
+ (do
+ (swap! env assoc (str k) v)
+ v)))
+
+;;(prn "loaded env.mal")
diff --git a/mal/presentation.mal b/mal/presentation.mal
new file mode 100755
index 0000000..3e88d38
--- /dev/null
+++ b/mal/presentation.mal
@@ -0,0 +1,125 @@
+;; Mal Presentation
+
+(def! clear
+ (fn* ()
+ (str "")))
+
+(def! bold
+ (fn* (s)
+ (str "" s "")))
+
+(def! blue
+ (fn* (s)
+ (str "" s "")))
+
+(def! title
+ (fn* (s)
+ (bold (blue (str s "\n")))))
+
+(def! title2
+ (fn* (s)
+ (bold (blue s))))
+
+
+(def! conj-slides
+ (list
+ (list
+ (title2 " __ __ _ _")
+ (title2 "| \/ | / \ | |")
+ (title2 "| |\/| | / _ \ | | ")
+ (title2 "| | | |/ ___ \| |___ ")
+ (title2 "|_| |_/_/ \_\_____|"))
+ (list
+ (title "gherkin")
+ "- a lisp1 written in bash4")
+ (list
+ (title "mal - an interpreter for a subset of Clojure"))
+ (list
+ (title "mal - an interpreter for a subset of Clojure")
+ "- written in GNU make")
+ (list
+ (title "mal - an interpreter for a subset of Clojure")
+ "- written in GNU make"
+ "- and Bash 4")
+ (list
+ (title "mal - an interpreter for a subset of Clojure")
+ "- written in GNU make"
+ "- and Bash 4"
+ "- and Javascript")
+ (list
+ (title "mal - an interpreter for a subset of Clojure")
+ "- written in GNU make"
+ "- and Bash 4"
+ "- and Javascript"
+ "- and Python")
+ (list
+ (title "mal - an interpreter for a subset of Clojure")
+ "- written in GNU make"
+ "- and Bash 4"
+ "- and Javascript"
+ "- and Python"
+ "- and Clojure")
+ (list
+ (title "mal - an interpreter for a subset of Clojure")
+ "- written in GNU make"
+ "- and Bash 4"
+ "- and Javascript"
+ "- and Python"
+ "- and Clojure"
+ "- and C and Java and PHP")
+ (list
+ (title "things it has")
+ "- scalars: integers, strings, symbols, nil, true, false"
+ "- immutable collections: lists, vectors, hash-maps"
+ "- metadata, atoms"
+ "- def!, fn*, let*"
+ " - varargs: (fn* (x y & more) ...)"
+ "- tail call optimization"
+ " - except GNU make implementation (no iteration)"
+ "- macros (quote, unquote, quasiquote, splice-quote)"
+ "- almost 300 unit tests")
+ (list
+ (title "things it does not have")
+ "- performance"
+ "- namespaces"
+ "- keywords"
+ "- GC (in bash, make, C implmentations)")
+ (list
+ (title "why?")
+ "- because!")
+ (list
+ (title "why?")
+ "- because!"
+ "- gherkin was an inspiration to higher levels of crazy"
+ "- evolved into learning tool"
+ "- each implementation broken into small 10 steps"
+ "- way to learn about Lisp and also the target language")
+ (list
+ (title "thanks to:")
+ "- Peter Norvig: inspiration: lispy"
+ " - http://norvig.com/lispy.html"
+ "- Alan Dipert: gherkin, original gherkin slides"
+ " - https://github.com/alandipert/gherkin")
+ (list
+ (title "mal - Make a Lisp")
+ "https://github.com/kanaka/mal")
+ (list
+ (title "demo"))))
+
+(def! present
+ (fn* (slides)
+ (if (> (count slides) 0)
+ (do
+ ;;(py!* "import os; r = os.system('clear')")
+ ;;(sh* "clear")
+ ;;(make* "$(shell clear)")
+ (println (clear))
+
+ ;;(prn (first slides))
+ (apply println (map (fn* (line) (str "\n " line)) (first slides)))
+ (println "\n\n\n")
+ (readline "")
+ (present (rest slides))))))
+
+(present conj-slides)
+
diff --git a/mal/step1_read_print.mal b/mal/step1_read_print.mal
new file mode 100644
index 0000000..aba1f82
--- /dev/null
+++ b/mal/step1_read_print.mal
@@ -0,0 +1,26 @@
+;; read
+(def! READ (fn* [strng]
+ (read-string strng)))
+
+;; eval
+(def! EVAL (fn* [ast env]
+ ast))
+
+;; print
+(def! PRINT (fn* [exp] (pr-str exp)))
+
+;; repl
+(def! rep (fn* [strng]
+ (PRINT (EVAL (READ strng), {}))))
+
+(def! -main (fn* []
+ (let* [line (readline "mal-user> ")]
+ (if line
+ (do
+ (if (not (= "" line))
+ (try*
+ (println (rep line))
+ (catch* exc
+ (println "Uncaught exception:" exc))))
+ (-main))))))
+(-main)
diff --git a/mal/step2_eval.mal b/mal/step2_eval.mal
new file mode 100644
index 0000000..65a5c78
--- /dev/null
+++ b/mal/step2_eval.mal
@@ -0,0 +1,59 @@
+;; read
+(def! READ (fn* [strng]
+ (read-string strng)))
+
+
+;; eval
+(def! eval-ast (fn* [ast env] (do
+ ;;(do (prn "eval-ast" ast "/" (keys env)) )
+ (cond
+ (symbol? ast) (or (get env (str ast))
+ (throw (str ast " not found")))
+
+ (list? ast) (map (fn* [exp] (EVAL exp env)) ast)
+
+ (vector? ast) (apply vector (map (fn* [exp] (EVAL exp env)) ast))
+
+ (map? ast) (apply hash-map
+ (apply concat
+ (map (fn* [k] [k (EVAL (get ast k) env)])
+ (keys ast))))
+
+ "else" ast))))
+
+
+(def! EVAL (fn* [ast env] (do
+ ;;(do (prn "EVAL" ast "/" (keys @env)) )
+ (if (not (list? ast))
+ (eval-ast ast env)
+
+ ;; apply list
+ (let* [el (eval-ast ast env)
+ f (first el)
+ args (rest el)]
+ (apply f args))))))
+
+
+;; print
+(def! PRINT (fn* [exp] (pr-str exp)))
+
+;; repl
+(def! repl-env {"+" +
+ "-" -
+ "*" *
+ "/" /})
+(def! rep (fn* [strng]
+ (PRINT (EVAL (READ strng), repl-env))))
+
+(def! -main (fn* []
+ (let* [line (readline "mal-user> ")]
+ (if line
+ (do
+ (if (not (= "" line))
+ (try*
+ (let* [res (rep line)]
+ (println res))
+ (catch* exc
+ (println "Uncaught exception:" exc))))
+ (-main))))))
+(-main)
diff --git a/mal/step3_env.mal b/mal/step3_env.mal
new file mode 100644
index 0000000..13c1d7f
--- /dev/null
+++ b/mal/step3_env.mal
@@ -0,0 +1,80 @@
+(load-file "../mal/env.mal")
+
+;; read
+(def! READ (fn* [strng]
+ (read-string strng)))
+
+
+;; eval
+(def! eval-ast (fn* [ast env] (do
+ ;;(do (prn "eval-ast" ast "/" (keys env)) )
+ (cond
+ (symbol? ast) (env-get env ast)
+
+ (list? ast) (map (fn* [exp] (EVAL exp env)) ast)
+
+ (vector? ast) (apply vector (map (fn* [exp] (EVAL exp env)) ast))
+
+ (map? ast) (apply hash-map
+ (apply concat
+ (map (fn* [k] [k (EVAL (get ast k) env)])
+ (keys ast))))
+
+ "else" ast))))
+
+(def! LET (fn* [env args]
+ (if (> (count args) 0)
+ (do
+ (env-set env (nth args 0) (EVAL (nth args 1) env))
+ (LET env (rest (rest args)))))))
+
+(def! EVAL (fn* [ast env] (do
+ ;;(do (prn "EVAL" ast "/" (keys @env)) )
+ (if (not (list? ast))
+ (eval-ast ast env)
+
+ ;; apply list
+ (let* [a0 (first ast)]
+ (cond
+ (= 'def! a0)
+ (env-set env (nth ast 1) (EVAL (nth ast 2) env))
+
+ (= 'let* a0)
+ (let* [let-env (new-env env)]
+ (do
+ (LET let-env (nth ast 1))
+ (EVAL (nth ast 2) let-env)))
+
+ "else"
+ (let* [el (eval-ast ast env)
+ f (first el)
+ args (rest el)]
+ (apply f args))))))))
+
+
+;; print
+(def! PRINT (fn* [exp] (pr-str exp)))
+
+;; repl
+(def! repl-env (new-env))
+(def! rep (fn* [strng]
+ (PRINT (EVAL (READ strng), repl-env))))
+
+(def! _ref (fn* [k v] (env-set repl-env k v)))
+(_ref "+" +)
+(_ref "-" -)
+(_ref "*" *)
+(_ref "/" /)
+
+(def! -main (fn* []
+ (let* [line (readline "mal-user> ")]
+ (if line
+ (do
+ (if (not (= "" line))
+ (try*
+ (let* [res (rep line)]
+ (println res))
+ (catch* exc
+ (println "Uncaught exception:" exc))))
+ (-main))))))
+(-main)
diff --git a/mal/step4_if_fn_do.mal b/mal/step4_if_fn_do.mal
new file mode 100644
index 0000000..bdefd59
--- /dev/null
+++ b/mal/step4_if_fn_do.mal
@@ -0,0 +1,99 @@
+(load-file "../mal/types.mal")
+(load-file "../mal/env.mal")
+
+;; read
+(def! READ (fn* [strng]
+ (read-string strng)))
+
+
+;; eval
+(def! eval-ast (fn* [ast env] (do
+ ;;(do (prn "eval-ast" ast "/" (keys env)) )
+ (cond
+ (symbol? ast) (env-get env ast)
+
+ (list? ast) (map (fn* [exp] (EVAL exp env)) ast)
+
+ (vector? ast) (apply vector (map (fn* [exp] (EVAL exp env)) ast))
+
+ (map? ast) (apply hash-map
+ (apply concat
+ (map (fn* [k] [k (EVAL (get ast k) env)])
+ (keys ast))))
+
+ "else" ast))))
+
+(def! LET (fn* [env args]
+ (if (> (count args) 0)
+ (do
+ (env-set env (nth args 0) (EVAL (nth args 1) env))
+ (LET env (rest (rest args)))))))
+
+(def! EVAL (fn* [ast env] (do
+ ;;(do (prn "EVAL" ast "/" (keys @env)) )
+ (if (not (list? ast))
+ (eval-ast ast env)
+
+ ;; apply list
+ (let* [a0 (first ast)]
+ (cond
+ (= 'def! a0)
+ (env-set env (nth ast 1) (EVAL (nth ast 2) env))
+
+ (= 'let* a0)
+ (let* [let-env (new-env env)]
+ (do
+ (LET let-env (nth ast 1))
+ (EVAL (nth ast 2) let-env)))
+
+ (= 'do a0)
+ (let* [el (eval-ast (rest ast) env)]
+ (nth el (- (count el) 1)))
+
+ (= 'if a0)
+ (let* [cond (EVAL (nth ast 1) env)]
+ (if (or (= cond nil) (= cond false))
+ (if (> (count ast) 3)
+ (EVAL (nth ast 3) env)
+ nil)
+ (EVAL (nth ast 2) env)))
+
+ (= 'fn* a0)
+ (fn* [& args]
+ (EVAL (nth ast 2) (new-env env (nth ast 1) args)))
+
+ "else"
+ (let* [el (eval-ast ast env)
+ f (first el)
+ args (rest el)]
+ (apply f args))))))))
+
+
+;; print
+(def! PRINT (fn* [exp] (pr-str exp)))
+
+;; repl
+(def! repl-env (new-env))
+(def! rep (fn* [strng]
+ (PRINT (EVAL (READ strng), repl-env))))
+
+(def! _ref (fn* [k v] (env-set repl-env k v)))
+
+;; Import types related functions
+(map (fn* [data] (_ref (nth data 0) (nth data 1))) types_ns)
+
+;; Defined using the language itself
+(rep "(def! not (fn* [a] (if a false true)))")
+
+(def! -main (fn* []
+ (let* [line (readline "mal-user> ")]
+ (if line
+ (do
+ (if (not (= "" line))
+ (try*
+ (let* [res (rep line)]
+ (println res))
+ (catch* exc
+ (println "Uncaught exception:" exc))))
+ (-main))))))
+(-main)
diff --git a/mal/step6_file.mal b/mal/step6_file.mal
new file mode 100644
index 0000000..34acd67
--- /dev/null
+++ b/mal/step6_file.mal
@@ -0,0 +1,105 @@
+(load-file "../mal/types.mal")
+(load-file "../mal/env.mal")
+
+;; read
+(def! READ (fn* [strng]
+ (read-string strng)))
+
+
+;; eval
+(def! eval-ast (fn* [ast env] (do
+ ;;(do (prn "eval-ast" ast "/" (keys env)) )
+ (cond
+ (symbol? ast) (env-get env ast)
+
+ (list? ast) (map (fn* [exp] (EVAL exp env)) ast)
+
+ (vector? ast) (apply vector (map (fn* [exp] (EVAL exp env)) ast))
+
+ (map? ast) (apply hash-map
+ (apply concat
+ (map (fn* [k] [k (EVAL (get ast k) env)])
+ (keys ast))))
+
+ "else" ast))))
+
+(def! LET (fn* [env args]
+ (if (> (count args) 0)
+ (do
+ (env-set env (nth args 0) (EVAL (nth args 1) env))
+ (LET env (rest (rest args)))))))
+
+(def! EVAL (fn* [ast env] (do
+ ;;(do (prn "EVAL" ast "/" (keys @env)) )
+ (if (not (list? ast))
+ (eval-ast ast env)
+
+ ;; apply list
+ (let* [a0 (first ast)]
+ (cond
+ (= 'def! a0)
+ (env-set env (nth ast 1) (EVAL (nth ast 2) env))
+
+ (= 'let* a0)
+ (let* [let-env (new-env env)]
+ (do
+ (LET let-env (nth ast 1))
+ (EVAL (nth ast 2) let-env)))
+
+ (= 'do a0)
+ (let* [el (eval-ast (rest ast) env)]
+ (nth el (- (count el) 1)))
+
+ (= 'if a0)
+ (let* [cond (EVAL (nth ast 1) env)]
+ (if (or (= cond nil) (= cond false))
+ (if (> (count ast) 3)
+ (EVAL (nth ast 3) env)
+ nil)
+ (EVAL (nth ast 2) env)))
+
+ (= 'fn* a0)
+ (fn* [& args]
+ (EVAL (nth ast 2) (new-env env (nth ast 1) args)))
+
+ "else"
+ (let* [el (eval-ast ast env)
+ f (first el)
+ args (rest el)]
+ (apply f args))))))))
+
+
+;; print
+(def! PRINT (fn* [exp] (pr-str exp)))
+
+;; repl
+(def! repl-env (new-env))
+(def! rep (fn* [strng]
+ (PRINT (EVAL (READ strng), repl-env))))
+
+(def! _ref (fn* [k v] (env-set repl-env k v)))
+
+;; Import types related functions
+(map (fn* [data] (_ref (nth data 0) (nth data 1))) types_ns)
+
+;; Defined using the language itself
+(_ref 'read-string read-string)
+(_ref 'eval (fn* [ast] (EVAL ast repl-env)))
+(_ref 'slurp slurp)
+(_ref 'slurp-do slurp-do)
+
+(rep "(def! not (fn* [a] (if a false true)))")
+(rep "(def! load-file (fn* [f] (eval (read-string (slurp-do f)))))")
+
+(def! -main (fn* []
+ (let* [line (readline "mal-user> ")]
+ (if line
+ (do
+ (if (not (= "" line))
+ (try*
+ (let* [res (rep line)]
+ (println res))
+ (catch* exc
+ (println "Uncaught exception:" exc))))
+ (-main))))))
+(-main)
diff --git a/mal/step7_quote.mal b/mal/step7_quote.mal
new file mode 100644
index 0000000..b6e130d
--- /dev/null
+++ b/mal/step7_quote.mal
@@ -0,0 +1,133 @@
+(load-file "../mal/types.mal")
+(load-file "../mal/env.mal")
+
+;; read
+(def! READ (fn* [strng]
+ (read-string strng)))
+
+
+;; eval
+(def! is-pair (fn* [x]
+ (if (sequential? x)
+ (if (> (count x) 0)
+ true))))
+
+(def! QUASIQUOTE (fn* [ast]
+ (cond
+ (not (is-pair ast))
+ (list 'quote ast)
+
+ (= 'unquote (first ast))
+ (nth ast 1)
+
+ (if (is-pair (first ast))
+ (if (= 'splice-unquote (first (first ast)))
+ true))
+ (list 'concat (nth (first ast) 1) (QUASIQUOTE (rest ast)))
+
+ "else"
+ (list 'cons (QUASIQUOTE (first ast)) (QUASIQUOTE (rest ast))))))
+
+(def! eval-ast (fn* [ast env] (do
+ ;;(do (prn "eval-ast" ast "/" (keys env)) )
+ (cond
+ (symbol? ast) (env-get env ast)
+
+ (list? ast) (map (fn* [exp] (EVAL exp env)) ast)
+
+ (vector? ast) (apply vector (map (fn* [exp] (EVAL exp env)) ast))
+
+ (map? ast) (apply hash-map
+ (apply concat
+ (map (fn* [k] [k (EVAL (get ast k) env)])
+ (keys ast))))
+
+ "else" ast))))
+
+(def! LET (fn* [env args]
+ (if (> (count args) 0)
+ (do
+ (env-set env (nth args 0) (EVAL (nth args 1) env))
+ (LET env (rest (rest args)))))))
+
+(def! EVAL (fn* [ast env] (do
+ ;;(do (prn "EVAL" ast "/" (keys @env)) )
+ (if (not (list? ast))
+ (eval-ast ast env)
+
+ ;; apply list
+ (let* [a0 (first ast)]
+ (cond
+ (= 'def! a0)
+ (env-set env (nth ast 1) (EVAL (nth ast 2) env))
+
+ (= 'let* a0)
+ (let* [let-env (new-env env)]
+ (do
+ (LET let-env (nth ast 1))
+ (EVAL (nth ast 2) let-env)))
+
+ (= 'quote a0)
+ (nth ast 1)
+
+ (= 'quasiquote a0)
+ (let* [a1 (nth ast 1)]
+ (EVAL (QUASIQUOTE a1) env))
+
+ (= 'do a0)
+ (let* [el (eval-ast (rest ast) env)]
+ (nth el (- (count el) 1)))
+
+ (= 'if a0)
+ (let* [cond (EVAL (nth ast 1) env)]
+ (if (or (= cond nil) (= cond false))
+ (if (> (count ast) 3)
+ (EVAL (nth ast 3) env)
+ nil)
+ (EVAL (nth ast 2) env)))
+
+ (= 'fn* a0)
+ (fn* [& args]
+ (EVAL (nth ast 2) (new-env env (nth ast 1) args)))
+
+ "else"
+ (let* [el (eval-ast ast env)
+ f (first el)
+ args (rest el)]
+ (apply f args))))))))
+
+
+;; print
+(def! PRINT (fn* [exp] (pr-str exp)))
+
+;; repl
+(def! repl-env (new-env))
+(def! rep (fn* [strng]
+ (PRINT (EVAL (READ strng), repl-env))))
+
+(def! _ref (fn* [k v] (env-set repl-env k v)))
+
+;; Import types related functions
+(map (fn* [data] (_ref (nth data 0) (nth data 1))) types_ns)
+
+;; Defined using the language itself
+(_ref 'read-string read-string)
+(_ref 'eval (fn* [ast] (EVAL ast repl-env)))
+(_ref 'slurp slurp)
+(_ref 'slurp-do slurp-do)
+
+(rep "(def! not (fn* [a] (if a false true)))")
+(rep "(def! load-file (fn* [f] (eval (read-string (slurp-do f)))))")
+
+(def! -main (fn* []
+ (let* [line (readline "mal-user> ")]
+ (if line
+ (do
+ (if (not (= "" line))
+ (try*
+ (let* [res (rep line)]
+ (println res))
+ (catch* exc
+ (println "Uncaught exception:" exc))))
+ (-main))))))
+(-main)
diff --git a/mal/step8_macros.mal b/mal/step8_macros.mal
new file mode 100644
index 0000000..cb8909e
--- /dev/null
+++ b/mal/step8_macros.mal
@@ -0,0 +1,165 @@
+(load-file "../mal/types.mal")
+(load-file "../mal/env.mal")
+
+;; read
+(def! READ (fn* [strng]
+ (read-string strng)))
+
+
+;; eval
+(def! is-pair (fn* [x]
+ (if (sequential? x)
+ (if (> (count x) 0)
+ true))))
+
+(def! QUASIQUOTE (fn* [ast]
+ (cond
+ (not (is-pair ast))
+ (list 'quote ast)
+
+ (= 'unquote (first ast))
+ (nth ast 1)
+
+ (if (is-pair (first ast))
+ (if (= 'splice-unquote (first (first ast)))
+ true))
+ (list 'concat (nth (first ast) 1) (QUASIQUOTE (rest ast)))
+
+ "else"
+ (list 'cons (QUASIQUOTE (first ast)) (QUASIQUOTE (rest ast))))))
+
+(def! is-macro-call (fn* [ast env]
+ (if (list? ast)
+ (let* [a0 (first ast)]
+ (if (symbol? a0)
+ (if (env-find env a0)
+ (let* [m (meta (env-get env a0))]
+ (if m
+ (if (get m "ismacro")
+ true)))))))))
+
+(def! MACROEXPAND (fn* [ast env]
+ (if (is-macro-call ast env)
+ (let* [mac (env-get env (first ast))]
+ (MACROEXPAND (apply mac (rest ast)) env))
+ ast)))
+
+(def! eval-ast (fn* [ast env] (do
+ ;;(do (prn "eval-ast" ast "/" (keys env)) )
+ (cond
+ (symbol? ast) (env-get env ast)
+
+ (list? ast) (map (fn* [exp] (EVAL exp env)) ast)
+
+ (vector? ast) (apply vector (map (fn* [exp] (EVAL exp env)) ast))
+
+ (map? ast) (apply hash-map
+ (apply concat
+ (map (fn* [k] [k (EVAL (get ast k) env)])
+ (keys ast))))
+
+ "else" ast))))
+
+(def! LET (fn* [env args]
+ (if (> (count args) 0)
+ (do
+ (env-set env (nth args 0) (EVAL (nth args 1) env))
+ (LET env (rest (rest args)))))))
+
+(def! EVAL (fn* [ast env] (do
+ ;;(do (prn "EVAL" ast "/" (keys @env)) )
+ (if (not (list? ast))
+ (eval-ast ast env)
+
+ ;; apply list
+ (let* [ast (MACROEXPAND ast env)]
+ (if (not (list? ast))
+ ast
+
+ (let* [a0 (first ast)]
+ (cond
+ (= 'def! a0)
+ (env-set env (nth ast 1) (EVAL (nth ast 2) env))
+
+ (= 'let* a0)
+ (let* [let-env (new-env env)]
+ (do
+ (LET let-env (nth ast 1))
+ (EVAL (nth ast 2) let-env)))
+
+ (= 'quote a0)
+ (nth ast 1)
+
+ (= 'quasiquote a0)
+ (let* [a1 (nth ast 1)]
+ (EVAL (QUASIQUOTE a1) env))
+
+ (= 'defmacro! a0)
+ (let* [a1 (nth ast 1)
+ a2 (nth ast 2)
+ f (EVAL a2 env)
+ m (or (meta f) {})
+ mac (with-meta f (assoc m "ismacro" true))]
+ (env-set env a1 mac))
+
+ (= 'macroexpand a0)
+ (let* [a1 (nth ast 1)]
+ (MACROEXPAND a1 env))
+
+ (= 'do a0)
+ (let* [el (eval-ast (rest ast) env)]
+ (nth el (- (count el) 1)))
+
+ (= 'if a0)
+ (let* [cond (EVAL (nth ast 1) env)]
+ (if (or (= cond nil) (= cond false))
+ (if (> (count ast) 3)
+ (EVAL (nth ast 3) env)
+ nil)
+ (EVAL (nth ast 2) env)))
+
+ (= 'fn* a0)
+ (fn* [& args]
+ (EVAL (nth ast 2) (new-env env (nth ast 1) args)))
+
+ "else"
+ (let* [el (eval-ast ast env)
+ f (first el)
+ args (rest el)]
+ (apply f args))))))))))
+
+
+;; print
+(def! PRINT (fn* [exp] (pr-str exp)))
+
+;; repl
+(def! repl-env (new-env))
+(def! rep (fn* [strng]
+ (PRINT (EVAL (READ strng) repl-env))))
+
+(def! _ref (fn* [k v] (env-set repl-env k v)))
+
+;; Import types related functions
+(map (fn* [data] (_ref (nth data 0) (nth data 1))) types_ns)
+
+;; Defined using the language itself
+(_ref 'read-string read-string)
+(_ref 'eval (fn* [ast] (EVAL ast repl-env)))
+(_ref 'slurp slurp)
+(_ref 'slurp-do slurp-do)
+
+(rep "(def! not (fn* [a] (if a false true)))")
+(rep "(def! load-file (fn* [f] (eval (read-string (slurp-do f)))))")
+
+(def! -main (fn* []
+ (let* [line (readline "mal-user> ")]
+ (if line
+ (do
+ (if (not (= "" line))
+ (try*
+ (let* [res (rep line)]
+ (println res))
+ (catch* exc
+ (println "Uncaught exception:" exc))))
+ (-main))))))
+(-main)
diff --git a/mal/stepA_more.mal b/mal/stepA_more.mal
new file mode 100644
index 0000000..5426d59
--- /dev/null
+++ b/mal/stepA_more.mal
@@ -0,0 +1,179 @@
+(load-file "../mal/types.mal")
+(load-file "../mal/env.mal")
+
+;; read
+(def! READ (fn* [strng]
+ (read-string strng)))
+
+
+;; eval
+(def! is-pair (fn* [x]
+ (if (sequential? x)
+ (if (> (count x) 0)
+ true))))
+
+(def! QUASIQUOTE (fn* [ast]
+ (cond
+ (not (is-pair ast))
+ (list 'quote ast)
+
+ (= 'unquote (first ast))
+ (nth ast 1)
+
+ (if (is-pair (first ast))
+ (if (= 'splice-unquote (first (first ast)))
+ true))
+ (list 'concat (nth (first ast) 1) (QUASIQUOTE (rest ast)))
+
+ "else"
+ (list 'cons (QUASIQUOTE (first ast)) (QUASIQUOTE (rest ast))))))
+
+(def! is-macro-call (fn* [ast env]
+ (if (list? ast)
+ (let* [a0 (first ast)]
+ (if (symbol? a0)
+ (if (env-find env a0)
+ (let* [m (meta (env-get env a0))]
+ (if m
+ (if (get m "ismacro")
+ true)))))))))
+
+(def! MACROEXPAND (fn* [ast env]
+ (if (is-macro-call ast env)
+ (let* [mac (env-get env (first ast))]
+ (MACROEXPAND (apply mac (rest ast)) env))
+ ast)))
+
+(def! eval-ast (fn* [ast env] (do
+ ;;(do (prn "eval-ast" ast "/" (keys env)) )
+ (cond
+ (symbol? ast) (env-get env ast)
+
+ (list? ast) (map (fn* [exp] (EVAL exp env)) ast)
+
+ (vector? ast) (apply vector (map (fn* [exp] (EVAL exp env)) ast))
+
+ (map? ast) (apply hash-map
+ (apply concat
+ (map (fn* [k] [k (EVAL (get ast k) env)])
+ (keys ast))))
+
+ "else" ast))))
+
+(def! LET (fn* [env args]
+ (if (> (count args) 0)
+ (do
+ (env-set env (nth args 0) (EVAL (nth args 1) env))
+ (LET env (rest (rest args)))))))
+
+(def! EVAL (fn* [ast env] (do
+ ;;(do (prn "EVAL" ast "/" (keys @env)) )
+ (if (not (list? ast))
+ (eval-ast ast env)
+
+ ;; apply list
+ (let* [ast (MACROEXPAND ast env)]
+ (if (not (list? ast))
+ ast
+
+ (let* [a0 (first ast)]
+ (cond
+ (= 'def! a0)
+ (env-set env (nth ast 1) (EVAL (nth ast 2) env))
+
+ (= 'let* a0)
+ (let* [let-env (new-env env)]
+ (do
+ (LET let-env (nth ast 1))
+ (EVAL (nth ast 2) let-env)))
+
+ (= 'quote a0)
+ (nth ast 1)
+
+ (= 'quasiquote a0)
+ (let* [a1 (nth ast 1)]
+ (EVAL (QUASIQUOTE a1) env))
+
+ (= 'defmacro! a0)
+ (let* [a1 (nth ast 1)
+ a2 (nth ast 2)
+ f (EVAL a2 env)
+ m (or (meta f) {})
+ mac (with-meta f (assoc m "ismacro" true))]
+ (env-set env a1 mac))
+
+ (= 'macroexpand a0)
+ (let* [a1 (nth ast 1)]
+ (MACROEXPAND a1 env))
+
+ (= 'try* a0)
+ (if (= 'catch* (nth (nth ast 2) 0))
+ (try*
+ (EVAL (nth ast 1) env)
+ (catch* exc
+ (EVAL (nth (nth ast 2) 2)
+ (new-env env
+ [(nth (nth ast 2)1)]
+ [exc]))))
+ (EVAL (nth ast 1) env))
+
+ (= 'do a0)
+ (let* [el (eval-ast (rest ast) env)]
+ (nth el (- (count el) 1)))
+
+ (= 'if a0)
+ (let* [cond (EVAL (nth ast 1) env)]
+ (if (or (= cond nil) (= cond false))
+ (if (> (count ast) 3)
+ (EVAL (nth ast 3) env)
+ nil)
+ (EVAL (nth ast 2) env)))
+
+ (= 'fn* a0)
+ (fn* [& args]
+ (EVAL (nth ast 2) (new-env env (nth ast 1) args)))
+
+ "else"
+ (let* [el (eval-ast ast env)
+ f (first el)
+ args (rest el)]
+ (apply f args))))))))))
+
+
+;; print
+(def! PRINT (fn* [exp] (pr-str exp)))
+
+;; repl
+(def! repl-env (new-env))
+(def! rep (fn* [strng]
+ (PRINT (EVAL (READ strng) repl-env))))
+
+(def! _ref (fn* [k v] (env-set repl-env k v)))
+
+;; Import types related functions
+(map (fn* [data] (_ref (nth data 0) (nth data 1))) types_ns)
+
+;; Defined using the language itself
+(_ref 'readline readline)
+(_ref 'read-string read-string)
+(_ref 'eval (fn* [ast] (EVAL ast repl-env)))
+(_ref 'slurp slurp)
+(_ref 'slurp-do slurp-do)
+
+(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)))))")
+
+(def! -main (fn* []
+ (let* [line (readline "mal-user> ")]
+ (if line
+ (do
+ (if (not (= "" line))
+ (try*
+ (let* [res (rep line)]
+ (println res))
+ (catch* exc
+ (println "Uncaught exception:" exc))))
+ (-main))))))
+(-main)
diff --git a/mal/types.mal b/mal/types.mal
new file mode 100644
index 0000000..6eaa388
--- /dev/null
+++ b/mal/types.mal
@@ -0,0 +1,16 @@
+(def! types_ns
+ [["pr-str" pr-str] ["str" str] ["prn" prn] ["println" println]
+ ["with-meta" with-meta] ["meta" meta] ["=" =]
+ ["nil?" nil?] ["true?" true?] ["false?" false?] ["symbol?" symbol?]
+ [">" >] [">=" >=] ["<" <] ["<=" <=] ["+" +] ["-" -] ["*" *] ["/" /]
+ ["hash-map" hash-map] ["map?" 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!] ["swap!" swap!]
+ ["sequential?" sequential?] ["cons" cons] ["nth" nth]
+ ["empty?" empty?] ["count" count] ["concat" concat]
+ ["conj" conj] ["first" first] ["rest" rest]
+ ["apply" apply] ["map" map]])
diff --git a/php/reader.php b/php/reader.php
new file mode 100644
index 0000000..0524b31
--- /dev/null
+++ b/php/reader.php
@@ -0,0 +1,115 @@
+<?php
+
+require_once 'types.php';
+
+class Reader {
+ protected $tokens = array();
+ protected $position = 0;
+ public function __construct($tokens) {
+ $this->tokens = $tokens;
+ $this->position = 0;
+ }
+ public function next() {
+ return $this->tokens[$this->position++];
+ }
+ public function peek() {
+ return $this->tokens[$this->position];
+ }
+}
+
+class BlankException extends Exception {
+}
+
+function _real_token($s) {
+ return $s !== '' && $s[0] !== ';';
+}
+
+function tokenize($str) {
+ $pat = "/[\s,]*(~@|[\[\]{}()'`~^@]|\"(?:\\\\.|[^\\\\\"])*\"|;.*|[^\s\[\]{}('\"`,;)]*)/";
+ preg_match_all($pat, $str, $matches);
+ return array_values(array_filter($matches[1], '_real_token'));
+}
+
+function read_atom($reader) {
+ $token = $reader->next();
+ if (preg_match("/^-?[0-9]+$/", $token)) {
+ return intval($token, 10);
+ } elseif ($token[0] === "\"") {
+ $str = substr($token, 1, -1);
+ $str = preg_replace('/\\\\"/', '"', $str);
+ return $str;
+ } elseif ($token === "nil") {
+ return NULL;
+ } elseif ($token === "true") {
+ return true;
+ } elseif ($token === "false") {
+ return false;
+ } else {
+ return new_symbol($token);
+ }
+}
+
+function read_list($reader, $constr='new_list', $start='(', $end=')') {
+ $ast = $constr();
+ $token = $reader->next();
+ if ($token !== $start) {
+ throw new Exception("expected '" . $start . "'");
+ }
+ while (($token = $reader->peek()) !== $end) {
+ if ($token === "") {
+ throw new Exception("expected '" . $end . "', got EOF");
+ }
+ $ast[] = read_form($reader);
+ }
+ $reader->next();
+ return $ast;
+}
+
+function read_hash_map($reader) {
+ $lst = read_list($reader, 'new_list', '{', '}');
+ return call_user_func_array('new_hash_map', $lst->getArrayCopy());
+}
+
+function read_form($reader) {
+ $token = $reader->peek();
+ switch ($token) {
+ case '\'': $reader->next();
+ return new_list(new_symbol('quote'),
+ read_form($reader));
+ case '`': $reader->next();
+ return new_list(new_symbol('quasiquote'),
+ read_form($reader));
+ case '~': $reader->next();
+ return new_list(new_symbol('unquote'),
+ read_form($reader));
+ case '~@': $reader->next();
+ return new_list(new_symbol('splice-unquote'),
+ read_form($reader));
+ case '^': $reader->next();
+ $meta = read_form($reader);
+ return new_list(new_symbol('with-meta'),
+ read_form($reader),
+ $meta);
+
+ case '@': $reader->next();
+ return new_list(new_symbol('deref'),
+ read_form($reader));
+
+ case ')': throw new Exception("unexpected ')'");
+ case '(': return read_list($reader);
+ case ']': throw new Exception("unexpected ']'");
+ case '[': return read_list($reader, 'new_vector', '[', ']');
+ case '}': throw new Exception("unexpected '}'");
+ case '{': return read_hash_map($reader);
+
+ default: return read_atom($reader);
+ }
+}
+
+function read_str($str) {
+ $tokens = tokenize($str);
+ if (count($tokens) === 0) { throw new BlankException(); }
+ return read_form(new Reader($tokens));
+}
+
+?>
diff --git a/php/readline.php b/php/readline.php
new file mode 100644
index 0000000..28d720d
--- /dev/null
+++ b/php/readline.php
@@ -0,0 +1,34 @@
+<?php
+
+$HISTORY_FILE = "/home/joelm/.mal-history";
+
+function mal_readline($prompt) {
+ global $HISTORY_FILE;
+ static $history_loaded = false;
+
+ // Load the history file
+ if (! $history_loaded) {
+ $history_loaded = true;
+ if ($file = fopen($HISTORY_FILE, "r")) {
+ while (!feof($file)) {
+ $line = fgets($file);
+ if ($line) { readline_add_history($line); }
+ }
+ fclose($file);
+ }
+ }
+
+ $line = readline($prompt);
+ if ($line === false) { return NULL; }
+ readline_add_history($line);
+
+ // Append to the history file
+ if ($file = fopen($HISTORY_FILE, "a")) {
+ fputs($file, $line . "\n");
+ fclose($file);
+ }
+
+ return $line;
+}
+
+?>
diff --git a/php/step0_repl.php b/php/step0_repl.php
new file mode 100644
index 0000000..64b086b
--- /dev/null
+++ b/php/step0_repl.php
@@ -0,0 +1,33 @@
+<?php
+
+require_once 'readline.php';
+
+// read
+function READ($str) {
+ return $str;
+}
+
+// eval
+function MAL_EVAL($ast, $env) {
+ return eval($ast);
+}
+
+// print
+function MAL_PRINT($exp) {
+ return var_export($exp, true) . "\n";
+}
+
+// repl
+function rep($str) {
+ return MAL_PRINT(MAL_EVAL(READ($str), array()));
+}
+
+do {
+ $line = mal_readline("user> ");
+ if ($line === NULL) { break; }
+ if (!empty($line)) {
+ print(rep($line));
+ }
+} while (true);
+
+?>
diff --git a/php/step1_read_print.php b/php/step1_read_print.php
new file mode 100644
index 0000000..01334e0
--- /dev/null
+++ b/php/step1_read_print.php
@@ -0,0 +1,42 @@
+<?php
+
+require_once 'readline.php';
+require_once 'types.php';
+require_once 'reader.php';
+
+// read
+function READ($str) {
+ return read_str($str);
+}
+
+// eval
+function MAL_EVAL($ast, $env) {
+ return $ast;
+}
+
+// print
+function MAL_PRINT($exp) {
+ return _pr_str($exp, True) . "\n";
+}
+
+// repl
+function rep($str) {
+ return MAL_PRINT(MAL_EVAL(READ($str), array()));
+}
+
+do {
+ try {
+ $line = mal_readline("user> ");
+ if ($line === NULL) { break; }
+ if ($line !== "") {
+ print(rep($line));
+ }
+ } catch (BlankException $e) {
+ continue;
+ } catch (Exception $e) {
+ echo "Error: " . $e->getMessage() . "\n";
+ echo $e->getTraceAsString() . "\n";
+ }
+} while (true);
+
+?>
diff --git a/php/step2_eval.php b/php/step2_eval.php
new file mode 100644
index 0000000..c9c3562
--- /dev/null
+++ b/php/step2_eval.php
@@ -0,0 +1,77 @@
+<?php
+
+require_once 'readline.php';
+require_once 'types.php';
+require_once 'reader.php';
+
+// read
+function READ($str) {
+ return read_str($str);
+}
+
+// eval
+function eval_ast($ast, $env) {
+ if (symbol_Q($ast)) {
+ return $env[$ast->value];
+ } elseif (list_Q($ast) || vector_Q($ast)) {
+ if (list_Q($ast)) {
+ $el = new_list();
+ } else {
+ $el = new_vector();
+ }
+ foreach ($ast as $a) { $el[] = MAL_EVAL($a, $env); }
+ return $el;
+ } elseif (hash_map_Q($ast)) {
+ $new_hm = new_hash_map();
+ foreach (array_keys($ast->getArrayCopy()) as $key) {
+ $new_hm[$key] = MAL_EVAL($ast[$key], $env);
+ }
+ return $new_hm;
+ } else {
+ return $ast;
+ }
+}
+
+function MAL_EVAL($ast, $env) {
+ if (!list_Q($ast)) {
+ return eval_ast($ast, $env);
+ }
+
+ // apply list
+ $el = eval_ast($ast, $env);
+ $f = $el[0];
+ return call_user_func_array($f, array_slice($el->getArrayCopy(), 1));
+}
+
+// print
+function MAL_PRINT($exp) {
+ return _pr_str($exp, True) . "\n";
+}
+
+// repl
+$repl_env = array();
+function rep($str) {
+ global $repl_env;
+ return MAL_PRINT(MAL_EVAL(READ($str), $repl_env));
+}
+$repl_env['+'] = function ($a, $b) { return intval($a + $b,10); };
+$repl_env['-'] = function ($a, $b) { return intval($a - $b,10); };
+$repl_env['*'] = function ($a, $b) { return intval($a * $b,10); };
+$repl_env['/'] = function ($a, $b) { return intval($a / $b,10); };
+
+do {
+ try {
+ $line = mal_readline("user> ");
+ if ($line === NULL) { break; }
+ if ($line !== "") {
+ print(rep($line));
+ }
+ } catch (BlankException $e) {
+ continue;
+ } catch (Exception $e) {
+ echo "Error: " . $e->getMessage() . "\n";
+ echo $e->getTraceAsString() . "\n";
+ }
+} while (true);
+
+?>
diff --git a/php/step3_env.php b/php/step3_env.php
new file mode 100644
index 0000000..15d7c5c
--- /dev/null
+++ b/php/step3_env.php
@@ -0,0 +1,94 @@
+<?php
+
+require_once 'readline.php';
+require_once 'types.php';
+require_once 'reader.php';
+
+// read
+function READ($str) {
+ return read_str($str);
+}
+
+// eval
+function eval_ast($ast, $env) {
+ if (symbol_Q($ast)) {
+ return $env->get($ast->value);
+ } elseif (list_Q($ast) || vector_Q($ast)) {
+ if (list_Q($ast)) {
+ $el = new_list();
+ } else {
+ $el = new_vector();
+ }
+ foreach ($ast as $a) { $el[] = MAL_EVAL($a, $env); }
+ return $el;
+ } elseif (hash_map_Q($ast)) {
+ $new_hm = new_hash_map();
+ foreach (array_keys($ast->getArrayCopy()) as $key) {
+ $new_hm[$key] = MAL_EVAL($ast[$key], $env);
+ }
+ return $new_hm;
+ } else {
+ return $ast;
+ }
+}
+
+function MAL_EVAL($ast, $env) {
+ if (!list_Q($ast)) {
+ return eval_ast($ast, $env);
+ }
+
+ // apply list
+ $a0 = $ast[0];
+ $a0v = (symbol_Q($a0) ? $a0->value : $a0);
+ switch ($a0v) {
+ case "def!":
+ $res = MAL_EVAL($ast[2], $env);
+ return $env->set($ast[1]->value, $res);
+ case "let*":
+ $a1 = $ast[1];
+ $let_env = new Env($env);
+ for ($i=0; $i < count($a1); $i+=2) {
+ $let_env->set($a1[$i]->value, MAL_EVAL($a1[$i+1], $let_env));
+ }
+ return MAL_EVAL($ast[2], $let_env);
+ default:
+ $el = eval_ast($ast, $env);
+ $f = $el[0];
+ return call_user_func_array($f, array_slice($el->getArrayCopy(), 1));
+ }
+}
+
+// print
+function MAL_PRINT($exp) {
+ return _pr_str($exp, True) . "\n";
+}
+
+// repl
+$repl_env = new Env(NULL);
+function rep($str) {
+ global $repl_env;
+ return MAL_PRINT(MAL_EVAL(READ($str), $repl_env));
+}
+function _ref($k, $v) { global $repl_env; $repl_env->set($k, $v); }
+
+_ref('+', function ($a, $b) { return intval($a + $b,10); });
+_ref('-', function ($a, $b) { return intval($a - $b,10); });
+_ref('*', function ($a, $b) { return intval($a * $b,10); });
+_ref('/', function ($a, $b) { return intval($a / $b,10); });
+
+do {
+ try {
+ $line = mal_readline("user> ");
+ if ($line === NULL) { break; }
+ if ($line !== "") {
+ print(rep($line));
+ }
+ } catch (BlankException $e) {
+ continue;
+ } catch (Exception $e) {
+ echo "Error: " . $e->getMessage() . "\n";
+ echo $e->getTraceAsString() . "\n";
+ }
+} while (true);
+
+?>
diff --git a/php/step4_if_fn_do.php b/php/step4_if_fn_do.php
new file mode 100644
index 0000000..3b9593d
--- /dev/null
+++ b/php/step4_if_fn_do.php
@@ -0,0 +1,112 @@
+<?php
+
+require_once 'readline.php';
+require_once 'types.php';
+require_once 'reader.php';
+
+// read
+function READ($str) {
+ return read_str($str);
+}
+
+// eval
+function eval_ast($ast, $env) {
+ if (symbol_Q($ast)) {
+ return $env->get($ast->value);
+ } elseif (list_Q($ast) || vector_Q($ast)) {
+ if (list_Q($ast)) {
+ $el = new_list();
+ } else {
+ $el = new_vector();
+ }
+ foreach ($ast as $a) { $el[] = MAL_EVAL($a, $env); }
+ return $el;
+ } elseif (hash_map_Q($ast)) {
+ $new_hm = new_hash_map();
+ foreach (array_keys($ast->getArrayCopy()) as $key) {
+ $new_hm[$key] = MAL_EVAL($ast[$key], $env);
+ }
+ return $new_hm;
+ } else {
+ return $ast;
+ }
+}
+
+function MAL_EVAL($ast, $env) {
+ #echo "MAL_EVAL: " . _pr_str($ast) . "\n";
+ if (!list_Q($ast)) {
+ return eval_ast($ast, $env);
+ }
+
+ // apply list
+ $a0 = $ast[0];
+ $a0v = (symbol_Q($a0) ? $a0->value : $a0);
+ switch ($a0v) {
+ case "def!":
+ $res = MAL_EVAL($ast[2], $env);
+ return $env->set($ast[1]->value, $res);
+ case "let*":
+ $a1 = $ast[1];
+ $let_env = new Env($env);
+ for ($i=0; $i < count($a1); $i+=2) {
+ $let_env->set($a1[$i]->value, MAL_EVAL($a1[$i+1], $let_env));
+ }
+ return MAL_EVAL($ast[2], $let_env);
+ case "do":
+ #$el = eval_ast(array_slice($ast->getArrayCopy(), 1), $env);
+ $el = eval_ast($ast->slice(1), $env);
+ return $el[count($el)-1];
+ case "if":
+ $cond = MAL_EVAL($ast[1], $env);
+ if ($cond === NULL || $cond === false) {
+ if (count($ast) === 4) { return MAL_EVAL($ast[3], $env); }
+ else { return NULL; }
+ } else {
+ return MAL_EVAL($ast[2], $env);
+ }
+ case "fn*":
+ return function() use ($env, $ast) {
+ $fn_env = new Env($env, $ast[1], func_get_args());
+ return MAL_EVAL($ast[2], $fn_env);
+ };
+ default:
+ $el = eval_ast($ast, $env);
+ $f = $el[0];
+ return call_user_func_array($f, array_slice($el->getArrayCopy(), 1));
+ }
+}
+
+// print
+function MAL_PRINT($exp) {
+ return _pr_str($exp, True) . "\n";
+}
+
+// repl
+$repl_env = new Env(NULL);
+function rep($str) {
+ global $repl_env;
+ return MAL_PRINT(MAL_EVAL(READ($str), $repl_env));
+}
+function _ref($k, $v) { global $repl_env; $repl_env->set($k, $v); }
+// Import types functions
+foreach ($types_ns as $k=>$v) { _ref($k, $v); }
+
+// Defined using the language itself
+rep("(def! not (fn* (a) (if a false true)))");
+
+do {
+ try {
+ $line = mal_readline("user> ");
+ if ($line === NULL) { break; }
+ if ($line !== "") {
+ print(rep($line));
+ }
+ } catch (BlankException $e) {
+ continue;
+ } catch (Exception $e) {
+ echo "Error: " . $e->getMessage() . "\n";
+ echo $e->getTraceAsString() . "\n";
+ }
+} while (true);
+
+?>
diff --git a/php/step5_tco.php b/php/step5_tco.php
new file mode 100644
index 0000000..54d7699
--- /dev/null
+++ b/php/step5_tco.php
@@ -0,0 +1,124 @@
+<?php
+
+require_once 'readline.php';
+require_once 'types.php';
+require_once 'reader.php';
+
+// read
+function READ($str) {
+ return read_str($str);
+}
+
+// eval
+function eval_ast($ast, $env) {
+ if (symbol_Q($ast)) {
+ return $env->get($ast->value);
+ } elseif (list_Q($ast) || vector_Q($ast)) {
+ if (list_Q($ast)) {
+ $el = new_list();
+ } else {
+ $el = new_vector();
+ }
+ foreach ($ast as $a) { $el[] = MAL_EVAL($a, $env); }
+ return $el;
+ } elseif (hash_map_Q($ast)) {
+ $new_hm = new_hash_map();
+ foreach (array_keys($ast->getArrayCopy()) as $key) {
+ $new_hm[$key] = MAL_EVAL($ast[$key], $env);
+ }
+ return $new_hm;
+ } else {
+ return $ast;
+ }
+}
+
+function MAL_EVAL($ast, $env) {
+ while (true) {
+ #echo "MAL_EVAL: " . _pr_str($ast) . "\n";
+ if (!list_Q($ast)) {
+ return eval_ast($ast, $env);
+ }
+
+ // apply list
+ $a0 = $ast[0];
+ $a0v = (symbol_Q($a0) ? $a0->value : $a0);
+ switch ($a0v) {
+ case "def!":
+ $res = MAL_EVAL($ast[2], $env);
+ return $env->set($ast[1]->value, $res);
+ case "let*":
+ $a1 = $ast[1];
+ $let_env = new Env($env);
+ for ($i=0; $i < count($a1); $i+=2) {
+ $let_env->set($a1[$i]->value, MAL_EVAL($a1[$i+1], $let_env));
+ }
+ return MAL_EVAL($ast[2], $let_env);
+ case "do":
+ eval_ast($ast->slice(1, -1), $env);
+ $ast = $ast[count($ast)-1];
+ break;
+ case "if":
+ $cond = MAL_EVAL($ast[1], $env);
+ if ($cond === NULL || $cond === false) {
+ if (count($ast) === 4) { $ast = $ast[3]; }
+ else { $ast = NULL; }
+ } else {
+ $ast = $ast[2];
+ }
+ break;
+ case "fn*":
+ return new_function('MAL_EVAL', 'native',
+ new_hash_map('exp', $ast[2],
+ 'env', $env,
+ 'params', $ast[1]));
+ default:
+ $el = eval_ast($ast, $env);
+ $f = $el[0];
+ $args = array_slice($el->getArrayCopy(), 1);
+ if ($f->type === 'native') {
+ $ast = $f->meta['exp'];
+ $env = new Env($f->meta['env'], $f->meta['params'], $args);
+ } else {
+ return $f->apply($args);
+ }
+ }
+ }
+}
+
+// print
+function MAL_PRINT($exp) {
+ return _pr_str($exp, True) . "\n";
+}
+
+// repl
+$repl_env = new Env(NULL);
+function rep($str) {
+ global $repl_env;
+ return MAL_PRINT(MAL_EVAL(READ($str), $repl_env));
+}
+function _ref($k, $v) {
+ global $repl_env;
+ $repl_env->set($k, new_function($v));
+}
+// Import types functions
+foreach ($types_ns as $k=>$v) { _ref($k, $v); }
+
+// Defined using the language itself
+rep("(def! not (fn* (a) (if a false true)))");
+
+do {
+ try {
+ $line = mal_readline("user> ");
+ if ($line === NULL) { break; }
+ if ($line !== "") {
+ print(rep($line));
+ }
+ } catch (BlankException $e) {
+ continue;
+ } catch (Exception $e) {
+ echo "Error: " . $e->getMessage() . "\n";
+ echo $e->getTraceAsString() . "\n";
+ }
+} while (true);
+
+?>
diff --git a/php/step6_file.php b/php/step6_file.php
new file mode 100644
index 0000000..8e923e1
--- /dev/null
+++ b/php/step6_file.php
@@ -0,0 +1,142 @@
+<?php
+
+require_once 'readline.php';
+require_once 'types.php';
+require_once 'reader.php';
+
+// read
+function READ($str) {
+ return read_str($str);
+}
+
+// eval
+function eval_ast($ast, $env) {
+ if (symbol_Q($ast)) {
+ return $env->get($ast->value);
+ } elseif (list_Q($ast) || vector_Q($ast)) {
+ if (list_Q($ast)) {
+ $el = new_list();
+ } else {
+ $el = new_vector();
+ }
+ foreach ($ast as $a) { $el[] = MAL_EVAL($a, $env); }
+ return $el;
+ } elseif (hash_map_Q($ast)) {
+ $new_hm = new_hash_map();
+ foreach (array_keys($ast->getArrayCopy()) as $key) {
+ $new_hm[$key] = MAL_EVAL($ast[$key], $env);
+ }
+ return $new_hm;
+ } else {
+ return $ast;
+ }
+}
+
+function MAL_EVAL($ast, $env) {
+ while (true) {
+ #echo "MAL_EVAL: " . _pr_str($ast) . "\n";
+ if (!list_Q($ast)) {
+ return eval_ast($ast, $env);
+ }
+
+ // apply list
+ $a0 = $ast[0];
+ $a0v = (symbol_Q($a0) ? $a0->value : $a0);
+ switch ($a0v) {
+ case "def!":
+ $res = MAL_EVAL($ast[2], $env);
+ return $env->set($ast[1]->value, $res);
+ case "let*":
+ $a1 = $ast[1];
+ $let_env = new Env($env);
+ for ($i=0; $i < count($a1); $i+=2) {
+ $let_env->set($a1[$i]->value, MAL_EVAL($a1[$i+1], $let_env));
+ }
+ return MAL_EVAL($ast[2], $let_env);
+ case "do":
+ eval_ast($ast->slice(1, -1), $env);
+ $ast = $ast[count($ast)-1];
+ break;
+ case "if":
+ $cond = MAL_EVAL($ast[1], $env);
+ if ($cond === NULL || $cond === false) {
+ if (count($ast) === 4) { $ast = $ast[3]; }
+ else { $ast = NULL; }
+ } else {
+ $ast = $ast[2];
+ }
+ break;
+ case "fn*":
+ return new_function('MAL_EVAL', 'native',
+ new_hash_map('exp', $ast[2],
+ 'env', $env,
+ 'params', $ast[1]));
+ default:
+ $el = eval_ast($ast, $env);
+ $f = $el[0];
+ $args = array_slice($el->getArrayCopy(), 1);
+ if ($f->type === 'native') {
+ $ast = $f->meta['exp'];
+ $env = new Env($f->meta['env'], $f->meta['params'], $args);
+ } else {
+ return $f->apply($args);
+ }
+ }
+ }
+}
+
+// print
+function MAL_PRINT($exp) {
+ return _pr_str($exp, True) . "\n";
+}
+
+// repl
+$repl_env = new Env(NULL);
+function rep($str) {
+ global $repl_env;
+ return MAL_PRINT(MAL_EVAL(READ($str), $repl_env));
+}
+function _ref($k, $v) {
+ global $repl_env;
+ $repl_env->set($k, new_function($v));
+}
+// Import types functions
+foreach ($types_ns as $k=>$v) { _ref($k, $v); }
+
+_ref('read-string', 'read_str');
+_ref('eval', function($ast) {
+ global $repl_env; return MAL_EVAL($ast, $repl_env);
+});
+_ref('slurp', function($f) {
+ return file_get_contents($f);
+});
+_ref('slurp-do', function($f) {
+ return "(do " . file_get_contents($f) . ")";
+});
+
+// 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 (count($argv) > 1) {
+ for ($i=1; $i < count($argv); $i++) {
+ rep('(load-file "' . $argv[$i] . '")');
+ }
+} else {
+ do {
+ try {
+ $line = mal_readline("user> ");
+ if ($line === NULL) { break; }
+ if ($line !== "") {
+ print(rep($line));
+ }
+ } catch (BlankException $e) {
+ continue;
+ } catch (Exception $e) {
+ echo "Error: " . $e->getMessage() . "\n";
+ echo $e->getTraceAsString() . "\n";
+ }
+ } while (true);
+}
+
+?>
diff --git a/php/step7_quote.php b/php/step7_quote.php
new file mode 100644
index 0000000..2ccd130
--- /dev/null
+++ b/php/step7_quote.php
@@ -0,0 +1,165 @@
+<?php
+
+require_once 'readline.php';
+require_once 'types.php';
+require_once 'reader.php';
+
+// read
+function READ($str) {
+ return read_str($str);
+}
+
+// eval
+function is_pair($x) {
+ return sequential_Q($x) and count($x) > 0;
+}
+
+function quasiquote($ast) {
+ if (!is_pair($ast)) {
+ return new_list(new_symbol("quote"), $ast);
+ } elseif (symbol_Q($ast[0]) && $ast[0]->value === 'unquote') {
+ return $ast[1];
+ } elseif (is_pair($ast[0]) && symbol_Q($ast[0][0]) &&
+ $ast[0][0]->value === 'splice-unquote') {
+ return new_list(new_symbol("concat"), $ast[0][1],
+ quasiquote($ast->slice(1)));
+ } else {
+ return new_list(new_symbol("cons"), quasiquote($ast[0]),
+ quasiquote($ast->slice(1)));
+ }
+}
+
+function eval_ast($ast, $env) {
+ if (symbol_Q($ast)) {
+ return $env->get($ast->value);
+ } elseif (list_Q($ast) || vector_Q($ast)) {
+ if (list_Q($ast)) {
+ $el = new_list();
+ } else {
+ $el = new_vector();
+ }
+ foreach ($ast as $a) { $el[] = MAL_EVAL($a, $env); }
+ return $el;
+ } elseif (hash_map_Q($ast)) {
+ $new_hm = new_hash_map();
+ foreach (array_keys($ast->getArrayCopy()) as $key) {
+ $new_hm[$key] = MAL_EVAL($ast[$key], $env);
+ }
+ return $new_hm;
+ } else {
+ return $ast;
+ }
+}
+
+function MAL_EVAL($ast, $env) {
+ while (true) {
+ #echo "MAL_EVAL: " . _pr_str($ast) . "\n";
+ if (!list_Q($ast)) {
+ return eval_ast($ast, $env);
+ }
+
+ // apply list
+ $a0 = $ast[0];
+ $a0v = (symbol_Q($a0) ? $a0->value : $a0);
+ switch ($a0v) {
+ case "def!":
+ $res = MAL_EVAL($ast[2], $env);
+ return $env->set($ast[1]->value, $res);
+ case "let*":
+ $a1 = $ast[1];
+ $let_env = new Env($env);
+ for ($i=0; $i < count($a1); $i+=2) {
+ $let_env->set($a1[$i]->value, MAL_EVAL($a1[$i+1], $let_env));
+ }
+ return MAL_EVAL($ast[2], $let_env);
+ case "quote":
+ return $ast[1];
+ case "quasiquote":
+ return MAL_EVAL(quasiquote($ast[1]), $env);
+ case "do":
+ eval_ast($ast->slice(1, -1), $env);
+ $ast = $ast[count($ast)-1];
+ break;
+ case "if":
+ $cond = MAL_EVAL($ast[1], $env);
+ if ($cond === NULL || $cond === false) {
+ if (count($ast) === 4) { $ast = $ast[3]; }
+ else { $ast = NULL; }
+ } else {
+ $ast = $ast[2];
+ }
+ break;
+ case "fn*":
+ return new_function('MAL_EVAL', 'native',
+ new_hash_map('exp', $ast[2],
+ 'env', $env,
+ 'params', $ast[1]));
+ default:
+ $el = eval_ast($ast, $env);
+ $f = $el[0];
+ $args = array_slice($el->getArrayCopy(), 1);
+ if ($f->type === 'native') {
+ $ast = $f->meta['exp'];
+ $env = new Env($f->meta['env'], $f->meta['params'], $args);
+ } else {
+ return $f->apply($args);
+ }
+ }
+ }
+}
+
+// print
+function MAL_PRINT($exp) {
+ return _pr_str($exp, True) . "\n";
+}
+
+// repl
+$repl_env = new Env(NULL);
+function rep($str) {
+ global $repl_env;
+ return MAL_PRINT(MAL_EVAL(READ($str), $repl_env));
+}
+function _ref($k, $v) {
+ global $repl_env;
+ $repl_env->set($k, new_function($v));
+}
+// Import types functions
+foreach ($types_ns as $k=>$v) { _ref($k, $v); }
+
+_ref('read-string', 'read_str');
+_ref('eval', function($ast) {
+ global $repl_env; return MAL_EVAL($ast, $repl_env);
+});
+_ref('slurp', function($f) {
+ return file_get_contents($f);
+});
+_ref('slurp-do', function($f) {
+ return "(do " . file_get_contents($f) . ")";
+});
+
+// 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 (count($argv) > 1) {
+ for ($i=1; $i < count($argv); $i++) {
+ rep('(load-file "' . $argv[$i] . '")');
+ }
+} else {
+ do {
+ try {
+ $line = mal_readline("user> ");
+ if ($line === NULL) { break; }
+ if ($line !== "") {
+ print(rep($line));
+ }
+ } catch (BlankException $e) {
+ continue;
+ } catch (Exception $e) {
+ echo "Error: " . $e->getMessage() . "\n";
+ echo $e->getTraceAsString() . "\n";
+ }
+ } while (true);
+}
+
+?>
diff --git a/php/step8_macros.php b/php/step8_macros.php
new file mode 100644
index 0000000..20e0f6a
--- /dev/null
+++ b/php/step8_macros.php
@@ -0,0 +1,190 @@
+<?php
+
+require_once 'readline.php';
+require_once 'types.php';
+require_once 'reader.php';
+
+// read
+function READ($str) {
+ return read_str($str);
+}
+
+// eval
+function is_pair($x) {
+ return sequential_Q($x) and count($x) > 0;
+}
+
+function quasiquote($ast) {
+ if (!is_pair($ast)) {
+ return new_list(new_symbol("quote"), $ast);
+ } elseif (symbol_Q($ast[0]) && $ast[0]->value === 'unquote') {
+ return $ast[1];
+ } elseif (is_pair($ast[0]) && symbol_Q($ast[0][0]) &&
+ $ast[0][0]->value === 'splice-unquote') {
+ return new_list(new_symbol("concat"), $ast[0][1],
+ quasiquote($ast->slice(1)));
+ } else {
+ return new_list(new_symbol("cons"), quasiquote($ast[0]),
+ quasiquote($ast->slice(1)));
+ }
+}
+
+function is_macro_call($ast, $env) {
+ return is_pair($ast) &&
+ symbol_Q($ast[0]) &&
+ $env->find($ast[0]->value) &&
+ $env->get($ast[0]->value)->ismacro;
+}
+
+function macroexpand($ast, $env) {
+ while (is_macro_call($ast, $env)) {
+ $mac = $env->get($ast[0]->value);
+ $args = array_slice($ast->getArrayCopy(),1);
+ $ast = $mac->apply($args);
+ }
+ return $ast;
+}
+
+function eval_ast($ast, $env) {
+ if (symbol_Q($ast)) {
+ return $env->get($ast->value);
+ } elseif (list_Q($ast) || vector_Q($ast)) {
+ if (list_Q($ast)) {
+ $el = new_list();
+ } else {
+ $el = new_vector();
+ }
+ foreach ($ast as $a) { $el[] = MAL_EVAL($a, $env); }
+ return $el;
+ } elseif (hash_map_Q($ast)) {
+ $new_hm = new_hash_map();
+ foreach (array_keys($ast->getArrayCopy()) as $key) {
+ $new_hm[$key] = MAL_EVAL($ast[$key], $env);
+ }
+ return $new_hm;
+ } else {
+ return $ast;
+ }
+}
+
+function MAL_EVAL($ast, $env) {
+ while (true) {
+ #echo "MAL_EVAL: " . _pr_str($ast) . "\n";
+ if (!list_Q($ast)) {
+ return eval_ast($ast, $env);
+ }
+
+ // apply list
+ $ast = macroexpand($ast, $env);
+ if (!list_Q($ast)) { return $ast; }
+
+ $a0 = $ast[0];
+ $a0v = (symbol_Q($a0) ? $a0->value : $a0);
+ switch ($a0v) {
+ case "def!":
+ $res = MAL_EVAL($ast[2], $env);
+ return $env->set($ast[1]->value, $res);
+ case "let*":
+ $a1 = $ast[1];
+ $let_env = new Env($env);
+ for ($i=0; $i < count($a1); $i+=2) {
+ $let_env->set($a1[$i]->value, MAL_EVAL($a1[$i+1], $let_env));
+ }
+ return MAL_EVAL($ast[2], $let_env);
+ case "quote":
+ return $ast[1];
+ case "quasiquote":
+ return MAL_EVAL(quasiquote($ast[1]), $env);
+ case "defmacro!":
+ $func = MAL_EVAL($ast[2], $env);
+ $func->ismacro = true;
+ return $env->set($ast[1]->value, $func);
+ case "macroexpand":
+ return macroexpand($ast[1], $env);
+ case "do":
+ eval_ast($ast->slice(1, -1), $env);
+ $ast = $ast[count($ast)-1];
+ break;
+ case "if":
+ $cond = MAL_EVAL($ast[1], $env);
+ if ($cond === NULL || $cond === false) {
+ if (count($ast) === 4) { $ast = $ast[3]; }
+ else { $ast = NULL; }
+ } else {
+ $ast = $ast[2];
+ }
+ break;
+ case "fn*":
+ return new_function('MAL_EVAL', 'native',
+ new_hash_map('exp', $ast[2],
+ 'env', $env,
+ 'params', $ast[1]));
+ default:
+ $el = eval_ast($ast, $env);
+ $f = $el[0];
+ $args = array_slice($el->getArrayCopy(), 1);
+ if ($f->type === 'native') {
+ $ast = $f->meta['exp'];
+ $env = new Env($f->meta['env'], $f->meta['params'], $args);
+ } else {
+ return $f->apply($args);
+ }
+ }
+ }
+}
+
+// print
+function MAL_PRINT($exp) {
+ return _pr_str($exp, True) . "\n";
+}
+
+// repl
+$repl_env = new Env(NULL);
+function rep($str) {
+ global $repl_env;
+ return MAL_PRINT(MAL_EVAL(READ($str), $repl_env));
+}
+function _ref($k, $v) {
+ global $repl_env;
+ $repl_env->set($k, new_function($v));
+}
+// Import types functions
+foreach ($types_ns as $k=>$v) { _ref($k, $v); }
+
+_ref('read-string', 'read_str');
+_ref('eval', function($ast) {
+ global $repl_env; return MAL_EVAL($ast, $repl_env);
+});
+_ref('slurp', function($f) {
+ return file_get_contents($f);
+});
+_ref('slurp-do', function($f) {
+ return "(do " . file_get_contents($f) . ")";
+});
+
+// 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 (count($argv) > 1) {
+ for ($i=1; $i < count($argv); $i++) {
+ rep('(load-file "' . $argv[$i] . '")');
+ }
+} else {
+ do {
+ try {
+ $line = mal_readline("user> ");
+ if ($line === NULL) { break; }
+ if ($line !== "") {
+ print(rep($line));
+ }
+ } catch (BlankException $e) {
+ continue;
+ } catch (Exception $e) {
+ echo "Error: " . $e->getMessage() . "\n";
+ echo $e->getTraceAsString() . "\n";
+ }
+ } while (true);
+}
+
+?>
diff --git a/php/step9_interop.php b/php/step9_interop.php
new file mode 100644
index 0000000..fd7c1d7
--- /dev/null
+++ b/php/step9_interop.php
@@ -0,0 +1,192 @@
+<?php
+
+require_once 'readline.php';
+require_once 'types.php';
+require_once 'reader.php';
+
+// read
+function READ($str) {
+ return read_str($str);
+}
+
+// eval
+function is_pair($x) {
+ return sequential_Q($x) and count($x) > 0;
+}
+
+function quasiquote($ast) {
+ if (!is_pair($ast)) {
+ return new_list(new_symbol("quote"), $ast);
+ } elseif (symbol_Q($ast[0]) && $ast[0]->value === 'unquote') {
+ return $ast[1];
+ } elseif (is_pair($ast[0]) && symbol_Q($ast[0][0]) &&
+ $ast[0][0]->value === 'splice-unquote') {
+ return new_list(new_symbol("concat"), $ast[0][1],
+ quasiquote($ast->slice(1)));
+ } else {
+ return new_list(new_symbol("cons"), quasiquote($ast[0]),
+ quasiquote($ast->slice(1)));
+ }
+}
+
+function is_macro_call($ast, $env) {
+ return is_pair($ast) &&
+ symbol_Q($ast[0]) &&
+ $env->find($ast[0]->value) &&
+ $env->get($ast[0]->value)->ismacro;
+}
+
+function macroexpand($ast, $env) {
+ while (is_macro_call($ast, $env)) {
+ $mac = $env->get($ast[0]->value);
+ $args = array_slice($ast->getArrayCopy(),1);
+ $ast = $mac->apply($args);
+ }
+ return $ast;
+}
+
+function eval_ast($ast, $env) {
+ if (symbol_Q($ast)) {
+ return $env->get($ast->value);
+ } elseif (list_Q($ast) || vector_Q($ast)) {
+ if (list_Q($ast)) {
+ $el = new_list();
+ } else {
+ $el = new_vector();
+ }
+ foreach ($ast as $a) { $el[] = MAL_EVAL($a, $env); }
+ return $el;
+ } elseif (hash_map_Q($ast)) {
+ $new_hm = new_hash_map();
+ foreach (array_keys($ast->getArrayCopy()) as $key) {
+ $new_hm[$key] = MAL_EVAL($ast[$key], $env);
+ }
+ return $new_hm;
+ } else {
+ return $ast;
+ }
+}
+
+function MAL_EVAL($ast, $env) {
+ while (true) {
+ #echo "MAL_EVAL: " . _pr_str($ast) . "\n";
+ if (!list_Q($ast)) {
+ return eval_ast($ast, $env);
+ }
+
+ // apply list
+ $ast = macroexpand($ast, $env);
+ if (!list_Q($ast)) { return $ast; }
+
+ $a0 = $ast[0];
+ $a0v = (symbol_Q($a0) ? $a0->value : $a0);
+ switch ($a0v) {
+ case "def!":
+ $res = MAL_EVAL($ast[2], $env);
+ return $env->set($ast[1]->value, $res);
+ case "let*":
+ $a1 = $ast[1];
+ $let_env = new Env($env);
+ for ($i=0; $i < count($a1); $i+=2) {
+ $let_env->set($a1[$i]->value, MAL_EVAL($a1[$i+1], $let_env));
+ }
+ return MAL_EVAL($ast[2], $let_env);
+ case "quote":
+ return $ast[1];
+ case "quasiquote":
+ return MAL_EVAL(quasiquote($ast[1]), $env);
+ case "defmacro!":
+ $func = MAL_EVAL($ast[2], $env);
+ $func->ismacro = true;
+ return $env->set($ast[1]->value, $func);
+ case "macroexpand":
+ return macroexpand($ast[1], $env);
+ case "php*":
+ return eval($ast[1]);
+ case "do":
+ eval_ast($ast->slice(1, -1), $env);
+ $ast = $ast[count($ast)-1];
+ break;
+ case "if":
+ $cond = MAL_EVAL($ast[1], $env);
+ if ($cond === NULL || $cond === false) {
+ if (count($ast) === 4) { $ast = $ast[3]; }
+ else { $ast = NULL; }
+ } else {
+ $ast = $ast[2];
+ }
+ break;
+ case "fn*":
+ return new_function('MAL_EVAL', 'native',
+ new_hash_map('exp', $ast[2],
+ 'env', $env,
+ 'params', $ast[1]));
+ default:
+ $el = eval_ast($ast, $env);
+ $f = $el[0];
+ $args = array_slice($el->getArrayCopy(), 1);
+ if ($f->type === 'native') {
+ $ast = $f->meta['exp'];
+ $env = new Env($f->meta['env'], $f->meta['params'], $args);
+ } else {
+ return $f->apply($args);
+ }
+ }
+ }
+}
+
+// print
+function MAL_PRINT($exp) {
+ return _pr_str($exp, True) . "\n";
+}
+
+// repl
+$repl_env = new Env(NULL);
+function rep($str) {
+ global $repl_env;
+ return MAL_PRINT(MAL_EVAL(READ($str), $repl_env));
+}
+function _ref($k, $v) {
+ global $repl_env;
+ $repl_env->set($k, new_function($v));
+}
+// Import types functions
+foreach ($types_ns as $k=>$v) { _ref($k, $v); }
+
+_ref('read-string', 'read_str');
+_ref('eval', function($ast) {
+ global $repl_env; return MAL_EVAL($ast, $repl_env);
+});
+_ref('slurp', function($f) {
+ return file_get_contents($f);
+});
+_ref('slurp-do', function($f) {
+ return "(do " . file_get_contents($f) . ")";
+});
+
+// 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 (count($argv) > 1) {
+ for ($i=1; $i < count($argv); $i++) {
+ rep('(load-file "' . $argv[$i] . '")');
+ }
+} else {
+ do {
+ try {
+ $line = mal_readline("user> ");
+ if ($line === NULL) { break; }
+ if ($line !== "") {
+ print(rep($line));
+ }
+ } catch (BlankException $e) {
+ continue;
+ } catch (Exception $e) {
+ echo "Error: " . $e->getMessage() . "\n";
+ echo $e->getTraceAsString() . "\n";
+ }
+ } while (true);
+}
+
+?>
diff --git a/php/stepA_more.php b/php/stepA_more.php
new file mode 100644
index 0000000..cac80ed
--- /dev/null
+++ b/php/stepA_more.php
@@ -0,0 +1,213 @@
+<?php
+
+require_once 'readline.php';
+require_once 'types.php';
+require_once 'reader.php';
+
+// read
+function READ($str) {
+ return read_str($str);
+}
+
+// eval
+function is_pair($x) {
+ return sequential_Q($x) and count($x) > 0;
+}
+
+function quasiquote($ast) {
+ if (!is_pair($ast)) {
+ return new_list(new_symbol("quote"), $ast);
+ } elseif (symbol_Q($ast[0]) && $ast[0]->value === 'unquote') {
+ return $ast[1];
+ } elseif (is_pair($ast[0]) && symbol_Q($ast[0][0]) &&
+ $ast[0][0]->value === 'splice-unquote') {
+ return new_list(new_symbol("concat"), $ast[0][1],
+ quasiquote($ast->slice(1)));
+ } else {
+ return new_list(new_symbol("cons"), quasiquote($ast[0]),
+ quasiquote($ast->slice(1)));
+ }
+}
+
+function is_macro_call($ast, $env) {
+ return is_pair($ast) &&
+ symbol_Q($ast[0]) &&
+ $env->find($ast[0]->value) &&
+ $env->get($ast[0]->value)->ismacro;
+}
+
+function macroexpand($ast, $env) {
+ while (is_macro_call($ast, $env)) {
+ $mac = $env->get($ast[0]->value);
+ $args = array_slice($ast->getArrayCopy(),1);
+ $ast = $mac->apply($args);
+ }
+ return $ast;
+}
+
+function eval_ast($ast, $env) {
+ if (symbol_Q($ast)) {
+ return $env->get($ast->value);
+ } elseif (list_Q($ast) || vector_Q($ast)) {
+ if (list_Q($ast)) {
+ $el = new_list();
+ } else {
+ $el = new_vector();
+ }
+ foreach ($ast as $a) { $el[] = MAL_EVAL($a, $env); }
+ return $el;
+ } elseif (hash_map_Q($ast)) {
+ $new_hm = new_hash_map();
+ foreach (array_keys($ast->getArrayCopy()) as $key) {
+ $new_hm[$key] = MAL_EVAL($ast[$key], $env);
+ }
+ return $new_hm;
+ } else {
+ return $ast;
+ }
+}
+
+function MAL_EVAL($ast, $env) {
+ while (true) {
+ #echo "MAL_EVAL: " . _pr_str($ast) . "\n";
+ if (!list_Q($ast)) {
+ return eval_ast($ast, $env);
+ }
+
+ // apply list
+ $ast = macroexpand($ast, $env);
+ if (!list_Q($ast)) { return $ast; }
+
+ $a0 = $ast[0];
+ $a0v = (symbol_Q($a0) ? $a0->value : $a0);
+ switch ($a0v) {
+ case "def!":
+ $res = MAL_EVAL($ast[2], $env);
+ return $env->set($ast[1]->value, $res);
+ case "let*":
+ $a1 = $ast[1];
+ $let_env = new Env($env);
+ for ($i=0; $i < count($a1); $i+=2) {
+ $let_env->set($a1[$i]->value, MAL_EVAL($a1[$i+1], $let_env));
+ }
+ return MAL_EVAL($ast[2], $let_env);
+ case "quote":
+ return $ast[1];
+ case "quasiquote":
+ return MAL_EVAL(quasiquote($ast[1]), $env);
+ case "defmacro!":
+ $func = MAL_EVAL($ast[2], $env);
+ $func->ismacro = true;
+ return $env->set($ast[1]->value, $func);
+ case "macroexpand":
+ return macroexpand($ast[1], $env);
+ case "php*":
+ return eval($ast[1]);
+ case "try*":
+ $a1 = $ast[1];
+ $a2 = $ast[2];
+ if ($a2[0]->value === "catch*") {
+ try {
+ return MAL_EVAL($a1, $env);
+ } catch (Error $e) {
+ $catch_env = new Env($env, array($a2[1]),
+ array($e->obj));
+ return MAL_EVAL($a2[2], $catch_env);
+ } catch (Exception $e) {
+ $catch_env = new Env($env, array($a2[1]),
+ array($e->getMessage()));
+ return MAL_EVAL($a2[2], $catch_env);
+ }
+ } else {
+ return MAL_EVAL($a1, $env);
+ }
+ case "do":
+ eval_ast($ast->slice(1, -1), $env);
+ $ast = $ast[count($ast)-1];
+ break;
+ case "if":
+ $cond = MAL_EVAL($ast[1], $env);
+ if ($cond === NULL || $cond === false) {
+ if (count($ast) === 4) { $ast = $ast[3]; }
+ else { $ast = NULL; }
+ } else {
+ $ast = $ast[2];
+ }
+ break;
+ case "fn*":
+ return new_function('MAL_EVAL', 'native',
+ new_hash_map('exp', $ast[2],
+ 'env', $env,
+ 'params', $ast[1]));
+ default:
+ $el = eval_ast($ast, $env);
+ $f = $el[0];
+ $args = array_slice($el->getArrayCopy(), 1);
+ if ($f->type === 'native') {
+ $ast = $f->meta['exp'];
+ $env = new Env($f->meta['env'], $f->meta['params'], $args);
+ } else {
+ return $f->apply($args);
+ }
+ }
+ }
+}
+
+// print
+function MAL_PRINT($exp) {
+ return _pr_str($exp, True) . "\n";
+}
+
+// repl
+$repl_env = new Env(NULL);
+function rep($str) {
+ global $repl_env;
+ return MAL_PRINT(MAL_EVAL(READ($str), $repl_env));
+}
+function _ref($k, $v) {
+ global $repl_env;
+ $repl_env->set($k, new_function($v));
+}
+// Import types functions
+foreach ($types_ns as $k=>$v) { _ref($k, $v); }
+
+_ref('readline', 'mal_readline');
+_ref('read-string', 'read_str');
+_ref('eval', function($ast) {
+ global $repl_env; return MAL_EVAL($ast, $repl_env);
+});
+_ref('slurp', function($f) {
+ return file_get_contents($f);
+});
+_ref('slurp-do', function($f) {
+ return "(do " . file_get_contents($f) . ")";
+});
+
+// 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 (count($argv) > 1) {
+ for ($i=1; $i < count($argv); $i++) {
+ rep('(load-file "' . $argv[$i] . '")');
+ }
+} else {
+ do {
+ try {
+ $line = mal_readline("user> ");
+ if ($line === NULL) { break; }
+ if ($line !== "") {
+ print(rep($line));
+ }
+ } catch (BlankException $e) {
+ continue;
+ } catch (Exception $e) {
+ echo "Error: " . $e->getMessage() . "\n";
+ echo $e->getTraceAsString() . "\n";
+ }
+ } while (true);
+}
+
+?>
diff --git a/php/types.php b/php/types.php
new file mode 100644
index 0000000..0c4ab33
--- /dev/null
+++ b/php/types.php
@@ -0,0 +1,488 @@
+<?php
+
+function _pr_str($obj, $print_readably=True) {
+ if (list_Q($obj)) {
+ $ret = array();
+ foreach ($obj as $e) {
+ array_push($ret, _pr_str($e, $print_readably));
+ }
+ return "(" . implode(" ", $ret) . ")";
+ } elseif (vector_Q($obj)) {
+ $ret = array();
+ foreach ($obj as $e) {
+ array_push($ret, _pr_str($e, $print_readably));
+ }
+ return "[" . implode(" ", $ret) . "]";
+ } elseif (hash_map_Q($obj)) {
+ $ret = array();
+ foreach (array_keys($obj->getArrayCopy()) as $k) {
+ $ret[] = _pr_str($k, $print_readably);
+ $ret[] = _pr_str($obj[$k], $print_readably);
+ }
+ return "{" . implode(" ", $ret) . "}";
+ } elseif (is_string($obj)) {
+ if ($print_readably) {
+ $obj = preg_replace('/"/', '\\"', preg_replace('/\\\\/', '\\\\\\\\', $obj));
+ return '"' . $obj . '"';
+ } else {
+ return $obj;
+ }
+ } elseif (is_integer($obj)) {
+ return $obj;
+ } elseif ($obj === NULL) {
+ return "nil";
+ } elseif ($obj === true) {
+ return "true";
+ } elseif ($obj === false) {
+ return "false";
+ } elseif (symbol_Q($obj)) {
+ return $obj->value;
+ } elseif (atom_Q($obj)) {
+ return "(atom " . _pr_str($obj->value, $print_readably) . ")";
+ } elseif (function_Q($obj)) {
+ return "(fn* [...] ...)";
+ } elseif (is_callable($obj)) { // only step4 and below
+ return "#<function ...>";
+ } else {
+ throw new Exception("_pr_str unknown type: " . gettype($obj));
+ }
+}
+
+function pr_str() {
+ $ps = array_map(function ($obj) { return _pr_str($obj, True); },
+ func_get_args());
+ return implode(" ", $ps);
+}
+
+function str() {
+ $ps = array_map(function ($obj) { return _pr_str($obj, False); },
+ func_get_args());
+ return implode("", $ps);
+}
+
+function prn() {
+ $ps = array_map(function ($obj) { return _pr_str($obj, True); },
+ func_get_args());
+ print implode(" ", $ps) . "\n";
+ return null;
+}
+
+function println() {
+ $ps = array_map(function ($obj) { return _pr_str($obj, False); },
+ func_get_args());
+ print implode(" ", $ps) . "\n";
+ return null;
+}
+
+function with_meta($obj, $m) {
+ $new_obj = clone $obj;
+ $new_obj->meta = $m;
+ return $new_obj;
+}
+
+function meta($obj) {
+ return $obj->meta;
+}
+
+function equal_Q($a, $b) {
+ $ota = gettype($a) === "object" ? get_class($a) : gettype($a);
+ $otb = gettype($b) === "object" ? get_class($b) : gettype($b);
+ if (!($ota === $otb or (sequential_Q($a) and sequential_Q($b)))) {
+ return false;
+ } elseif (symbol_Q($a)) {
+ #print "ota: $ota, otb: $otb\n";
+ return $a->value === $b->value;
+ } elseif (list_Q($a) or vector_Q($a)) {
+ if ($a->count() !== $b->count()) { return false; }
+ for ($i=0; $i<$a->count(); $i++) {
+ if (!equal_Q($a[$i], $b[$i])) { return false; }
+ }
+ return true;
+ } else {
+ return $a === $b;
+ }
+}
+
+// nil, true, false, string
+function nil_Q($obj) { return $obj === NULL; }
+function true_Q($obj) { return $obj === true; }
+function false_Q($obj) { return $obj === false; }
+function string_Q($obj) { return is_string($obj); }
+
+
+// symbols
+class SymbolClass {
+ public $value = NULL;
+ public $meta = NULL;
+ public function __construct($value) {
+ $this->value = $value;
+ }
+}
+
+function new_symbol($name) { return new SymbolClass($name); }
+
+function symbol_Q($obj) { return ($obj instanceof SymbolClass); }
+
+
+// Functions
+class FunctionClass {
+ public $func = NULL;
+ public $type = 'native'; // 'native' or 'platform'
+ public $meta = NULL;
+ public $ismacro = False;
+ public function __construct($func, $type, $meta=NULL, $ismacro=False) {
+ $this->func = $func;
+ $this->type = $type;
+ $this->meta = $meta;
+ $this->ismacro = $ismacro;
+ }
+ public function __invoke() {
+ $args = func_get_args();
+ if ($this->type === 'native') {
+ $fn_env = new Env($this->meta['env'],
+ $this->meta['params'], $args);
+ $evalf = $this->func;
+ return $evalf($this->meta['exp'], $fn_env);
+ } else {
+ return call_user_func_array($this->func, $args);
+ }
+ }
+ public function apply($args) {
+ return call_user_func_array(array(&$this, '__invoke'),$args);
+ }
+}
+
+function new_function($func, $type='platform', $meta=NULL, $ismacro=False) {
+ return new FunctionClass($func, $type, $meta, $ismacro);
+}
+
+function function_Q($obj) { return $obj instanceof FunctionClass; }
+
+// Parent class of list, vector, hash-map
+// http://www.php.net/manual/en/class.arrayobject.php
+class SeqClass extends ArrayObject {
+ public function slice($start, $length=NULL) {
+ $sc = new $this();
+ if ($start >= count($this)) {
+ $arr = array();
+ } else {
+ $arr = array_slice($this->getArrayCopy(), $start, $length);
+ }
+ $sc->exchangeArray($arr);
+ return $sc;
+ }
+}
+
+
+// Hash Maps
+class HashMapClass extends ArrayObject {
+ public $meta = NULL;
+}
+
+function new_hash_map() {
+ $args = func_get_args();
+ if (count($args) % 2 === 1) {
+ throw new Exception("Odd number of hash map arguments");
+ }
+ $hm = new HashMapClass();
+ array_unshift($args, $hm);
+ return call_user_func_array('assoc_BANG', $args);
+}
+
+function hash_map_Q($obj) { return $obj instanceof HashMapClass; }
+
+function assoc_BANG($hm) {
+ $args = func_get_args();
+ if (count($args) % 2 !== 1) {
+ throw new Exception("Odd number of assoc arguments");
+ }
+ for ($i=1; $i<count($args); $i+=2) {
+ $ktoken = $args[$i];
+ $vtoken = $args[$i+1];
+ // TODO: support more than string keys
+ if (gettype($ktoken) !== "string") {
+ throw new Exception("expected hash-map key string, got: " . gettype($ktoken));
+ }
+ $hm[$ktoken] = $vtoken;
+ }
+ return $hm;
+}
+
+function assoc($src_hm) {
+ $args = func_get_args();
+ $hm = clone $src_hm;
+ $args[0] = $hm;
+ return call_user_func_array('assoc_BANG', $args);
+}
+
+function dissoc_BANG($hm) {
+ $args = func_get_args();
+ for ($i=1; $i<count($args); $i++) {
+ $ktoken = $args[$i];
+ unset($hm[$ktoken]);
+ }
+ return $hm;
+}
+
+function dissoc($src_hm) {
+ $args = func_get_args();
+ $hm = clone $src_hm;
+ $args[0] = $hm;
+ return call_user_func_array('dissoc_BANG', $args);
+}
+
+function get($hm, $k) {
+ if ($hm && $hm->offsetExists($k)) {
+ return $hm[$k];
+ } else {
+ return NULL;
+ }
+}
+
+function contains_Q($hm, $k) { return array_key_exists($k, $hm); }
+
+function keys($hm) {
+ return call_user_func_array('new_list', array_keys($hm->getArrayCopy()));
+}
+function vals($hm) {
+ return call_user_func_array('new_list', array_values($hm->getArrayCopy()));
+}
+
+
+// errors/exceptions
+class Error extends Exception {
+ public $obj = null;
+ public function __construct($obj) {
+ parent::__construct("Mal Error", 0, null);
+ $this->obj = $obj;
+ }
+}
+
+function mal_throw($obj) { throw new Error($obj); }
+
+
+// lists
+class ListClass extends SeqClass {
+ public $meta = NULL;
+}
+
+function new_list() {
+ $v = new ListClass();
+ $v->exchangeArray(func_get_args());
+ return $v;
+}
+
+function list_Q($obj) { return $obj instanceof ListClass; }
+
+// vectors
+class VectorClass extends SeqClass {
+ public $meta = NULL;
+}
+
+function new_vector() {
+ $v = new VectorClass();
+ $v->exchangeArray(func_get_args());
+ return $v;
+}
+
+function vector_Q($obj) { return $obj instanceof VectorClass; }
+
+
+// Atoms
+
+class Atom {
+ public $value = NULL;
+ public $meta = NULL;
+ public function __construct($value) {
+ $this->value = $value;
+ }
+}
+function new_atom($val) { return new Atom($val); }
+function atom_Q($atm) { return $atm instanceof Atom; }
+function deref($atm) { return $atm->value; }
+function reset_BANG($atm, $val) { return $atm->value = $val; }
+function swap_BANG($atm, $f) {
+ $args = array_slice(func_get_args(),2);
+ array_unshift($args, $atm->value);
+ $atm->value = call_user_func_array($f, $args);
+ return $atm->value;
+}
+
+
+// Sequence operations
+function sequential_Q($seq) { return list_Q($seq) or vector_Q($seq); }
+
+function scount($seq) { return ($seq === NULL ? 0 : $seq->count()); }
+
+function empty_Q($seq) { return $seq->count() === 0; }
+
+function cons($a, $b) {
+ $tmp = $b->getArrayCopy();
+ array_unshift($tmp, $a);
+ $l = new ListClass();
+ $l->exchangeArray($tmp);
+ return $l;
+}
+
+function concat() {
+ $args = func_get_args();
+ $tmp = array();
+ foreach ($args as $arg) {
+ $tmp = array_merge($tmp, $arg->getArrayCopy());
+ }
+ $l = new ListClass();
+ $l->exchangeArray($tmp);
+ return $l;
+}
+
+function conj($src) {
+ $args = array_slice(func_get_args(), 1);
+ $tmp = $src->getArrayCopy();
+ foreach ($args as $arg) {
+ $tmp[] = $arg;
+ }
+ if (list_Q($src)) {
+ $s = new ListClass();
+ } else {
+ $s = new VectorClass();
+ }
+ $s->exchangeArray($tmp);
+ return $s;
+}
+
+function first($seq) {
+ if (count($seq) === 0) {
+ return NULL;
+ } else {
+ return $seq[0];
+ }
+}
+
+function rest($seq) {
+ $l = new ListClass();
+ $l->exchangeArray(array_slice($seq->getArrayCopy(), 1));
+ return $l;
+}
+
+function nth($seq, $idx) {
+ return $seq[$idx];
+}
+
+function apply($f, $args) {
+ return $f->apply($args->getArrayCopy());
+}
+
+function map($f, $seq) {
+ $l = new ListClass();
+ $l->exchangeArray(array_map($f, $seq->getArrayCopy()));
+ return $l;
+}
+
+
+// Environment
+class Env {
+ public $data = array();
+ public $outer = NULL;
+ public function __construct($outer, $binds=NULL, $exprs=NULL) {
+ $this->outer = $outer;
+ if ($binds) {
+ if (sequential_Q($exprs)) {
+ $exprs = $exprs->getArrayCopy();
+ }
+ for ($i=0; $i<count($binds); $i++) {
+ if ($binds[$i]->value === "&") {
+ if ($exprs !== NULL && $i < count($exprs)) {
+ $lst = call_user_func_array('new_list', array_slice($exprs, $i));
+ } else {
+ $lst = new_list();
+ }
+ $this->data[$binds[$i+1]->value] = $lst;
+ break;
+ } else {
+ if ($exprs !== NULL && $i < count($exprs)) {
+ $this->data[$binds[$i]->value] = $exprs[$i];
+ } else {
+ $this->data[$binds[$i]->value] = NULL;
+ }
+ }
+ }
+ }
+ }
+ public function find($key) {
+ if (array_key_exists($key, $this->data)) {
+ return $this;
+ } elseif ($this->outer) {
+ return $this->outer->find($key);
+ } else {
+ return NULL;
+ }
+ }
+ public function set($key, $value) {
+ $this->data[$key] = $value;
+ return $value;
+ }
+ public function get($key) {
+ $env = $this->find($key);
+ if (!$env) {
+ throw new Exception("'" . $key . "' not found");
+ } else {
+ return $env->data[$key];
+ }
+ }
+}
+
+// types_ns is namespace of type functions
+$types_ns = array(
+ 'pr-str'=> function () { return call_user_func_array('pr_str', func_get_args()); },
+ 'str'=> function () { return call_user_func_array('str', func_get_args()); },
+ 'prn'=> function () { return call_user_func_array('prn', func_get_args()); },
+ 'println'=>function () { return call_user_func_array('println', func_get_args()); },
+ 'with-meta'=> function ($a, $b) { return with_meta($a, $b); },
+ 'meta'=> function ($a) { return meta($a); },
+ '='=> function ($a, $b) { return equal_Q($a, $b); },
+ 'nil?'=> function ($a) { return nil_Q($a); },
+ 'true?'=> function ($a) { return true_Q($a); },
+ 'false?'=> function ($a) { return false_Q($a); },
+ '+'=> function ($a, $b) { return intval($a + $b,10); },
+ '-'=> function ($a, $b) { return intval($a - $b,10); },
+ '*'=> function ($a, $b) { return intval($a * $b,10); },
+ '/'=> function ($a, $b) { return intval($a / $b,10); },
+ '<'=> function ($a, $b) { return $a < $b; },
+ '<='=> function ($a, $b) { return $a <= $b; },
+ '>'=> function ($a, $b) { return $a > $b; },
+ '>='=> function ($a, $b) { return $a >= $b; },
+ 'symbol?'=> function ($a) { return symbol_Q($a); },
+ 'string?'=> function ($a) { return string_Q($a); },
+ 'hash-map' => function () { return call_user_func_array('new_hash_map', func_get_args()); },
+ 'map?'=> function ($a) { return hash_map_Q($a); },
+ 'assoc' => function () { return call_user_func_array('assoc', func_get_args()); },
+ 'dissoc' => function () { return call_user_func_array('dissoc', func_get_args()); },
+ 'get' => function ($a, $b) { return get($a, $b); },
+ 'contains?' => function ($a, $b) { return contains_Q($a, $b); },
+ 'keys' => function ($a) { return keys($a); },
+ 'vals' => function ($a) { return vals($a); },
+ 'throw'=> function ($a) { return mal_throw($a); },
+ 'list'=> function () { return call_user_func_array('new_list', func_get_args()); },
+ 'list?'=> function ($a) { return list_Q($a); },
+ 'vector'=> function () { return call_user_func_array('new_vector', func_get_args()); },
+ 'vector?'=> function ($a) { return vector_Q($a); },
+ 'atom'=> function ($a) { return new_atom($a); },
+ 'atom?'=> function ($a) { return atom_Q($a); },
+ 'deref'=> function ($a) { return deref($a); },
+ 'reset!'=> function ($a, $b) { return reset_BANG($a, $b); },
+ 'swap!'=> function () { return call_user_func_array('swap_BANG', func_get_args()); },
+ 'sequential?'=> function ($a) { return sequential_Q($a); },
+ 'count'=> function ($a) { return scount($a); },
+ 'empty?'=> function ($a) { return empty_Q($a); },
+ 'cons'=> function ($a, $b) { return cons($a, $b); },
+ 'concat'=> function () { return call_user_func_array('concat', func_get_args()); },
+ 'conj'=> function () { return call_user_func_array('conj', func_get_args()); },
+ 'first'=> function ($a) { return first($a); },
+ 'rest'=> function ($a) { return rest($a); },
+ 'nth'=> function ($a, $b) { return nth($a, $b); },
+ 'apply'=> function ($a, $b) { return apply($a, $b); },
+ 'map'=> function ($a, $b) { return map($a, $b); }
+);
+
+
+?>
diff --git a/python/Makefile b/python/Makefile
new file mode 100644
index 0000000..1c8e467
--- /dev/null
+++ b/python/Makefile
@@ -0,0 +1,27 @@
+
+TESTS =
+
+
+SOURCES = mal_types.py mal_readline.py reader.py stepA_more.py
+
+#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 $@"; \
+ python $@ || exit 1; \
diff --git a/python/mal_readline.py b/python/mal_readline.py
new file mode 100644
index 0000000..e8cf957
--- /dev/null
+++ b/python/mal_readline.py
@@ -0,0 +1,24 @@
+import os, readline as pyreadline
+
+history_loaded = False
+histfile = os.path.expanduser("~/.mal-history")
+
+def readline(prompt="user> "):
+ if not history_loaded:
+ try:
+ with open(histfile, "r") as hf:
+ for line in hf.readlines():
+ pyreadline.add_history(line.rstrip("\r\n"))
+ pass
+ except IOError:
+ print("Could not open %s" % histfile)
+ pass
+
+ try:
+ line = raw_input(prompt)
+ pyreadline.add_history(line)
+ with open(histfile, "a") as hf:
+ hf.write(line + "\n")
+ return line
+ except EOFError:
+ return None
diff --git a/python/mal_types.py b/python/mal_types.py
new file mode 100644
index 0000000..fa0a11e
--- /dev/null
+++ b/python/mal_types.py
@@ -0,0 +1,268 @@
+import copy
+from itertools import chain
+
+# General functions
+
+def _pr_str(obj, print_readably=True):
+ _r = print_readably
+ if list_Q(obj):
+ return "(" + " ".join(map(lambda e: _pr_str(e,_r), obj)) + ")"
+ elif vector_Q(obj):
+ return "[" + " ".join(map(lambda e: _pr_str(e,_r), obj)) + "]"
+ elif hash_map_Q(obj):
+ ret = []
+ for k in obj.keys():
+ ret.extend((_pr_str(k), _pr_str(obj[k],_r)))
+ return "{" + " ".join(ret) + "}"
+ elif string_Q(obj):
+ if print_readably:
+ return '"' + obj.encode('unicode_escape').replace('"', '\\"') + '"'
+ else:
+ return obj
+ elif nil_Q(obj):
+ return "nil"
+ elif true_Q(obj):
+ return "true"
+ elif false_Q(obj):
+ return "false"
+ elif atom_Q(obj):
+ return "(atom " + _pr_str(obj.val,_r) + ")"
+ else:
+ return obj.__str__()
+
+def pr_str(*args):
+ return " ".join(map(lambda exp: _pr_str(exp, True), args))
+
+def do_str(*args):
+ return "".join(map(lambda exp: _pr_str(exp, False), args))
+
+def prn(*args):
+ print " ".join(map(lambda exp: _pr_str(exp, True), args))
+ return None
+
+def println(*args):
+ line = " ".join(map(lambda exp: _pr_str(exp, False), args))
+ print line.replace('\\n', '\n')
+ return None
+
+def with_meta(obj, meta):
+ new_obj = copy.copy(obj)
+ new_obj.__meta__ = meta
+ return new_obj
+
+def meta(obj):
+ if hasattr(obj, "__meta__"): return obj.__meta__
+ else: return None
+
+def equal_Q(a, b):
+ ota, otb = type(a), type(b)
+ if not (ota == otb or (sequential_Q(a) and sequential_Q(b))):
+ return False;
+ if symbol_Q(a):
+ return a == b
+ elif list_Q(a) or vector_Q(a):
+ if len(a) != len(b): return False
+ for i in range(len(a)):
+ if not equal_Q(a[i], b[i]): return False
+ return True
+ elif hash_map_Q(a):
+ akeys = a.keys()
+ akeys.sort()
+ bkeys = b.keys()
+ bkeys.sort()
+ if len(akeys) != len(bkeys): return False
+ for i in range(len(akeys)):
+ if akeys[i] != bkeys[i]: return False
+ if not equal_Q(a[akeys[i]], b[bkeys[i]]): return False
+ return True
+ else:
+ return a == b
+
+# nil, true, false
+def nil_Q(exp): return exp is None
+def true_Q(exp): return exp is True
+def false_Q(exp): return exp is False
+def string_Q(exp): return type(exp) in [str, unicode]
+
+# numbers
+int_plus = lambda a,b: a+b
+int_minus = lambda a,b: a-b
+int_multiply = lambda a,b: a*b
+int_divide = lambda a,b: a/b
+int_lt = lambda a,b: a<b
+int_lte = lambda a,b: a<=b
+int_gt = lambda a,b: a>b
+int_gte = lambda a,b: a>=b
+
+# symbols
+class Symbol(str): pass
+def new_symbol(str): return Symbol(str)
+def symbol_Q(exp): return type(exp) == Symbol
+
+
+# functions
+def new_function(func, exp, env, params):
+ def f(*args):
+ return func(exp, Env(env, params, args))
+ f.__meta__ = {"exp": exp, "env": env, "params": params}
+ return f
+def function_Q(f): return type(f) == type(function_Q)
+
+# hash maps
+class Hash_Map(dict): pass
+def new_hash_map(*key_vals):
+ hm = Hash_Map()
+ for i in range(0,len(key_vals),2): hm[key_vals[i]] = key_vals[i+1]
+ return hm
+def hash_map_Q(exp): return type(exp) == Hash_Map
+
+def assoc(src_hm, *key_vals):
+ hm = copy.copy(src_hm)
+ for i in range(0,len(key_vals),2): hm[key_vals[i]] = key_vals[i+1]
+ return hm
+
+def dissoc(src_hm, *keys):
+ hm = copy.copy(src_hm)
+ for key in keys: del hm[key]
+ return hm
+
+def get(hm, key):
+ if key in hm:
+ return hm[key]
+ else:
+ return None
+
+def contains_Q(hm, key): return key in hm
+
+def keys(hm): return new_list(*hm.keys())
+
+def vals(hm): return new_list(*hm.values())
+
+
+# errors/exceptions
+def throw(exc): raise Exception(exc)
+
+
+# lists
+class List(list):
+ def __add__(self, rhs): return List(list.__add__(self, rhs))
+ def __getitem__(self, i):
+ if type(i) == slice: return List(list.__getitem__(self, i))
+ elif i >= len(self): return None
+ else: return list.__getitem__(self, i)
+ def __getslice__(self, *a): return List(list.__getslice__(self, *a))
+def new_list(*vals): return List(vals)
+def list_Q(exp): return type(exp) == List
+
+
+# vectors
+class Vector(list):
+ def __add__(self, rhs): return Vector(list.__add__(self, rhs))
+ def __getitem__(self, i):
+ if type(i) == slice: return Vector(list.__getitem__(self, i))
+ elif i >= len(self): return None
+ else: return list.__getitem__(self, i)
+ def __getslice__(self, *a): return Vector(list.__getslice__(self, *a))
+def new_vector(*vals): return Vector(vals)
+def vector_Q(exp): return type(exp) == Vector
+
+
+# atoms
+class Atom(object):
+ def __init__(self, val):
+ self.val = val
+def new_atom(val): return Atom(val)
+def atom_Q(exp): return type(exp) == Atom
+def deref(atm): return atm.val
+def reset_BANG(atm,val):
+ atm.val = val
+ return atm.val
+def swap_BANG(atm,f,*args):
+ atm.val = f(atm.val,*args)
+ return atm.val
+
+
+
+# Sequence operations
+def sequential_Q(seq): return list_Q(seq) or vector_Q(seq)
+
+def coll_Q(coll): return sequential_Q(coll) or hash_map_Q(coll)
+
+def cons(x, seq): return List([x]) + List(seq)
+
+def nth(lst, idx): return lst[idx]
+
+def count(lst): return len(lst)
+
+def empty_Q(lst): return len(lst) == 0
+
+def concat(*lsts): return List(chain(*lsts))
+
+# retains metadata
+def conj(lst, *args):
+ new_lst = List(lst + list(args))
+ if hasattr(lst, "__meta__"):
+ new_lst.__meta__ = lst.__meta__
+ return new_lst
+
+def first(lst): return lst[0]
+
+def rest(lst): return List(lst[1:])
+
+def apply(f, *args):
+ return f(*(list(args[0:-1])+args[-1]))
+
+def mapf(f, lst):
+ return List(map(f, lst))
+
+
+# Environment
+
+class Env():
+ def __init__(self, outer=None, binds=None, exprs=None):
+ self.data = {}
+ self.outer = outer or None
+
+ if binds:
+ for i in range(len(binds)):
+ if binds[i] == "&":
+ self.data[binds[i+1]] = exprs[i:]
+ break
+ else:
+ self.data[binds[i]] = exprs[i]
+
+ def find(self, key):
+ if key in self.data: return self
+ elif self.outer: return self.outer.find(key)
+ else: return None
+
+ def set(self, key, value):
+ self.data[key] = value
+ return value
+
+ def get(self, key):
+ env = self.find(key)
+ if not env: raise Exception("'" + key + "' not found")
+ return env.data[key]
+
+types_ns = {
+ 'pr-str': pr_str, 'str': do_str, 'prn': prn, 'println': println,
+ 'with-meta': with_meta, 'meta': meta,
+ '=': equal_Q,
+ 'nil?': nil_Q, 'true?': true_Q, 'false?': false_Q,
+ 'symbol?': symbol_Q,
+ '<': int_lt, '<=': int_lte, '>': int_gt, '>=': int_gte,
+ '+': int_plus, '-': int_minus, '*': int_multiply, '/': int_divide,
+ 'hash-map': new_hash_map, 'map?': hash_map_Q,
+ 'assoc': assoc, 'dissoc': dissoc, 'get': get,
+ 'contains?': contains_Q, 'keys': keys, 'vals': vals,
+ 'throw': throw,
+ 'list': new_list, 'list?': list_Q,
+ 'vector': new_vector, 'vector?': vector_Q,
+ 'atom': new_atom, 'atom?': atom_Q, 'deref': deref,
+ 'reset!': reset_BANG, 'swap!': swap_BANG,
+ 'sequential?': sequential_Q,
+ 'cons': cons, 'nth': nth, 'count': count, 'empty?': empty_Q,
+ 'concat': concat, "conj": conj, "first": first, "rest": rest,
+ 'apply': apply, 'map': mapf}
+
diff --git a/python/reader.py b/python/reader.py
new file mode 100644
index 0000000..ddd6a32
--- /dev/null
+++ b/python/reader.py
@@ -0,0 +1,104 @@
+import re
+from mal_types import (new_symbol, Symbol, new_hash_map, List, new_list, Vector)
+
+class Blank(Exception): pass
+
+class Reader():
+ def __init__(self, tokens, position=0):
+ self.tokens = tokens
+ self.position = position
+
+ def next(self):
+ self.position += 1
+ return self.tokens[self.position-1]
+
+ def peek(self):
+ if len(self.tokens) > self.position:
+ return self.tokens[self.position]
+ else:
+ return None
+
+def tokenize(str):
+ tre = re.compile(r"""[\s,]*(~@|[\[\]{}()'`~^@]|"(?:[\\].|[^\\"])*"|;.*|[^\s\[\]{}()'"`@,;]+)""");
+ return [t for t in re.findall(tre, str) if t[0] != ';']
+
+def read_atom(reader):
+ int_re = re.compile(r"-?[0-9]+$")
+ float_re = re.compile(r"-?[0-9][0-9.]*$")
+ token = reader.next()
+ if re.match(int_re, token): return int(token)
+ elif re.match(float_re, token): return int(token)
+ elif token[0] == '"': return token[1:-1].replace('\\"', '"')
+ elif token == "nil": return None
+ elif token == "true": return True
+ elif token == "false": return False
+ else: return Symbol(token)
+
+def read_sequence(reader, typ=list, start='(', end=')'):
+ ast = typ()
+ token = reader.next()
+ if token != start: raise Exception("expected '" + start + "'")
+
+ token = reader.peek()
+ while token != end:
+ if not token: raise Exception("expected '" + end + "', got EOF")
+ ast.append(read_form(reader))
+ token = reader.peek()
+ reader.next()
+ return ast
+
+def read_hash_map(reader):
+ lst = read_sequence(reader, list, '{', '}')
+ return new_hash_map(*lst)
+
+def read_list(reader):
+ return read_sequence(reader, List, '(', ')')
+
+def read_vector(reader):
+ return read_sequence(reader, Vector, '[', ']')
+
+def read_form(reader):
+ token = reader.peek()
+ # reader macros/transforms
+ if token[0] == ';':
+ reader.next()
+ return None
+ elif token == '\'':
+ reader.next()
+ return new_list(Symbol('quote'), read_form(reader))
+ elif token == '`':
+ reader.next()
+ return new_list(Symbol('quasiquote'), read_form(reader))
+ elif token == '~':
+ reader.next()
+ return new_list(Symbol('unquote'), read_form(reader))
+ elif token == '~@':
+ reader.next()
+ return new_list(Symbol('splice-unquote'), read_form(reader))
+ elif token == '^':
+ reader.next()
+ meta = read_form(reader)
+ return new_list(Symbol('with-meta'), read_form(reader), meta)
+ elif token == '@':
+ reader.next()
+ return new_list(Symbol('deref'), read_form(reader))
+
+ # list
+ elif token == ')': raise Exception("unexpected ')'")
+ elif token == '(': return read_list(reader)
+
+ # vector
+ elif token == ']': raise Exception("unexpected ']'");
+ elif token == '[': return read_vector(reader);
+
+ # hash-map
+ elif token == '}': raise Exception("unexpected '}'");
+ elif token == '{': return read_hash_map(reader);
+
+ # atom
+ else: return read_atom(reader);
+
+def read_str(str):
+ tokens = tokenize(str)
+ if len(tokens) == 0: raise Blank
+ return read_form(Reader(tokens))
diff --git a/python/step0_repl.py b/python/step0_repl.py
new file mode 100644
index 0000000..8d42c33
--- /dev/null
+++ b/python/step0_repl.py
@@ -0,0 +1,32 @@
+import sys, traceback
+import mal_readline
+
+# read
+def READ(str):
+ return str
+
+# eval
+def EVAL(ast, env):
+ # try it as an expression then a statement
+ try:
+ return eval(ast)
+ except SyntaxError:
+ exec compile(ast, '', 'single') in globals()
+ return None
+
+# print
+def PRINT(exp):
+ return exp
+
+# repl
+def REP(str):
+ return PRINT(EVAL(READ(str), {}))
+
+while True:
+ try:
+ line = mal_readline.readline("user> ")
+ if line == None: break
+ if line == "": continue
+ print(REP(line))
+ except Exception as e:
+ print "".join(traceback.format_exception(sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2]))
diff --git a/python/step1_read_print.py b/python/step1_read_print.py
new file mode 100644
index 0000000..165dfa3
--- /dev/null
+++ b/python/step1_read_print.py
@@ -0,0 +1,32 @@
+import sys, traceback
+import mal_readline
+from mal_types import (pr_str, sequential_Q, symbol_Q, coll_Q, list_Q,
+ vector_Q, hash_map_Q, new_symbol, new_function,
+ new_list, new_vector, new_hash_map, Env, types_ns)
+from reader import (read_str, Blank)
+
+# read
+def READ(str):
+ return read_str(str)
+
+# eval
+def EVAL(ast, env):
+ #print("EVAL %s" % ast)
+ return ast
+
+def PRINT(exp):
+ return pr_str(exp)
+
+# repl
+def REP(str):
+ return PRINT(EVAL(READ(str), {}))
+
+while True:
+ try:
+ line = mal_readline.readline("user> ")
+ if line == None: break
+ if line == "": continue
+ print(REP(line))
+ except Blank: continue
+ except Exception as e:
+ print "".join(traceback.format_exception(*sys.exc_info()))
diff --git a/python/step2_eval.py b/python/step2_eval.py
new file mode 100644
index 0000000..bb5d6f8
--- /dev/null
+++ b/python/step2_eval.py
@@ -0,0 +1,60 @@
+import sys, traceback
+import mal_readline
+from mal_types import (pr_str, sequential_Q, symbol_Q, coll_Q, list_Q,
+ vector_Q, hash_map_Q, new_symbol, new_function,
+ new_list, new_vector, new_hash_map, Env, types_ns)
+from reader import (read_str, Blank)
+
+# read
+def READ(str):
+ return read_str(str)
+
+# eval
+def eval_ast(ast, env):
+ if symbol_Q(ast):
+ return env[ast]
+ elif list_Q(ast):
+ return new_list(*map(lambda a: EVAL(a, env), ast))
+ elif vector_Q(ast):
+ return new_vector(*map(lambda a: EVAL(a, env), ast))
+ elif hash_map_Q(ast):
+ keyvals = []
+ for k in ast.keys():
+ keyvals.append(EVAL(k, env))
+ keyvals.append(EVAL(ast[k], env))
+ return new_hash_map(*keyvals)
+ else:
+ return ast # primitive value, return unchanged
+
+def EVAL(ast, env):
+ #print("EVAL %s" % ast)
+ if not list_Q(ast):
+ return eval_ast(ast, env)
+
+ # apply list
+ el = eval_ast(ast, env)
+ f = el[0]
+ return f(*el[1:])
+
+def PRINT(exp):
+ return pr_str(exp)
+
+# repl
+repl_env = {}
+def REP(str):
+ return PRINT(EVAL(READ(str), repl_env))
+
+repl_env['+'] = lambda a,b: a+b
+repl_env['-'] = lambda a,b: a-b
+repl_env['*'] = lambda a,b: a*b
+repl_env['/'] = lambda a,b: a/b
+
+while True:
+ try:
+ line = mal_readline.readline("user> ")
+ if line == None: break
+ if line == "": continue
+ print(REP(line))
+ except Blank: continue
+ except Exception as e:
+ print "".join(traceback.format_exception(*sys.exc_info()))
diff --git a/python/step3_env.py b/python/step3_env.py
new file mode 100644
index 0000000..f95a978
--- /dev/null
+++ b/python/step3_env.py
@@ -0,0 +1,76 @@
+import sys, traceback
+import mal_readline
+from mal_types import (pr_str, sequential_Q, symbol_Q, coll_Q, list_Q,
+ vector_Q, hash_map_Q, new_symbol, new_function,
+ new_list, new_vector, new_hash_map, Env, types_ns)
+from reader import (read_str, Blank)
+
+# read
+def READ(str):
+ return read_str(str)
+
+# eval
+def eval_ast(ast, env):
+ if symbol_Q(ast):
+ return env.get(ast)
+ elif list_Q(ast):
+ return new_list(*map(lambda a: EVAL(a, env), ast))
+ elif vector_Q(ast):
+ return new_vector(*map(lambda a: EVAL(a, env), ast))
+ elif hash_map_Q(ast):
+ keyvals = []
+ for k in ast.keys():
+ keyvals.append(EVAL(k, env))
+ keyvals.append(EVAL(ast[k], env))
+ return new_hash_map(*keyvals)
+ else:
+ return ast # primitive value, return unchanged
+
+def EVAL(ast, env):
+ #print("EVAL %s" % ast)
+ if not list_Q(ast):
+ return eval_ast(ast, env)
+
+ # apply list
+ if len(ast) == 0: return ast
+ a0 = ast[0]
+
+ if "def!" == a0:
+ a1, a2 = ast[1], ast[2]
+ res = EVAL(a2, env)
+ return env.set(a1, res)
+ elif "let*" == a0:
+ a1, a2 = ast[1], ast[2]
+ let_env = Env(env)
+ for i in range(0, len(a1), 2):
+ let_env.set(a1[i], EVAL(a1[i+1], let_env))
+ return EVAL(a2, let_env)
+ else:
+ el = eval_ast(ast, env)
+ f = el[0]
+ return f(*el[1:])
+
+# print
+def PRINT(exp):
+ return pr_str(exp)
+
+# repl
+repl_env = Env()
+def REP(str):
+ return PRINT(EVAL(READ(str), repl_env))
+def _ref(k,v): repl_env.set(k, v)
+
+_ref('+', lambda a,b: a+b)
+_ref('-', lambda a,b: a-b)
+_ref('*', lambda a,b: a*b)
+_ref('/', lambda a,b: a/b)
+
+while True:
+ try:
+ line = mal_readline.readline("user> ")
+ if line == None: break
+ if line == "": continue
+ print(REP(line))
+ except Blank: continue
+ except Exception as e:
+ print "".join(traceback.format_exception(*sys.exc_info()))
diff --git a/python/step4_if_fn_do.py b/python/step4_if_fn_do.py
new file mode 100644
index 0000000..4b54d8f
--- /dev/null
+++ b/python/step4_if_fn_do.py
@@ -0,0 +1,91 @@
+import sys, traceback
+import mal_readline
+from mal_types import (pr_str, sequential_Q, symbol_Q, coll_Q, list_Q,
+ vector_Q, hash_map_Q, new_symbol, new_function,
+ new_list, new_vector, new_hash_map, Env, types_ns)
+from reader import (read_str, Blank)
+
+# read
+def READ(str):
+ return read_str(str)
+
+# eval
+def eval_ast(ast, env):
+ if symbol_Q(ast):
+ return env.get(ast)
+ elif list_Q(ast):
+ return new_list(*map(lambda a: EVAL(a, env), ast))
+ elif vector_Q(ast):
+ return new_vector(*map(lambda a: EVAL(a, env), ast))
+ elif hash_map_Q(ast):
+ keyvals = []
+ for k in ast.keys():
+ keyvals.append(EVAL(k, env))
+ keyvals.append(EVAL(ast[k], env))
+ return new_hash_map(*keyvals)
+ else:
+ return ast # primitive value, return unchanged
+
+def EVAL(ast, env):
+ #print("EVAL %s" % ast)
+ if not list_Q(ast):
+ return eval_ast(ast, env)
+
+ # apply list
+ if len(ast) == 0: return ast
+ a0 = ast[0]
+
+ if "def!" == a0:
+ a1, a2 = ast[1], ast[2]
+ res = EVAL(a2, env)
+ return env.set(a1, res)
+ elif "let*" == a0:
+ a1, a2 = ast[1], ast[2]
+ let_env = Env(env)
+ for i in range(0, len(a1), 2):
+ let_env.set(a1[i], EVAL(a1[i+1], let_env))
+ return EVAL(a2, let_env)
+ elif "do" == a0:
+ el = eval_ast(ast[1:], env)
+ return el[-1]
+ elif "if" == a0:
+ a1, a2 = ast[1], ast[2]
+ cond = EVAL(a1, env)
+ if cond is None or cond is False:
+ if len(ast) > 3: return EVAL(ast[3], env)
+ else: return None
+ else:
+ return EVAL(a2, env)
+ elif "fn*" == a0:
+ a1, a2 = ast[1], ast[2]
+ return new_function(EVAL, a2, env, a1)
+ else:
+ el = eval_ast(ast, env)
+ f = el[0]
+ return f(*el[1:])
+
+# print
+def PRINT(exp):
+ return pr_str(exp)
+
+# repl
+repl_env = Env()
+def REP(str):
+ return PRINT(EVAL(READ(str), repl_env))
+def _ref(k,v): repl_env.set(k, v)
+
+# Import types functions
+for name, val in types_ns.items(): _ref(name, val)
+
+# Defined using the language itself
+REP("(def! not (fn* (a) (if a false true)))")
+
+while True:
+ try:
+ line = mal_readline.readline("user> ")
+ if line == None: break
+ if line == "": continue
+ print(REP(line))
+ except Blank: continue
+ except Exception as e:
+ print "".join(traceback.format_exception(*sys.exc_info()))
diff --git a/python/step5_tco.py b/python/step5_tco.py
new file mode 100644
index 0000000..ffde863
--- /dev/null
+++ b/python/step5_tco.py
@@ -0,0 +1,99 @@
+import sys, traceback
+import mal_readline
+from mal_types import (pr_str, sequential_Q, symbol_Q, coll_Q, list_Q,
+ vector_Q, hash_map_Q, new_symbol, new_function,
+ new_list, new_vector, new_hash_map, Env, types_ns)
+from reader import (read_str, Blank)
+
+# read
+def READ(str):
+ return read_str(str)
+
+# eval
+def eval_ast(ast, env):
+ if symbol_Q(ast):
+ return env.get(ast)
+ elif list_Q(ast):
+ return new_list(*map(lambda a: EVAL(a, env), ast))
+ elif vector_Q(ast):
+ return new_vector(*map(lambda a: EVAL(a, env), ast))
+ elif hash_map_Q(ast):
+ keyvals = []
+ for k in ast.keys():
+ keyvals.append(EVAL(k, env))
+ keyvals.append(EVAL(ast[k], env))
+ return new_hash_map(*keyvals)
+ else:
+ return ast # primitive value, return unchanged
+
+def EVAL(ast, env):
+ while True:
+ #print("EVAL %s" % ast)
+ if not list_Q(ast):
+ return eval_ast(ast, env)
+
+ # apply list
+ if len(ast) == 0: return ast
+ a0 = ast[0]
+
+ if "def!" == a0:
+ a1, a2 = ast[1], ast[2]
+ res = EVAL(a2, env)
+ return env.set(a1, res)
+ elif "let*" == a0:
+ a1, a2 = ast[1], ast[2]
+ let_env = Env(env)
+ for i in range(0, len(a1), 2):
+ let_env.set(a1[i], EVAL(a1[i+1], let_env))
+ return EVAL(a2, let_env)
+ elif "do" == a0:
+ eval_ast(ast[1:-1], env)
+ ast = ast[-1]
+ # Continue loop (TCO)
+ elif "if" == a0:
+ a1, a2 = ast[1], ast[2]
+ cond = EVAL(a1, env)
+ if cond is None or cond is False:
+ if len(ast) > 3: ast = ast[3]
+ else: ast = None
+ else:
+ ast = a2
+ # Continue loop (TCO)
+ elif "fn*" == a0:
+ a1, a2 = ast[1], ast[2]
+ return new_function(EVAL, a2, env, a1)
+ else:
+ el = eval_ast(ast, env)
+ f = el[0]
+ if hasattr(f, '__meta__') and f.__meta__.has_key("exp"):
+ m = f.__meta__
+ ast = m['exp']
+ env = Env(m['env'], m['params'], el[1:])
+ else:
+ return f(*el[1:])
+
+# print
+def PRINT(exp):
+ return pr_str(exp)
+
+# repl
+repl_env = Env()
+def REP(str):
+ return PRINT(EVAL(READ(str), repl_env))
+def _ref(k,v): repl_env.set(k, v)
+
+# Import types functions
+for name, val in types_ns.items(): _ref(name, val)
+
+# Defined using the language itself
+REP("(def! not (fn* (a) (if a false true)))")
+
+while True:
+ try:
+ line = mal_readline.readline("user> ")
+ if line == None: break
+ if line == "": continue
+ print(REP(line))
+ except Blank: continue
+ except Exception as e:
+ print "".join(traceback.format_exception(*sys.exc_info()))
diff --git a/python/step6_file.py b/python/step6_file.py
new file mode 100644
index 0000000..b53863a
--- /dev/null
+++ b/python/step6_file.py
@@ -0,0 +1,108 @@
+import sys, traceback
+import mal_readline
+from mal_types import (pr_str, sequential_Q, symbol_Q, coll_Q, list_Q,
+ vector_Q, hash_map_Q, new_symbol, new_function,
+ new_list, new_vector, new_hash_map, Env, types_ns)
+from reader import (read_str, Blank)
+
+# read
+def READ(str):
+ return read_str(str)
+
+# eval
+def eval_ast(ast, env):
+ if symbol_Q(ast):
+ return env.get(ast)
+ elif list_Q(ast):
+ return new_list(*map(lambda a: EVAL(a, env), ast))
+ elif vector_Q(ast):
+ return new_vector(*map(lambda a: EVAL(a, env), ast))
+ elif hash_map_Q(ast):
+ keyvals = []
+ for k in ast.keys():
+ keyvals.append(EVAL(k, env))
+ keyvals.append(EVAL(ast[k], env))
+ return new_hash_map(*keyvals)
+ else:
+ return ast # primitive value, return unchanged
+
+def EVAL(ast, env):
+ while True:
+ #print("EVAL %s" % ast)
+ if not list_Q(ast):
+ return eval_ast(ast, env)
+
+ # apply list
+ if len(ast) == 0: return ast
+ a0 = ast[0]
+
+ if "def!" == a0:
+ a1, a2 = ast[1], ast[2]
+ res = EVAL(a2, env)
+ return env.set(a1, res)
+ elif "let*" == a0:
+ a1, a2 = ast[1], ast[2]
+ let_env = Env(env)
+ for i in range(0, len(a1), 2):
+ let_env.set(a1[i], EVAL(a1[i+1], let_env))
+ return EVAL(a2, let_env)
+ elif "do" == a0:
+ eval_ast(ast[1:-1], env)
+ ast = ast[-1]
+ # Continue loop (TCO)
+ elif "if" == a0:
+ a1, a2 = ast[1], ast[2]
+ cond = EVAL(a1, env)
+ if cond is None or cond is False:
+ if len(ast) > 3: ast = ast[3]
+ else: ast = None
+ else:
+ ast = a2
+ # Continue loop (TCO)
+ elif "fn*" == a0:
+ a1, a2 = ast[1], ast[2]
+ return new_function(EVAL, a2, env, a1)
+ else:
+ el = eval_ast(ast, env)
+ f = el[0]
+ if hasattr(f, '__meta__') and f.__meta__.has_key("exp"):
+ m = f.__meta__
+ ast = m['exp']
+ env = Env(m['env'], m['params'], el[1:])
+ else:
+ return f(*el[1:])
+
+# print
+def PRINT(exp):
+ return pr_str(exp)
+
+# repl
+repl_env = Env()
+def REP(str):
+ return PRINT(EVAL(READ(str), repl_env))
+def _ref(k,v): repl_env.set(k, v)
+
+# Import types functions
+for name, val in types_ns.items(): _ref(name, val)
+
+_ref('read-string', read_str)
+_ref('eval', lambda ast: EVAL(ast, repl_env))
+_ref('slurp', lambda file: open(file).read())
+_ref('slurp-do', lambda file: "(do" + open(file).read() + ")")
+
+# 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 len(sys.argv) >= 2:
+ REP('(load-file "' + sys.argv[1] + '")')
+else:
+ while True:
+ try:
+ line = mal_readline.readline("user> ")
+ if line == None: break
+ if line == "": continue
+ print(REP(line))
+ except Blank: continue
+ except Exception as e:
+ print "".join(traceback.format_exception(*sys.exc_info()))
diff --git a/python/step7_quote.py b/python/step7_quote.py
new file mode 100644
index 0000000..3054bb0
--- /dev/null
+++ b/python/step7_quote.py
@@ -0,0 +1,125 @@
+import sys, traceback
+import mal_readline
+from mal_types import (pr_str, sequential_Q, symbol_Q, coll_Q, list_Q,
+ vector_Q, hash_map_Q, new_symbol, new_function,
+ new_list, new_vector, new_hash_map, Env, types_ns)
+from reader import (read_str, Blank)
+
+# read
+def READ(str):
+ return read_str(str)
+
+# eval
+def is_pair(x):
+ return sequential_Q(x) and len(x) > 0
+
+def quasiquote(ast):
+ if not is_pair(ast):
+ return new_list(new_symbol("quote"), ast)
+ elif ast[0] == 'unquote':
+ return ast[1]
+ elif is_pair(ast[0]) and ast[0][0] == 'splice-unquote':
+ return new_list(new_symbol("concat"), ast[0][1], quasiquote(ast[1:]))
+ else:
+ return new_list(new_symbol("cons"), quasiquote(ast[0]), quasiquote(ast[1:]))
+
+def eval_ast(ast, env):
+ if symbol_Q(ast):
+ return env.get(ast)
+ elif list_Q(ast):
+ return new_list(*map(lambda a: EVAL(a, env), ast))
+ elif vector_Q(ast):
+ return new_vector(*map(lambda a: EVAL(a, env), ast))
+ elif hash_map_Q(ast):
+ keyvals = []
+ for k in ast.keys():
+ keyvals.append(EVAL(k, env))
+ keyvals.append(EVAL(ast[k], env))
+ return new_hash_map(*keyvals)
+ else:
+ return ast # primitive value, return unchanged
+
+def EVAL(ast, env):
+ while True:
+ #print("EVAL %s" % ast)
+ if not list_Q(ast):
+ return eval_ast(ast, env)
+
+ # apply list
+ if len(ast) == 0: return ast
+ a0 = ast[0]
+
+ if "def!" == a0:
+ a1, a2 = ast[1], ast[2]
+ res = EVAL(a2, env)
+ return env.set(a1, res)
+ elif "let*" == a0:
+ a1, a2 = ast[1], ast[2]
+ let_env = Env(env)
+ for i in range(0, len(a1), 2):
+ let_env.set(a1[i], EVAL(a1[i+1], let_env))
+ return EVAL(a2, let_env)
+ elif "quote" == a0:
+ return ast[1]
+ elif "quasiquote" == a0:
+ return EVAL(quasiquote(ast[1]), env)
+ elif "do" == a0:
+ eval_ast(ast[1:-1], env)
+ ast = ast[-1]
+ # Continue loop (TCO)
+ elif "if" == a0:
+ a1, a2 = ast[1], ast[2]
+ cond = EVAL(a1, env)
+ if cond is None or cond is False:
+ if len(ast) > 3: ast = ast[3]
+ else: ast = None
+ else:
+ ast = a2
+ # Continue loop (TCO)
+ elif "fn*" == a0:
+ a1, a2 = ast[1], ast[2]
+ return new_function(EVAL, a2, env, a1)
+ else:
+ el = eval_ast(ast, env)
+ f = el[0]
+ if hasattr(f, '__meta__') and f.__meta__.has_key("exp"):
+ m = f.__meta__
+ ast = m['exp']
+ env = Env(m['env'], m['params'], el[1:])
+ else:
+ return f(*el[1:])
+
+# print
+def PRINT(exp):
+ return pr_str(exp)
+
+# repl
+repl_env = Env()
+def REP(str):
+ return PRINT(EVAL(READ(str), repl_env))
+def _ref(k,v): repl_env.set(k, v)
+
+# Import types functions
+for name, val in types_ns.items(): _ref(name, val)
+
+_ref('read-string', read_str)
+_ref('eval', lambda ast: EVAL(ast, repl_env))
+_ref('slurp', lambda file: open(file).read())
+_ref('slurp-do', lambda file: "(do" + open(file).read() + ")")
+
+# 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 len(sys.argv) >= 2:
+ REP('(load-file "' + sys.argv[1] + '")')
+else:
+ while True:
+ try:
+ line = mal_readline.readline("user> ")
+ if line == None: break
+ if line == "": continue
+ print(REP(line))
+ except Blank: continue
+ except Exception as e:
+ print "".join(traceback.format_exception(*sys.exc_info()))
diff --git a/python/step8_macros.py b/python/step8_macros.py
new file mode 100644
index 0000000..616e7d3
--- /dev/null
+++ b/python/step8_macros.py
@@ -0,0 +1,145 @@
+import sys, traceback
+import mal_readline
+from mal_types import (pr_str, sequential_Q, symbol_Q, coll_Q, list_Q,
+ vector_Q, hash_map_Q, new_symbol, new_function,
+ new_list, new_vector, new_hash_map, Env, types_ns)
+from reader import (read_str, Blank)
+
+# read
+def READ(str):
+ return read_str(str)
+
+# eval
+def is_pair(x):
+ return sequential_Q(x) and len(x) > 0
+
+def quasiquote(ast):
+ if not is_pair(ast):
+ return new_list(new_symbol("quote"), ast)
+ elif ast[0] == 'unquote':
+ return ast[1]
+ elif is_pair(ast[0]) and ast[0][0] == 'splice-unquote':
+ return new_list(new_symbol("concat"), ast[0][1], quasiquote(ast[1:]))
+ else:
+ return new_list(new_symbol("cons"), quasiquote(ast[0]), quasiquote(ast[1:]))
+
+def is_macro_call(ast, env):
+ return (list_Q(ast) and
+ symbol_Q(ast[0]) and
+ env.find(ast[0]) and
+ hasattr(env.get(ast[0]), '_ismacro_'))
+
+def macroexpand(ast, env):
+ while is_macro_call(ast, env):
+ mac = env.get(ast[0])
+ ast = macroexpand(mac(*ast[1:]), env)
+ return ast
+
+def eval_ast(ast, env):
+ if symbol_Q(ast):
+ return env.get(ast)
+ elif list_Q(ast):
+ return new_list(*map(lambda a: EVAL(a, env), ast))
+ elif vector_Q(ast):
+ return new_vector(*map(lambda a: EVAL(a, env), ast))
+ elif hash_map_Q(ast):
+ keyvals = []
+ for k in ast.keys():
+ keyvals.append(EVAL(k, env))
+ keyvals.append(EVAL(ast[k], env))
+ return new_hash_map(*keyvals)
+ else:
+ return ast # primitive value, return unchanged
+
+def EVAL(ast, env):
+ while True:
+ #print("EVAL %s" % ast)
+ if not list_Q(ast):
+ return eval_ast(ast, env)
+
+ # apply list
+ ast = macroexpand(ast, env)
+ if not list_Q(ast): return ast
+ if len(ast) == 0: return ast
+
+ a0 = ast[0]
+ if "def!" == a0:
+ a1, a2 = ast[1], ast[2]
+ res = EVAL(a2, env)
+ return env.set(a1, res)
+ elif "let*" == a0:
+ a1, a2 = ast[1], ast[2]
+ let_env = Env(env)
+ for i in range(0, len(a1), 2):
+ let_env.set(a1[i], EVAL(a1[i+1], let_env))
+ return EVAL(a2, let_env)
+ elif "quote" == a0:
+ return ast[1]
+ elif "quasiquote" == a0:
+ return EVAL(quasiquote(ast[1]), env)
+ elif 'defmacro!' == a0:
+ func = EVAL(ast[2], env)
+ func._ismacro_ = True
+ return env.set(ast[1], func)
+ elif 'macroexpand' == a0:
+ return macroexpand(ast[1], env)
+ elif "do" == a0:
+ eval_ast(ast[1:-1], env)
+ ast = ast[-1]
+ # Continue loop (TCO)
+ elif "if" == a0:
+ a1, a2 = ast[1], ast[2]
+ cond = EVAL(a1, env)
+ if cond is None or cond is False:
+ if len(ast) > 3: ast = ast[3]
+ else: ast = None
+ else:
+ ast = a2
+ # Continue loop (TCO)
+ elif "fn*" == a0:
+ a1, a2 = ast[1], ast[2]
+ return new_function(EVAL, a2, env, a1)
+ else:
+ el = eval_ast(ast, env)
+ f = el[0]
+ if hasattr(f, '__meta__') and f.__meta__.has_key("exp"):
+ m = f.__meta__
+ ast = m['exp']
+ env = Env(m['env'], m['params'], el[1:])
+ else:
+ return f(*el[1:])
+
+# print
+def PRINT(exp):
+ return pr_str(exp)
+
+# repl
+repl_env = Env()
+def REP(str):
+ return PRINT(EVAL(READ(str), repl_env))
+def _ref(k,v): repl_env.set(k, v)
+
+# Import types functions
+for name, val in types_ns.items(): _ref(name, val)
+
+_ref('read-string', read_str)
+_ref('eval', lambda ast: EVAL(ast, repl_env))
+_ref('slurp', lambda file: open(file).read())
+_ref('slurp-do', lambda file: "(do" + open(file).read() + ")")
+
+# 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 len(sys.argv) >= 2:
+ REP('(load-file "' + sys.argv[1] + '")')
+else:
+ while True:
+ try:
+ line = mal_readline.readline("user> ")
+ if line == None: break
+ if line == "": continue
+ print(REP(line))
+ except Blank: continue
+ except Exception as e:
+ print "".join(traceback.format_exception(*sys.exc_info()))
diff --git a/python/step9_interop.py b/python/step9_interop.py
new file mode 100644
index 0000000..3a20960
--- /dev/null
+++ b/python/step9_interop.py
@@ -0,0 +1,154 @@
+import sys, traceback
+import mal_readline
+from mal_types import (pr_str, sequential_Q, symbol_Q, coll_Q, list_Q,
+ vector_Q, hash_map_Q, new_symbol, new_function,
+ new_list, new_vector, new_hash_map, Env, types_ns)
+from reader import (read_str, Blank)
+
+# read
+def READ(str):
+ return read_str(str)
+
+# eval
+def is_pair(x):
+ return sequential_Q(x) and len(x) > 0
+
+def quasiquote(ast):
+ if not is_pair(ast):
+ return new_list(new_symbol("quote"), ast)
+ elif ast[0] == 'unquote':
+ return ast[1]
+ elif is_pair(ast[0]) and ast[0][0] == 'splice-unquote':
+ return new_list(new_symbol("concat"), ast[0][1], quasiquote(ast[1:]))
+ else:
+ return new_list(new_symbol("cons"), quasiquote(ast[0]), quasiquote(ast[1:]))
+
+def is_macro_call(ast, env):
+ return (list_Q(ast) and
+ symbol_Q(ast[0]) and
+ env.find(ast[0]) and
+ hasattr(env.get(ast[0]), '_ismacro_'))
+
+def macroexpand(ast, env):
+ while is_macro_call(ast, env):
+ mac = env.get(ast[0])
+ ast = macroexpand(mac(*ast[1:]), env)
+ return ast
+
+def eval_ast(ast, env):
+ if symbol_Q(ast):
+ return env.get(ast)
+ elif list_Q(ast):
+ return new_list(*map(lambda a: EVAL(a, env), ast))
+ elif vector_Q(ast):
+ return new_vector(*map(lambda a: EVAL(a, env), ast))
+ elif hash_map_Q(ast):
+ keyvals = []
+ for k in ast.keys():
+ keyvals.append(EVAL(k, env))
+ keyvals.append(EVAL(ast[k], env))
+ return new_hash_map(*keyvals)
+ else:
+ return ast # primitive value, return unchanged
+
+def EVAL(ast, env):
+ while True:
+ #print("EVAL %s" % ast)
+ if not list_Q(ast):
+ return eval_ast(ast, env)
+
+ # apply list
+ ast = macroexpand(ast, env)
+ if not list_Q(ast): return ast
+ if len(ast) == 0: return ast
+
+ a0 = ast[0]
+ if "def!" == a0:
+ a1, a2 = ast[1], ast[2]
+ res = EVAL(a2, env)
+ return env.set(a1, res)
+ elif "let*" == a0:
+ a1, a2 = ast[1], ast[2]
+ let_env = Env(env)
+ for i in range(0, len(a1), 2):
+ let_env.set(a1[i], EVAL(a1[i+1], let_env))
+ return EVAL(a2, let_env)
+ elif "quote" == a0:
+ return ast[1]
+ elif "quasiquote" == a0:
+ return EVAL(quasiquote(ast[1]), env)
+ elif 'defmacro!' == a0:
+ func = EVAL(ast[2], env)
+ func._ismacro_ = True
+ return env.set(ast[1], func)
+ elif 'macroexpand' == a0:
+ return macroexpand(ast[1], env)
+ elif "py!*" == a0:
+ exec compile(ast[1], '', 'single') in globals()
+ return None
+ elif "py*" == a0:
+ return eval(ast[1])
+ elif "." == a0:
+ el = eval_ast(ast[2:], env)
+ f = eval(ast[1])
+ return f(*el)
+ elif "do" == a0:
+ eval_ast(ast[1:-1], env)
+ ast = ast[-1]
+ # Continue loop (TCO)
+ elif "if" == a0:
+ a1, a2 = ast[1], ast[2]
+ cond = EVAL(a1, env)
+ if cond is None or cond is False:
+ if len(ast) > 3: ast = ast[3]
+ else: ast = None
+ else:
+ ast = a2
+ # Continue loop (TCO)
+ elif "fn*" == a0:
+ a1, a2 = ast[1], ast[2]
+ return new_function(EVAL, a2, env, a1)
+ else:
+ el = eval_ast(ast, env)
+ f = el[0]
+ if hasattr(f, '__meta__') and f.__meta__.has_key("exp"):
+ m = f.__meta__
+ ast = m['exp']
+ env = Env(m['env'], m['params'], el[1:])
+ else:
+ return f(*el[1:])
+
+# print
+def PRINT(exp):
+ return pr_str(exp)
+
+# repl
+repl_env = Env()
+def REP(str):
+ return PRINT(EVAL(READ(str), repl_env))
+def _ref(k,v): repl_env.set(k, v)
+
+# Import types functions
+for name, val in types_ns.items(): _ref(name, val)
+
+_ref('read-string', read_str)
+_ref('eval', lambda ast: EVAL(ast, repl_env))
+_ref('slurp', lambda file: open(file).read())
+_ref('slurp-do', lambda file: "(do" + open(file).read() + ")")
+
+# 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 len(sys.argv) >= 2:
+ REP('(load-file "' + sys.argv[1] + '")')
+else:
+ while True:
+ try:
+ line = mal_readline.readline("user> ")
+ if line == None: break
+ if line == "": continue
+ print(REP(line))
+ except Blank: continue
+ except Exception as e:
+ print "".join(traceback.format_exception(*sys.exc_info()))
diff --git a/python/stepA_more.py b/python/stepA_more.py
new file mode 100644
index 0000000..c0c5004
--- /dev/null
+++ b/python/stepA_more.py
@@ -0,0 +1,168 @@
+import sys, traceback
+import mal_readline
+from mal_types import (pr_str, sequential_Q, symbol_Q, coll_Q, list_Q,
+ vector_Q, hash_map_Q, new_symbol, new_function,
+ new_list, new_vector, new_hash_map, Env, types_ns)
+from reader import (read_str, Blank)
+
+# read
+def READ(str):
+ return read_str(str)
+
+# eval
+def is_pair(x):
+ return sequential_Q(x) and len(x) > 0
+
+def quasiquote(ast):
+ if not is_pair(ast):
+ return new_list(new_symbol("quote"), ast)
+ elif ast[0] == 'unquote':
+ return ast[1]
+ elif is_pair(ast[0]) and ast[0][0] == 'splice-unquote':
+ return new_list(new_symbol("concat"), ast[0][1], quasiquote(ast[1:]))
+ else:
+ return new_list(new_symbol("cons"), quasiquote(ast[0]), quasiquote(ast[1:]))
+
+def is_macro_call(ast, env):
+ return (list_Q(ast) and
+ symbol_Q(ast[0]) and
+ env.find(ast[0]) and
+ hasattr(env.get(ast[0]), '_ismacro_'))
+
+def macroexpand(ast, env):
+ while is_macro_call(ast, env):
+ mac = env.get(ast[0])
+ ast = macroexpand(mac(*ast[1:]), env)
+ return ast
+
+def eval_ast(ast, env):
+ if symbol_Q(ast):
+ return env.get(ast)
+ elif list_Q(ast):
+ return new_list(*map(lambda a: EVAL(a, env), ast))
+ elif vector_Q(ast):
+ return new_vector(*map(lambda a: EVAL(a, env), ast))
+ elif hash_map_Q(ast):
+ keyvals = []
+ for k in ast.keys():
+ keyvals.append(EVAL(k, env))
+ keyvals.append(EVAL(ast[k], env))
+ return new_hash_map(*keyvals)
+ else:
+ return ast # primitive value, return unchanged
+
+def EVAL(ast, env):
+ while True:
+ #print("EVAL %s" % ast)
+ if not list_Q(ast):
+ return eval_ast(ast, env)
+
+ # apply list
+ ast = macroexpand(ast, env)
+ if not list_Q(ast): return ast
+ if len(ast) == 0: return ast
+
+ a0 = ast[0]
+ if "def!" == a0:
+ a1, a2 = ast[1], ast[2]
+ res = EVAL(a2, env)
+ return env.set(a1, res)
+ elif "let*" == a0:
+ a1, a2 = ast[1], ast[2]
+ let_env = Env(env)
+ for i in range(0, len(a1), 2):
+ let_env.set(a1[i], EVAL(a1[i+1], let_env))
+ return EVAL(a2, let_env)
+ elif "quote" == a0:
+ return ast[1]
+ elif "quasiquote" == a0:
+ return EVAL(quasiquote(ast[1]), env)
+ elif 'defmacro!' == a0:
+ func = EVAL(ast[2], env)
+ func._ismacro_ = True
+ return env.set(ast[1], func)
+ elif 'macroexpand' == a0:
+ return macroexpand(ast[1], env)
+ elif "py!*" == a0:
+ exec compile(ast[1], '', 'single') in globals()
+ return None
+ elif "py*" == a0:
+ return eval(ast[1])
+ elif "." == a0:
+ el = eval_ast(ast[2:], env)
+ f = eval(ast[1])
+ return f(*el)
+ elif "try*" == a0:
+ a1, a2 = ast[1], ast[2]
+ if a2[0] == "catch*":
+ try:
+ return EVAL(a1, env);
+ except Exception as exc:
+ exc = exc.message
+ catch_env = Env(env, [a2[1]], [exc])
+ return EVAL(a2[2], catch_env)
+ else:
+ return EVAL(a1, env);
+ elif "do" == a0:
+ eval_ast(ast[1:-1], env)
+ ast = ast[-1]
+ # Continue loop (TCO)
+ elif "if" == a0:
+ a1, a2 = ast[1], ast[2]
+ cond = EVAL(a1, env)
+ if cond is None or cond is False:
+ if len(ast) > 3: ast = ast[3]
+ else: ast = None
+ else:
+ ast = a2
+ # Continue loop (TCO)
+ elif "fn*" == a0:
+ a1, a2 = ast[1], ast[2]
+ return new_function(EVAL, a2, env, a1)
+ else:
+ el = eval_ast(ast, env)
+ f = el[0]
+ if hasattr(f, '__meta__') and f.__meta__.has_key("exp"):
+ m = f.__meta__
+ ast = m['exp']
+ env = Env(m['env'], m['params'], el[1:])
+ else:
+ return f(*el[1:])
+
+# print
+def PRINT(exp):
+ return pr_str(exp)
+
+# repl
+repl_env = Env()
+def REP(str):
+ return PRINT(EVAL(READ(str), repl_env))
+def _ref(k,v): repl_env.set(k, v)
+
+# Import types functions
+for name, val in types_ns.items(): _ref(name, val)
+
+_ref('readline', lambda prompt: mal_readline.readline(prompt))
+_ref('read-string', read_str)
+_ref('eval', lambda ast: EVAL(ast, repl_env))
+_ref('slurp', lambda file: open(file).read())
+_ref('slurp-do', lambda file: "(do" + open(file).read() + ")")
+
+# 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 len(sys.argv) >= 2:
+ REP('(load-file "' + sys.argv[1] + '")')
+else:
+ while True:
+ try:
+ line = mal_readline.readline("user> ")
+ if line == None: break
+ if line == "": continue
+ print(REP(line))
+ except Blank: continue
+ except Exception as e:
+ print "".join(traceback.format_exception(*sys.exc_info()))
diff --git a/runtest.py b/runtest.py
new file mode 100755
index 0000000..736768a
--- /dev/null
+++ b/runtest.py
@@ -0,0 +1,115 @@
+#!/usr/bin/env python
+
+import os, sys, re
+import argparse
+
+# http://pexpect.sourceforge.net/pexpect.html
+from pexpect import spawn, EOF, TIMEOUT
+
+# TODO: do we need to support '\n' too
+sep = "\r\n"
+rundir = None
+
+parser = argparse.ArgumentParser(
+ description="Run a test file against a Mal implementation")
+parser.add_argument('--rundir',
+ help="change to the directory before running tests")
+parser.add_argument('--start-timeout', default=10, type=int,
+ help="default timeout for initial prompt")
+parser.add_argument('--test-timeout', default=20, type=int,
+ help="default timeout for each individual test action")
+
+parser.add_argument('test_file', type=argparse.FileType('r'),
+ help="a test file formatted as with mal test data")
+parser.add_argument('mal_cmd', nargs="*",
+ help="Mal implementation command line. Use '--' to "
+ "specify a Mal command line with dashed options.")
+
+args = parser.parse_args(sys.argv[1:])
+test_data = args.test_file.read().split('\n')
+
+if args.rundir: os.chdir(args.rundir)
+
+p = spawn(args.mal_cmd[0], args.mal_cmd[1:])
+
+test_idx = 0
+def read_test(data):
+ global test_idx
+ form, output, ret = None, "", None
+ while data:
+ test_idx += 1
+ line = data.pop(0)
+ if re.match(r"^\s*$", line): # blank line
+ continue
+ elif line[0:3] == ";;;": # ignore comment
+ continue
+ elif line[0:2] == ";;": # output comment
+ print line[3:]
+ continue
+ elif line[0:2] == ";": # unexpected comment
+ print "Test data error at line %d:\n%s" % (test_idx, line)
+ return None, None, None, test_idx
+ form = line # the line is a form to send
+
+ # Now find the output and return value
+ while data:
+ line = data[0]
+ if line[0:3] == ";=>":
+ ret = line[3:].replace('\\r', '\r').replace('\\n', '\n')
+ test_idx += 1
+ data.pop(0)
+ break
+ elif line[0:2] == "; ":
+ output = output + line[2:] + sep
+ test_idx += 1
+ data.pop(0)
+ else:
+ ret = "*"
+ break
+ if ret: break
+
+ return form, output, ret, test_idx
+
+
+# Wait for the initial prompt
+idx = p.expect(['user> ', 'mal-user> ', EOF, TIMEOUT],
+ timeout=args.start_timeout)
+if idx not in [0,1]:
+ print "Never got 'user> ' prompt"
+ print " Got : %s" % repr(p.before)
+ sys.exit(1)
+
+fail_cnt = 0
+
+while test_data:
+ form, out, ret, line_num = read_test(test_data)
+ if form == None:
+ break
+ sys.stdout.write("TEST: %s -> [%s,%s]" % (form, repr(out), repr(ret)))
+ sys.stdout.flush()
+ expected = "%s%s%s%s" % (form, sep, out, ret)
+
+ p.sendline(form)
+ try:
+ idx = p.expect(['\r\nuser> ', '\nuser> ',
+ '\r\nmal-user> ', '\nmal-user> '],
+ timeout=args.test_timeout)
+ #print "%s,%s,%s" % (idx, repr(p.before), repr(p.after))
+ if ret == "*" or p.before == expected:
+ print " -> SUCCESS"
+ else:
+ print " -> FAIL (line %d):" % line_num
+ print " Expected : %s" % repr(expected)
+ print " Got : %s" % repr(p.before)
+ fail_cnt += 1
+ except EOF:
+ print "Got EOF"
+ sys.exit(1)
+ except TIMEOUT:
+ print "Got TIMEOUT, received: %s" % repr(p.before)
+ sys.exit(1)
+
+if fail_cnt > 0:
+ print "FAILURES: %d" % fail_cnt
+ sys.exit(2)
+sys.exit(0)
diff --git a/tests/inc.mal b/tests/inc.mal
new file mode 100644
index 0000000..39ebc55
--- /dev/null
+++ b/tests/inc.mal
@@ -0,0 +1,4 @@
+(def! inc1 (fn* (a) (+ 1 a)))
+(def! inc2 (fn* (a) (+ 2 a)))
+(def! inc3 (fn* (a)
+ (+ 3 a)))
diff --git a/tests/incB.mal b/tests/incB.mal
new file mode 100644
index 0000000..ed28734
--- /dev/null
+++ b/tests/incB.mal
@@ -0,0 +1,14 @@
+;; A comment in a file
+(def! inc4 (fn* (a) (+ 4 a)))
+(def! inc5 (fn* (a) ;; a comment after code
+ (+ 5 a)))
+
+;; Test map split across lines
+(def! mymap {"a"
+ 1})
+
+;; Test commas as whitespace
+(def! myvec [1 2, 3])
+
+(prn "incB.mal finished")
+"incB.mal return string"
diff --git a/tests/step1_read_print.mal b/tests/step1_read_print.mal
new file mode 100644
index 0000000..f0d7a9a
--- /dev/null
+++ b/tests/step1_read_print.mal
@@ -0,0 +1,112 @@
+;; Testing read of comments
+ ;; whole line comment (not an exception)
+1 ; comment after expression
+;=>1
+1; comment after expression
+;=>1
+
+
+;; Testing read of nil/true/false
+nil
+;=>nil
+true
+;=>true
+false
+;=>false
+
+
+;; Testing read of numbers
+1
+;=>1
+7
+;=>7
+ 7
+;=>7
+
+
+;; Testing read of symbols
++
+;=>+
+abc
+;=>abc
+ abc
+;=>abc
+abc5
+;=>abc5
+abc-def
+;=>abc-def
+
+
+;; Testing read of strings
+"abc"
+;=>"abc"
+ "abc"
+;=>"abc"
+"abc (with parens)"
+;=>"abc (with parens)"
+"abc\"def"
+;=>"abc\"def"
+;;;"abc\ndef"
+;;;;=>"abc\ndef"
+
+
+;; Testing read of lists
+(+ 1 2)
+;=>(+ 1 2)
+((3 4))
+;=>((3 4))
+(+ 1 (+ 2 3))
+;=>(+ 1 (+ 2 3))
+ ( + 1 (+ 2 3 ) )
+;=>(+ 1 (+ 2 3))
+
+
+;; Testing read of vectors
+[+ 1 2]
+;=>[+ 1 2]
+[[3 4]]
+;=>[[3 4]]
+[+ 1 [+ 2 3]]
+;=>[+ 1 [+ 2 3]]
+ [ + 1 [+ 2 3 ] ]
+;=>[+ 1 [+ 2 3]]
+
+
+;; Testing read of hash maps
+{"abc" 1}
+;=>{"abc" 1}
+{"a" {"b" 2}}
+;=>{"a" {"b" 2}}
+{"a" {"b" {"c" 3}}}
+;=>{"a" {"b" {"c" 3}}}
+{ "a" {"b" { "cde" 3 } }}
+;=>{"a" {"b" {"cde" 3}}}
+
+
+;; Testing read of quoting
+'1
+;=>(quote 1)
+'(1 2 3)
+;=>(quote (1 2 3))
+`1
+;=>(quasiquote 1)
+`(1 2 3)
+;=>(quasiquote (1 2 3))
+~1
+;=>(unquote 1)
+~(1 2 3)
+;=>(unquote (1 2 3))
+~@(1 2 3)
+;=>(splice-unquote (1 2 3))
+
+
+;; Testing read of ^/metadata
+^{"a" 1} [1 2 3]
+;=>(with-meta [1 2 3] {"a" 1})
+
+
+;; Testing read of @/deref
+@a
+;=>(deref a)
+
+
diff --git a/tests/step2_eval.mal b/tests/step2_eval.mal
new file mode 100644
index 0000000..33c7b17
--- /dev/null
+++ b/tests/step2_eval.mal
@@ -0,0 +1,19 @@
+;; Testing evaluation of arithmetic operations
+(+ 1 2)
+;=>3
+
+(+ 5 (* 2 3))
+;=>11
+
+(- (+ 5 (* 2 3)) 3)
+;=>8
+
+(/ (- (+ 5 (* 2 3)) 3) 4)
+;=>2
+
+;; Testing evaluation within collection literals
+[1 2 (+ 1 2)]
+;=>[1 2 3]
+
+{"a" (+ 7 8)}
+;=>{"a" 15}
diff --git a/tests/step3_env.mal b/tests/step3_env.mal
new file mode 100644
index 0000000..448a446
--- /dev/null
+++ b/tests/step3_env.mal
@@ -0,0 +1,38 @@
+;; Testing REPL_ENV
+(+ 1 2)
+;=>3
+(/ (- (+ 5 (* 2 3)) 3) 4)
+;=>2
+
+
+;; Testing def!
+(def! x 3)
+;=>3
+x
+;=>3
+(def! x 4)
+;=>4
+x
+;=>4
+(def! y (+ 1 7))
+;=>8
+y
+;=>8
+
+
+;; Testing let*
+(let* (z 9) z)
+;=>9
+(let* (x 9) x)
+;=>9
+x
+;=>4
+(let* (z (+ 2 3)) (+ 1 z))
+;=>6
+(let* (p (+ 2 3) q (+ 2 p)) (+ p q))
+;=>12
+
+
+;; Testing vector evaluation
+(let* (a 5 b 6) [3 4 a [b 7] 8])
+;=>[3 4 5 [6 7] 8]
diff --git a/tests/step4_if_fn_do.mal b/tests/step4_if_fn_do.mal
new file mode 100644
index 0000000..a4ce46f
--- /dev/null
+++ b/tests/step4_if_fn_do.mal
@@ -0,0 +1,345 @@
+;; -----------------------------------------------------
+
+;; Testing string quoting
+
+""
+;=>""
+
+"abc"
+;=>"abc"
+
+"abc def"
+;=>"abc def"
+
+"\""
+;=>"\""
+
+
+;; Testing pr-str
+
+(pr-str)
+;=>""
+
+(pr-str "")
+;=>"\"\""
+
+(pr-str "abc")
+;=>"\"abc\""
+
+(pr-str "abc def" "ghi jkl")
+;=>"\"abc def\" \"ghi jkl\""
+
+(pr-str "\"")
+;=>"\"\\\"\""
+
+(pr-str (list 1 2 "abc" "\"") "def")
+;=>"(1 2 \"abc\" \"\\\"\") \"def\""
+
+
+;; Testing str
+
+(str)
+;=>""
+
+(str "")
+;=>""
+
+(str "abc")
+;=>"abc"
+
+(str "\"")
+;=>"\""
+
+(str 1 "abc" 3)
+;=>"1abc3"
+
+(str "abc def" "ghi jkl")
+;=>"abc defghi jkl"
+
+;;; TODO: get this working properly
+;;;(str (list 1 2 "abc" "\"") "def")
+;;;;=>"(1 2 \"abc\" \"\\\"\")def"
+
+
+;; Testing prn
+(prn)
+;
+;=>nil
+
+(prn "")
+; ""
+;=>nil
+
+(prn "abc")
+; "abc"
+;=>nil
+
+(prn "abc def" "ghi jkl")
+; "abc def" "ghi jkl"
+
+(prn "\"")
+; "\""
+;=>nil
+
+(prn (list 1 2 "abc" "\"") "def")
+; (1 2 "abc" "\"") "def"
+;=>nil
+
+
+;; Testing println
+(println)
+;
+;=>nil
+
+(println "")
+;
+;=>nil
+
+(println "abc")
+; abc
+;=>nil
+
+(println "abc def" "ghi jkl")
+; abc def ghi jkl
+
+(println "\"")
+; "
+;=>nil
+
+(println (list 1 2 "abc" "\"") "def")
+; (1 2 abc ") def
+;=>nil
+
+;; -----------------------------------------------------
+
+
+;; Testing list functions
+(list)
+;=>()
+(list? (list))
+;=>true
+(empty? (list))
+;=>true
+(empty? (list 1))
+;=>false
+(list 1 2 3)
+;=>(1 2 3)
+(count (list 1 2 3))
+;=>3
+(if (> (count (list 1 2 3)) 3) "yes" "no")
+;=>"no"
+(if (>= (count (list 1 2 3)) 3) "yes" "no")
+;=>"yes"
+
+
+;; Testing if form
+(if true 7 8)
+;=>7
+(if false 7 8)
+;=>8
+(if true (+ 1 7) (+ 1 8))
+;=>8
+(if false (+ 1 7) (+ 1 8))
+;=>9
+(if nil 7 8)
+;=>8
+(if 0 7 8)
+;=>7
+(if "" 7 8)
+;=>7
+(if (list) 7 8)
+;=>7
+(if (list 1 2 3) 7 8)
+;=>7
+(if [] 7 8)
+;=>7
+
+
+;; Testing 1-way if form
+(if false (+ 1 7))
+;=>nil
+(if nil 8 7)
+;=>7
+(if true (+ 1 7))
+;=>8
+
+
+;; Testing basic conditionals
+(= 2 1)
+;=>false
+(= 1 1)
+;=>true
+(= 1 2)
+;=>false
+(= 1 (+ 1 1))
+;=>false
+(= 2 (+ 1 1))
+;=>true
+
+(> 2 1)
+;=>true
+(> 1 1)
+;=>false
+(> 1 2)
+;=>false
+
+(>= 2 1)
+;=>true
+(>= 1 1)
+;=>true
+(>= 1 2)
+;=>false
+
+(< 2 1)
+;=>false
+(< 1 1)
+;=>false
+(< 1 2)
+;=>true
+
+(<= 2 1)
+;=>false
+(<= 1 1)
+;=>true
+(<= 1 2)
+;=>true
+
+
+;; Testing equality
+(= 1 1)
+;=>true
+(= 0 0)
+;=>true
+(= 1 0)
+;=>false
+(= "" "")
+;=>true
+(= "abc" "")
+;=>false
+(= "" "abc")
+;=>false
+(= "abc" "def")
+;=>false
+
+(= (list) (list))
+;=>true
+(= (list 1 2) (list 1 2))
+;=>true
+(= (list 1) (list))
+;=>false
+(= (list) (list 1))
+;=>false
+(= 0 (list))
+;=>false
+(= (list) 0)
+;=>false
+(= (list) "")
+;=>false
+(= "" (list))
+;=>false
+
+(= [] (list))
+;=>true
+(= (list 1 2) [1 2])
+;=>true
+(= (list 1) [])
+;=>false
+(= [] [1])
+;=>false
+(= 0 [])
+;=>false
+(= [] 0)
+;=>false
+(= [] "")
+;=>false
+(= "" [])
+;=>false
+
+
+;; Testing builtin and user defined functions
+(+ 1 2)
+;=>3
+( (fn* (a b) (+ b a)) 3 4)
+;=>7
+( (fn* () 4) )
+;=>4
+
+
+;; Testing closures
+( ( (fn* (a) (fn* (b) (+ a b))) 5) 7)
+;=>12
+
+(def! gen-plus5 (fn* () (fn* (b) (+ 5 b))))
+(def! plus5 (gen-plus5))
+(plus5 7)
+;=>12
+
+(def! gen-plusX (fn* (x) (fn* (b) (+ x b))))
+(def! plus7 (gen-plusX 7))
+(plus7 8)
+;=>15
+
+
+;; Testing variable length arguments
+
+( (fn* (& more) (count more)) 1 2 3)
+;=>3
+( (fn* (& more) (count more)) 1)
+;=>1
+( (fn* (& more) (count more)) )
+;=>0
+( (fn* (a & more) (count more)) 1 2 3)
+;=>2
+( (fn* (a & more) (count more)) 1)
+;=>0
+
+
+;; Testing language defined not function
+(not false)
+;=>true
+(not true)
+;=>false
+(not "a")
+;=>false
+(not 0)
+;=>false
+
+
+;; Testing do form
+(do (prn "prn output1"))
+; "prn output1"
+;=>nil
+(do (prn "prn output2") 7)
+; "prn output2"
+;=>7
+(do (prn "prn output1") (prn "prn output2") (+ 1 2))
+; "prn output1"
+; "prn output2"
+;=>3
+
+(do (def! a 6) 7 (+ a 8))
+;=>14
+a
+;=>6
+
+
+;; Testing recursive sumdown function
+(def! sumdown (fn* (N) (if (> N 0) (+ N (sumdown (- N 1))) 0)))
+(sumdown 1)
+;=>1
+(sumdown 2)
+;=>3
+(sumdown 6)
+;=>21
+
+
+;; Testing recursive fibonacci function
+(def! fib (fn* (N) (if (= N 0) 1 (if (= N 1) 1 (+ (fib (- N 1)) (fib (- N 2)))))))
+(fib 1)
+;=>1
+(fib 2)
+;=>2
+(fib 4)
+;=>5
+(fib 10)
+;=>89
diff --git a/tests/step6_file.mal b/tests/step6_file.mal
new file mode 100644
index 0000000..c6df3eb
--- /dev/null
+++ b/tests/step6_file.mal
@@ -0,0 +1,17 @@
+;; Testing load-file
+
+(load-file "../tests/inc.mal")
+(inc1 7)
+;=>8
+(inc2 7)
+;=>9
+(inc3 9)
+;=>12
+
+(load-file "../tests/incB.mal")
+; "incB.mal finished"
+;=>"incB.mal return string"
+(inc4 7)
+;=>11
+(inc5 7)
+;=>12
diff --git a/tests/step7_quote.mal b/tests/step7_quote.mal
new file mode 100644
index 0000000..c41c7e2
--- /dev/null
+++ b/tests/step7_quote.mal
@@ -0,0 +1,69 @@
+;; Testing regular quote
+(quote 7)
+;=>7
+'7
+;=>7
+(quote (1 2 3))
+;=>(1 2 3)
+'(1 2 3)
+;=>(1 2 3)
+(quote (1 2 (3 4)))
+;=>(1 2 (3 4))
+'(1 2 (3 4))
+;=>(1 2 (3 4))
+
+
+;; Testing simple quasiquote
+(quasiquote 7)
+;=>7
+`7
+;=>7
+(quasiquote (1 2 3))
+;=>(1 2 3)
+`(1 2 3)
+;=>(1 2 3)
+(quasiquote (1 2 (3 4)))
+;=>(1 2 (3 4))
+`(1 2 (3 4))
+;=>(1 2 (3 4))
+
+
+;; Testing unquote
+`~7
+;=>7
+(def! a 8)
+;=>8
+`a
+;=>a
+`~a
+;=>8
+`(1 a 3)
+;=>(1 a 3)
+`(1 ~a 3)
+;=>(1 8 3)
+(def! b '(1 "b" "d"))
+;=>(1 "b" "d")
+`(1 b 3)
+;=>(1 b 3)
+`(1 ~b 3)
+;=>(1 (1 "b" "d") 3)
+
+
+;; Testing splice-unquote
+(def! c '(1 "b" "d"))
+;=>(1 "b" "d")
+`(1 c 3)
+;=>(1 c 3)
+`(1 ~@c 3)
+;=>(1 1 "b" "d" 3)
+
+
+;; Testing symbol equality
+(= 'abc 'abc)
+;=>true
+(= 'abc 'abcd)
+;=>false
+(= 'abc "abc")
+;=>false
+(= "abc" 'abc)
+;=>false
diff --git a/tests/step8_macros.mal b/tests/step8_macros.mal
new file mode 100644
index 0000000..351e0ca
--- /dev/null
+++ b/tests/step8_macros.mal
@@ -0,0 +1,94 @@
+;; Testing trivial macros
+(defmacro! one (fn* () 1))
+(one)
+;=>1
+(defmacro! two (fn* () 2))
+(two)
+;=>2
+
+;; Testing unless macros
+(defmacro! unless (fn* (pred a b) `(if ~pred ~b ~a)))
+(unless false 7 8)
+;=>7
+(unless true 7 8)
+;=>8
+(defmacro! unless2 (fn* (pred a b) `(if (not ~pred) ~a ~b)))
+(unless2 false 7 8)
+;=>7
+(unless2 true 7 8)
+;=>8
+
+;; Testing macroexpand
+(macroexpand (unless2 2 3 4))
+;=>(if (not 2) 3 4)
+
+;;
+;; Loading core.mal
+(load-file "../core.mal")
+
+;; Testing and macro
+(and)
+;=>true
+(and 1)
+;=>1
+(and 1 2)
+;=>2
+(and 1 2 3)
+;=>3
+(and 1 2 3 4)
+;=>4
+(and 1 2 3 4 false)
+;=>false
+(and 1 2 3 4 false 5)
+;=>false
+
+;; Testing or macro
+(or)
+;=>nil
+(or 1)
+;=>1
+(or 1 2 3 4)
+;=>1
+(or false 2)
+;=>2
+(or false nil 3)
+;=>3
+(or false nil false false nil 4)
+;=>4
+(or false nil 3 false nil 4)
+;=>3
+
+;; Testing -> macro
+
+(-> 7)
+;=>7
+(-> (list 7 8 9) first)
+;=>7
+(-> (list 7 8 9) (first))
+;=>7
+(-> (list 7 8 9) first (+ 7))
+;=>14
+(-> (list 7 8 9) rest (rest) first (+ 7))
+;=>16
+
+;; Testing cond macro
+
+(cond)
+;=>nil
+(cond true 7)
+;=>7
+(cond true 7 true 8)
+;=>7
+(cond false 7 true 8)
+;=>8
+(cond false 7 false 8 "else" 9)
+;=>9
+(cond false 7 (= 2 2) 8 "else" 9)
+;=>8
+(cond false 7 false 8 false 9)
+;=>nil
+
+;Testing all EVAL of non-default locations
+(let* [x (or nil "yes")] x)
+;=>"yes"
+
diff --git a/tests/stepA_more.mal b/tests/stepA_more.mal
new file mode 100644
index 0000000..bae226d
--- /dev/null
+++ b/tests/stepA_more.mal
@@ -0,0 +1,294 @@
+;;
+;; Testing try*/catch*
+
+(try* (abc 1 2) (catch* exc (prn exc))))
+; "'abc' not found"
+;=>nil
+
+;;;TODO: fix so long lines don't trigger ANSI escape codes
+;;;(try* (throw {"data" "foo"}) (catch* exc (do (prn "exc is:" exc) 7)))
+;;;; "exc is:" {"data" "foo"}
+;;;;=>7
+
+(try* (throw "my exception") (catch* exc (do (prn "exc:" exc) 7)))
+; "exc:" "my exception"
+;=>7
+
+
+;;
+;; Testing builtin functions
+
+(symbol? 'abc)
+;=>true
+(symbol? "abc")
+;=>false
+
+(nil? nil)
+;=>true
+(nil? true)
+;=>false
+
+(true? true)
+;=>true
+(true? false)
+;=>false
+(true? true?)
+;=>false
+
+(false? false)
+;=>true
+(false? true)
+;=>false
+
+(sequential? (list 1 2 3))
+;=>true
+(sequential? [15])
+;=>true
+(sequential? sequential?)
+;=>false
+(sequential? nil)
+;=>false
+(sequential? "abc")
+;=>false
+
+
+;; Testing apply function
+(apply + (list 2 3))
+;=>5
+(apply + 4 (list 5))
+;=>9
+(apply prn (list 1 2 "3" (list)))
+; 1 2 "3" ()
+;=>nil
+
+
+;; Testing map function
+(def! nums (list 1 2 3))
+(def! double (fn* (a) (* 2 a)))
+(double 3)
+;=>6
+(map double nums)
+;=>(2 4 6)
+
+
+;; Testing concat function
+(concat)
+;=>()
+(concat (list 1 2))
+;=>(1 2)
+(concat (list 1 2) (list 3 4))
+;=>(1 2 3 4)
+(concat (list 1 2) (list 3 4) (list 5 6))
+;=>(1 2 3 4 5 6)
+(concat [1 2] (list 3 4) [5 6])
+;=>(1 2 3 4 5 6)
+(concat (concat))
+;=>()
+
+;; Testing cons function
+(cons 1 (list))
+;=>(1)
+(cons 1 (list 2))
+;=>(1 2)
+(cons 1 (list 2 3))
+;=>(1 2 3)
+(cons (list 1) (list 2 3))
+;=>((1) 2 3)
+(cons [1] [2 3])
+;=>([1] 2 3)
+(cons 1 [2 3])
+;=>(1 2 3)
+
+;; Testing conj function
+(conj (list) 1)
+;=>(1)
+(conj (list 1) 2)
+;=>(1 2)
+(conj (list 2 3) 4)
+;=>(2 3 4)
+(conj (list 2 3) 4 5 6)
+;=>(2 3 4 5 6)
+(conj (list 1) (list 2 3))
+;=>(1 (2 3))
+(conj [1 2] [3 4] )
+;=>(1 2 [3 4])
+
+;; Testing first/rest functions
+(first '())
+;=>nil
+(first '(6))
+;=>6
+(first '(7 8 9))
+;=>7
+(first [])
+;=>nil
+(first [10])
+;=>10
+(first [10 11 12])
+;=>10
+
+(rest '())
+;=>()
+(rest '(6))
+;=>()
+(rest '(7 8 9))
+;=>(8 9)
+(rest [])
+;=>()
+(rest [10])
+;=>()
+(rest [10 11 12])
+;=>(11 12)
+
+
+
+;;
+;; Testing hash-maps
+(hash-map "a" 1)
+;=>{"a" 1}
+
+{"a" 1}
+;=>{"a" 1}
+
+(assoc {} "a" 1)
+;=>{"a" 1}
+
+(def! hm1 (hash-map))
+;=>{}
+
+(map? hm1)
+;=>true
+(map? 1)
+;=>false
+(map? [])
+;=>false
+
+(get hm1 "a")
+;=>nil
+
+(contains? hm1 "a")
+;=>false
+
+(def! hm2 (assoc hm1 "a" 1))
+;=>{"a" 1}
+
+(get hm1 "a")
+;=>nil
+
+(contains? hm1 "a")
+;=>false
+
+(get hm2 "a")
+;=>1
+
+(contains? hm2 "a")
+;=>true
+
+(keys hm2)
+;=>("a")
+
+(vals hm2)
+;=>(1)
+
+(def! hm3 (assoc hm2 "b" 2))
+(count (keys hm3))
+;=>2
+(count (vals hm3))
+;=>2
+
+(dissoc hm3 "a")
+;=>{"b" 2}
+
+(dissoc hm3 "a" "b")
+;=>{}
+
+(count (keys hm3))
+;=>2
+
+
+;;
+;; Testing metadata
+(meta [1 2 3])
+;=>nil
+
+(with-meta [1 2 3] {"a" 1})
+;=>[1 2 3]
+
+(meta (with-meta [1 2 3] {"a" 1}))
+;=>{"a" 1}
+
+(def! lst (with-meta [4 5 6] {"b" 2}))
+;=>[4 5 6]
+
+(def! f-wm (with-meta (fn* [a] (+ 1 a)) {"abc" 1}))
+(meta f-wm)
+;=>{"abc" 1}
+
+
+;;
+;; Testing atoms
+
+(def! inc3 (fn* (a) (+ 3 a)))
+
+(def! a (atom 2))
+;=>(atom 2)
+
+;;;(type a)
+;;;;=>"atom"
+
+(deref a)
+;=>2
+
+@a
+;=>2
+
+(reset! a 3)
+;=>3
+
+@a
+;=>3
+
+(swap! a inc3)
+;=>6
+
+@a
+;=>6
+
+(swap! a (fn* (a) a))
+;=>6
+
+(swap! a (fn* (a) (* 2 a)))
+;=>12
+
+
+;;
+;; Testing read-str and eval
+(read-string "[1 2 (3 4) nil]")
+;=>[1 2 (3 4) nil]
+
+(eval (read-string "(+ 4 5)"))
+;=>9
+
+;;
+;; Testing readline
+(readline "mal-user> ")
+"hello"
+;=>"\"hello\""
+
+;;
+;; Testing macros cond and or
+(cond 1 2 3 4)
+;=>2
+(cond false 2 3 4)
+;=>4
+(cond false 2 false 4)
+;=>nil
+
+(or)
+;=>nil
+(or 1)
+;=>1
+(or 1 2)
+;=>1
+(or nil 2)
+;=>2