aboutsummaryrefslogtreecommitdiff
path: root/make
diff options
context:
space:
mode:
Diffstat (limited to 'make')
-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
20 files changed, 2437 insertions, 0 deletions
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