diff options
| author | Joel Martin <github@martintribe.org> | 2014-03-24 16:32:24 -0500 |
|---|---|---|
| committer | Joel Martin <github@martintribe.org> | 2014-03-24 16:32:24 -0500 |
| commit | 3169070063b2cb877200117ebb384269d73bcb93 (patch) | |
| tree | 23de3db1ea5c37afd21a45b6ed7771f56a08c0c4 | |
| download | mal-3169070063b2cb877200117ebb384269d73bcb93.tar.gz mal-3169070063b2cb877200117ebb384269d73bcb93.zip | |
Current state of mal for Clojure West lighting talk.
171 files changed, 22973 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2d3759a --- /dev/null +++ b/.gitignore @@ -0,0 +1,34 @@ +make/mal.mk +js/node_modules +js/mal.js +js/mal_web.js +bash/mal.sh +c/*.o +*.pyc +c/mal +c/step0_repl +c/step1_read_print +c/step2_eval +c/step3_env +c/step4_if_fn_do +c/step5_tco +c/step6_file +c/step7_quote +c/step8_macros +c/step9_interop +c/stepA_more +clojure/target +clojure/.lein-repl-history +java/target/ +java/dependency-reduced-pom.xml +rust/step0_repl +rust/step1_read_print +rust/step2_eval +rust/step3_env +rust/step4_if_fn_do +rust/step5_tco +rust/step6_file +rust/step7_quote +rust/step8_macros +rust/step9_interop +rust/stepA_more diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..247fccb --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "josh.js"] + path = js/josh.js + url = https://github.com/sdether/josh.js/ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2bfa3c4 --- /dev/null +++ b/Makefile @@ -0,0 +1,105 @@ +# +# Command line settings +# + +MAL_IMPL = js + +# +# Settings +# + +IMPLS = bash c clojure java js make php python mal + +step0 = step0_repl +step1 = step1_read_print +step2 = step2_eval +step3 = step3_env +step4 = step4_if_fn_do +step5 = step5_tco +step6 = step6_file +step7 = step7_quote +step8 = step8_macros +step9 = step9_interop +stepA = stepA_more + +EXCLUDE_TESTS = test^make^step5 test^mal^step0 test^mal^step5 test^mal^step9 test^java^step9 + +# +# Utility functions +# + +STEP_TEST_FILES = $(strip $(wildcard $(1)/tests/$($(2)).mal) $(wildcard tests/$($(2)).mal)) + +bash_STEP_TO_PROG = bash/$($(1)).sh +c_STEP_TO_PROG = c/$($(1)) +clojure_STEP_TO_PROG = clojure/src/$($(1)).clj +java_STEP_TO_PROG = java/src/main/java/mal/$($(1)).java +js_STEP_TO_PROG = js/$($(1)).js +make_STEP_TO_PROG = make/$($(1)).mk +php_STEP_TO_PROG = php/$($(1)).php +python_STEP_TO_PROG = python/$($(1)).py +mal_STEP_TO_PROG = mal/$($(1)).mal + + +bash_RUNTEST = ../runtest.py $(4) ../$(1) -- bash ../$(2) $(5) +c_RUNTEST = ../runtest.py $(4) ../$(1) -- ../$(2) $(5) +clojure_RUNTEST = ../runtest.py $(4) ../$(1) -- lein with-profile +$(3) trampoline run $(5) +java_RUNTEST = ../runtest.py $(4) ../$(1) -- mvn -quiet exec:java -Dexec.mainClass="mal.$($(3))" -Dexec.args="--raw$(if $(5), $(5),)" +js_RUNTEST = ../runtest.py $(4) ../$(1) -- node ../$(2) $(5) +make_RUNTEST = ../runtest.py $(4) ../$(1) -- make -f ../$(2) $(5) +php_RUNTEST = ../runtest.py $(4) ../$(1) -- php ../$(2) $(5) +python_RUNTEST = ../runtest.py $(4) ../$(1) -- python ../$(2) $(5) +mal_RUNTEST = $(call $(MAL_IMPL)_RUNTEST,$(1),$(call $(MAL_IMPL)_STEP_TO_PROG,stepA),stepA,--start-timeout 30 --test-timeout 120,../$(2)) + + +# Derived lists +STEPS = $(sort $(filter step%,$(.VARIABLES))) +IMPL_TESTS = $(foreach impl,$(IMPLS),test^$(impl)) +STEP_TESTS = $(foreach step,$(STEPS),test^$(step)) +ALL_TESTS = $(filter-out $(EXCLUDE_TESTS),\ + $(strip $(sort \ + $(foreach impl,$(IMPLS),\ + $(foreach step,$(STEPS),test^$(impl)^$(step)))))) + +IMPL_STATS = $(foreach impl,$(IMPLS),stats^$(impl)) + +# +# Build rules +# + +# Build a program in 'c' directory +c/%: + $(MAKE) -C $(dir $(@)) $(notdir $(@)) + +# Allow test, test^STEP, test^IMPL, and test^IMPL^STEP +.SECONDEXPANSION: +$(IMPL_TESTS): $$(filter $$@^%,$$(ALL_TESTS)) + +.SECONDEXPANSION: +$(STEP_TESTS): $$(foreach step,$$(subst test^,,$$@),$$(filter %^$$(step),$$(ALL_TESTS))) + +.SECONDEXPANSION: +$(ALL_TESTS): $$(call $$(word 2,$$(subst ^, ,$$(@)))_STEP_TO_PROG,$$(word 3,$$(subst ^, ,$$(@)))) + @$(foreach impl,$(word 2,$(subst ^, ,$(@))),\ + $(foreach step,$(word 3,$(subst ^, ,$(@))),\ + cd $(if $(filter mal,$(impl)),$(MAL_IMPL),$(impl)); \ + $(foreach test,$(call STEP_TEST_FILES,$(impl),$(step)),\ + echo '----------------------------------------------'; \ + echo 'Testing $@, step file: $+, test file: $(test)'; \ + echo 'Running: $(call $(impl)_RUNTEST,$(test),$(+),$(step))'; \ + $(call $(impl)_RUNTEST,$(test),$(+),$(step))))) + +test: $(ALL_TESTS) +tests: $(ALL_TESTS) + + +# Stats rules + +.SECONDEXPANSION: +$(IMPL_STATS): + @echo "----------------------------------------------"; \ + $(foreach impl,$(word 2,$(subst ^, ,$(@))),\ + echo "Stats for $(impl):"; \ + $(MAKE) --no-print-directory -C $(impl) stats) + +stats: $(IMPL_STATS) @@ -0,0 +1,2 @@ +http://norvig.com/lispy.html +ftp://ftp.cs.wpi.edu/pub/techreports/pdf/05-07.pdf diff --git a/bash/Makefile b/bash/Makefile new file mode 100644 index 0000000..53f0d09 --- /dev/null +++ b/bash/Makefile @@ -0,0 +1,25 @@ +TESTS = tests/types.sh tests/reader.sh + +SOURCES = types.sh reader.sh stepA_more.sh + +all: mal.sh + +mal.sh: $(SOURCES) + cat $+ > $@ + echo "#!/bin/bash" > $@ + cat $+ | grep -v "^source " >> $@ + chmod +x $@ + +clean: + rm -f mal.sh + +.PHONY: stats tests $(TESTS) + +stats: $(SOURCES) + @wc $^ + +tests: $(TESTS) + +$(TESTS): + @echo "Running $@"; \ + bash $@ || exit 1; \ diff --git a/bash/reader.sh b/bash/reader.sh new file mode 100644 index 0000000..bc32fa7 --- /dev/null +++ b/bash/reader.sh @@ -0,0 +1,153 @@ +# +# mal (Make Lisp) Parser/Reader +# + +source $(dirname $0)/types.sh + +READ_ATOM () { + local token=${__reader_tokens[${__reader_idx}]} + __reader_idx=$(( __reader_idx + 1 )) + case "${token}" in + [0-9]*) number "${token}" ;; + \"*) token="${token:1:-1}" + token="${token//\\\"/\"}" + string "${token}" ;; + nil) r="${__nil}" ;; + true) r="${__true}" ;; + false) r="${__false}" ;; + *) symbol "${token}" ;; + esac +} + +# Return seqence of tokens into r. +# ${1}: Type of r (vector, list) +# ${2}: starting symbol +# ${3}: ending symbol +READ_SEQ () { + local start="${1}" + local end="${2}" + local items="" + local token=${__reader_tokens[${__reader_idx}]} + __reader_idx=$(( __reader_idx + 1 )) + if [[ "${token}" != "${start}" ]]; then + r= + _error "expected '${start}'" + return + fi + token=${__reader_tokens[${__reader_idx}]} + while [[ "${token}" != "${end}" ]]; do + if [[ ! "${token}" ]]; then + r= + _error "exepected '${end}', got EOF" + return + fi + READ_FORM + items="${items} ${r}" + token=${__reader_tokens[${__reader_idx}]} + done + __reader_idx=$(( __reader_idx + 1 )) + r="${items:1}" +} + +# Return form in r +READ_FORM () { + local token=${__reader_tokens[${__reader_idx}]} + case "${token}" in + \') __reader_idx=$(( __reader_idx + 1 )) + symbol quote; local q="${r}" + READ_FORM; local f="${r}" + list "${q}" "${f}" ;; + \`) __reader_idx=$(( __reader_idx + 1 )) + symbol quasiquote; local q="${r}" + READ_FORM; local f="${r}" + list "${q}" "${f}" ;; + \~) __reader_idx=$(( __reader_idx + 1 )) + symbol unquote; local q="${r}" + READ_FORM; local f="${r}" + list "${q}" "${f}" ;; + \~\@) __reader_idx=$(( __reader_idx + 1 )) + symbol splice-unquote; local q="${r}" + READ_FORM; local f="${r}" + list "${q}" "${f}" ;; + ^) __reader_idx=$(( __reader_idx + 1 )) + symbol with-meta; local wm="${r}" + READ_FORM; local meta="${r}" + READ_FORM; local obj="${r}" + list "${wm}" "${obj}" "${meta}" ;; + @) __reader_idx=$(( __reader_idx + 1 )) + symbol deref; local d="${r}" + READ_FORM; local f="${r}" + list "${d}" "${f}" ;; + \)) _error "unexpected ')'" ;; + \() READ_SEQ "(" ")" + list ${r} ;; + \]) _error "unexpected ']'" ;; + \[) READ_SEQ "[" "]" + vector ${r} ;; + \}) _error "unexpected '}'" ;; + \{) READ_SEQ "{" "}" + hash_map ${r} ;; + *) READ_ATOM + esac +} + +# Returns __reader_tokens as an indexed array of tokens +TOKENIZE () { + local data="${*}" + local datalen=${#data} + local idx=0 + local chunk=0 + local chunksz=500 + local match= + local token= + local str= + + __reader_idx=0 + __reader_tokens= + while true; do + if (( ${#str} < ( chunksz / 2) )) && (( chunk < datalen )); then + str="${str}${data:${chunk}:${chunksz}}" + chunk=$(( chunk + ${chunksz} )) + fi + (( ${#str} == 0 )) && break + [[ "${str}" =~ ^^([][{}\(\)^@])|^(~@)|(\"(\\.|[^\\\"])*\")|^(;[^$'\n']*)|^([~\'\`])|^([^][ ~\`\'\";{}\(\)^@]+)|^[,]|^[[:space:]]+ ]] + match=${BASH_REMATCH[0]} + str="${str:${#match}}" + token="${match//$'\n'/}" + #echo "MATCH: '${token}' / [${str}]" + if ! [[ "${token}" =~ (^[,]$|^[[:space:]]*;.*$|^[[:space:]]*$) ]]; then + __reader_tokens[${idx}]="${token}" + idx=$(( idx + 1 )) + fi + if [ -z "${match}" ]; then + echo >&2 "Tokenizing error at: ${str:0:50}" + _error "Tokenizing error at: ${str:0:50}" + break + fi + done +} + +# read-str from a raw "string" or from a string object. Retruns object +# read in r. +READ_STR () { + declare -a __reader_tokens + TOKENIZE "${*}" # sets __reader_tokens + #set | grep ^__reader_tokens + if [ -z "${__reader_tokens[k]}" ]; then + r= + return 1 # No tokens + fi + READ_FORM + #echo "Token: ${r}: <${ANON["${r}"]}>" + return +} + +# Call readline and save the history. Returns the string read in r. +READLINE_EOF= +READLINE_HISTORY_FILE=${HOME}/.mal-history +READLINE () { + history -r "${READLINE_HISTORY_FILE}" + read -r -e -p "${1}" r || return "$?" + history -s -- "${r}" + history -a "${READLINE_HISTORY_FILE}" +} diff --git a/bash/step0_repl.sh b/bash/step0_repl.sh new file mode 100755 index 0000000..261ecc2 --- /dev/null +++ b/bash/step0_repl.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +READ () { + read -u 0 -e -p "user> " r +} + +EVAL () { + r= + eval "${1}" +} + +PRINT () { + r="${1}" +} + +while true; do + READ + EVAL "${r}" + PRINT "${r}" + echo "${r}" +done diff --git a/bash/step1_read_print.sh b/bash/step1_read_print.sh new file mode 100755 index 0000000..ba94208 --- /dev/null +++ b/bash/step1_read_print.sh @@ -0,0 +1,45 @@ +#!/bin/bash + +INTERACTIVE=${INTERACTIVE-yes} + +source $(dirname $0)/reader.sh + +# READ: read and parse input +READ () { + [ "${1}" ] && r="${1}" || READLINE + READ_STR "${r}" +} + +# EVAL: just return the input +EVAL () { + local ast="${1}" + local env="${2}" + r= + [[ "${__ERROR}" ]] && return 1 + r="${ast}" +} + +# PRINT: +PRINT () { + if [[ "${__ERROR}" ]]; then + _pr_str "${__ERROR}" yes + r="Error: ${r}" + __ERROR= + else + _pr_str "${1}" yes + fi +} + +# REPL: read, eval, print, loop +REP () { + READ "${1}" || return 1 + EVAL "${r}" + PRINT "${r}" +} + +if [[ -n "${INTERACTIVE}" ]]; then + while true; do + READLINE "user> " || exit "$?" + [[ "${r}" ]] && REP "${r}" && echo "${r}" + done +fi diff --git a/bash/step2_eval.sh b/bash/step2_eval.sh new file mode 100755 index 0000000..4d571e4 --- /dev/null +++ b/bash/step2_eval.sh @@ -0,0 +1,92 @@ +#!/bin/bash + +INTERACTIVE=${INTERACTIVE-yes} + +source $(dirname $0)/reader.sh + +# READ: read and parse input +READ () { + READLINE + READ_STR "${r}" +} + +EVAL_AST () { + local ast="${1}" env="${2}" + #_pr_str "${ast}"; echo "EVAL_AST '${ast}:${r} / ${env}'" + _obj_type "${ast}"; local ot="${r}" + case "${ot}" in + symbol) + local val="${ANON["${ast}"]}" + eval r="\${${env}["${val}"]}" + [ "${r}" ] || _error "'${val}' not found" ;; + list) + _map_with_type list EVAL "${ast}" "${env}" ;; + vector) + _map_with_type vector EVAL "${ast}" "${env}" ;; + hash_map) + local res="" val="" hm="${ANON["${ast}"]}" + hash_map; local new_hm="${r}" + eval local keys="\${!${hm}[@]}" + for key in ${keys}; do + eval val="\${${hm}[\"${key}\"]}" + EVAL "${val}" "${env}" + assoc! "${new_hm}" "${key}" "${r}" + done + r="${new_hm}" ;; + *) + r="${ast}" ;; + esac +} + +# EVAL: evaluate the parameter +EVAL () { + local ast="${1}" env="${2}" + r= + [[ "${__ERROR}" ]] && return 1 + #_pr_str "${ast}"; echo "EVAL '${r} / ${env}'" + _obj_type "${ast}"; local ot="${r}" + if [[ "${ot}" != "list" ]]; then + EVAL_AST "${ast}" "${env}" + return + fi + + # apply list + EVAL_AST "${ast}" "${env}" + [[ "${__ERROR}" ]] && return 1 + local el="${r}" + first "${el}"; local f="${r}" + rest "${el}"; local args="${ANON["${r}"]}" + #echo "invoke: ${f} ${args}" + eval ${f} ${args} +} + +# PRINT: +PRINT () { + if [[ "${__ERROR}" ]]; then + _pr_str "${__ERROR}" yes + r="Error: ${r}" + __ERROR= + else + _pr_str "${1}" yes + fi +} + +# REPL: read, eval, print, loop +declare -A REPL_ENV +REP () { + READ_STR "${1}" + EVAL "${r}" REPL_ENV + PRINT "${r}" +} + +REPL_ENV["+"]=num_plus +REPL_ENV["-"]=num_minus +REPL_ENV["__STAR__"]=num_multiply +REPL_ENV["/"]=num_divide + +if [[ -n "${INTERACTIVE}" ]]; then + while true; do + READLINE "user> " || exit "$?" + [[ "${r}" ]] && REP "${r}" && echo "${r}" + done +fi diff --git a/bash/step3_env.sh b/bash/step3_env.sh new file mode 100755 index 0000000..cbc0867 --- /dev/null +++ b/bash/step3_env.sh @@ -0,0 +1,116 @@ +#!/bin/bash + +INTERACTIVE=${INTERACTIVE-yes} + +source $(dirname $0)/reader.sh + +# READ: read and parse input +READ () { + READLINE + READ_STR "${r}" +} + +EVAL_AST () { + local ast="${1}" env="${2}" + #_pr_str "${ast}"; echo "EVAL_AST '${ast}:${r} / ${env}'" + _obj_type "${ast}"; local ot="${r}" + case "${ot}" in + symbol) + local val="${ANON["${ast}"]}" + ENV_GET "${env}" "${val}" + return ;; + list) + _map_with_type list EVAL "${ast}" "${env}" ;; + vector) + _map_with_type vector EVAL "${ast}" "${env}" ;; + hash_map) + local res="" val="" hm="${ANON["${ast}"]}" + hash_map; local new_hm="${r}" + eval local keys="\${!${hm}[@]}" + for key in ${keys}; do + eval val="\${${hm}[\"${key}\"]}" + EVAL "${val}" "${env}" + assoc! "${new_hm}" "${key}" "${r}" + done + r="${new_hm}" ;; + *) + r="${ast}" ;; + esac +} + +# EVAL: evaluate the parameter +EVAL () { + local ast="${1}" env="${2}" + r= + [[ "${__ERROR}" ]] && return 1 + #_pr_str "${ast}"; echo "EVAL '${r} / ${env}'" + _obj_type "${ast}"; local ot="${r}" + if [[ "${ot}" != "list" ]]; then + EVAL_AST "${ast}" "${env}" + return + fi + + # apply list + _nth "${ast}" 0; local a0="${r}" + _nth "${ast}" 1; local a1="${r}" + _nth "${ast}" 2; local a2="${r}" + case "${ANON["${a0}"]}" in + def!) local k="${ANON["${a1}"]}" + #echo "def! ${k} to ${a2} in ${env}" + EVAL "${a2}" "${env}" + ENV_SET "${env}" "${k}" "${r}" + return ;; + let*) ENV "${env}"; local let_env="${r}" + local let_pairs=(${ANON["${a1}"]}) + local idx=0 + #echo "let: [${let_pairs[*]}] for ${a2}" + while [[ "${let_pairs["${idx}"]}" ]]; do + EVAL "${let_pairs[$(( idx + 1))]}" "${let_env}" + ENV_SET "${let_env}" "${ANON["${let_pairs[${idx}]}"]}" "${r}" + idx=$(( idx + 2)) + done + EVAL "${a2}" "${let_env}" + return ;; + *) EVAL_AST "${ast}" "${env}" + [[ "${__ERROR}" ]] && r= && return 1 + local el="${r}" + first "${el}"; local f="${r}" + rest "${el}"; local args="${ANON["${r}"]}" + #echo "invoke: ${f} ${args}" + eval ${f} ${args} + return ;; + esac +} + +# PRINT: +PRINT () { + if [[ "${__ERROR}" ]]; then + _pr_str "${__ERROR}" yes + r="Error: ${r}" + __ERROR= + else + _pr_str "${1}" yes + fi +} + +# REPL: read, eval, print, loop +ENV; REPL_ENV="${r}" +REP () { + r= + READ_STR "${1}" + EVAL "${r}" ${REPL_ENV} + PRINT "${r}" +} + +_ref () { ENV_SET "${REPL_ENV}" "${1}" "${2}"; } +_ref "+" num_plus +_ref "-" num_minus +_ref "__STAR__" num_multiply +_ref "/" num_divide + +if [[ -n "${INTERACTIVE}" ]]; then + while true; do + READLINE "user> " || exit "$?" + [[ "${r}" ]] && REP "${r}" && echo "${r}" + done +fi diff --git a/bash/step4_if_fn_do.sh b/bash/step4_if_fn_do.sh new file mode 100755 index 0000000..fedb324 --- /dev/null +++ b/bash/step4_if_fn_do.sh @@ -0,0 +1,140 @@ +#!/bin/bash + +INTERACTIVE=${INTERACTIVE-yes} + +source $(dirname $0)/reader.sh + +# READ: read and parse input +READ () { + READLINE + READ_STR "${r}" +} + +EVAL_AST () { + local ast="${1}" env="${2}" + #_pr_str "${ast}"; echo "EVAL_AST '${ast}:${r} / ${env}'" + _obj_type "${ast}"; local ot="${r}" + case "${ot}" in + symbol) + local val="${ANON["${ast}"]}" + ENV_GET "${env}" "${val}" + return ;; + list) + _map_with_type list EVAL "${ast}" "${env}" ;; + vector) + _map_with_type vector EVAL "${ast}" "${env}" ;; + hash_map) + local res="" val="" hm="${ANON["${ast}"]}" + hash_map; local new_hm="${r}" + eval local keys="\${!${hm}[@]}" + for key in ${keys}; do + eval val="\${${hm}[\"${key}\"]}" + EVAL "${val}" "${env}" + assoc! "${new_hm}" "${key}" "${r}" + done + r="${new_hm}" ;; + *) + r="${ast}" ;; + esac +} + +# EVAL: evaluate the parameter +EVAL () { + local ast="${1}" env="${2}" + r= + [[ "${__ERROR}" ]] && return 1 + #_pr_str "${ast}"; echo "EVAL '${r} / ${env}'" + _obj_type "${ast}"; local ot="${r}" + if [[ "${ot}" != "list" ]]; then + EVAL_AST "${ast}" "${env}" + return + fi + + # apply list + _nth "${ast}" 0; local a0="${r}" + _nth "${ast}" 1; local a1="${r}" + _nth "${ast}" 2; local a2="${r}" + case "${ANON["${a0}"]}" in + def!) local k="${ANON["${a1}"]}" + #echo "def! ${k} to ${a2} in ${env}" + EVAL "${a2}" "${env}" + ENV_SET "${env}" "${k}" "${r}" + return ;; + let*) ENV "${env}"; local let_env="${r}" + local let_pairs=(${ANON["${a1}"]}) + local idx=0 + #echo "let: [${let_pairs[*]}] for ${a2}" + while [[ "${let_pairs["${idx}"]}" ]]; do + EVAL "${let_pairs[$(( idx + 1))]}" "${let_env}" + ENV_SET "${let_env}" "${ANON["${let_pairs[${idx}]}"]}" "${r}" + idx=$(( idx + 2)) + done + EVAL "${a2}" "${let_env}" + return ;; + do) rest "${ast}" + EVAL_AST "${r}" "${env}" + [[ "${__ERROR}" ]] && r= && return 1 + last "${r}" + return ;; + if) EVAL "${a1}" "${env}" + if [[ "${r}" == "${__false}" || "${r}" == "${__nil}" ]]; then + # eval false form + _nth "${ast}" 3; local a3="${r}" + if [[ "${a3}" ]]; then + EVAL "${a3}" "${env}" + else + r="${__nil}" + fi + else + # eval true condition + EVAL "${a2}" "${env}" + fi + return ;; + fn*) new_function "ENV \"${env}\" \"${a1}\" \"\${@}\"; \ + EVAL \"${a2}\" \"\${r}\"" + return ;; + *) EVAL_AST "${ast}" "${env}" + [[ "${__ERROR}" ]] && r= && return 1 + local el="${r}" + first "${el}"; local f="${ANON["${r}"]}" + rest "${el}"; local args="${ANON["${r}"]}" + #echo "invoke: ${f} ${args}" + eval ${f} ${args} + return ;; + esac +} + +# PRINT: +PRINT () { + if [[ "${__ERROR}" ]]; then + _pr_str "${__ERROR}" yes + r="Error: ${r}" + __ERROR= + else + _pr_str "${1}" yes + fi +} + +# REPL: read, eval, print, loop +ENV; REPL_ENV="${r}" +REP () { + r= + READ_STR "${1}" + EVAL "${r}" ${REPL_ENV} + PRINT "${r}" +} + +_fref () { new_function "${2} \"\${@}\""; ENV_SET "${REPL_ENV}" "${1}" "${r}"; } + +# Import types functions +for n in "${!types_ns[@]}"; do _fref "${n}" "${types_ns["${n}"]}"; done + +# Defined using the language itself +REP "(def! not (fn* (a) (if a false true)))" + +if [[ -n "${INTERACTIVE}" ]]; then + while true; do + READLINE "user> " || exit "$?" + [[ "${r}" ]] && REP "${r}" && echo "${r}" + done +fi diff --git a/bash/step5_tco.sh b/bash/step5_tco.sh new file mode 100755 index 0000000..409ec87 --- /dev/null +++ b/bash/step5_tco.sh @@ -0,0 +1,157 @@ +#!/bin/bash + +INTERACTIVE=${INTERACTIVE-yes} + +source $(dirname $0)/reader.sh + +# READ: read and parse input +READ () { + READLINE + READ_STR "${r}" +} + +EVAL_AST () { + local ast="${1}" env="${2}" + #_pr_str "${ast}"; echo "EVAL_AST '${ast}:${r} / ${env}'" + _obj_type "${ast}"; local ot="${r}" + case "${ot}" in + symbol) + local val="${ANON["${ast}"]}" + ENV_GET "${env}" "${val}" + return ;; + list) + _map_with_type list EVAL "${ast}" "${env}" ;; + vector) + _map_with_type vector EVAL "${ast}" "${env}" ;; + hash_map) + local res="" val="" hm="${ANON["${ast}"]}" + hash_map; local new_hm="${r}" + eval local keys="\${!${hm}[@]}" + for key in ${keys}; do + eval val="\${${hm}[\"${key}\"]}" + EVAL "${val}" "${env}" + assoc! "${new_hm}" "${key}" "${r}" + done + r="${new_hm}" ;; + *) + r="${ast}" ;; + esac +} + +# EVAL: evaluate the parameter +EVAL () { + local ast="${1}" env="${2}" + while true; do + r= + [[ "${__ERROR}" ]] && return 1 + #_pr_str "${ast}"; echo "EVAL '${r} / ${env}'" + _obj_type "${ast}"; local ot="${r}" + if [[ "${ot}" != "list" ]]; then + EVAL_AST "${ast}" "${env}" + return + fi + + # apply list + _nth "${ast}" 0; local a0="${r}" + _nth "${ast}" 1; local a1="${r}" + _nth "${ast}" 2; local a2="${r}" + case "${ANON["${a0}"]}" in + def!) local k="${ANON["${a1}"]}" + #echo "def! ${k} to ${a2} in ${env}" + EVAL "${a2}" "${env}" + ENV_SET "${env}" "${k}" "${r}" + return ;; + let*) ENV "${env}"; local let_env="${r}" + local let_pairs=(${ANON["${a1}"]}) + local idx=0 + #echo "let: [${let_pairs[*]}] for ${a2}" + while [[ "${let_pairs["${idx}"]}" ]]; do + EVAL "${let_pairs[$(( idx + 1))]}" "${let_env}" + ENV_SET "${let_env}" "${ANON["${let_pairs[${idx}]}"]}" "${r}" + idx=$(( idx + 2)) + done + EVAL "${a2}" "${let_env}" + return ;; + do) _count "${ast}" + _slice "${ast}" 1 $(( ${r} - 2 )) + EVAL_AST "${r}" "${env}" + [[ "${__ERROR}" ]] && r= && return 1 + last "${ast}" + ast="${r}" + # Continue loop + ;; + if) EVAL "${a1}" "${env}" + if [[ "${r}" == "${__false}" || "${r}" == "${__nil}" ]]; then + # eval false form + _nth "${ast}" 3; local a3="${r}" + if [[ "${a3}" ]]; then + ast="${a3}" + else + r="${__nil}" + return + fi + else + # eval true condition + ast="${a2}" + fi + # Continue loop + ;; + fn*) new_function "ENV \"${env}\" \"${a1}\" \"\${@}\"; \ + EVAL \"${a2}\" \"\${r}\"" \ + "${a2}" "${env}" "${a1}" + return ;; + *) EVAL_AST "${ast}" "${env}" + [[ "${__ERROR}" ]] && r= && return 1 + local el="${r}" + first "${el}"; local f="${ANON["${r}"]}" + rest "${el}"; local args="${ANON["${r}"]}" + #echo "invoke: [${f}] ${args}" + if [[ "${f//@/ }" != "${f}" ]]; then + set -- ${f//@/ } + ast="${2}" + ENV "${3}" "${4}" ${args} + env="${r}" + else + eval ${f%%@*} ${args} + return + fi + # Continue loop + ;; + esac + done +} + +# PRINT: +PRINT () { + if [[ "${__ERROR}" ]]; then + _pr_str "${__ERROR}" yes + r="Error: ${r}" + __ERROR= + else + _pr_str "${1}" yes + fi +} + +# REPL: read, eval, print, loop +ENV; REPL_ENV="${r}" +REP () { + r= + READ_STR "${1}" + EVAL "${r}" ${REPL_ENV} + PRINT "${r}" +} + +_fref () { new_function "${2} \"\${@}\""; ENV_SET "${REPL_ENV}" "${1}" "${r}"; } + +# Import types functions +for n in "${!types_ns[@]}"; do _fref "${n}" "${types_ns["${n}"]}"; done + +# Defined using the language itself +REP "(def! not (fn* (a) (if a false true)))" + +if [[ -n "${INTERACTIVE}" ]]; then + while true; do + READLINE "user> " || exit "$?" + [[ "${r}" ]] && REP "${r}" && echo "${r}" + done +fi diff --git a/bash/step6_file.sh b/bash/step6_file.sh new file mode 100755 index 0000000..9656125 --- /dev/null +++ b/bash/step6_file.sh @@ -0,0 +1,170 @@ +#!/bin/bash + +INTERACTIVE=${INTERACTIVE-yes} + +source $(dirname $0)/reader.sh + +# READ: read and parse input +READ () { + READLINE + READ_STR "${r}" +} + +EVAL_AST () { + local ast="${1}" env="${2}" + #_pr_str "${ast}"; echo "EVAL_AST '${ast}:${r} / ${env}'" + _obj_type "${ast}"; local ot="${r}" + case "${ot}" in + symbol) + local val="${ANON["${ast}"]}" + ENV_GET "${env}" "${val}" + return ;; + list) + _map_with_type list EVAL "${ast}" "${env}" ;; + vector) + _map_with_type vector EVAL "${ast}" "${env}" ;; + hash_map) + local res="" val="" hm="${ANON["${ast}"]}" + hash_map; local new_hm="${r}" + eval local keys="\${!${hm}[@]}" + for key in ${keys}; do + eval val="\${${hm}[\"${key}\"]}" + EVAL "${val}" "${env}" + assoc! "${new_hm}" "${key}" "${r}" + done + r="${new_hm}" ;; + *) + r="${ast}" ;; + esac +} + +# EVAL: evaluate the parameter +EVAL () { + local ast="${1}" env="${2}" + while true; do + r= + [[ "${__ERROR}" ]] && return 1 + #_pr_str "${ast}"; echo "EVAL '${r} / ${env}'" + _obj_type "${ast}"; local ot="${r}" + if [[ "${ot}" != "list" ]]; then + EVAL_AST "${ast}" "${env}" + return + fi + + # apply list + _nth "${ast}" 0; local a0="${r}" + _nth "${ast}" 1; local a1="${r}" + _nth "${ast}" 2; local a2="${r}" + case "${ANON["${a0}"]}" in + def!) local k="${ANON["${a1}"]}" + #echo "def! ${k} to ${a2} in ${env}" + EVAL "${a2}" "${env}" + ENV_SET "${env}" "${k}" "${r}" + return ;; + let*) ENV "${env}"; local let_env="${r}" + local let_pairs=(${ANON["${a1}"]}) + local idx=0 + #echo "let: [${let_pairs[*]}] for ${a2}" + while [[ "${let_pairs["${idx}"]}" ]]; do + EVAL "${let_pairs[$(( idx + 1))]}" "${let_env}" + ENV_SET "${let_env}" "${ANON["${let_pairs[${idx}]}"]}" "${r}" + idx=$(( idx + 2)) + done + EVAL "${a2}" "${let_env}" + return ;; + do) _count "${ast}" + _slice "${ast}" 1 $(( ${r} - 2 )) + EVAL_AST "${r}" "${env}" + [[ "${__ERROR}" ]] && r= && return 1 + last "${ast}" + ast="${r}" + # Continue loop + ;; + if) EVAL "${a1}" "${env}" + if [[ "${r}" == "${__false}" || "${r}" == "${__nil}" ]]; then + # eval false form + _nth "${ast}" 3; local a3="${r}" + if [[ "${a3}" ]]; then + ast="${a3}" + else + r="${__nil}" + return + fi + else + # eval true condition + ast="${a2}" + fi + # Continue loop + ;; + fn*) new_function "ENV \"${env}\" \"${a1}\" \"\${@}\"; \ + EVAL \"${a2}\" \"\${r}\"" \ + "${a2}" "${env}" "${a1}" + return ;; + *) EVAL_AST "${ast}" "${env}" + [[ "${__ERROR}" ]] && r= && return 1 + local el="${r}" + first "${el}"; local f="${ANON["${r}"]}" + rest "${el}"; local args="${ANON["${r}"]}" + #echo "invoke: [${f}] ${args}" + if [[ "${f//@/ }" != "${f}" ]]; then + set -- ${f//@/ } + ast="${2}" + ENV "${3}" "${4}" ${args} + env="${r}" + else + eval ${f%%@*} ${args} + return + fi + # Continue loop + ;; + esac + done +} + +# PRINT: +PRINT () { + if [[ "${__ERROR}" ]]; then + _pr_str "${__ERROR}" yes + r="Error: ${r}" + __ERROR= + else + _pr_str "${1}" yes + fi +} + +# REPL: read, eval, print, loop +ENV; REPL_ENV="${r}" +REP () { + r= + READ_STR "${1}" + EVAL "${r}" ${REPL_ENV} + PRINT "${r}" +} + +_fref () { new_function "${2} \"\${@}\""; ENV_SET "${REPL_ENV}" "${1}" "${r}"; } + +# Import types functions +for n in "${!types_ns[@]}"; do _fref "${n}" "${types_ns["${n}"]}"; done + +read_string () { READ_STR "${ANON["${1}"]}"; } +_fref "read-string" read_string +_eval () { EVAL "${1}" "${REPL_ENV}"; } +_fref "eval" _eval +slurp () { string "$(cat "${ANON["${1}"]}")"; } +_fref "slurp" slurp +slurp_do () { string "(do $(cat "${ANON["${1}"]}"))"; } +_fref "slurp-do" slurp_do + +# Defined using the language itself +REP "(def! not (fn* (a) (if a false true)))" +REP "(def! load-file (fn* (f) (eval (read-string (slurp-do f)))))" + +if [[ "${1}" ]]; then + echo "${@}" + REP "(load-file \"${1}\")" && echo "${r}" +elif [[ -n "${INTERACTIVE}" ]]; then + while true; do + READLINE "user> " || exit "$?" + [[ "${r}" ]] && REP "${r}" && echo "${r}" + done +fi diff --git a/bash/step7_quote.sh b/bash/step7_quote.sh new file mode 100755 index 0000000..4bb74ba --- /dev/null +++ b/bash/step7_quote.sh @@ -0,0 +1,215 @@ +#!/bin/bash + +INTERACTIVE=${INTERACTIVE-yes} + +source $(dirname $0)/reader.sh + +# READ: read and parse input +READ () { + READLINE + READ_STR "${r}" +} + +IS_PAIR () { + if _list? "${1}"; then + _count "${1}" + [[ "${r}" > 0 ]] && return 0 + fi + return 1 +} + +QUASIQUOTE () { + if ! IS_PAIR "${1}"; then + symbol quote + list "${r}" "${1}" + return + else + _nth "${1}" 0; local a0="${r}" + if [[ "${ANON["${a0}"]}" == "unquote" ]]; then + _nth "${1}" 1 + return + elif IS_PAIR "${a0}"; then + _nth "${a0}" 0; local a00="${r}" + if [[ "${ANON["${a00}"]}" == "splice-unquote" ]]; then + symbol concat; local a="${r}" + _nth "${a0}" 1; local b="${r}" + rest "${1}" + QUASIQUOTE "${r}"; local c="${r}" + list "${a}" "${b}" "${c}" + return + fi + fi + fi + symbol cons; local a="${r}" + QUASIQUOTE "${a0}"; local b="${r}" + rest "${1}" + QUASIQUOTE "${r}"; local c="${r}" + list "${a}" "${b}" "${c}" + return +} + +EVAL_AST () { + local ast="${1}" env="${2}" + #_pr_str "${ast}"; echo "EVAL_AST '${ast}:${r} / ${env}'" + _obj_type "${ast}"; local ot="${r}" + case "${ot}" in + symbol) + local val="${ANON["${ast}"]}" + ENV_GET "${env}" "${val}" + return ;; + list) + _map_with_type list EVAL "${ast}" "${env}" ;; + vector) + _map_with_type vector EVAL "${ast}" "${env}" ;; + hash_map) + local res="" val="" hm="${ANON["${ast}"]}" + hash_map; local new_hm="${r}" + eval local keys="\${!${hm}[@]}" + for key in ${keys}; do + eval val="\${${hm}[\"${key}\"]}" + EVAL "${val}" "${env}" + assoc! "${new_hm}" "${key}" "${r}" + done + r="${new_hm}" ;; + *) + r="${ast}" ;; + esac +} + +# EVAL: evaluate the parameter +EVAL () { + local ast="${1}" env="${2}" + while true; do + r= + [[ "${__ERROR}" ]] && return 1 + #_pr_str "${ast}"; echo "EVAL '${r} / ${env}'" + _obj_type "${ast}"; local ot="${r}" + if [[ "${ot}" != "list" ]]; then + EVAL_AST "${ast}" "${env}" + return + fi + + # apply list + _nth "${ast}" 0; local a0="${r}" + _nth "${ast}" 1; local a1="${r}" + _nth "${ast}" 2; local a2="${r}" + case "${ANON["${a0}"]}" in + def!) local k="${ANON["${a1}"]}" + #echo "def! ${k} to ${a2} in ${env}" + EVAL "${a2}" "${env}" + ENV_SET "${env}" "${k}" "${r}" + return ;; + let*) ENV "${env}"; local let_env="${r}" + local let_pairs=(${ANON["${a1}"]}) + local idx=0 + #echo "let: [${let_pairs[*]}] for ${a2}" + while [[ "${let_pairs["${idx}"]}" ]]; do + EVAL "${let_pairs[$(( idx + 1))]}" "${let_env}" + ENV_SET "${let_env}" "${ANON["${let_pairs[${idx}]}"]}" "${r}" + idx=$(( idx + 2)) + done + EVAL "${a2}" "${let_env}" + return ;; + quote) + r="${a1}" + return ;; + quasiquote) + QUASIQUOTE "${a1}" + EVAL "${r}" "${env}" + return ;; + do) _count "${ast}" + _slice "${ast}" 1 $(( ${r} - 2 )) + EVAL_AST "${r}" "${env}" + [[ "${__ERROR}" ]] && r= && return 1 + last "${ast}" + ast="${r}" + # Continue loop + ;; + if) EVAL "${a1}" "${env}" + if [[ "${r}" == "${__false}" || "${r}" == "${__nil}" ]]; then + # eval false form + _nth "${ast}" 3; local a3="${r}" + if [[ "${a3}" ]]; then + ast="${a3}" + else + r="${__nil}" + return + fi + else + # eval true condition + ast="${a2}" + fi + # Continue loop + ;; + fn*) new_function "ENV \"${env}\" \"${a1}\" \"\${@}\"; \ + EVAL \"${a2}\" \"\${r}\"" \ + "${a2}" "${env}" "${a1}" + return ;; + *) EVAL_AST "${ast}" "${env}" + [[ "${__ERROR}" ]] && r= && return 1 + local el="${r}" + first "${el}"; local f="${ANON["${r}"]}" + rest "${el}"; local args="${ANON["${r}"]}" + #echo "invoke: [${f}] ${args}" + if [[ "${f//@/ }" != "${f}" ]]; then + set -- ${f//@/ } + ast="${2}" + ENV "${3}" "${4}" ${args} + env="${r}" + else + eval ${f%%@*} ${args} + return + fi + # Continue loop + ;; + esac + done +} + +# PRINT: +PRINT () { + if [[ "${__ERROR}" ]]; then + _pr_str "${__ERROR}" yes + r="Error: ${r}" + __ERROR= + else + _pr_str "${1}" yes + fi +} + +# REPL: read, eval, print, loop +ENV; REPL_ENV="${r}" +REP () { + r= + READ_STR "${1}" + EVAL "${r}" ${REPL_ENV} + PRINT "${r}" +} + +_fref () { new_function "${2} \"\${@}\""; ENV_SET "${REPL_ENV}" "${1}" "${r}"; } + +# Import types functions +for n in "${!types_ns[@]}"; do _fref "${n}" "${types_ns["${n}"]}"; done + +read_string () { READ_STR "${ANON["${1}"]}"; } +_fref "read-string" read_string +_eval () { EVAL "${1}" "${REPL_ENV}"; } +_fref "eval" _eval +slurp () { string "$(cat "${ANON["${1}"]}")"; } +_fref "slurp" slurp +slurp_do () { string "(do $(cat "${ANON["${1}"]}"))"; } +_fref "slurp-do" slurp_do + +# Defined using the language itself +REP "(def! not (fn* (a) (if a false true)))" +REP "(def! load-file (fn* (f) (eval (read-string (slurp-do f)))))" + +if [[ "${1}" ]]; then + echo "${@}" + REP "(load-file \"${1}\")" && echo "${r}" +elif [[ -n "${INTERACTIVE}" ]]; then + while true; do + READLINE "user> " || exit "$?" + [[ "${r}" ]] && REP "${r}" && echo "${r}" + done +fi diff --git a/bash/step8_macros.sh b/bash/step8_macros.sh new file mode 100755 index 0000000..e86a032 --- /dev/null +++ b/bash/step8_macros.sh @@ -0,0 +1,252 @@ +#!/bin/bash + +INTERACTIVE=${INTERACTIVE-yes} + +source $(dirname $0)/reader.sh + +# READ: read and parse input +READ () { + READLINE + READ_STR "${r}" +} + +IS_PAIR () { + if _list? "${1}"; then + _count "${1}" + [[ "${r}" > 0 ]] && return 0 + fi + return 1 +} + +QUASIQUOTE () { + if ! IS_PAIR "${1}"; then + symbol quote + list "${r}" "${1}" + return + else + _nth "${1}" 0; local a0="${r}" + if [[ "${ANON["${a0}"]}" == "unquote" ]]; then + _nth "${1}" 1 + return + elif IS_PAIR "${a0}"; then + _nth "${a0}" 0; local a00="${r}" + if [[ "${ANON["${a00}"]}" == "splice-unquote" ]]; then + symbol concat; local a="${r}" + _nth "${a0}" 1; local b="${r}" + rest "${1}" + QUASIQUOTE "${r}"; local c="${r}" + list "${a}" "${b}" "${c}" + return + fi + fi + fi + symbol cons; local a="${r}" + QUASIQUOTE "${a0}"; local b="${r}" + rest "${1}" + QUASIQUOTE "${r}"; local c="${r}" + list "${a}" "${b}" "${c}" + return +} + +IS_MACRO_CALL () { + if ! _list? "${1}"; then return 1; fi + _nth "${1}" 0; local a0="${r}" + if _symbol? "${a0}"; then + ENV_FIND "${2}" "${ANON["${a0}"]}_ismacro_" + if [[ "${r}" ]]; then + return 0 + fi + fi + return 1 +} + +MACROEXPAND () { + local ast="${1}" env="${2}" + while IS_MACRO_CALL "${ast}" "${env}"; do + _nth "${ast}" 0; local a0="${r}" + ENV_GET "${env}" "${ANON["${a0}"]}"; local mac="${ANON["${r}"]}" + rest "${ast}" + ${mac%%@*} ${ANON["${r}"]} + ast="${r}" + done + r="${ast}" +} + + +EVAL_AST () { + local ast="${1}" env="${2}" + #_pr_str "${ast}"; echo "EVAL_AST '${ast}:${r} / ${env}'" + _obj_type "${ast}"; local ot="${r}" + case "${ot}" in + symbol) + local val="${ANON["${ast}"]}" + ENV_GET "${env}" "${val}" + return ;; + list) + _map_with_type list EVAL "${ast}" "${env}" ;; + vector) + _map_with_type vector EVAL "${ast}" "${env}" ;; + hash_map) + local res="" val="" hm="${ANON["${ast}"]}" + hash_map; local new_hm="${r}" + eval local keys="\${!${hm}[@]}" + for key in ${keys}; do + eval val="\${${hm}[\"${key}\"]}" + EVAL "${val}" "${env}" + assoc! "${new_hm}" "${key}" "${r}" + done + r="${new_hm}" ;; + *) + r="${ast}" ;; + esac +} + +# EVAL: evaluate the parameter +EVAL () { + local ast="${1}" env="${2}" + while true; do + r= + [[ "${__ERROR}" ]] && return 1 + #_pr_str "${ast}"; echo "EVAL '${r} / ${env}'" + if ! _list? "${ast}"; then + EVAL_AST "${ast}" "${env}" + return + fi + + # apply list + MACROEXPAND "${ast}" "${env}" + ast="${r}" + if ! _list? "${ast}"; then return; fi + _nth "${ast}" 0; local a0="${r}" + _nth "${ast}" 1; local a1="${r}" + _nth "${ast}" 2; local a2="${r}" + case "${ANON["${a0}"]}" in + def!) local k="${ANON["${a1}"]}" + #echo "def! ${k} to ${a2} in ${env}" + EVAL "${a2}" "${env}" + ENV_SET "${env}" "${k}" "${r}" + return ;; + let*) ENV "${env}"; local let_env="${r}" + local let_pairs=(${ANON["${a1}"]}) + local idx=0 + #echo "let: [${let_pairs[*]}] for ${a2}" + while [[ "${let_pairs["${idx}"]}" ]]; do + EVAL "${let_pairs[$(( idx + 1))]}" "${let_env}" + ENV_SET "${let_env}" "${ANON["${let_pairs[${idx}]}"]}" "${r}" + idx=$(( idx + 2)) + done + EVAL "${a2}" "${let_env}" + return ;; + quote) + r="${a1}" + return ;; + quasiquote) + QUASIQUOTE "${a1}" + EVAL "${r}" "${env}" + return ;; + defmacro!) + local k="${ANON["${a1}"]}" + EVAL "${a2}" "${env}" + ENV_SET "${env}" "${k}" "${r}" + ENV_SET "${env}" "${k}_ismacro_" "yes" + return ;; + macroexpand) + MACROEXPAND "${a1}" "${env}" + return ;; + do) _count "${ast}" + _slice "${ast}" 1 $(( ${r} - 2 )) + EVAL_AST "${r}" "${env}" + [[ "${__ERROR}" ]] && r= && return 1 + last "${ast}" + ast="${r}" + # Continue loop + ;; + if) EVAL "${a1}" "${env}" + if [[ "${r}" == "${__false}" || "${r}" == "${__nil}" ]]; then + # eval false form + _nth "${ast}" 3; local a3="${r}" + if [[ "${a3}" ]]; then + ast="${a3}" + else + r="${__nil}" + return + fi + else + # eval true condition + ast="${a2}" + fi + # Continue loop + ;; + fn*) new_function "ENV \"${env}\" \"${a1}\" \"\${@}\"; \ + EVAL \"${a2}\" \"\${r}\"" \ + "${a2}" "${env}" "${a1}" + return ;; + *) EVAL_AST "${ast}" "${env}" + [[ "${__ERROR}" ]] && r= && return 1 + local el="${r}" + first "${el}"; local f="${ANON["${r}"]}" + rest "${el}"; local args="${ANON["${r}"]}" + #echo "invoke: [${f}] ${args}" + if [[ "${f//@/ }" != "${f}" ]]; then + set -- ${f//@/ } + ast="${2}" + ENV "${3}" "${4}" ${args} + env="${r}" + else + eval ${f%%@*} ${args} + return + fi + # Continue loop + ;; + esac + done +} +# PRINT: +PRINT () { + if [[ "${__ERROR}" ]]; then + _pr_str "${__ERROR}" yes + r="Error: ${r}" + __ERROR= + else + _pr_str "${1}" yes + fi +} + +# REPL: read, eval, print, loop +ENV; REPL_ENV="${r}" +REP () { + r= + READ_STR "${1}" + EVAL "${r}" "${REPL_ENV}" + PRINT "${r}" +} + +_fref () { new_function "${2} \"\${@}\""; ENV_SET "${REPL_ENV}" "${1}" "${r}"; } + +# Import types functions +for n in "${!types_ns[@]}"; do _fref "${n}" "${types_ns["${n}"]}"; done + +read_string () { READ_STR "${ANON["${1}"]}"; } +_fref "read-string" read_string +_eval () { + EVAL "${1}" "${REPL_ENV}" +} +_fref "eval" _eval +slurp () { string "$(cat "${ANON["${1}"]}")"; } +_fref "slurp" slurp +slurp_do () { string "(do $(cat "${ANON["${1}"]}"))"; } +_fref "slurp-do" slurp_do + +# Defined using the language itself +REP "(def! not (fn* (a) (if a false true)))" +REP "(def! load-file (fn* (f) (eval (read-string (slurp-do f)))))" + +if [[ "${1}" ]]; then + echo "${@}" + REP "(load-file \"${1}\")" && echo "${r}" +elif [[ -n "${INTERACTIVE}" ]]; then + while true; do + READLINE "user> " || exit "$?" + [[ "${r}" ]] && REP "${r}" && echo "${r}" + done +fi diff --git a/bash/step9_interop.sh b/bash/step9_interop.sh new file mode 100755 index 0000000..930aa2e --- /dev/null +++ b/bash/step9_interop.sh @@ -0,0 +1,261 @@ +#!/bin/bash + +INTERACTIVE=${INTERACTIVE-yes} + +source $(dirname $0)/reader.sh + +# READ: read and parse input +READ () { + READLINE + READ_STR "${r}" +} + +IS_PAIR () { + if _list? "${1}"; then + _count "${1}" + [[ "${r}" > 0 ]] && return 0 + fi + return 1 +} + +QUASIQUOTE () { + if ! IS_PAIR "${1}"; then + symbol quote + list "${r}" "${1}" + return + else + _nth "${1}" 0; local a0="${r}" + if [[ "${ANON["${a0}"]}" == "unquote" ]]; then + _nth "${1}" 1 + return + elif IS_PAIR "${a0}"; then + _nth "${a0}" 0; local a00="${r}" + if [[ "${ANON["${a00}"]}" == "splice-unquote" ]]; then + symbol concat; local a="${r}" + _nth "${a0}" 1; local b="${r}" + rest "${1}" + QUASIQUOTE "${r}"; local c="${r}" + list "${a}" "${b}" "${c}" + return + fi + fi + fi + symbol cons; local a="${r}" + QUASIQUOTE "${a0}"; local b="${r}" + rest "${1}" + QUASIQUOTE "${r}"; local c="${r}" + list "${a}" "${b}" "${c}" + return +} + +IS_MACRO_CALL () { + if ! _list? "${1}"; then return 1; fi + _nth "${1}" 0; local a0="${r}" + if _symbol? "${a0}"; then + ENV_FIND "${2}" "${ANON["${a0}"]}_ismacro_" + if [[ "${r}" ]]; then + return 0 + fi + fi + return 1 +} + +MACROEXPAND () { + local ast="${1}" env="${2}" + while IS_MACRO_CALL "${ast}" "${env}"; do + _nth "${ast}" 0; local a0="${r}" + ENV_GET "${env}" "${ANON["${a0}"]}"; local mac="${ANON["${r}"]}" + rest "${ast}" + ${mac%%@*} ${ANON["${r}"]} + ast="${r}" + done + r="${ast}" +} + + +EVAL_AST () { + local ast="${1}" env="${2}" + #_pr_str "${ast}"; echo "EVAL_AST '${ast}:${r} / ${env}'" + _obj_type "${ast}"; local ot="${r}" + case "${ot}" in + symbol) + local val="${ANON["${ast}"]}" + ENV_GET "${env}" "${val}" + return ;; + list) + _map_with_type list EVAL "${ast}" "${env}" ;; + vector) + _map_with_type vector EVAL "${ast}" "${env}" ;; + hash_map) + local res="" val="" hm="${ANON["${ast}"]}" + hash_map; local new_hm="${r}" + eval local keys="\${!${hm}[@]}" + for key in ${keys}; do + eval val="\${${hm}[\"${key}\"]}" + EVAL "${val}" "${env}" + assoc! "${new_hm}" "${key}" "${r}" + done + r="${new_hm}" ;; + *) + r="${ast}" ;; + esac +} + +# EVAL: evaluate the parameter +EVAL () { + local ast="${1}" env="${2}" + while true; do + r= + [[ "${__ERROR}" ]] && return 1 + #_pr_str "${ast}"; echo "EVAL '${r} / ${env}'" + if ! _list? "${ast}"; then + EVAL_AST "${ast}" "${env}" + return + fi + + # apply list + MACROEXPAND "${ast}" "${env}" + ast="${r}" + if ! _list? "${ast}"; then return; fi + _nth "${ast}" 0; local a0="${r}" + _nth "${ast}" 1; local a1="${r}" + _nth "${ast}" 2; local a2="${r}" + case "${ANON["${a0}"]}" in + def!) local k="${ANON["${a1}"]}" + #echo "def! ${k} to ${a2} in ${env}" + EVAL "${a2}" "${env}" + ENV_SET "${env}" "${k}" "${r}" + return ;; + let*) ENV "${env}"; local let_env="${r}" + local let_pairs=(${ANON["${a1}"]}) + local idx=0 + #echo "let: [${let_pairs[*]}] for ${a2}" + while [[ "${let_pairs["${idx}"]}" ]]; do + EVAL "${let_pairs[$(( idx + 1))]}" "${let_env}" + ENV_SET "${let_env}" "${ANON["${let_pairs[${idx}]}"]}" "${r}" + idx=$(( idx + 2)) + done + EVAL "${a2}" "${let_env}" + return ;; + quote) + r="${a1}" + return ;; + quasiquote) + QUASIQUOTE "${a1}" + EVAL "${r}" "${env}" + return ;; + defmacro!) + local k="${ANON["${a1}"]}" + EVAL "${a2}" "${env}" + ENV_SET "${env}" "${k}" "${r}" + ENV_SET "${env}" "${k}_ismacro_" "yes" + return ;; + macroexpand) + MACROEXPAND "${a1}" "${env}" + return ;; + sh*) MACROEXPAND "${a1}" "${env}" + EVAL "${r}" "${env}" + local output="" + local line="" + while read line; do + output="${output}${line}\n" + done < <(eval ${ANON["${r}"]}) + string "${output}" + return ;; + do) _count "${ast}" + _slice "${ast}" 1 $(( ${r} - 2 )) + EVAL_AST "${r}" "${env}" + [[ "${__ERROR}" ]] && r= && return 1 + last "${ast}" + ast="${r}" + # Continue loop + ;; + if) EVAL "${a1}" "${env}" + if [[ "${r}" == "${__false}" || "${r}" == "${__nil}" ]]; then + # eval false form + _nth "${ast}" 3; local a3="${r}" + if [[ "${a3}" ]]; then + ast="${a3}" + else + r="${__nil}" + return + fi + else + # eval true condition + ast="${a2}" + fi + # Continue loop + ;; + fn*) new_function "ENV \"${env}\" \"${a1}\" \"\${@}\"; \ + EVAL \"${a2}\" \"\${r}\"" \ + "${a2}" "${env}" "${a1}" + return ;; + *) EVAL_AST "${ast}" "${env}" + [[ "${__ERROR}" ]] && r= && return 1 + local el="${r}" + first "${el}"; local f="${ANON["${r}"]}" + rest "${el}"; local args="${ANON["${r}"]}" + #echo "invoke: [${f}] ${args}" + if [[ "${f//@/ }" != "${f}" ]]; then + set -- ${f//@/ } + ast="${2}" + ENV "${3}" "${4}" ${args} + env="${r}" + else + eval ${f%%@*} ${args} + return + fi + # Continue loop + ;; + esac + done +} +# PRINT: +PRINT () { + if [[ "${__ERROR}" ]]; then + _pr_str "${__ERROR}" yes + r="Error: ${r}" + __ERROR= + else + _pr_str "${1}" yes + fi +} + +# REPL: read, eval, print, loop +ENV; REPL_ENV="${r}" +REP () { + r= + READ_STR "${1}" + EVAL "${r}" "${REPL_ENV}" + PRINT "${r}" +} + +_fref () { new_function "${2} \"\${@}\""; ENV_SET "${REPL_ENV}" "${1}" "${r}"; } + +# Import types functions +for n in "${!types_ns[@]}"; do _fref "${n}" "${types_ns["${n}"]}"; done + +read_string () { READ_STR "${ANON["${1}"]}"; } +_fref "read-string" read_string +_eval () { + EVAL "${1}" "${REPL_ENV}" +} +_fref "eval" _eval +slurp () { string "$(cat "${ANON["${1}"]}")"; } +_fref "slurp" slurp +slurp_do () { string "(do $(cat "${ANON["${1}"]}"))"; } +_fref "slurp-do" slurp_do + +# Defined using the language itself +REP "(def! not (fn* (a) (if a false true)))" +REP "(def! load-file (fn* (f) (eval (read-string (slurp-do f)))))" + +if [[ "${1}" ]]; then + echo "${@}" + REP "(load-file \"${1}\")" && echo "${r}" +elif [[ -n "${INTERACTIVE}" ]]; then + while true; do + READLINE "user> " || exit "$?" + [[ "${r}" ]] && REP "${r}" && echo "${r}" + done +fi diff --git a/bash/stepA_more.sh b/bash/stepA_more.sh new file mode 100755 index 0000000..8caa72d --- /dev/null +++ b/bash/stepA_more.sh @@ -0,0 +1,282 @@ +#!/bin/bash + +INTERACTIVE=${INTERACTIVE-yes} + +source $(dirname $0)/reader.sh + +# READ: read and parse input +READ () { + READLINE + READ_STR "${r}" +} + +IS_PAIR () { + if _list? "${1}"; then + _count "${1}" + [[ "${r}" > 0 ]] && return 0 + fi + return 1 +} + +QUASIQUOTE () { + if ! IS_PAIR "${1}"; then + symbol quote + list "${r}" "${1}" + return + else + _nth "${1}" 0; local a0="${r}" + if [[ "${ANON["${a0}"]}" == "unquote" ]]; then + _nth "${1}" 1 + return + elif IS_PAIR "${a0}"; then + _nth "${a0}" 0; local a00="${r}" + if [[ "${ANON["${a00}"]}" == "splice-unquote" ]]; then + symbol concat; local a="${r}" + _nth "${a0}" 1; local b="${r}" + rest "${1}" + QUASIQUOTE "${r}"; local c="${r}" + list "${a}" "${b}" "${c}" + return + fi + fi + fi + symbol cons; local a="${r}" + QUASIQUOTE "${a0}"; local b="${r}" + rest "${1}" + QUASIQUOTE "${r}"; local c="${r}" + list "${a}" "${b}" "${c}" + return +} + +IS_MACRO_CALL () { + if ! _list? "${1}"; then return 1; fi + _nth "${1}" 0; local a0="${r}" + if _symbol? "${a0}"; then + ENV_FIND "${2}" "${ANON["${a0}"]}_ismacro_" + if [[ "${r}" ]]; then + return 0 + fi + fi + return 1 +} + +MACROEXPAND () { + local ast="${1}" env="${2}" + while IS_MACRO_CALL "${ast}" "${env}"; do + _nth "${ast}" 0; local a0="${r}" + ENV_GET "${env}" "${ANON["${a0}"]}"; local mac="${ANON["${r}"]}" + rest "${ast}" + ${mac%%@*} ${ANON["${r}"]} + ast="${r}" + done + r="${ast}" +} + + +EVAL_AST () { + local ast="${1}" env="${2}" + #_pr_str "${ast}"; echo "EVAL_AST '${ast}:${r} / ${env}'" + _obj_type "${ast}"; local ot="${r}" + case "${ot}" in + symbol) + local val="${ANON["${ast}"]}" + ENV_GET "${env}" "${val}" + return ;; + list) + _map_with_type list EVAL "${ast}" "${env}" ;; + vector) + _map_with_type vector EVAL "${ast}" "${env}" ;; + hash_map) + local res="" val="" hm="${ANON["${ast}"]}" + hash_map; local new_hm="${r}" + eval local keys="\${!${hm}[@]}" + for key in ${keys}; do + eval val="\${${hm}[\"${key}\"]}" + EVAL "${val}" "${env}" + assoc! "${new_hm}" "${key}" "${r}" + done + r="${new_hm}" ;; + *) + r="${ast}" ;; + esac +} + +# EVAL: evaluate the parameter +EVAL () { + local ast="${1}" env="${2}" + while true; do + r= + [[ "${__ERROR}" ]] && return 1 + #_pr_str "${ast}"; echo "EVAL '${r} / ${env}'" + if ! _list? "${ast}"; then + EVAL_AST "${ast}" "${env}" + return + fi + + # apply list + MACROEXPAND "${ast}" "${env}" + ast="${r}" + if ! _list? "${ast}"; then return; fi + _nth "${ast}" 0; local a0="${r}" + _nth "${ast}" 1; local a1="${r}" + _nth "${ast}" 2; local a2="${r}" + case "${ANON["${a0}"]}" in + def!) local k="${ANON["${a1}"]}" + #echo "def! ${k} to ${a2} in ${env}" + EVAL "${a2}" "${env}" + ENV_SET "${env}" "${k}" "${r}" + return ;; + let*) ENV "${env}"; local let_env="${r}" + local let_pairs=(${ANON["${a1}"]}) + local idx=0 + #echo "let: [${let_pairs[*]}] for ${a2}" + while [[ "${let_pairs["${idx}"]}" ]]; do + EVAL "${let_pairs[$(( idx + 1))]}" "${let_env}" + ENV_SET "${let_env}" "${ANON["${let_pairs[${idx}]}"]}" "${r}" + idx=$(( idx + 2)) + done + EVAL "${a2}" "${let_env}" + return ;; + quote) + r="${a1}" + return ;; + quasiquote) + QUASIQUOTE "${a1}" + EVAL "${r}" "${env}" + return ;; + defmacro!) + local k="${ANON["${a1}"]}" + EVAL "${a2}" "${env}" + ENV_SET "${env}" "${k}" "${r}" + ENV_SET "${env}" "${k}_ismacro_" "yes" + return ;; + macroexpand) + MACROEXPAND "${a1}" "${env}" + return ;; + sh*) MACROEXPAND "${a1}" "${env}" + EVAL "${r}" "${env}" + local output="" + local line="" + while read line; do + output="${output}${line}\n" + done < <(eval ${ANON["${r}"]}) + string "${output}" + return ;; + try*) MACROEXPAND "${a1}" "${env}" + EVAL "${r}" "${env}" + [[ -z "${__ERROR}" ]] && return + _nth "${a2}" 0; local a20="${r}" + if [ "${ANON["${a20}"]}" == "catch__STAR__" ]; then + _nth "${a2}" 1; local a21="${r}" + _nth "${a2}" 2; local a22="${r}" + list "${a21}"; local binds="${r}" + ENV "${env}" "${binds}" "${__ERROR}" + local try_env="${r}" + __ERROR= + MACROEXPAND "${a22}" "${try_env}" + EVAL "${r}" "${try_env}" + fi # if no catch* clause, just propagate __ERROR + return ;; + do) _count "${ast}" + _slice "${ast}" 1 $(( ${r} - 2 )) + EVAL_AST "${r}" "${env}" + [[ "${__ERROR}" ]] && r= && return 1 + last "${ast}" + ast="${r}" + # Continue loop + ;; + if) EVAL "${a1}" "${env}" + if [[ "${r}" == "${__false}" || "${r}" == "${__nil}" ]]; then + # eval false form + _nth "${ast}" 3; local a3="${r}" + if [[ "${a3}" ]]; then + ast="${a3}" + else + r="${__nil}" + return + fi + else + # eval true condition + ast="${a2}" + fi + # Continue loop + ;; + fn*) new_function "ENV \"${env}\" \"${a1}\" \"\${@}\"; \ + EVAL \"${a2}\" \"\${r}\"" \ + "${a2}" "${env}" "${a1}" + return ;; + *) EVAL_AST "${ast}" "${env}" + [[ "${__ERROR}" ]] && r= && return 1 + local el="${r}" + first "${el}"; local f="${ANON["${r}"]}" + rest "${el}"; local args="${ANON["${r}"]}" + #echo "invoke: [${f}] ${args}" + if [[ "${f//@/ }" != "${f}" ]]; then + set -- ${f//@/ } + ast="${2}" + ENV "${3}" "${4}" ${args} + env="${r}" + else + eval ${f%%@*} ${args} + return + fi + # Continue loop + ;; + esac + done +} +# PRINT: +PRINT () { + if [[ "${__ERROR}" ]]; then + _pr_str "${__ERROR}" yes + r="Error: ${r}" + __ERROR= + else + _pr_str "${1}" yes + fi +} + +# REPL: read, eval, print, loop +ENV; REPL_ENV="${r}" +REP () { + r= + READ_STR "${1}" + EVAL "${r}" "${REPL_ENV}" + PRINT "${r}" +} + +_fref () { new_function "${2} \"\${@}\""; ENV_SET "${REPL_ENV}" "${1}" "${r}"; } + +# Import types functions +for n in "${!types_ns[@]}"; do _fref "${n}" "${types_ns["${n}"]}"; done + +readline () { + READLINE "${ANON["${1}"]}" && string "${r}" || r="${__nil}"; +} +_fref "readline" readline +read_string () { READ_STR "${ANON["${1}"]}"; } +_fref "read-string" read_string +_eval () { + EVAL "${1}" "${REPL_ENV}" +} +_fref "eval" _eval +slurp () { string "$(cat "${ANON["${1}"]}")"; } +_fref "slurp" slurp +slurp_do () { string "(do $(cat "${ANON["${1}"]}"))"; } +_fref "slurp-do" slurp_do + +# Defined using the language itself +REP "(def! not (fn* (a) (if a false true)))" +REP "(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))" +REP "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) \`(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))" +REP "(def! load-file (fn* (f) (eval (read-string (slurp-do f)))))" + +if [[ "${1}" ]]; then + echo "${@}" + REP "(load-file \"${1}\")" && echo "${r}" +elif [[ -n "${INTERACTIVE}" ]]; then + while true; do + READLINE "user> " || exit "$?" + [[ "${r}" ]] && REP "${r}" && echo "${r}" + done +fi diff --git a/bash/tests/common.sh b/bash/tests/common.sh new file mode 100644 index 0000000..9924107 --- /dev/null +++ b/bash/tests/common.sh @@ -0,0 +1,25 @@ + +assert () { + if ! eval "${2}"; then + echo "assert failure line ${1}" + exit 1 + fi +} + +assert_eq () { + if eval "${3}"; then + if [[ "${2}" != "${r}" ]]; then + echo "assert_eq failure line ${1}: '${2}' != '${r}'" + exit 1 + fi + else + echo "assert_eq failure line ${1}: could not evaluate '${3}'" + exit 1 + fi +} + +TEST_RE () { + r= + READ_STR "${1}" + EVAL "${r}" ${REPL_ENV} +} diff --git a/bash/tests/reader.sh b/bash/tests/reader.sh new file mode 100644 index 0000000..8516b06 --- /dev/null +++ b/bash/tests/reader.sh @@ -0,0 +1,88 @@ +#!/bin/bash + +INTERACTIVE= + +source tests/common.sh +source reader.sh + +echo "Testing read of constants/strings" +assert_eq $LINENO 2 "READ_STR '2'; number_pr_str \$r" +assert_eq $LINENO 12345 "READ_STR '12345'; number_pr_str \$r" +assert_eq $LINENO 12345 "READ_STR '12345 \"abc\"'; number_pr_str \$r" +assert_eq $LINENO 'abc' "READ_STR '\"abc\"'; number_pr_str \$r" +assert_eq $LINENO 'a string (with parens)' "READ_STR '\"a string (with parens)\"'; number_pr_str \$r" + +echo "Testing read of symbols" +assert $LINENO "READ_STR 'abc'; _symbol? \$r" +assert_eq $LINENO 'abc' "READ_STR 'abc'; symbol_pr_str \$r" +assert_eq $LINENO '.' "READ_STR '.'; symbol_pr_str \$r" + +raw_val () { + r="${ANON["${1}"]}" +} + +echo "Testing READ_STR of strings" +assert_eq $LINENO 'a string' "READ_STR '\"a string\"'; raw_val \$r" +assert_eq $LINENO 'a string (with parens)' "READ_STR '\"a string (with parens)\"'; raw_val \$r" +assert_eq $LINENO 'a string' "READ_STR '\"a string\"()'; raw_val \$r" +assert_eq $LINENO 'a string' "READ_STR '\"a string\"123'; raw_val \$r" +assert_eq $LINENO 'a string' "READ_STR '\"a string\"abc'; raw_val \$r" +assert_eq $LINENO '' "READ_STR '\"\"'; raw_val \$r" +assert_eq $LINENO 'abc ' "READ_STR '\"abc \"'; raw_val \$r" +assert_eq $LINENO ' abc' "READ_STR '\" abc\"'; raw_val \$r" +assert_eq $LINENO '$abc' "READ_STR '\"\$abc\"'; raw_val \$r" +assert_eq $LINENO 'abc$()' "READ_STR '\"abc\$()\"'; raw_val \$r" +# TODO: fix parsing of escaped characters +#assert_eq $LINENO '"xyz"' "READ_STR '\"\\\"xyz\\\"\"'; raw_val \$r" + +echo "Testing READ_STR of lists" +assert_eq $LINENO 2 "READ_STR '(2 3)'; _count \$r" +assert_eq $LINENO 2 "READ_STR '(2 3)'; first \$r; number_pr_str \$r" +assert_eq $LINENO 3 "READ_STR '(2 3)'; rest \$r; first \$r; number_pr_str \$r" + +READ_STR "(+ 1 2 \"str1\" \"string (with parens) and 'single quotes'\")" +L="${r}" +assert_eq $LINENO 5 "_count \$r" +assert_eq $LINENO 'str1' "_nth ${L} 3; raw_val \$r" +assert_eq $LINENO "string (with parens) and 'single quotes'" "_nth ${L} 4; raw_val \$r" +assert_eq $LINENO '(2 3)' "READ_STR '(2 3)'; list_pr_str \$r" +assert_eq $LINENO '(2 3 "string (with parens)")' "READ_STR '(2 3 \"string (with parens)\")'; list_pr_str \$r yes" + + +echo "Testing READ_STR of vectors" +assert_eq $LINENO 2 "READ_STR '[2 3]'; _count \$r" +assert_eq $LINENO 2 "READ_STR '[2 3]'; first \$r; number_pr_str \$r" +assert_eq $LINENO 3 "READ_STR '[2 3]'; rest \$r; first \$r; number_pr_str \$r" + +READ_STR "[+ 1 2 \"str1\" \"string (with parens) and 'single quotes'\"]" +L="${r}" +assert_eq $LINENO 5 "_count \$r" +assert_eq $LINENO 'str1' "_nth ${L} 3; raw_val \$r" +assert_eq $LINENO "string (with parens) and 'single quotes'" "_nth ${L} 4; raw_val \$r" +assert_eq $LINENO '[2 3]' "READ_STR '[2 3]'; vector_pr_str \$r yes" +assert_eq $LINENO '[2 3 "string (with parens)"]' "READ_STR '[2 3 \"string (with parens)\"]'; vector_pr_str \$r yes" + + +echo "Testing READ_STR of quote/quasiquote" +assert_eq $LINENO 'quote' "READ_STR \"'1\"; _nth \$r 0; raw_val \$r" +assert_eq $LINENO 1 "READ_STR \"'1\"; _nth \$r 1; raw_val \$r" +assert_eq $LINENO 'quote' "READ_STR \"'(1 2 3)\"; _nth \$r 0; raw_val \$r" +assert_eq $LINENO 3 "READ_STR \"'(1 2 3)\"; _nth \$r 1; _nth \$r 2; raw_val \$r" + +assert_eq $LINENO 'quasiquote' "READ_STR \"\\\`1\"; _nth \$r 0; raw_val \$r" +assert_eq $LINENO 1 "READ_STR \"\\\`1\"; _nth \$r 1; raw_val \$r" +assert_eq $LINENO 'quasiquote' "READ_STR \"\\\`(1 2 3)\"; _nth \$r 0; raw_val \$r" +assert_eq $LINENO 3 "READ_STR \"\\\`(1 2 3)\"; _nth \$r 1; _nth \$r 2; raw_val \$r" + +assert_eq $LINENO 'unquote' "READ_STR \"~1\"; _nth \$r 0; raw_val \$r" +assert_eq $LINENO 1 "READ_STR \"~1\"; _nth \$r 1; raw_val \$r" +assert_eq $LINENO 'unquote' "READ_STR \"~(1 2 3)\"; _nth \$r 0; raw_val \$r" +assert_eq $LINENO 3 "READ_STR \"~(1 2 3)\"; _nth \$r 1; _nth \$r 2; raw_val \$r" + +assert_eq $LINENO 'splice-unquote' "READ_STR \"~@1\"; _nth \$r 0; raw_val \$r" +assert_eq $LINENO 1 "READ_STR \"~@1\"; _nth \$r 1; raw_val \$r" +assert_eq $LINENO 'splice-unquote' "READ_STR \"~@(1 2 3)\"; _nth \$r 0; raw_val \$r" +assert_eq $LINENO 3 "READ_STR \"~@(1 2 3)\"; _nth \$r 1; _nth \$r 2; raw_val \$r" + + +echo "All tests completed" diff --git a/bash/tests/types.sh b/bash/tests/types.sh new file mode 100644 index 0000000..7ce1ce4 --- /dev/null +++ b/bash/tests/types.sh @@ -0,0 +1,161 @@ +#!/bin/bash + +source tests/common.sh +source types.sh + +echo "Testing type function" +assert_eq $LINENO bash "_obj_type xyz" +assert_eq $LINENO nil "_obj_type ${__nil}" +assert_eq $LINENO true "_obj_type ${__true}" +assert_eq $LINENO false "_obj_type ${__false}" + + +echo "Testing number? function" +assert_eq $LINENO number "number 1; _obj_type \$r" +assert_eq $LINENO number "number 10; _obj_type \$r" +assert_eq $LINENO number "number 12345; _obj_type \$r" + + +echo "Testing symbols" +assert_eq $LINENO symbol "symbol abc; _obj_type \$r" +symbol "a sym value"; SYM1="${r}" +assert_eq $LINENO "a sym value" "symbol_pr_str ${SYM1} yes" +assert_eq $LINENO ${__true} "symbol? ${SYM1}" + + +echo "Testing strings" +assert_eq $LINENO string "string abc; _obj_type \$r" +string "a string value"; STR1="${r}" +assert_eq $LINENO "\"a string value\"" "string_pr_str ${STR1} yes" +assert_eq $LINENO ${__true} "string? ${STR1}" +# TODO: fix to count characters instead of words +#assert_eq $LINENO 14 "_count ${STR1}" + +string "a string (with parens)"; STR2="${r}" +assert_eq $LINENO "\"a string (with parens)\"" "string_pr_str ${STR2} yes" +assert_eq $LINENO ${__true} "string? ${STR2}" + +# TODO: test str and subs + + +echo "Testing function objects" +assert_eq $LINENO "function" "new_function \"echo hello\"; _obj_type \$r" +new_function "r=\"arg1:'\$1' arg2:'\$2'\""; FN1="${r}" +assert_eq $LINENO ${__true} "function? ${FN1}" +assert_eq $LINENO "arg1:'A' arg2:'B'" "${ANON["${FN1}"]} A B" + + + +echo "Testing lists" +list; LE="${r}" +assert_eq $LINENO list "_obj_type ${LE}" + +echo "Testing lists (cons)" +list; cons P ${r}; L1="${r}" +cons Q ${L1}; L2="${r}" +assert_eq $LINENO ${__true} "list? ${L1}" +assert_eq $LINENO ${__true} "list? ${L2}" +assert_eq $LINENO P "first ${L1}" +assert_eq $LINENO 2 "_count ${L2}" +assert_eq $LINENO Q "first ${L2}" +assert_eq $LINENO P "_nth ${L2} 1" +rest ${L2}; L2R="${r}" + +echo "Testing lists (concat)" +concat ${L1} ${L2}; L1_2="${r}" +assert_eq $LINENO 3 "_count ${L1_2}" +assert_eq $LINENO P "first ${L1_2}" +assert_eq $LINENO Q "_nth ${L1_2} 1" +assert_eq $LINENO P "_nth ${L1_2} 2" +rest ${L1_2}; L1_2R="${r}" + +echo "Testing lists (conj)" +list; conj ${r} A B; L3="${r}" +list; conj ${r} X ${L3}; L4="${r}" +assert_eq $LINENO ${__true} "list? ${L3}" +assert_eq $LINENO ${__true} "list? ${L4}" +assert_eq $LINENO A "first ${L3}" +assert_eq $LINENO X "first ${L4}" +_nth ${L4} 1; L4_1="${r}" +assert_eq $LINENO ${__true} "list? ${L4_1}" +assert_eq $LINENO A "first ${L4_1}" + + +echo "Testing hash maps" +hash_map; X="${r}" +hash_map; Y="${r}" +assert_eq $LINENO ${__true} "hash_map? ${X}" +assert_eq $LINENO ${__true} "hash_map? ${Y}" + +string "a" +mykey="${r}" +assert_eq $LINENO "" "_get ${X} a" +assert_eq $LINENO ${__false} "contains? ${X} ${mykey}" +assoc! ${X} a 'value of X a' +assert_eq $LINENO "value of X a" "_get ${X} a" +assert_eq $LINENO ${__true} "contains? ${X} ${mykey}" + +# TODO: more testing of Y, assoc!, dissoc! + + +# TODO: vectors + + +echo "Testing _map/map function" +list; conj "${r}" 1 2 3; L5="${r}" +inc () { r=$(( ${1} + 1)); } +assert_eq $LINENO "2 3 4" "_map inc ${L5}; r=\${ANON[\$r]}" +new_function "r=\$(( \$1 + 1 ));"; inc_func="${r}" +assert_eq $LINENO "2 3 4" "map ${inc_func} ${L5}; r=\${ANON[\$r]}" + + +echo "Testing equal? function" +assert_eq $LINENO ${__true} "equal? 2 2" +assert_eq $LINENO ${__false} "equal? 2 3" +assert_eq $LINENO ${__false} "equal? 2 3" +assert_eq $LINENO ${__true} "equal? abc abc" +assert_eq $LINENO ${__false} "equal? abc abz" +assert_eq $LINENO ${__false} "equal? zbc abc" +assert_eq $LINENO ${__true} "string abc; A=\$r; string abc; B=\$r; equal? \$A \$B" +assert_eq $LINENO ${__false} "string abc; A=\$r; string abz; B=\$r; equal? \$A \$B" +assert_eq $LINENO ${__false} "string zbc; A=\$r; string abc; B=\$r; equal? \$A \$B" +assert_eq $LINENO ${__true} "symbol abc; A=\$r; symbol abc; B=\$r; equal? \$A \$B" +assert_eq $LINENO ${__false} "symbol abc; A=\$r; symbol abz; B=\$r; equal? \$A \$B" +assert_eq $LINENO ${__false} "symbol zbc; A=\$r; symbol abc; B=\$r; equal? \$A \$B" +list; conj "${r}" 1 2 3; L6="${r}" +list; conj "${r}" 1 2 3; L7="${r}" +list; conj "${r}" 1 2 Z; L8="${r}" +list; conj "${r}" Z 2 3; L9="${r}" +list; conj "${r}" 1 2; L10="${r}" +assert_eq $LINENO ${__true} "equal? ${L6} ${L7}" +assert_eq $LINENO ${__false} "equal? ${L6} ${L8}" +assert_eq $LINENO ${__false} "equal? ${L6} ${L9}" +assert_eq $LINENO ${__false} "equal? ${L6} ${L10}" +assert_eq $LINENO ${__false} "equal? ${L10} ${L6}" + +# TODO: empty? function tests + +echo "Testing ENV environment (1 level)" +ENV; env1="${r}" +assert_eq $LINENO "" "ENV_GET \"${env1}\" a" +ENV_SET "${env1}" a "val_a" +ENV_SET "${env1}" b "val_b" +ENV_SET "${env1}" = "val_eq" +assert_eq $LINENO "val_a" "ENV_GET \"${env1}\" a" +assert_eq $LINENO "val_b" "ENV_GET \"${env1}\" b" +assert_eq $LINENO "val_eq" "ENV_GET \"${env1}\" =" +assert_eq $LINENO "${env1}" "ENV_FIND \"${env1}\" =" + +echo "Testing ENV environment (2 levels)" +ENV "${env1}"; env2="${r}" +ENV_SET "${env2}" b "val_b2" +ENV_SET "${env2}" c "val_c" +assert_eq $LINENO "${env1}" "ENV_FIND \"${env2}\" a" +assert_eq $LINENO "${env2}" "ENV_FIND \"${env2}\" b" +assert_eq $LINENO "${env2}" "ENV_FIND \"${env2}\" c" +assert_eq $LINENO "val_a" "ENV_GET \"${env2}\" a" +assert_eq $LINENO "val_b2" "ENV_GET \"${env2}\" b" +assert_eq $LINENO "val_c" "ENV_GET \"${env2}\" c" + + +echo "All tests completed" diff --git a/bash/types.sh b/bash/types.sh new file mode 100644 index 0000000..e678321 --- /dev/null +++ b/bash/types.sh @@ -0,0 +1,730 @@ +# +# mal: Object Types and Functions +# + +declare -A ANON + +__obj_magic=__5bal7 +__obj_hash_code=${__obj_hash_code:-0} + +__new_obj_hash_code () { + __obj_hash_code=$(( __obj_hash_code + 1)) + r="${__obj_hash_code}" +} + +__new_obj () { + __new_obj_hash_code + r="${1}_${r}" +} + +__new_obj_like () { + __new_obj_hash_code + r="${1%_*}_${r}" +} + +__ERROR= + + +# +# General functions +# + +# Return the type of the object (or "make" if it's not a object +_obj_type () { + local type="${1:0:4}" + r= + case "${type}" in + symb) r="symbol" ;; + list) r="list" ;; + numb) r="number" ;; + func) r="function" ;; + strn) r="string" ;; + _nil) r="nil" ;; + true) r="true" ;; + fals) r="false" ;; + vect) r="vector" ;; + hmap) r="hash_map" ;; + atom) r="atom" ;; + undf) r="undefined" ;; + *) r="bash" ;; + esac +} + +obj_type () { + _obj_type "${1}" + string "${r}" +} + +_pr_str () { + local print_readably="${2}" + _obj_type "${1}"; local ot="${r}" + if [[ -z "${ot}" ]]; then + _error "_pr_str failed on '${1}'" + r="<${1}>" + else + eval ${ot}_pr_str "${1}" "${print_readably}" + fi +} + +pr_str () { + local res="" + for x in "${@}"; do _pr_str "${x}" yes; res="${res} ${r}"; done + string "${res:1}" +} + +str () { + local res="" + for x in "${@}"; do _pr_str "${x}"; res="${res}${r}"; done + string "${res}" +} + +prn () { + local res="" + for x in "${@}"; do _pr_str "${x}" yes; res="${res} ${r}"; done + echo "${res:1}" + r="${__nil}"; +} + +println () { + local res="" + for x in "${@}"; do _pr_str "${x}"; res="${res} ${r}"; done + res="${res//\\n/$'\n'}" + echo -e "${res:1}" + r="${__nil}"; +} + +with_meta () { + local obj="${1}"; shift + local meta_data="${1}"; shift + __new_obj_like "${obj}" + ANON["${r}"]="${ANON["${obj}"]}" + local meta_obj="meta_${r#*_}" + ANON["${meta_obj}"]="${meta_data}" +} + +meta () { + r="${ANON["meta_${1#*_}"]}" + [[ "${r}" ]] || r="${__nil}" +} + +# +# Constant atomic values +# + +__undefined=undf_0 +__nil=_nil_0 +__true=true_0 +__false=fals_0 + +_undefined? () { [[ ${1} =~ ^undf_ ]]; } +undefined? () { _undefined? "${1}" && r="${__true}" || r="${__false}"; } + +_nil? () { [[ ${1} =~ ^_nil_ ]]; } +nil? () { _nil? "${1}" && r="${__true}" || r="${__false}"; } +nil_pr_str () { r="nil"; } + +_true? () { [[ ${1} =~ ^true_ ]]; } +true? () { _true? "${1}" && r="${__true}" || r="${__false}"; } +true_pr_str () { r="true"; } + +_false? () { [[ ${1} =~ ^fals_ ]]; } +false? () { _false? "${1}" && r="${__false}" || r="${__false}"; } +false_pr_str () { r="false"; } + + +# +# Numbers +# + +number () { + __new_obj_hash_code + r="numb_${r}" + ANON["${r}"]="${1}" +} +_number? () { [[ ${1} =~ ^numb_ ]]; } +number? () { _number? "${1}" && r="${__true}" || r="${__false}"; } +number_pr_str () { r="${ANON["${1}"]}"; } + +num_plus () { r=$(( ${ANON["${1}"]} + ${ANON["${2}"]} )); number "${r}"; } +num_minus () { r=$(( ${ANON["${1}"]} - ${ANON["${2}"]} )); number "${r}"; } +num_multiply () { r=$(( ${ANON["${1}"]} * ${ANON["${2}"]} )); number "${r}"; } +num_divide () { r=$(( ${ANON["${1}"]} / ${ANON["${2}"]} )); number "${r}"; } + +_num_bool () { [[ "${1}" = "1" ]] && r="${__true}" || r="${__false}"; } +num_gt () { r=$(( ${ANON["${1}"]} > ${ANON["${2}"]} )); _num_bool "${r}"; } +num_gte () { r=$(( ${ANON["${1}"]} >= ${ANON["${2}"]} )); _num_bool "${r}"; } +num_lt () { r=$(( ${ANON["${1}"]} < ${ANON["${2}"]} )); _num_bool "${r}"; } +num_lte () { r=$(( ${ANON["${1}"]} <= ${ANON["${2}"]} )); _num_bool "${r}"; } + +# +# Symbols +# + +symbol () { + __new_obj_hash_code + r="symb_${r}" + ANON["${r}"]="${1//$'\*'/__STAR__}" +} +_symbol? () { [[ ${1} =~ ^symb_ ]]; } +symbol? () { _symbol? "${1}" && r="${__true}" || r="${__false}"; } +symbol_pr_str () { + r="${ANON["${1}"]}" + r="${r//__STAR__/*}" +} + + +# +# Strings +# + +string () { + __new_obj_hash_code + r="strn_${r}" + ANON["${r}"]="${1//$'\*'/__STAR__}" +} +_string? () { [[ ${1} =~ ^strn_ ]]; } +string? () { _string? "${1}" && r="${__true}" || r="${__false}"; } +string_pr_str () { + local print_readably="${2}" + if [ "${print_readably}" == "yes" ]; then + local s="${ANON["${1}"]}" + s="${s//\\/\\\\}" + r="\"${s//\"/\\\"}\"" + else + r="${ANON["${1}"]}" + fi + r="${r//__STAR__/$'*'}" +} + +# TODO: subs + + +# +# Function objects +# + +# Return a function object. The first parameter is the +# function 'source'. +new_function () { + __new_obj_hash_code + eval "function ${__obj_magic}_func_${r} () { ${1%;} ; }" + r="func_${r}" + if [[ "${2}" ]]; then + # Native function + ANON["${r}"]="${__obj_magic}_${r}@${2}@${3}@${4}" + else + # Bash function + ANON["${r}"]="${__obj_magic}_${r}" + fi +} +_function? () { [[ ${1} =~ ^func_ ]]; } +function? () { _function? "${1}" && r="${__true}" || r="${__false}"; } +function_pr_str () { r="${ANON["${1}"]}"; } + + +# +# hash maps (associative arrays) +# + +hash_map () { + __new_obj_hash_code + local name="hmap_${r}" + local obj="${__obj_magic}_${name}" + declare -A -g ${obj} + ANON["${name}"]="${obj}" + + while [[ "${1}" ]]; do + eval ${obj}[\"${ANON["${1}"]}\"]=\"${2}\" + shift; shift + done + + r="${name}" +} +_hash_map? () { [[ ${1} =~ ^hmap_ ]]; } +hash_map? () { _hash_map? "${1}" && r="${__true}" || r="${__false}"; } + +hash_map_pr_str () { + local print_readably="${2}" + local res=""; local val="" + local hm="${ANON["${1}"]}" + eval local keys="\${!${hm}[@]}" + for key in ${keys}; do + #res="${res} \"${ANON["${key}"]}\"" + res="${res} \"${key//__STAR__/$'*'}\"" + eval val="\${${hm}[\"${key}\"]}" + _pr_str "${val}" "${print_readably}" + res="${res} ${r}" + done + r="{${res:1}}" +} + +_copy_hash_map () { + local orig_obj="${ANON["${1}"]}" + hash_map + local name="${r}" + local obj="${ANON["${name}"]}" + + # Copy the existing key/values to the new object + local temp=$(typeset -p ${orig_obj}) + eval ${temp/#declare -A ${orig_obj}=/declare -A -g ${obj}=} + r="${name}" +} + +# Return same hash map with keys/values added/mutated in place +assoc! () { + local obj=${ANON["${1}"]}; shift + declare -A -g ${obj} + + # Set the key/values specified + while [[ "${1}" ]]; do + eval ${obj}[\"${1}\"]=\"${2}\" + shift; shift + done +} + +# Return same hash map with keys/values deleted/mutated in place +dissoc! () { + local obj=${ANON["${1}"]}; shift + declare -A -g ${obj} + + # Delete the key/values specified + while [[ "${1}" ]]; do + eval unset ${obj}[\"${1}\"] + shift + done +} + +# Return new hash map with keys/values updated +assoc () { + if ! _hash_map? "${1}"; then + _error "assoc onto non-hash-map" + return + fi + _copy_hash_map "${1}"; shift + local name="${r}" + local obj=${ANON["${name}"]} + declare -A -g ${obj} + + while [[ "${1}" ]]; do + eval ${obj}[\"${ANON["${1}"]}\"]=\"${2}\" + shift; shift + done + r="${name}" +} + +dissoc () { + if ! _hash_map? "${1}"; then + _error "dissoc from non-hash-map" + return + fi + _copy_hash_map "${1}"; shift + local name="${r}" + local obj=${ANON["${name}"]} + declare -A -g ${obj} + + while [[ "${1}" ]]; do + eval unset ${obj}[\"${ANON["${1}"]}\"] + shift + done + r="${name}" +} + +_get () { + _obj_type "${1}"; local ot="${r}" + case "${ot}" in + hash_map) + local obj="${ANON["${1}"]}" + eval r="\${${obj}[\"${2}\"]}" ;; + list|vector) + _nth "${1}" "${2}" + esac +} +get () { + _get "${1}" "${ANON["${2}"]}" + [[ "${r}" ]] || r="${__nil}" +} + +_contains? () { + local obj="${ANON["${1}"]}" + #echo "_contains? ${1} ${2} -> \${${obj}[\"${2}\"]+isset}" + eval [[ "\${${obj}[\"${2}\"]+isset}" ]] +} +contains? () { _contains? "${1}" "${ANON["${2}"]}" && r="${__true}" || r="${__false}"; } + +keys () { + local obj="${ANON["${1}"]}" + local kstrs= + eval local keys="\${!${obj}[@]}" + for k in ${keys}; do + string "${k}" + kstrs="${kstrs} ${r}" + done + + __new_obj_hash_code + r="list_${r}" + ANON["${r}"]="${kstrs:1}" +} + +vals () { + local obj="${ANON["${1}"]}" + local kvals= + local val= + eval local keys="\${!${obj}[@]}" + for k in ${keys}; do + eval val="\${${obj}["\${k}"]}" + kvals="${kvals} ${val}" + done + + __new_obj_hash_code + r="list_${r}" + ANON["${r}"]="${kvals:1}" +} + +# +# Exceptions/Errors +# + +_error() { + string "${1}" + __ERROR="${r}" + r= +} +throw() { + __ERROR="${1}" + r= +} + +# +# vectors +# + +# +# vector (same as lists for now) +# + +vector () { + __new_obj_hash_code + r="vector_${r}" + ANON["${r}"]="${*}" +} +_vector? () { [[ ${1} =~ ^vector_ ]]; } +vector? () { _vector? "${1}" && r="${__true}" || r="${__false}"; } + +vector_pr_str () { + local print_readably="${2}" + local res="" + for elem in ${ANON["${1}"]}; do + _pr_str "${elem}" "${print_readably}" + res="${res} ${r}" + done + r="[${res:1}]" +} + + +# +# list (same as vectors for now) +# + +list () { + __new_obj_hash_code + r="list_${r}" + ANON["${r}"]="${*}" +} +_list? () { [[ ${1} =~ ^list_ ]]; } +list? () { _list? "${1}" && r="${__true}" || r="${__false}"; } + +list_pr_str () { + local print_readably="${2}" + local res="" + for elem in ${ANON["${1}"]}; do + _pr_str "${elem}" "${print_readably}" + res="${res} ${r}" + done + r="(${res:1})" +} + +cons () { + list ${1} ${ANON["${2}"]} +} + + +# +# atoms +# +atom() { + __new_obj_hash_code + r="atom_${r}" + ANON["${r}"]="${*}" +} +_atom? () { [[ ${1} =~ ^atom_ ]]; } +atom? () { _atom? "${1}" && r="${__true}" || r="${__false}"; } +atom_pr_str () { + local print_readably="${2}" + _pr_str "${ANON["${1}"]}" "${print_readably}" + r="(atom ${r})"; +} +deref () { + # TODO: double-check atom type + r=${ANON["${1}"]} +} +reset_BANG () { + local atm="${1}"; shift + ANON["${atm}"]="${*}" + r="${*}" +} +swap_BANG () { + local atm="${1}"; shift + local f="${ANON["${1}"]}"; shift + ${f%%@*} "${ANON["${atm}"]}" "${@}" + ANON["${atm}"]="${r}" +} + + +# +# sequence operations +# + +_sequential? () { + _list? "${1}" || _vector? "${1}" +} +sequential? () { + _sequential? "${1}" && r="${__true}" || r="${__false}" +} + +_nth () { + local temp=(${ANON["${1}"]}) + r=${temp[${2}]} +} +nth () { + _nth "${1}" "${ANON["${2}"]}" +} + + +_empty? () { [[ -z "${ANON["${1}"]}" ]]; } +empty? () { _empty? "${1}" && r="${__true}" || r="${__false}"; } + +concat () { + list + local acc="" + for item in "${@}"; do + acc="${acc} ${ANON["${item}"]}" + done + ANON["${r}"]="${acc:1}" +} + +conj () { + local obj="${1}"; shift + local obj_data="${ANON["${obj}"]}" + __new_obj_like "${obj}" + ANON["${r}"]="${obj_data:+${obj_data} }${*}" +} + +# conj that mutates in place +conj! () { + local obj="${1}"; shift + local obj_data="${ANON["${obj}"]}" + ANON["${obj}"]="${obj_data:+${obj_data} }${*}" + r="${1}" +} + + + +_count () { + local temp=(${ANON["${1}"]}) + r=${#temp[*]} +} +count () { + _count "${1}" + number "${r}" +} + +first () { + local temp="${ANON["${1}"]}" + r="${temp%% *}" +} + +last () { + local temp="${ANON["${1}"]}" + r="${temp##* }" +} + +# Slice a sequence object $1 starting at $2 of length $3 +_slice () { + local temp=(${ANON["${1}"]}) + __new_obj_like "${1}" + ANON["${r}"]="${temp[@]:${2}:${3}}" +} + +# Creates a new vector/list of the everything after but the first +# element +rest () { + local temp="${ANON["${1}"]}" + __new_obj_like "${1}" + if [[ "${temp#* }" == "${temp}" ]]; then + ANON["${r}"]= + else + ANON["${r}"]="${temp#* }" + fi +} + +apply () { + local f="${ANON["${1}"]}" + local args="${2}" + local items="${ANON["${2}"]}" + eval ${f%%@*} ${items} +} + +# Takes a bash function and an list object and invokes the function on +# each element of the list, returning a new list (or vector) of the results. +_map_with_type () { + local ot="${1}"; shift + local f="${1}"; shift + local items="${ANON["${1}"]}"; shift + eval "${ot}"; local new_seq="${r}" + for v in ${items}; do + #echo eval ${f%%@*} "${v}" "${@}" + eval ${f%%@*} "${v}" "${@}" + [[ "${__ERROR}" ]] && r= && return 1 + conj! "${new_seq}" "${r}" + done + r="${new_seq}" +} + +_map () { + _map_with_type list "${@}" +} + +# Takes a function object and an list object and invokes the function +# on each element of the list, returning a new list of the results. +map () { + local f="${ANON["${1}"]}"; shift + #echo _map "${f}" "${@}" + _map "${f}" "${@}" +} + +_equal? () { + _obj_type "${1}"; local ot1="${r}" + _obj_type "${2}"; local ot2="${r}" + if [[ "${ot1}" != "${ot2}" ]]; then + if ! _sequential? "${1}" || ! _sequential? "${2}"; then + return 1 + fi + fi + case "${ot1}" in + string|symbol|number) + [[ "${ANON["${1}"]}" == "${ANON["${2}"]}" ]] ;; + list|vector|hash_map) + _count "${1}"; local sz1="${r}" + _count "${2}"; local sz2="${r}" + [[ "${sz1}" == "${sz2}" ]] || return 1 + local a1=(${ANON["${1}"]}) + local a2=(${ANON["${2}"]}) + for ((i=0;i<${#a1[*]};i++)); do + _equal? "${a1[${i}]}" "${a2[${i}]}" || return 1 + done + ;; + *) + [[ "${1}" == "${2}" ]] ;; + esac +} +equal? () { + _equal? "${1}" "${2}" && r="${__true}" || r="${__false}" +} + +# +# ENV +# + +# Any environment is a hash_map with an __outer__ key that refers to +# a parent environment (or nil) +ENV () { + r= + hash_map + local env="${r}" + if [[ "${1}" ]]; then + outer="${1}"; shift + assoc! "${env}" "__outer__" "${outer}" + else + assoc! "${env}" "__outer__" "${__nil}" + fi + r="${env}" + + if [[ "${1}" && "${@}" ]]; then + local binds=(${ANON["${1}"]}); shift + local idx=0 + while [[ "${binds["${idx}"]}" ]]; do + local fp="${ANON["${binds["${idx}"]}"]}" + if [[ "${fp}" == "&" ]]; then + idx=$(( idx + 1 )) + fp="${ANON["${binds["${idx}"]}"]}" + list "${@}" + assoc! "${env}" "${fp}" "${r}" + break + else + assoc! "${env}" "${fp}" "${1}" + shift + idx=$(( idx + 1 )) + fi + done + fi + r="${env}" +} + +# Find the environment with the key set and return the environment +ENV_FIND () { + if _contains? "${1}" "${2}"; then + r="${1}" + else + local obj="${ANON["${1}"]}" + eval local outer="\${${obj}["__outer__"]}" + if [[ "${outer}" && "${outer}" != "${__nil}" ]]; then + ENV_FIND "${outer}" "${2}" + else + r= + fi + fi +} + +# Find the environment with the key set and return the value of the +# key in that environment. If no environment contains the key then +# return an error +ENV_GET () { + ENV_FIND "${1}" "${2}" + local env="${r}" + if [[ "${r}" ]]; then + local obj="${ANON["${env}"]}" + eval r="\${${obj}["${2}"]}" + else + _error "'${2}' not found" + fi +} + +ENV_SET () { + assoc! "${1}" "${2}" "${3}" +} + +# TODO: memory visualizer (like Make implementation) + +# Namespace of type functions + +declare -A types_ns=( + [type]=obj_type + [pr-str]=pr_str [str]=str [prn]=prn [println]=println + [with-meta]=with_meta [meta]=meta + [=]=equal? + [nil?]=nil? [true?]=true? [false?]=false? + [symbol?]=symbol? + [>]=num_gt [>=]=num_gte [<]=num_lt [<=]=num_lte + [+]=num_plus [-]=num_minus [__STAR__]=num_multiply [/]=num_divide + [hash-map]=hash_map [map?]=hash_map? + [assoc]=assoc [dissoc]=dissoc [get]=get + [contains?]=contains? [keys]=keys [vals]=vals + [throw]=throw + [list]=list [list?]=list? + [vector]=vector [vector?]=vector? + [atom]=atom [atom?]=atom? [deref]=deref + [reset!]=reset_BANG [swap!]=swap_BANG + [sequential?]=sequential? + [cons]=cons [nth]=nth [count]=count [empty?]=empty? + [concat]=concat [conj]=conj [first]=first [rest]=rest + [apply]=apply [map]=map) diff --git a/c/Makefile b/c/Makefile new file mode 100644 index 0000000..397bcbf --- /dev/null +++ b/c/Makefile @@ -0,0 +1,61 @@ +USE_READLINE ?= +CFLAGS += -g +LDFLAGS += -g + +##################### + +TESTS = + +SOURCES = types.h types.c readline.h readline.c reader.h reader.c \ + interop.h interop.c stepA_more.c + +##################### + +SRCS = step0_repl.c step1_read_print.c step2_eval.c step3_env.c \ + step4_if_fn_do.c step5_tco.c step6_file.c step7_quote.c \ + step8_macros.c step9_interop.c stepA_more.c +OBJS = $(SRCS:%.c=%.o) +BINS = $(OBJS:%.o=%) +OTHER_OBJS = types.o readline.o reader.o interop.o +OTHER_HDRS = types.h readline.h reader.h interop.h + +GLIB_CFLAGS ?= $(shell pkg-config --cflags glib-2.0) +GLIB_LDFLAGS ?= $(shell pkg-config --libs glib-2.0) + +ifeq (,$(USE_READLINE)) +RL_LIBRARY ?= edit +else +RL_LIBRARY ?= readline +CFLAGS += -DUSE_READLINE=1 +endif + +CFLAGS += $(GLIB_CFLAGS) +LDFLAGS += -l$(RL_LIBRARY) $(GLIB_LDFLAGS) -ldl -lffi + +##################### + +all: $(BINS) mal + +mal: $(word $(words $(BINS)),$(BINS)) + cp $< $@ + +$(OBJS) $(OTHER_OBJS): %.o: %.c $(OTHER_HDRS) + gcc $(CFLAGS) -c $(@:%.o=%.c) -o $@ + +$(patsubst %.o,%,$(filter step%,$(OBJS))): $(OTHER_OBJS) +$(BINS): %: %.o + gcc $+ -o $@ $(LDFLAGS) + +clean: + rm -f $(OBJS) $(BINS) $(OTHER_OBJS) mal + +.PHONY: stats tests $(TESTS) + +stats: $(SOURCES) + @wc $^ + +tests: $(TESTS) + +$(TESTS): + @echo "Running $@"; \ + ./$@ || exit 1; \ diff --git a/c/interop.c b/c/interop.c new file mode 100644 index 0000000..276b99e --- /dev/null +++ b/c/interop.c @@ -0,0 +1,165 @@ +#include <dlfcn.h> +#include <ffi.h> +#include "types.h" + + +GHashTable *loaded_dls = NULL; + +int get_byte_size(char *type) { +} + +typedef struct Raw64 { + union { + gdouble floatnum; + gint64 integernum; + char *string; + } v; +} Raw64; + + +// obj must be a pointer to the object to store +ffi_type *_get_ffi_type(char *type) { + if ((strcmp("void", type) == 0)) { + return &ffi_type_void; + } else if ((strcmp("string", type) == 0) || + (strcmp("char*", type) == 0) || + (strcmp("char *", type) == 0)) { + return &ffi_type_pointer; + } else if ((strcmp("integer", type) == 0) || + (strcmp("int64", type) == 0)) { + return &ffi_type_sint64; + } else if ((strcmp("int32", type) == 0)) { + return &ffi_type_sint32; + } else if (strcmp("double", type) == 0) { + return &ffi_type_double; + } else if (strcmp("float", type) == 0) { + return &ffi_type_float; + } else { + abort("_get_ffi_type of unknown type '%s'", type); + } +} + +MalVal *_malval_new_by_type(char *type) { + if ((strcmp("void", type) == 0)) { + return NULL; + } else if ((strcmp("string", type) == 0) || + (strcmp("char*", type) == 0) || + (strcmp("char *", type) == 0)) { + return malval_new(MAL_STRING, NULL); + } else if ((strcmp("integer", type) == 0) || + (strcmp("int64", type) == 0)) { + return malval_new(MAL_INTEGER, NULL); + } else if ((strcmp("int32", type) == 0)) { + return malval_new(MAL_INTEGER, NULL); + } else if (strcmp("double", type) == 0) { + return malval_new(MAL_FLOAT, NULL); + } else if (strcmp("float", type) == 0) { + return malval_new(MAL_FLOAT, NULL); + } else { + abort("_malval_new_by_type of unknown type '%s'", type); + } +} + + + +// Mal syntax: +// (. {DYN_LIB_FILE|nil} RETURN_TYPE FUNC_NAME [ARG_TYPE ARG]...) +MalVal *invoke_native(MalVal *call_data) { + //g_print("invoke_native %s\n", pr_str(call_data)); + int cd_len = call_data->val.array->len; + int arg_len = (cd_len - 3)/2; + char *error; + void *dl_handle; + + assert_type(call_data, MAL_LIST, + "invoke_native called with non-list call_data: %s", + _pr_str(call_data,1)); + assert(cd_len >= 3, + "invoke_native called with %d args, needs at least 3", + cd_len); + assert((cd_len % 2) == 1, + "invoke_native called with an even number of args (%d)", + cd_len); + assert(arg_len <= 3, + "invoke_native called with more than 3 native args (%d)", + arg_len); + MalVal *dl_file = _nth(call_data, 0), + *ftype = _nth(call_data, 1), + *fname = _nth(call_data, 2); + assert_type(dl_file, MAL_STRING|MAL_NIL, + "invoke_native arg 1 (DYN_LIB_NAME) must be a string or nil"); + assert_type(ftype, MAL_STRING, + "invoke_native arg 2 (RETURN_TYPE) must be a string"); + assert_type(fname, MAL_STRING, + "invoke_native arg 3 (FUNC_NAME) must be a string"); + + // Cached load of the dynamic library handle + if (dl_file->type == MAL_NIL) { + dl_handle = dlopen(NULL, RTLD_LAZY); + } else { + // Load the library + if (loaded_dls == NULL) { + loaded_dls = g_hash_table_new(g_str_hash, g_str_equal); + } + dl_handle = g_hash_table_lookup(loaded_dls, dl_file->val.string); + dlerror(); // clear any existing error + if (!dl_handle) { + dl_handle = dlopen(dl_file->val.string, RTLD_LAZY); + } + if ((error = dlerror()) != NULL) { + abort("Could not dlopen '%s': %s", dl_file->val.string, error); + } + g_hash_table_insert(loaded_dls, dl_file->val.string, dl_handle); + } + + void * func = dlsym(dl_handle, fname->val.string); + if ((error = dlerror()) != NULL) { + abort("Could not dlsym '%s': %s", fname->val.string, error); + } + + + // + // Use FFI library to make a dynamic call + // + + // Based on: + // http://eli.thegreenplace.net/2013/03/04/flexible-runtime-interface-to-shared-libraries-with-libffi/ + ffi_cif cif; + ffi_type *ret_type; + ffi_type *arg_types[20]; + void *arg_vals[20]; + ffi_status status; + MalVal *ret_mv; + + // Set return type + ret_type = _get_ffi_type(ftype->val.string); + ret_mv = _malval_new_by_type(ftype->val.string); + if (mal_error) { return NULL; } + + // Set the argument types and values + int i; + for (i=0; i < arg_len; i++) { + arg_types[i] = _get_ffi_type(_nth(call_data, 3+i*2)->val.string); + if (arg_types[i] == NULL) { + return NULL; + } + arg_vals[i] = &_nth(call_data, 4+i*2)->val; + } + + status = ffi_prep_cif(&cif, FFI_DEFAULT_ABI, arg_len, + ret_type, arg_types); + if (status != FFI_OK) { + abort("ffi_prep_cif failed: %d\n", status); + } + + // Perform the call + //g_print("Calling %s[%p](%d)\n", fname->val.string, func, arg_len); + ffi_call(&cif, FFI_FN(func), &ret_mv->val, arg_vals); + + if (ret_type == &ffi_type_void) { + return &mal_nil; + } else { + return ret_mv; + } +} + diff --git a/c/interop.h b/c/interop.h new file mode 100644 index 0000000..bcb2350 --- /dev/null +++ b/c/interop.h @@ -0,0 +1,6 @@ +#ifndef __MAL_INTEROP__ +#define __MAL_INTEROP__ + +MalVal *invoke_native(MalVal *call_data); + +#endif diff --git a/c/reader.c b/c/reader.c new file mode 100644 index 0000000..044bb84 --- /dev/null +++ b/c/reader.c @@ -0,0 +1,285 @@ +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#include <glib/gregex.h> +#include <glib-object.h> + +#include "types.h" +#include "reader.h" + +// Declare +MalVal *read_form(Reader *reader); + +Reader *reader_new() { + Reader *reader = (Reader*)malloc(sizeof(Reader)); + reader->array = g_array_sized_new(TRUE, FALSE, sizeof(char *), 8); + reader->position = 0; + return reader; +} + +int reader_append(Reader *reader, char* token) { + g_array_append_val(reader->array, token); + return TRUE; +} + +char *reader_peek(Reader *reader) { + return g_array_index(reader->array, char*, reader->position); +} + +char *reader_next(Reader *reader) { + if (reader->position >= reader->array->len) { + return NULL; + } else { + return g_array_index(reader->array, char*, reader->position++); + } +} + +void reader_free(Reader *reader) { + int i; + for(i=0; i < reader->array->len; i++) { + free(g_array_index(reader->array, char*, i)); + } + g_array_free(reader->array, TRUE); + free(reader); +} + +Reader *tokenize(char *line) { + GRegex *regex; + GMatchInfo *matchInfo; + GError *err = NULL; + + Reader *reader = reader_new(); + + regex = g_regex_new ("[\\s ,]*(~@|[\\[\\]{}()'`~@]|\"(?:[\\\\].|[^\\\\\"])*\"|;.*|[^\\s \\[\\]{}()'\"`~@,;]*)", 0, 0, &err); + g_regex_match (regex, line, 0, &matchInfo); + + if (err != NULL) { + fprintf(stderr, "Tokenize error: %s\n", err->message); + return NULL; + } + + while (g_match_info_matches(matchInfo)) { + gchar *result = g_match_info_fetch(matchInfo, 1); + if (result[0] != '\0' && result[0] != ';') { + reader_append(reader, result); + } + g_match_info_next(matchInfo, &err); + } + g_match_info_free(matchInfo); + g_regex_unref(regex); + if (reader->array->len == 0) { + reader_free(reader); + return NULL; + } else { + return reader; + } +} + + +// From http://creativeandcritical.net/str-replace-c/ - Laird Shaw +char *replace_str(const char *str, const char *old, const char *new) +{ + char *ret, *r; + const char *p, *q; + size_t oldlen = strlen(old); + size_t count, retlen, newlen = strlen(new); + + if (oldlen != newlen) { + for (count = 0, p = str; (q = strstr(p, old)) != NULL; p = q + oldlen) + count++; + /* this is undefined if p - str > PTRDIFF_MAX */ + retlen = p - str + strlen(p) + count * (newlen - oldlen); + } else + retlen = strlen(str); + + if ((ret = malloc(retlen + 1)) == NULL) + return NULL; + + for (r = ret, p = str; (q = strstr(p, old)) != NULL; p = q + oldlen) { + /* this is undefined if q - p > PTRDIFF_MAX */ + ptrdiff_t l = q - p; + memcpy(r, p, l); + r += l; + memcpy(r, new, newlen); + r += newlen; + } + strcpy(r, p); + + return ret; +} + + +MalVal *read_atom(Reader *reader) { + char *token; + GRegex *regex; + GMatchInfo *matchInfo; + GError *err = NULL; + gint pos; + MalVal *atom; + + token = reader_next(reader); + //g_print("read_atom token: %s\n", token); + + regex = g_regex_new ("(^-?[0-9]+$)|(^-?[0-9][0-9.]*$)|(^nil$)|(^true$)|(^false$)|^\"(.*)\"$|(^[^\"]*$)", 0, 0, &err); + g_regex_match (regex, token, 0, &matchInfo); + + if (g_match_info_fetch_pos(matchInfo, 1, &pos, NULL) && pos != -1) { + //g_print("read_atom integer\n"); + atom = malval_new_integer(g_ascii_strtoll(token, NULL, 10)); + } else if (g_match_info_fetch_pos(matchInfo, 2, &pos, NULL) && pos != -1) { + //g_print("read_atom float\n"); + atom = malval_new_float(g_ascii_strtod(token, NULL)); + } else if (g_match_info_fetch_pos(matchInfo, 3, &pos, NULL) && pos != -1) { + //g_print("read_atom nil\n"); + atom = &mal_nil; + } else if (g_match_info_fetch_pos(matchInfo, 4, &pos, NULL) && pos != -1) { + //g_print("read_atom true\n"); + atom = &mal_true; + } else if (g_match_info_fetch_pos(matchInfo, 5, &pos, NULL) && pos != -1) { + //g_print("read_atom false\n"); + atom = &mal_false; + } else if (g_match_info_fetch_pos(matchInfo, 6, &pos, NULL) && pos != -1) { + //g_print("read_atom string: %s\n", token); + char *str_tmp = replace_str(g_match_info_fetch(matchInfo, 6), "\\\"", "\""); + atom = malval_new_string(str_tmp); + } else if (g_match_info_fetch_pos(matchInfo, 7, &pos, NULL) && pos != -1) { + //g_print("read_atom symbol\n"); + atom = malval_new_symbol(g_match_info_fetch(matchInfo, 7)); + } else { + malval_free(atom); + atom = NULL; + } + return atom; +} + +MalVal *read_list(Reader *reader, MalType type, char start, char end) { + MalVal *ast, *form; + char *token = reader_next(reader); + //g_print("read_list start token: %s\n", token); + if (token[0] != start) { abort("expected '(' or '['"); } + + ast = malval_new_list(type, g_array_new(TRUE, TRUE, sizeof(MalVal*))); + + while ((token = reader_peek(reader)) && + token[0] != end) { + //g_print("read_list internal token %s\n", token); + form = read_form(reader); + if (!form) { + if (!mal_error) { abort("unknown read_list failure"); } + g_array_free(ast->val.array, TRUE); + malval_free(ast); + return NULL; + } + g_array_append_val(ast->val.array, form); + } + if (!token) { abort("expected ')' or ']', got EOF"); } + reader_next(reader); + //g_print("read_list end token: %s\n", token); + return ast; +} + +MalVal *read_hash_map(Reader *reader) { + MalVal *lst = read_list(reader, MAL_LIST, '{', '}'); + MalVal *hm = hash_map(lst); + malval_free(lst); + return hm; +} + + +MalVal *read_form(Reader *reader) { + char *token; + MalVal *form = NULL, *tmp; + +// while(token = reader_next(reader)) { +// printf("token: %s\n", token); +// } +// return NULL; + + token = reader_peek(reader); + + if (!token) { return NULL; } + //g_print("read_form token: %s\n", token); + + switch (token[0]) { + case ';': + abort("comments not yet implemented"); + break; + case '\'': + reader_next(reader); + form = _list(2, malval_new_symbol("quote"), + read_form(reader)); + break; + case '`': + reader_next(reader); + form = _list(2, malval_new_symbol("quasiquote"), + read_form(reader)); + break; + case '~': + reader_next(reader); + if (token[1] == '@') { + form = _list(2, malval_new_symbol("splice-unquote"), + read_form(reader)); + } else { + form = _list(2, malval_new_symbol("unquote"), + read_form(reader)); + }; + break; + case '^': + reader_next(reader); + MalVal *meta = read_form(reader); + form = _list(3, malval_new_symbol("with-meta"), + read_form(reader), meta); + break; + case '@': + reader_next(reader); + form = _list(2, malval_new_symbol("deref"), + read_form(reader)); + break; + + + // list + case ')': + abort("unexpected ')'"); + break; + case '(': + form = read_list(reader, MAL_LIST, '(', ')'); + break; + + // vector + case ']': + abort("unexpected ']'"); + break; + case '[': + form = read_list(reader, MAL_VECTOR, '[', ']'); + break; + + // hash-map + case '}': + abort("unexpected '}'"); + break; + case '{': + form = read_hash_map(reader); + break; + + default: + form = read_atom(reader); + break; + } + return form; + +} + +MalVal *read_str (char *str) { + Reader *reader; + char *token; + MalVal *ast = NULL; + + reader = tokenize(str); + if (reader) { + ast = read_form(reader); + reader_free(reader); + } + + return ast; +} diff --git a/c/reader.h b/c/reader.h new file mode 100644 index 0000000..90f07ed --- /dev/null +++ b/c/reader.h @@ -0,0 +1,23 @@ +#ifndef __MAL_READER__ +#define __MAL_READER__ + +#include <glib.h> +#include <glib-object.h> + +#include "types.h" + +typedef struct { + GArray *array; + int position; +} Reader; + +Reader *reader_new(); +int reader_append(Reader *reader, char* token); +char *reader_peek(Reader *reader); +char *reader_next(Reader *reader); +void reader_free(Reader *reader); + +char *_readline (char prompt[]); +MalVal *read_str (); + +#endif diff --git a/c/readline.c b/c/readline.c new file mode 100644 index 0000000..b981ee7 --- /dev/null +++ b/c/readline.c @@ -0,0 +1,69 @@ +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> + +#if USE_READLINE + #include <readline/readline.h> + #include <readline/history.h> + #include <readline/tilde.h> +#else + #include <editline/readline.h> + #include <editline/history.h> +#endif + +int history_loaded = 0; + +char HISTORY_FILE[] = "~/.mal-history"; + +int load_history() { + if (history_loaded) { return 0; } + int ret; + char *hf = tilde_expand(HISTORY_FILE); + if (access(hf, F_OK) != -1) { + // TODO: check if file exists first, use non-static path +#if USE_READLINE + ret = read_history(hf); +#else + FILE *fp = fopen(hf, "r"); + char *line = malloc(80); // getline reallocs as necessary + size_t sz = 80; + while ((ret = getline(&line, &sz, fp)) > 0) { + add_history(line); // Add line to in-memory history + } + free(line); + fclose(fp); +#endif + history_loaded = 1; + } + free(hf); +} + +int append_to_history() { + char *hf = tilde_expand(HISTORY_FILE); +#ifdef USE_READLINE + append_history(1, hf); +#else + HIST_ENTRY *he = history_get(history_length-1); + FILE *fp = fopen(hf, "a"); + fprintf(fp, "%s\n", he->line); + fclose(fp); +#endif + free(hf); +} + + +// line must be freed by caller +char *_readline (char prompt[]) { + char *line; + + load_history(); + + line = readline(prompt); + if (!line) return NULL; // EOF + add_history(line); // Add input to in-memory history + + append_to_history(); // Flush new line of history to disk + + return line; +} + diff --git a/c/readline.h b/c/readline.h new file mode 100644 index 0000000..d524f4a --- /dev/null +++ b/c/readline.h @@ -0,0 +1,6 @@ +#ifndef __MAL_READLINE__ +#define __MAL_READLINE__ + +char *_readline (char prompt[]); + +#endif diff --git a/c/step0_repl.c b/c/step0_repl.c new file mode 100644 index 0000000..f6d8048 --- /dev/null +++ b/c/step0_repl.c @@ -0,0 +1,44 @@ +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> + +#ifdef USE_READLINE + #include <readline/readline.h> + #include <readline/history.h> +#else + #include <editline/readline.h> +#endif + +char *READ(char prompt[]) { + char *line; + line = readline(prompt); + if (!line) return NULL; // EOF + add_history(line); // Add input to history. + return line; +} + +char *EVAL(char *ast, void *env) { + return ast; +} + +char *PRINT(char *exp) { + return exp; +} + +int main() +{ + char *ast, *exp; + char prompt[100]; + + // Set the initial prompt + snprintf(prompt, sizeof(prompt), "user> "); + + for(;;) { + ast = READ(prompt); + if (!ast) return 0; + exp = EVAL(ast, NULL); + g_print("%s\n", PRINT(exp)); + + free(ast); // Free input string + } +} diff --git a/c/step1_read_print.c b/c/step1_read_print.c new file mode 100644 index 0000000..3612373 --- /dev/null +++ b/c/step1_read_print.c @@ -0,0 +1,81 @@ +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include "types.h" +#include "readline.h" +#include "reader.h" + +// read +MalVal *READ(char prompt[], char *str) { + char *line; + MalVal *ast; + if (str) { + line = str; + } else { + line = _readline(prompt); + if (!line) { + _error("EOF"); + return NULL; + } + } + ast = read_str(line); + if (!str) { free(line); } + return ast; +} + +// eval +MalVal *EVAL(MalVal *ast, GHashTable *env) { + if (!ast || mal_error) return NULL; + return ast; +} + +// print +char *PRINT(MalVal *exp) { + if (mal_error) { + fprintf(stderr, "Error: %s\n", mal_error->val.string); + malval_free(mal_error); + mal_error = NULL; + return NULL; + } + return _pr_str(exp,1); +} + +// repl + +// read and eval +MalVal *RE(GHashTable *env, char *prompt, char *str) { + MalVal *ast, *exp; + ast = READ(prompt, str); + if (!ast || mal_error) return NULL; + exp = EVAL(ast, env); + if (ast != exp) { + malval_free(ast); // Free input structure + } + return exp; +} + +int main() +{ + MalVal *exp; + char *output; + char prompt[100]; + + // Set the initial prompt + snprintf(prompt, sizeof(prompt), "user> "); + + // REPL loop + for(;;) { + exp = RE(NULL, prompt, NULL); + if (mal_error && strcmp("EOF", mal_error) == 0) { + return 0; + } + output = PRINT(exp); + + if (output) { + g_print("%s\n", output); + free(output); // Free output string + } + + //malval_free(exp); // Free evaluated expression + } +} diff --git a/c/step2_eval.c b/c/step2_eval.c new file mode 100644 index 0000000..509e795 --- /dev/null +++ b/c/step2_eval.c @@ -0,0 +1,145 @@ +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include "types.h" +#include "readline.h" +#include "reader.h" + +// Declarations +MalVal *EVAL(MalVal *ast, GHashTable *env); + +// read +MalVal *READ(char prompt[], char *str) { + char *line; + MalVal *ast; + if (str) { + line = str; + } else { + line = _readline(prompt); + if (!line) { + _error("EOF"); + return NULL; + } + } + ast = read_str(line); + if (!str) { free(line); } + return ast; +} + +// eval +MalVal *eval_ast(MalVal *ast, GHashTable *env) { + if (!ast || mal_error) return NULL; + if (ast->type == MAL_SYMBOL) { + //g_print("EVAL symbol: %s\n", ast->val.string); + // TODO: check if not found + return g_hash_table_lookup(env, ast->val.string); + } else if ((ast->type == MAL_LIST) || (ast->type == MAL_VECTOR)) { + //g_print("EVAL sequential: %s\n", _pr_str(ast,1)); + MalVal *el = _map2((MalVal *(*)(void*, void*))EVAL, ast, env); + if (!el || mal_error) return NULL; + el->type = ast->type; + return el; + } else if (ast->type == MAL_HASH_MAP) { + //g_print("EVAL hash_map: %s\n", _pr_str(ast,1)); + GHashTableIter iter; + gpointer key, value; + MalVal *seq = malval_new_list(MAL_LIST, + g_array_sized_new(TRUE, TRUE, sizeof(MalVal*), + _count(ast))); + g_hash_table_iter_init (&iter, ast->val.hash_table); + while (g_hash_table_iter_next (&iter, &key, &value)) { + MalVal *kname = malval_new_string((char *)key); + g_array_append_val(seq->val.array, kname); + MalVal *new_val = EVAL((MalVal *)value, env); + g_array_append_val(seq->val.array, new_val); + } + return hash_map(seq); + } else { + //g_print("EVAL scalar: %s\n", _pr_str(ast,1)); + return ast; + } +} + +MalVal *EVAL(MalVal *ast, GHashTable *env) { + //g_print("EVAL: %s\n", _pr_str(ast,1)); + if (!ast || mal_error) return NULL; + if (ast->type != MAL_LIST) { + return eval_ast(ast, env); + } + if (!ast || mal_error) return NULL; + + // apply list + //g_print("EVAL apply list: %s\n", _pr_str(ast,1)); + if (_count(ast) == 0) { return ast; } + MalVal *a0 = _nth(ast, 0); + assert_type(a0, MAL_SYMBOL, "Cannot invoke %s", _pr_str(a0,1)); + MalVal *el = eval_ast(ast, env); + if (!el || mal_error) { return NULL; } + MalVal *(*f)(void *, void*) = (MalVal *(*)(void*, void*))first(el); + //g_print("eval_invoke el: %s\n", _pr_str(el,1)); + return f(_nth(el, 1), _nth(el, 2)); +} + +// print +char *PRINT(MalVal *exp) { + if (mal_error) { + fprintf(stderr, "Error: %s\n", mal_error->val.string); + malval_free(mal_error); + mal_error = NULL; + return NULL; + } + return _pr_str(exp,1); +} + +// repl + +// read and eval +MalVal *RE(GHashTable *env, char *prompt, char *str) { + MalVal *ast, *exp; + ast = READ(prompt, str); + if (!ast || mal_error) return NULL; + exp = EVAL(ast, env); + if (ast != exp) { + malval_free(ast); // Free input structure + } + return exp; +} + +// Setup the initial REPL environment +GHashTable *repl_env; + +void init_repl_env() { + repl_env = g_hash_table_new(g_str_hash, g_str_equal); + + g_hash_table_insert(repl_env, "+", int_plus); + g_hash_table_insert(repl_env, "-", int_minus); + g_hash_table_insert(repl_env, "*", int_multiply); + g_hash_table_insert(repl_env, "/", int_divide); +} + +int main() +{ + MalVal *exp; + char *output; + char prompt[100]; + + // Set the initial prompt and environment + snprintf(prompt, sizeof(prompt), "user> "); + init_repl_env(); + + // REPL loop + for(;;) { + exp = RE(repl_env, prompt, NULL); + if (mal_error && strcmp("EOF", mal_error->val.string) == 0) { + return 0; + } + output = PRINT(exp); + + if (output) { + g_print("%s\n", output); + free(output); // Free output string + } + + //malval_free(exp); // Free evaluated expression + } +} diff --git a/c/step3_env.c b/c/step3_env.c new file mode 100644 index 0000000..bc645b8 --- /dev/null +++ b/c/step3_env.c @@ -0,0 +1,171 @@ +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include "types.h" +#include "readline.h" +#include "reader.h" + +// Declarations +MalVal *EVAL(MalVal *ast, Env *env); + +// read +MalVal *READ(char prompt[], char *str) { + char *line; + MalVal *ast; + if (str) { + line = str; + } else { + line = _readline(prompt); + if (!line) { + _error("EOF"); + return NULL; + } + } + ast = read_str(line); + if (!str) { free(line); } + return ast; +} + +// eval +MalVal *eval_ast(MalVal *ast, Env *env) { + if (!ast || mal_error) return NULL; + if (ast->type == MAL_SYMBOL) { + //g_print("EVAL symbol: %s\n", ast->val.string); + return env_get(env, ast->val.string); + } else if ((ast->type == MAL_LIST) || (ast->type == MAL_VECTOR)) { + //g_print("EVAL sequential: %s\n", _pr_str(ast,1)); + MalVal *el = _map2((MalVal *(*)(void*, void*))EVAL, ast, env); + if (!el || mal_error) return NULL; + el->type = ast->type; + return el; + } else if (ast->type == MAL_HASH_MAP) { + //g_print("EVAL hash_map: %s\n", _pr_str(ast,1)); + GHashTableIter iter; + gpointer key, value; + MalVal *seq = malval_new_list(MAL_LIST, + g_array_sized_new(TRUE, TRUE, sizeof(MalVal*), + _count(ast))); + g_hash_table_iter_init (&iter, ast->val.hash_table); + while (g_hash_table_iter_next (&iter, &key, &value)) { + MalVal *kname = malval_new_string((char *)key); + g_array_append_val(seq->val.array, kname); + MalVal *new_val = EVAL((MalVal *)value, env); + g_array_append_val(seq->val.array, new_val); + } + return hash_map(seq); + } else { + //g_print("EVAL scalar: %s\n", _pr_str(ast,1)); + return ast; + } +} + +MalVal *EVAL(MalVal *ast, Env *env) { + //g_print("EVAL: %s\n", _pr_str(ast,1)); + if (!ast || mal_error) return NULL; + if (ast->type != MAL_LIST) { + return eval_ast(ast, env); + } + if (!ast || mal_error) return NULL; + + // apply list + //g_print("EVAL apply list: %s\n", _pr_str(ast,1)); + int i, len; + if (_count(ast) == 0) { return ast; } + MalVal *a0 = _nth(ast, 0); + assert_type(a0, MAL_SYMBOL, "Cannot apply %s", _pr_str(a0,1)); + if (strcmp("def!", a0->val.string) == 0) { + //g_print("eval apply def!\n"); + MalVal *a1 = _nth(ast, 1), + *a2 = _nth(ast, 2); + MalVal *res = EVAL(a2, env); + env_set(env, a1->val.string, res); + return res; + } else if (strcmp("let*", a0->val.string) == 0) { + //g_print("eval apply let*\n"); + MalVal *a1 = _nth(ast, 1), + *a2 = _nth(ast, 2), + *key, *val; + assert_type(a1, MAL_LIST|MAL_VECTOR, + "let* bindings must be list or vector"); + len = _count(a1); + assert((len % 2) == 0, "odd number of let* bindings forms"); + Env *let_env = new_env(env, NULL, NULL); + for(i=0; i<len; i+=2) { + key = g_array_index(a1->val.array, MalVal*, i); + val = g_array_index(a1->val.array, MalVal*, i+1); + assert_type(key, MAL_SYMBOL, "let* bind to non-symbol"); + env_set(let_env, key->val.string, EVAL(val, let_env)); + } + return EVAL(a2, let_env); + } else { + //g_print("eval apply\n"); + MalVal *el = eval_ast(ast, env); + if (!el || mal_error) { return NULL; } + MalVal *(*f)(void *, void*) = (MalVal *(*)(void*, void*))first(el); + return f(_nth(el, 1), _nth(el, 2)); + } +} + +// print +char *PRINT(MalVal *exp) { + if (mal_error) { + fprintf(stderr, "Error: %s\n", mal_error->val.string); + malval_free(mal_error); + mal_error = NULL; + return NULL; + } + return _pr_str(exp,1); +} + +// repl + +// read and eval +MalVal *RE(Env *env, char *prompt, char *str) { + MalVal *ast, *exp; + ast = READ(prompt, str); + if (!ast || mal_error) return NULL; + exp = EVAL(ast, env); + if (ast != exp) { + malval_free(ast); // Free input structure + } + return exp; +} + +// Setup the initial REPL environment +Env *repl_env; + +void init_repl_env() { + repl_env = new_env(NULL, NULL, NULL); + + env_set(repl_env, "+", (MalVal *)int_plus); + env_set(repl_env, "-", (MalVal *)int_minus); + env_set(repl_env, "*", (MalVal *)int_multiply); + env_set(repl_env, "/", (MalVal *)int_divide); +} + +int main() +{ + MalVal *exp; + char *output; + char prompt[100]; + + // Set the initial prompt and environment + snprintf(prompt, sizeof(prompt), "user> "); + init_repl_env(); + + // REPL loop + for(;;) { + exp = RE(repl_env, prompt, NULL); + if (mal_error && strcmp("EOF", mal_error->val.string) == 0) { + return 0; + } + output = PRINT(exp); + + if (output) { + g_print("%s\n", output); + free(output); // Free output string + } + + //malval_free(exp); // Free evaluated expression + } +} diff --git a/c/step4_if_fn_do.c b/c/step4_if_fn_do.c new file mode 100644 index 0000000..a96641e --- /dev/null +++ b/c/step4_if_fn_do.c @@ -0,0 +1,215 @@ +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include "types.h" +#include "readline.h" +#include "reader.h" + +// Declarations +MalVal *EVAL(MalVal *ast, Env *env); + +// read +MalVal *READ(char prompt[], char *str) { + char *line; + MalVal *ast; + if (str) { + line = str; + } else { + line = _readline(prompt); + if (!line) { + _error("EOF"); + return NULL; + } + } + ast = read_str(line); + if (!str) { free(line); } + return ast; +} + +// eval +MalVal *eval_ast(MalVal *ast, Env *env) { + if (!ast || mal_error) return NULL; + if (ast->type == MAL_SYMBOL) { + //g_print("EVAL symbol: %s\n", ast->val.string); + return env_get(env, ast->val.string); + } else if ((ast->type == MAL_LIST) || (ast->type == MAL_VECTOR)) { + //g_print("EVAL sequential: %s\n", _pr_str(ast,1)); + MalVal *el = _map2((MalVal *(*)(void*, void*))EVAL, ast, env); + if (!el || mal_error) return NULL; + el->type = ast->type; + return el; + } else if (ast->type == MAL_HASH_MAP) { + //g_print("EVAL hash_map: %s\n", _pr_str(ast,1)); + GHashTableIter iter; + gpointer key, value; + MalVal *seq = malval_new_list(MAL_LIST, + g_array_sized_new(TRUE, TRUE, sizeof(MalVal*), + _count(ast))); + g_hash_table_iter_init (&iter, ast->val.hash_table); + while (g_hash_table_iter_next (&iter, &key, &value)) { + MalVal *kname = malval_new_string((char *)key); + g_array_append_val(seq->val.array, kname); + MalVal *new_val = EVAL((MalVal *)value, env); + g_array_append_val(seq->val.array, new_val); + } + return hash_map(seq); + } else { + //g_print("EVAL scalar: %s\n", _pr_str(ast,1)); + return ast; + } +} + +MalVal *EVAL(MalVal *ast, Env *env) { + //g_print("EVAL: %s\n", _pr_str(ast,1)); + if (!ast || mal_error) return NULL; + if (ast->type != MAL_LIST) { + return eval_ast(ast, env); + } + if (!ast || mal_error) return NULL; + + // apply list + //g_print("EVAL apply list: %s\n", _pr_str(ast,1)); + int i, len; + if (_count(ast) == 0) { return ast; } + MalVal *a0 = _nth(ast, 0); + if ((a0->type & MAL_SYMBOL) && + strcmp("def!", a0->val.string) == 0) { + //g_print("eval apply def!\n"); + MalVal *a1 = _nth(ast, 1), + *a2 = _nth(ast, 2); + MalVal *res = EVAL(a2, env); + env_set(env, a1->val.string, res); + return res; + } else if ((a0->type & MAL_SYMBOL) && + strcmp("let*", a0->val.string) == 0) { + //g_print("eval apply let*\n"); + MalVal *a1 = _nth(ast, 1), + *a2 = _nth(ast, 2), + *key, *val; + assert_type(a1, MAL_LIST|MAL_VECTOR, + "let* bindings must be list or vector"); + len = _count(a1); + assert((len % 2) == 0, "odd number of let* bindings forms"); + Env *let_env = new_env(env, NULL, NULL); + for(i=0; i<len; i+=2) { + key = g_array_index(a1->val.array, MalVal*, i); + val = g_array_index(a1->val.array, MalVal*, i+1); + assert_type(key, MAL_SYMBOL, "let* bind to non-symbol"); + env_set(let_env, key->val.string, EVAL(val, let_env)); + } + return EVAL(a2, let_env); + } else if ((a0->type & MAL_SYMBOL) && + strcmp("do", a0->val.string) == 0) { + //g_print("eval apply do\n"); + MalVal *el = eval_ast(rest(ast), env); + return last(el); + } else if ((a0->type & MAL_SYMBOL) && + strcmp("if", a0->val.string) == 0) { + //g_print("eval apply if\n"); + MalVal *a1 = _nth(ast, 1); + MalVal *cond = EVAL(a1, env); + if (!ast || mal_error) return NULL; + if (cond->type & (MAL_FALSE|MAL_NIL)) { + // eval false slot form + MalVal *a3 = _nth(ast, 3); + if (a3) { + return EVAL(a3, env); + } else { + return &mal_nil; + } + } else { + // eval true slot form + MalVal *a2 = _nth(ast, 2); + return EVAL(a2, env); + } + } else if ((a0->type & MAL_SYMBOL) && + strcmp("fn*", a0->val.string) == 0) { + //g_print("eval apply fn*\n"); + MalVal *mf = malval_new(MAL_FUNCTION_MAL, NULL); + mf->val.func.evaluator = EVAL; + mf->val.func.args = _nth(ast, 1); + mf->val.func.body = _nth(ast, 2); + mf->val.func.env = env; + return mf; + } else { + //g_print("eval apply\n"); + MalVal *el = eval_ast(ast, env); + if (!el || mal_error) { return NULL; } + MalVal *f = first(el), + *args = rest(el); + assert_type(f, MAL_FUNCTION_C|MAL_FUNCTION_MAL, + "cannot apply '%s'", _pr_str(f,1)); + return apply(f, args); + } +} + +// print +char *PRINT(MalVal *exp) { + if (mal_error) { + fprintf(stderr, "Error: %s\n", mal_error->val.string); + malval_free(mal_error); + mal_error = NULL; + return NULL; + } + return _pr_str(exp,1); +} + +// repl + +// read and eval +MalVal *RE(Env *env, char *prompt, char *str) { + MalVal *ast, *exp; + ast = READ(prompt, str); + if (!ast || mal_error) return NULL; + exp = EVAL(ast, env); + if (ast != exp) { + malval_free(ast); // Free input structure + } + return exp; +} + +// Setup the initial REPL environment +Env *repl_env; + +void init_repl_env() { + void _ref(char *name, MalVal*(*func)(MalVal*), int arg_cnt) { + void *(*f)(void *) = (void*(*)(void*))func; + env_set(repl_env, name, malval_new_function(f, arg_cnt, NULL)); + } + repl_env = new_env(NULL, NULL, NULL); + + int i; + for(i=0; i< (sizeof(types_ns) / sizeof(types_ns[0])); i++) { + MalVal *(*f)(MalVal *) = (MalVal*(*)(MalVal*))types_ns[i].func; + _ref(types_ns[i].name, f, types_ns[i].arg_cnt); + } + + RE(repl_env, "", "(def! not (fn* (a) (if a false true)))"); +} + +int main() +{ + MalVal *exp; + char *output; + char prompt[100]; + + // Set the initial prompt and environment + snprintf(prompt, sizeof(prompt), "user> "); + init_repl_env(); + + // REPL loop + for(;;) { + exp = RE(repl_env, prompt, NULL); + if (mal_error && strcmp("EOF", mal_error->val.string) == 0) { + return 0; + } + output = PRINT(exp); + + if (output) { + g_print("%s\n", output); + free(output); // Free output string + } + + //malval_free(exp); // Free evaluated expression + } +} diff --git a/c/step5_tco.c b/c/step5_tco.c new file mode 100644 index 0000000..dc0b28e --- /dev/null +++ b/c/step5_tco.c @@ -0,0 +1,222 @@ +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include "types.h" +#include "readline.h" +#include "reader.h" + +// Declarations +MalVal *EVAL(MalVal *ast, Env *env); + +// read +MalVal *READ(char prompt[], char *str) { + char *line; + MalVal *ast; + if (str) { + line = str; + } else { + line = _readline(prompt); + if (!line) { + _error("EOF"); + return NULL; + } + } + ast = read_str(line); + if (!str) { free(line); } + return ast; +} + +// eval +MalVal *eval_ast(MalVal *ast, Env *env) { + if (!ast || mal_error) return NULL; + if (ast->type == MAL_SYMBOL) { + //g_print("EVAL symbol: %s\n", ast->val.string); + return env_get(env, ast->val.string); + } else if ((ast->type == MAL_LIST) || (ast->type == MAL_VECTOR)) { + //g_print("EVAL sequential: %s\n", _pr_str(ast,1)); + MalVal *el = _map2((MalVal *(*)(void*, void*))EVAL, ast, env); + if (!el || mal_error) return NULL; + el->type = ast->type; + return el; + } else if (ast->type == MAL_HASH_MAP) { + //g_print("EVAL hash_map: %s\n", _pr_str(ast,1)); + GHashTableIter iter; + gpointer key, value; + MalVal *seq = malval_new_list(MAL_LIST, + g_array_sized_new(TRUE, TRUE, sizeof(MalVal*), + _count(ast))); + g_hash_table_iter_init (&iter, ast->val.hash_table); + while (g_hash_table_iter_next (&iter, &key, &value)) { + MalVal *kname = malval_new_string((char *)key); + g_array_append_val(seq->val.array, kname); + MalVal *new_val = EVAL((MalVal *)value, env); + g_array_append_val(seq->val.array, new_val); + } + return hash_map(seq); + } else { + //g_print("EVAL scalar: %s\n", _pr_str(ast,1)); + return ast; + } +} + +MalVal *EVAL(MalVal *ast, Env *env) { + while (TRUE) { + //g_print("EVAL: %s\n", _pr_str(ast,1)); + if (!ast || mal_error) return NULL; + if (ast->type != MAL_LIST) { + return eval_ast(ast, env); + } + if (!ast || mal_error) return NULL; + + // apply list + //g_print("EVAL apply list: %s\n", _pr_str(ast,1)); + int i, len; + if (_count(ast) == 0) { return ast; } + MalVal *a0 = _nth(ast, 0); + if ((a0->type & MAL_SYMBOL) && + strcmp("def!", a0->val.string) == 0) { + //g_print("eval apply def!\n"); + MalVal *a1 = _nth(ast, 1), + *a2 = _nth(ast, 2); + MalVal *res = EVAL(a2, env); + env_set(env, a1->val.string, res); + return res; + } else if ((a0->type & MAL_SYMBOL) && + strcmp("let*", a0->val.string) == 0) { + //g_print("eval apply let*\n"); + MalVal *a1 = _nth(ast, 1), + *a2 = _nth(ast, 2), + *key, *val; + assert_type(a1, MAL_LIST|MAL_VECTOR, + "let* bindings must be list or vector"); + len = _count(a1); + assert((len % 2) == 0, "odd number of let* bindings forms"); + Env *let_env = new_env(env, NULL, NULL); + for(i=0; i<len; i+=2) { + key = g_array_index(a1->val.array, MalVal*, i); + val = g_array_index(a1->val.array, MalVal*, i+1); + assert_type(key, MAL_SYMBOL, "let* bind to non-symbol"); + env_set(let_env, key->val.string, EVAL(val, let_env)); + } + return EVAL(a2, let_env); + } else if ((a0->type & MAL_SYMBOL) && + strcmp("do", a0->val.string) == 0) { + //g_print("eval apply do\n"); + eval_ast(_slice(ast, 1, _count(ast)-1), env); + ast = last(ast); + // Continue loop + } else if ((a0->type & MAL_SYMBOL) && + strcmp("if", a0->val.string) == 0) { + //g_print("eval apply if\n"); + MalVal *a1 = _nth(ast, 1); + MalVal *cond = EVAL(a1, env); + if (!cond || mal_error) return NULL; + if (cond->type & (MAL_FALSE|MAL_NIL)) { + // eval false slot form + ast = _nth(ast, 3); + if (!ast) { + return &mal_nil; + } + } else { + // eval true slot form + ast = _nth(ast, 2); + } + // Continue loop + } else if ((a0->type & MAL_SYMBOL) && + strcmp("fn*", a0->val.string) == 0) { + //g_print("eval apply fn*\n"); + MalVal *mf = malval_new(MAL_FUNCTION_MAL, NULL); + mf->val.func.evaluator = EVAL; + mf->val.func.args = _nth(ast, 1); + mf->val.func.body = _nth(ast, 2); + mf->val.func.env = env; + return mf; + } else { + //g_print("eval apply\n"); + MalVal *el = eval_ast(ast, env); + if (!el || mal_error) { return NULL; } + MalVal *f = first(el), + *args = rest(el); + assert_type(f, MAL_FUNCTION_C|MAL_FUNCTION_MAL, + "cannot invoke '%s'", _pr_str(f,1)); + if (f->type & MAL_FUNCTION_MAL) { + ast = f->val.func.body; + env = new_env(f->val.func.env, f->val.func.args, args); + // Continue loop + } else { + return apply(f, args); + } + } + } +} + +// print +char *PRINT(MalVal *exp) { + if (mal_error) { + fprintf(stderr, "Error: %s\n", mal_error->val.string); + malval_free(mal_error); + mal_error = NULL; + return NULL; + } + return _pr_str(exp,1); +} + +// repl + +// read and eval +MalVal *RE(Env *env, char *prompt, char *str) { + MalVal *ast, *exp; + ast = READ(prompt, str); + if (!ast || mal_error) return NULL; + exp = EVAL(ast, env); + if (ast != exp) { + malval_free(ast); // Free input structure + } + return exp; +} + +// Setup the initial REPL environment +Env *repl_env; + +void init_repl_env() { + void _ref(char *name, MalVal*(*func)(MalVal*), int arg_cnt) { + void *(*f)(void *) = (void*(*)(void*))func; + env_set(repl_env, name, malval_new_function(f, arg_cnt, NULL)); + } + repl_env = new_env(NULL, NULL, NULL); + + int i; + for(i=0; i< (sizeof(types_ns) / sizeof(types_ns[0])); i++) { + MalVal *(*f)(MalVal *) = (MalVal*(*)(MalVal*))types_ns[i].func; + _ref(types_ns[i].name, f, types_ns[i].arg_cnt); + } + + RE(repl_env, "", "(def! not (fn* (a) (if a false true)))"); +} + +int main() +{ + MalVal *exp; + char *output; + char prompt[100]; + + // Set the initial prompt and environment + snprintf(prompt, sizeof(prompt), "user> "); + init_repl_env(); + + // REPL loop + for(;;) { + exp = RE(repl_env, prompt, NULL); + if (mal_error && strcmp("EOF", mal_error->val.string) == 0) { + return 0; + } + output = PRINT(exp); + + if (output) { + g_print("%s\n", output); + free(output); // Free output string + } + + //malval_free(exp); // Free evaluated expression + } +} diff --git a/c/step6_file.c b/c/step6_file.c new file mode 100644 index 0000000..875c32c --- /dev/null +++ b/c/step6_file.c @@ -0,0 +1,282 @@ +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <sys/stat.h> +#include <fcntl.h> +#include "types.h" +#include "readline.h" +#include "reader.h" +#include "interop.h" + +// Declarations +MalVal *EVAL(MalVal *ast, Env *env); + +// read +MalVal *READ(char prompt[], char *str) { + char *line; + MalVal *ast; + if (str) { + line = str; + } else { + line = _readline(prompt); + if (!line) { + _error("EOF"); + return NULL; + } + } + ast = read_str(line); + if (!str) { free(line); } + return ast; +} + +// eval +MalVal *eval_ast(MalVal *ast, Env *env) { + if (!ast || mal_error) return NULL; + if (ast->type == MAL_SYMBOL) { + //g_print("EVAL symbol: %s\n", ast->val.string); + return env_get(env, ast->val.string); + } else if ((ast->type == MAL_LIST) || (ast->type == MAL_VECTOR)) { + //g_print("EVAL sequential: %s\n", _pr_str(ast,1)); + MalVal *el = _map2((MalVal *(*)(void*, void*))EVAL, ast, env); + if (!el || mal_error) return NULL; + el->type = ast->type; + return el; + } else if (ast->type == MAL_HASH_MAP) { + //g_print("EVAL hash_map: %s\n", _pr_str(ast,1)); + GHashTableIter iter; + gpointer key, value; + MalVal *seq = malval_new_list(MAL_LIST, + g_array_sized_new(TRUE, TRUE, sizeof(MalVal*), + _count(ast))); + g_hash_table_iter_init (&iter, ast->val.hash_table); + while (g_hash_table_iter_next (&iter, &key, &value)) { + MalVal *kname = malval_new_string((char *)key); + g_array_append_val(seq->val.array, kname); + MalVal *new_val = EVAL((MalVal *)value, env); + g_array_append_val(seq->val.array, new_val); + } + return hash_map(seq); + } else { + //g_print("EVAL scalar: %s\n", _pr_str(ast,1)); + return ast; + } +} + +MalVal *EVAL(MalVal *ast, Env *env) { + while (TRUE) { + //g_print("EVAL: %s\n", _pr_str(ast,1)); + if (!ast || mal_error) return NULL; + if (ast->type != MAL_LIST) { + return eval_ast(ast, env); + } + if (!ast || mal_error) return NULL; + + // apply list + //g_print("EVAL apply list: %s\n", _pr_str(ast,1)); + int i, len; + if (_count(ast) == 0) { return ast; } + MalVal *a0 = _nth(ast, 0); + if ((a0->type & MAL_SYMBOL) && + strcmp("def!", a0->val.string) == 0) { + //g_print("eval apply def!\n"); + MalVal *a1 = _nth(ast, 1), + *a2 = _nth(ast, 2); + MalVal *res = EVAL(a2, env); + env_set(env, a1->val.string, res); + return res; + } else if ((a0->type & MAL_SYMBOL) && + strcmp("let*", a0->val.string) == 0) { + //g_print("eval apply let*\n"); + MalVal *a1 = _nth(ast, 1), + *a2 = _nth(ast, 2), + *key, *val; + assert_type(a1, MAL_LIST|MAL_VECTOR, + "let* bindings must be list or vector"); + len = _count(a1); + assert((len % 2) == 0, "odd number of let* bindings forms"); + Env *let_env = new_env(env, NULL, NULL); + for(i=0; i<len; i+=2) { + key = g_array_index(a1->val.array, MalVal*, i); + val = g_array_index(a1->val.array, MalVal*, i+1); + assert_type(key, MAL_SYMBOL, "let* bind to non-symbol"); + env_set(let_env, key->val.string, EVAL(val, let_env)); + } + return EVAL(a2, let_env); + } else if ((a0->type & MAL_SYMBOL) && + strcmp("do", a0->val.string) == 0) { + //g_print("eval apply do\n"); + eval_ast(_slice(ast, 1, _count(ast)-1), env); + ast = last(ast); + // Continue loop + } else if ((a0->type & MAL_SYMBOL) && + strcmp("if", a0->val.string) == 0) { + //g_print("eval apply if\n"); + MalVal *a1 = _nth(ast, 1); + MalVal *cond = EVAL(a1, env); + if (!cond || mal_error) return NULL; + if (cond->type & (MAL_FALSE|MAL_NIL)) { + // eval false slot form + ast = _nth(ast, 3); + if (!ast) { + return &mal_nil; + } + } else { + // eval true slot form + ast = _nth(ast, 2); + } + // Continue loop + } else if ((a0->type & MAL_SYMBOL) && + strcmp("fn*", a0->val.string) == 0) { + //g_print("eval apply fn*\n"); + MalVal *mf = malval_new(MAL_FUNCTION_MAL, NULL); + mf->val.func.evaluator = EVAL; + mf->val.func.args = _nth(ast, 1); + mf->val.func.body = _nth(ast, 2); + mf->val.func.env = env; + return mf; + } else { + //g_print("eval apply\n"); + MalVal *el = eval_ast(ast, env); + if (!el || mal_error) { return NULL; } + MalVal *f = first(el), + *args = rest(el); + assert_type(f, MAL_FUNCTION_C|MAL_FUNCTION_MAL, + "cannot invoke '%s'", _pr_str(f,1)); + if (f->type & MAL_FUNCTION_MAL) { + ast = f->val.func.body; + env = new_env(f->val.func.env, f->val.func.args, args); + // Continue loop + } else { + return apply(f, args); + } + } + } +} + +// print +char *PRINT(MalVal *exp) { + if (mal_error) { + fprintf(stderr, "Error: %s\n", mal_error->val.string); + malval_free(mal_error); + mal_error = NULL; + return NULL; + } + return _pr_str(exp,1); +} + +// repl + +// read and eval +MalVal *RE(Env *env, char *prompt, char *str) { + MalVal *ast, *exp; + ast = READ(prompt, str); + if (!ast || mal_error) return NULL; + exp = EVAL(ast, env); + if (ast != exp) { + malval_free(ast); // Free input structure + } + return exp; +} + +// Setup the initial REPL environment +Env *repl_env; + +char *slurp_raw(char *path) { + char *data; + struct stat fst; + int fd = open(path, O_RDONLY), + sz; + if (fd < 0) { + abort("slurp failed to open '%s'", path); + } + if (fstat(fd, &fst) < 0) { + abort("slurp failed to stat '%s'", path); + } + data = malloc(fst.st_size+1); + sz = read(fd, data, fst.st_size); + if (sz < fst.st_size) { + abort("slurp failed to read '%s'", path); + } + data[sz] = '\0'; + return data; +} + +void init_repl_env() { + void _ref(char *name, MalVal*(*func)(MalVal*), int arg_cnt) { + void *(*f)(void *) = (void*(*)(void*))func; + env_set(repl_env, name, malval_new_function(f, arg_cnt, NULL)); + } + repl_env = new_env(NULL, NULL, NULL); + + int i; + for(i=0; i< (sizeof(types_ns) / sizeof(types_ns[0])); i++) { + MalVal *(*f)(MalVal *) = (MalVal*(*)(MalVal*))types_ns[i].func; + _ref(types_ns[i].name, f, types_ns[i].arg_cnt); + } + + MalVal *read_string(MalVal *str) { + assert_type(str, MAL_STRING, "read_string of non-string"); + return read_str(str->val.string); + } + _ref("read-string", read_string, 1); + + MalVal *do_eval(MalVal *ast) { + return EVAL(ast, repl_env); + } + _ref("eval", do_eval, 1); + + MalVal *slurp(MalVal *path) { + assert_type(path, MAL_STRING, "slurp of non-string"); + char *data = slurp_raw(path->val.string); + if (!data || mal_error) { return NULL; } + return malval_new_string(data); + } + _ref("slurp", slurp, 1); + + MalVal *slurp_do(MalVal *path) { + assert_type(path, MAL_STRING, "slurp of non-string"); + char *data = slurp_raw(path->val.string), + *wrapped_data; + if (!data || mal_error) { return NULL; } + wrapped_data = g_strdup_printf("(do %s)", data); + free(data); + return malval_new_string(wrapped_data); + } + _ref("slurp-do", slurp_do, 1); + + RE(repl_env, "", "(def! not (fn* (a) (if a false true)))"); + RE(repl_env, "", + "(def! load-file (fn* (f) (eval (read-string (slurp-do f)))))"); +} + +int main(int argc, char *argv[]) +{ + MalVal *exp; + char *output; + char prompt[100]; + + // Set the initial prompt and environment + snprintf(prompt, sizeof(prompt), "user> "); + init_repl_env(); + + if (argc > 1) { + char *cmd = g_strdup_printf("(load-file \"%s\")", argv[1]); + RE(repl_env, "", cmd); + } else { + // REPL loop + for(;;) { + exp = RE(repl_env, prompt, NULL); + if (mal_error && strcmp("EOF", mal_error->val.string) == 0) { + return 0; + } + output = PRINT(exp); + + if (output) { + g_print("%s\n", output); + free(output); // Free output string + } + + //malval_free(exp); // Free evaluated expression + } + } +} diff --git a/c/step7_quote.c b/c/step7_quote.c new file mode 100644 index 0000000..46ac6a9 --- /dev/null +++ b/c/step7_quote.c @@ -0,0 +1,318 @@ +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <sys/stat.h> +#include <fcntl.h> +#include "types.h" +#include "readline.h" +#include "reader.h" +#include "interop.h" + +// Declarations +MalVal *EVAL(MalVal *ast, Env *env); + +// read +MalVal *READ(char prompt[], char *str) { + char *line; + MalVal *ast; + if (str) { + line = str; + } else { + line = _readline(prompt); + if (!line) { + _error("EOF"); + return NULL; + } + } + ast = read_str(line); + if (!str) { free(line); } + return ast; +} + +// eval +int is_pair(MalVal *x) { + return _sequential_Q(x) && (_count(x) > 0); +} + +MalVal *quasiquote(MalVal *ast) { + if (!is_pair(ast)) { + return _list(2, malval_new_symbol("quote"), ast); + } else { + MalVal *a0 = _nth(ast, 0); + if ((a0->type & MAL_SYMBOL) && + strcmp("unquote", a0->val.string) == 0) { + return _nth(ast, 1); + } else if (is_pair(a0)) { + MalVal *a00 = _nth(a0, 0); + if ((a00->type & MAL_SYMBOL) && + strcmp("splice-unquote", a00->val.string) == 0) { + return _list(3, malval_new_symbol("concat"), + _nth(a0, 1), + quasiquote(rest(ast))); + } + } + return _list(3, malval_new_symbol("cons"), + quasiquote(a0), + quasiquote(rest(ast))); + } +} + +MalVal *eval_ast(MalVal *ast, Env *env) { + if (!ast || mal_error) return NULL; + if (ast->type == MAL_SYMBOL) { + //g_print("EVAL symbol: %s\n", ast->val.string); + return env_get(env, ast->val.string); + } else if ((ast->type == MAL_LIST) || (ast->type == MAL_VECTOR)) { + //g_print("EVAL sequential: %s\n", _pr_str(ast,1)); + MalVal *el = _map2((MalVal *(*)(void*, void*))EVAL, ast, env); + if (!el || mal_error) return NULL; + el->type = ast->type; + return el; + } else if (ast->type == MAL_HASH_MAP) { + //g_print("EVAL hash_map: %s\n", _pr_str(ast,1)); + GHashTableIter iter; + gpointer key, value; + MalVal *seq = malval_new_list(MAL_LIST, + g_array_sized_new(TRUE, TRUE, sizeof(MalVal*), + _count(ast))); + g_hash_table_iter_init (&iter, ast->val.hash_table); + while (g_hash_table_iter_next (&iter, &key, &value)) { + MalVal *kname = malval_new_string((char *)key); + g_array_append_val(seq->val.array, kname); + MalVal *new_val = EVAL((MalVal *)value, env); + g_array_append_val(seq->val.array, new_val); + } + return hash_map(seq); + } else { + //g_print("EVAL scalar: %s\n", _pr_str(ast,1)); + return ast; + } +} + +MalVal *EVAL(MalVal *ast, Env *env) { + while (TRUE) { + //g_print("EVAL: %s\n", _pr_str(ast,1)); + if (!ast || mal_error) return NULL; + if (ast->type != MAL_LIST) { + return eval_ast(ast, env); + } + if (!ast || mal_error) return NULL; + + // apply list + //g_print("EVAL apply list: %s\n", _pr_str(ast,1)); + int i, len; + if (_count(ast) == 0) { return ast; } + MalVal *a0 = _nth(ast, 0); + if ((a0->type & MAL_SYMBOL) && + strcmp("def!", a0->val.string) == 0) { + //g_print("eval apply def!\n"); + MalVal *a1 = _nth(ast, 1), + *a2 = _nth(ast, 2); + MalVal *res = EVAL(a2, env); + env_set(env, a1->val.string, res); + return res; + } else if ((a0->type & MAL_SYMBOL) && + strcmp("let*", a0->val.string) == 0) { + //g_print("eval apply let*\n"); + MalVal *a1 = _nth(ast, 1), + *a2 = _nth(ast, 2), + *key, *val; + assert_type(a1, MAL_LIST|MAL_VECTOR, + "let* bindings must be list or vector"); + len = _count(a1); + assert((len % 2) == 0, "odd number of let* bindings forms"); + Env *let_env = new_env(env, NULL, NULL); + for(i=0; i<len; i+=2) { + key = g_array_index(a1->val.array, MalVal*, i); + val = g_array_index(a1->val.array, MalVal*, i+1); + assert_type(key, MAL_SYMBOL, "let* bind to non-symbol"); + env_set(let_env, key->val.string, EVAL(val, let_env)); + } + return EVAL(a2, let_env); + } else if ((a0->type & MAL_SYMBOL) && + strcmp("quote", a0->val.string) == 0) { + //g_print("eval apply quote\n"); + return _nth(ast, 1); + } else if ((a0->type & MAL_SYMBOL) && + strcmp("quasiquote", a0->val.string) == 0) { + //g_print("eval apply quasiquote\n"); + MalVal *a1 = _nth(ast, 1); + return EVAL(quasiquote(a1), env); + } else if ((a0->type & MAL_SYMBOL) && + strcmp("do", a0->val.string) == 0) { + //g_print("eval apply do\n"); + eval_ast(_slice(ast, 1, _count(ast)-1), env); + ast = last(ast); + // Continue loop + } else if ((a0->type & MAL_SYMBOL) && + strcmp("if", a0->val.string) == 0) { + //g_print("eval apply if\n"); + MalVal *a1 = _nth(ast, 1); + MalVal *cond = EVAL(a1, env); + if (!cond || mal_error) return NULL; + if (cond->type & (MAL_FALSE|MAL_NIL)) { + // eval false slot form + ast = _nth(ast, 3); + if (!ast) { + return &mal_nil; + } + } else { + // eval true slot form + ast = _nth(ast, 2); + } + // Continue loop + } else if ((a0->type & MAL_SYMBOL) && + strcmp("fn*", a0->val.string) == 0) { + //g_print("eval apply fn*\n"); + MalVal *mf = malval_new(MAL_FUNCTION_MAL, NULL); + mf->val.func.evaluator = EVAL; + mf->val.func.args = _nth(ast, 1); + mf->val.func.body = _nth(ast, 2); + mf->val.func.env = env; + return mf; + } else { + //g_print("eval apply\n"); + MalVal *el = eval_ast(ast, env); + if (!el || mal_error) { return NULL; } + MalVal *f = first(el), + *args = rest(el); + assert_type(f, MAL_FUNCTION_C|MAL_FUNCTION_MAL, + "cannot invoke '%s'", _pr_str(f,1)); + if (f->type & MAL_FUNCTION_MAL) { + ast = f->val.func.body; + env = new_env(f->val.func.env, f->val.func.args, args); + // Continue loop + } else { + return apply(f, args); + } + } + } +} + +// print +char *PRINT(MalVal *exp) { + if (mal_error) { + fprintf(stderr, "Error: %s\n", mal_error->val.string); + malval_free(mal_error); + mal_error = NULL; + return NULL; + } + return _pr_str(exp,1); +} + +// repl + +// read and eval +MalVal *RE(Env *env, char *prompt, char *str) { + MalVal *ast, *exp; + ast = READ(prompt, str); + if (!ast || mal_error) return NULL; + exp = EVAL(ast, env); + if (ast != exp) { + malval_free(ast); // Free input structure + } + return exp; +} + +// Setup the initial REPL environment +Env *repl_env; + +char *slurp_raw(char *path) { + char *data; + struct stat fst; + int fd = open(path, O_RDONLY), + sz; + if (fd < 0) { + abort("slurp failed to open '%s'", path); + } + if (fstat(fd, &fst) < 0) { + abort("slurp failed to stat '%s'", path); + } + data = malloc(fst.st_size+1); + sz = read(fd, data, fst.st_size); + if (sz < fst.st_size) { + abort("slurp failed to read '%s'", path); + } + data[sz] = '\0'; + return data; +} + +void init_repl_env() { + void _ref(char *name, MalVal*(*func)(MalVal*), int arg_cnt) { + void *(*f)(void *) = (void*(*)(void*))func; + env_set(repl_env, name, malval_new_function(f, arg_cnt, NULL)); + } + repl_env = new_env(NULL, NULL, NULL); + + int i; + for(i=0; i< (sizeof(types_ns) / sizeof(types_ns[0])); i++) { + MalVal *(*f)(MalVal *) = (MalVal*(*)(MalVal*))types_ns[i].func; + _ref(types_ns[i].name, f, types_ns[i].arg_cnt); + } + + MalVal *read_string(MalVal *str) { + assert_type(str, MAL_STRING, "read_string of non-string"); + return read_str(str->val.string); + } + _ref("read-string", read_string, 1); + + MalVal *do_eval(MalVal *ast) { + return EVAL(ast, repl_env); + } + _ref("eval", do_eval, 1); + + MalVal *slurp(MalVal *path) { + assert_type(path, MAL_STRING, "slurp of non-string"); + char *data = slurp_raw(path->val.string); + if (!data || mal_error) { return NULL; } + return malval_new_string(data); + } + _ref("slurp", slurp, 1); + + MalVal *slurp_do(MalVal *path) { + assert_type(path, MAL_STRING, "slurp of non-string"); + char *data = slurp_raw(path->val.string), + *wrapped_data; + if (!data || mal_error) { return NULL; } + wrapped_data = g_strdup_printf("(do %s)", data); + free(data); + return malval_new_string(wrapped_data); + } + _ref("slurp-do", slurp_do, 1); + + RE(repl_env, "", "(def! not (fn* (a) (if a false true)))"); + RE(repl_env, "", + "(def! load-file (fn* (f) (eval (read-string (slurp-do f)))))"); +} + +int main(int argc, char *argv[]) +{ + MalVal *exp; + char *output; + char prompt[100]; + + // Set the initial prompt and environment + snprintf(prompt, sizeof(prompt), "user> "); + init_repl_env(); + + if (argc > 1) { + char *cmd = g_strdup_printf("(load-file \"%s\")", argv[1]); + RE(repl_env, "", cmd); + } else { + // REPL loop + for(;;) { + exp = RE(repl_env, prompt, NULL); + if (mal_error && strcmp("EOF", mal_error->val.string) == 0) { + return 0; + } + output = PRINT(exp); + + if (output) { + g_print("%s\n", output); + free(output); // Free output string + } + + //malval_free(exp); // Free evaluated expression + } + } +} diff --git a/c/step8_macros.c b/c/step8_macros.c new file mode 100644 index 0000000..23afc33 --- /dev/null +++ b/c/step8_macros.c @@ -0,0 +1,357 @@ +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <sys/stat.h> +#include <fcntl.h> +#include "types.h" +#include "readline.h" +#include "reader.h" +#include "interop.h" + +// Declarations +MalVal *EVAL(MalVal *ast, Env *env); +MalVal *macroexpand(MalVal *ast, Env *env); + +// read +MalVal *READ(char prompt[], char *str) { + char *line; + MalVal *ast; + if (str) { + line = str; + } else { + line = _readline(prompt); + if (!line) { + _error("EOF"); + return NULL; + } + } + ast = read_str(line); + if (!str) { free(line); } + return ast; +} + +// eval +int is_pair(MalVal *x) { + return _sequential_Q(x) && (_count(x) > 0); +} + +MalVal *quasiquote(MalVal *ast) { + if (!is_pair(ast)) { + return _list(2, malval_new_symbol("quote"), ast); + } else { + MalVal *a0 = _nth(ast, 0); + if ((a0->type & MAL_SYMBOL) && + strcmp("unquote", a0->val.string) == 0) { + return _nth(ast, 1); + } else if (is_pair(a0)) { + MalVal *a00 = _nth(a0, 0); + if ((a00->type & MAL_SYMBOL) && + strcmp("splice-unquote", a00->val.string) == 0) { + return _list(3, malval_new_symbol("concat"), + _nth(a0, 1), + quasiquote(rest(ast))); + } + } + return _list(3, malval_new_symbol("cons"), + quasiquote(a0), + quasiquote(rest(ast))); + } +} + +int is_macro_call(MalVal *ast, Env *env) { + if (!ast || ast->type != MAL_LIST) { return 0; } + MalVal *a0 = _nth(ast, 0); + return (a0->type & MAL_SYMBOL) && + env_find(env, a0->val.string) && + env_get(env, a0->val.string)->ismacro; +} + +MalVal *macroexpand(MalVal *ast, Env *env) { + if (!ast || mal_error) return NULL; + while (is_macro_call(ast, env)) { + MalVal *a0 = _nth(ast, 0); + MalVal *mac = env_get(env, a0->val.string); + // TODO: this is weird and limits it to 20. FIXME + ast = apply(mac, rest(ast)); + } + return ast; +} + +MalVal *eval_ast(MalVal *ast, Env *env) { + if (!ast || mal_error) return NULL; + if (ast->type == MAL_SYMBOL) { + //g_print("EVAL symbol: %s\n", ast->val.string); + return env_get(env, ast->val.string); + } else if ((ast->type == MAL_LIST) || (ast->type == MAL_VECTOR)) { + //g_print("EVAL sequential: %s\n", _pr_str(ast,1)); + MalVal *el = _map2((MalVal *(*)(void*, void*))EVAL, ast, env); + if (!el || mal_error) return NULL; + el->type = ast->type; + return el; + } else if (ast->type == MAL_HASH_MAP) { + //g_print("EVAL hash_map: %s\n", _pr_str(ast,1)); + GHashTableIter iter; + gpointer key, value; + MalVal *seq = malval_new_list(MAL_LIST, + g_array_sized_new(TRUE, TRUE, sizeof(MalVal*), + _count(ast))); + g_hash_table_iter_init (&iter, ast->val.hash_table); + while (g_hash_table_iter_next (&iter, &key, &value)) { + MalVal *kname = malval_new_string((char *)key); + g_array_append_val(seq->val.array, kname); + MalVal *new_val = EVAL((MalVal *)value, env); + g_array_append_val(seq->val.array, new_val); + } + return hash_map(seq); + } else { + //g_print("EVAL scalar: %s\n", _pr_str(ast,1)); + return ast; + } +} + +MalVal *EVAL(MalVal *ast, Env *env) { + while (TRUE) { + if (!ast || mal_error) return NULL; + //g_print("EVAL: %s\n", _pr_str(ast,1)); + if (ast->type != MAL_LIST) { + return eval_ast(ast, env); + } + if (!ast || mal_error) return NULL; + + // apply list + //g_print("EVAL apply list: %s\n", _pr_str(ast,1)); + ast = macroexpand(ast, env); + if (!ast || mal_error) return NULL; + if (ast->type != MAL_LIST) { return ast; } + if (_count(ast) == 0) { return ast; } + + int i, len; + MalVal *a0 = _nth(ast, 0); + if ((a0->type & MAL_SYMBOL) && + strcmp("def!", a0->val.string) == 0) { + //g_print("eval apply def!\n"); + MalVal *a1 = _nth(ast, 1), + *a2 = _nth(ast, 2); + MalVal *res = EVAL(a2, env); + env_set(env, a1->val.string, res); + return res; + } else if ((a0->type & MAL_SYMBOL) && + strcmp("let*", a0->val.string) == 0) { + //g_print("eval apply let*\n"); + MalVal *a1 = _nth(ast, 1), + *a2 = _nth(ast, 2), + *key, *val; + assert_type(a1, MAL_LIST|MAL_VECTOR, + "let* bindings must be list or vector"); + len = _count(a1); + assert((len % 2) == 0, "odd number of let* bindings forms"); + Env *let_env = new_env(env, NULL, NULL); + for(i=0; i<len; i+=2) { + key = g_array_index(a1->val.array, MalVal*, i); + val = g_array_index(a1->val.array, MalVal*, i+1); + assert_type(key, MAL_SYMBOL, "let* bind to non-symbol"); + env_set(let_env, key->val.string, EVAL(val, let_env)); + } + return EVAL(a2, let_env); + } else if ((a0->type & MAL_SYMBOL) && + strcmp("quote", a0->val.string) == 0) { + //g_print("eval apply quote\n"); + return _nth(ast, 1); + } else if ((a0->type & MAL_SYMBOL) && + strcmp("quasiquote", a0->val.string) == 0) { + //g_print("eval apply quasiquote\n"); + MalVal *a1 = _nth(ast, 1); + return EVAL(quasiquote(a1), env); + } else if ((a0->type & MAL_SYMBOL) && + strcmp("defmacro!", a0->val.string) == 0) { + //g_print("eval apply defmacro!\n"); + MalVal *a1 = _nth(ast, 1), + *a2 = _nth(ast, 2); + MalVal *res = EVAL(a2, env); + res->ismacro = TRUE; + env_set(env, a1->val.string, res); + return res; + } else if ((a0->type & MAL_SYMBOL) && + strcmp("macroexpand", a0->val.string) == 0) { + //g_print("eval apply macroexpand\n"); + MalVal *a1 = _nth(ast, 1); + return macroexpand(a1, env); + } else if ((a0->type & MAL_SYMBOL) && + strcmp("do", a0->val.string) == 0) { + //g_print("eval apply do\n"); + eval_ast(_slice(ast, 1, _count(ast)-1), env); + ast = last(ast); + // Continue loop + } else if ((a0->type & MAL_SYMBOL) && + strcmp("if", a0->val.string) == 0) { + //g_print("eval apply if\n"); + MalVal *a1 = _nth(ast, 1); + MalVal *cond = EVAL(a1, env); + if (!cond || mal_error) return NULL; + if (cond->type & (MAL_FALSE|MAL_NIL)) { + // eval false slot form + ast = _nth(ast, 3); + if (!ast) { + return &mal_nil; + } + } else { + // eval true slot form + ast = _nth(ast, 2); + } + // Continue loop + } else if ((a0->type & MAL_SYMBOL) && + strcmp("fn*", a0->val.string) == 0) { + //g_print("eval apply fn*\n"); + MalVal *mf = malval_new(MAL_FUNCTION_MAL, NULL); + mf->ismacro = FALSE; + mf->val.func.evaluator = EVAL; + mf->val.func.args = _nth(ast, 1); + mf->val.func.body = _nth(ast, 2); + mf->val.func.env = env; + return mf; + } else { + //g_print("eval apply\n"); + MalVal *el = eval_ast(ast, env); + if (!el || mal_error) { return NULL; } + MalVal *f = first(el), + *args = rest(el); + assert_type(f, MAL_FUNCTION_C|MAL_FUNCTION_MAL, + "cannot invoke '%s'", _pr_str(f,1)); + if (f->type & MAL_FUNCTION_MAL) { + ast = f->val.func.body; + env = new_env(f->val.func.env, f->val.func.args, args); + // Continue loop + } else { + return apply(f, args); + } + } + } +} + +// print +char *PRINT(MalVal *exp) { + if (mal_error) { + fprintf(stderr, "Error: %s\n", mal_error->val.string); + malval_free(mal_error); + mal_error = NULL; + return NULL; + } + return _pr_str(exp,1); +} + +// repl + +// read and eval +MalVal *RE(Env *env, char *prompt, char *str) { + MalVal *ast, *exp; + ast = READ(prompt, str); + if (!ast || mal_error) return NULL; + exp = EVAL(ast, env); + if (ast != exp) { + malval_free(ast); // Free input structure + } + return exp; +} + +// Setup the initial REPL environment +Env *repl_env; + +char *slurp_raw(char *path) { + char *data; + struct stat fst; + int fd = open(path, O_RDONLY), + sz; + if (fd < 0) { + abort("slurp failed to open '%s'", path); + } + if (fstat(fd, &fst) < 0) { + abort("slurp failed to stat '%s'", path); + } + data = malloc(fst.st_size+1); + sz = read(fd, data, fst.st_size); + if (sz < fst.st_size) { + abort("slurp failed to read '%s'", path); + } + data[sz] = '\0'; + return data; +} + +void init_repl_env() { + void _ref(char *name, MalVal*(*func)(MalVal*), int arg_cnt) { + void *(*f)(void *) = (void*(*)(void*))func; + env_set(repl_env, name, malval_new_function(f, arg_cnt, NULL)); + } + repl_env = new_env(NULL, NULL, NULL); + + int i; + for(i=0; i< (sizeof(types_ns) / sizeof(types_ns[0])); i++) { + MalVal *(*f)(MalVal *) = (MalVal*(*)(MalVal*))types_ns[i].func; + _ref(types_ns[i].name, f, types_ns[i].arg_cnt); + } + + MalVal *read_string(MalVal *str) { + assert_type(str, MAL_STRING, "read_string of non-string"); + return read_str(str->val.string); + } + _ref("read-string", read_string, 1); + + MalVal *do_eval(MalVal *ast) { + return EVAL(ast, repl_env); + } + _ref("eval", do_eval, 1); + + MalVal *slurp(MalVal *path) { + assert_type(path, MAL_STRING, "slurp of non-string"); + char *data = slurp_raw(path->val.string); + if (!data || mal_error) { return NULL; } + return malval_new_string(data); + } + _ref("slurp", slurp, 1); + + MalVal *slurp_do(MalVal *path) { + assert_type(path, MAL_STRING, "slurp of non-string"); + char *data = slurp_raw(path->val.string), + *wrapped_data; + if (!data || mal_error) { return NULL; } + wrapped_data = g_strdup_printf("(do %s)", data); + free(data); + return malval_new_string(wrapped_data); + } + _ref("slurp-do", slurp_do, 1); + + RE(repl_env, "", "(def! not (fn* (a) (if a false true)))"); + RE(repl_env, "", + "(def! load-file (fn* (f) (eval (read-string (slurp-do f)))))"); +} + +int main(int argc, char *argv[]) +{ + MalVal *exp; + char *output; + char prompt[100]; + + // Set the initial prompt and environment + snprintf(prompt, sizeof(prompt), "user> "); + init_repl_env(); + + if (argc > 1) { + char *cmd = g_strdup_printf("(load-file \"%s\")", argv[1]); + RE(repl_env, "", cmd); + } else { + // REPL loop + for(;;) { + exp = RE(repl_env, prompt, NULL); + if (mal_error && strcmp("EOF", mal_error->val.string) == 0) { + return 0; + } + output = PRINT(exp); + + if (output) { + g_print("%s\n", output); + free(output); // Free output string + } + + //malval_free(exp); // Free evaluated expression + } + } +} diff --git a/c/step9_interop.c b/c/step9_interop.c new file mode 100644 index 0000000..2a98dd8 --- /dev/null +++ b/c/step9_interop.c @@ -0,0 +1,362 @@ +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <sys/stat.h> +#include <fcntl.h> +#include "types.h" +#include "readline.h" +#include "reader.h" +#include "interop.h" + +// Declarations +MalVal *EVAL(MalVal *ast, Env *env); +MalVal *macroexpand(MalVal *ast, Env *env); + +// read +MalVal *READ(char prompt[], char *str) { + char *line; + MalVal *ast; + if (str) { + line = str; + } else { + line = _readline(prompt); + if (!line) { + _error("EOF"); + return NULL; + } + } + ast = read_str(line); + if (!str) { free(line); } + return ast; +} + +// eval +int is_pair(MalVal *x) { + return _sequential_Q(x) && (_count(x) > 0); +} + +MalVal *quasiquote(MalVal *ast) { + if (!is_pair(ast)) { + return _list(2, malval_new_symbol("quote"), ast); + } else { + MalVal *a0 = _nth(ast, 0); + if ((a0->type & MAL_SYMBOL) && + strcmp("unquote", a0->val.string) == 0) { + return _nth(ast, 1); + } else if (is_pair(a0)) { + MalVal *a00 = _nth(a0, 0); + if ((a00->type & MAL_SYMBOL) && + strcmp("splice-unquote", a00->val.string) == 0) { + return _list(3, malval_new_symbol("concat"), + _nth(a0, 1), + quasiquote(rest(ast))); + } + } + return _list(3, malval_new_symbol("cons"), + quasiquote(a0), + quasiquote(rest(ast))); + } +} + +int is_macro_call(MalVal *ast, Env *env) { + if (!ast || ast->type != MAL_LIST) { return 0; } + MalVal *a0 = _nth(ast, 0); + return (a0->type & MAL_SYMBOL) && + env_find(env, a0->val.string) && + env_get(env, a0->val.string)->ismacro; +} + +MalVal *macroexpand(MalVal *ast, Env *env) { + if (!ast || mal_error) return NULL; + while (is_macro_call(ast, env)) { + MalVal *a0 = _nth(ast, 0); + MalVal *mac = env_get(env, a0->val.string); + // TODO: this is weird and limits it to 20. FIXME + ast = apply(mac, rest(ast)); + } + return ast; +} + +MalVal *eval_ast(MalVal *ast, Env *env) { + if (!ast || mal_error) return NULL; + if (ast->type == MAL_SYMBOL) { + //g_print("EVAL symbol: %s\n", ast->val.string); + return env_get(env, ast->val.string); + } else if ((ast->type == MAL_LIST) || (ast->type == MAL_VECTOR)) { + //g_print("EVAL sequential: %s\n", _pr_str(ast,1)); + MalVal *el = _map2((MalVal *(*)(void*, void*))EVAL, ast, env); + if (!el || mal_error) return NULL; + el->type = ast->type; + return el; + } else if (ast->type == MAL_HASH_MAP) { + //g_print("EVAL hash_map: %s\n", _pr_str(ast,1)); + GHashTableIter iter; + gpointer key, value; + MalVal *seq = malval_new_list(MAL_LIST, + g_array_sized_new(TRUE, TRUE, sizeof(MalVal*), + _count(ast))); + g_hash_table_iter_init (&iter, ast->val.hash_table); + while (g_hash_table_iter_next (&iter, &key, &value)) { + MalVal *kname = malval_new_string((char *)key); + g_array_append_val(seq->val.array, kname); + MalVal *new_val = EVAL((MalVal *)value, env); + g_array_append_val(seq->val.array, new_val); + } + return hash_map(seq); + } else { + //g_print("EVAL scalar: %s\n", _pr_str(ast,1)); + return ast; + } +} + +MalVal *EVAL(MalVal *ast, Env *env) { + while (TRUE) { + if (!ast || mal_error) return NULL; + //g_print("EVAL: %s\n", _pr_str(ast,1)); + if (ast->type != MAL_LIST) { + return eval_ast(ast, env); + } + if (!ast || mal_error) return NULL; + + // apply list + //g_print("EVAL apply list: %s\n", _pr_str(ast,1)); + ast = macroexpand(ast, env); + if (!ast || mal_error) return NULL; + if (ast->type != MAL_LIST) { return ast; } + if (_count(ast) == 0) { return ast; } + + int i, len; + MalVal *a0 = _nth(ast, 0); + if ((a0->type & MAL_SYMBOL) && + strcmp("def!", a0->val.string) == 0) { + //g_print("eval apply def!\n"); + MalVal *a1 = _nth(ast, 1), + *a2 = _nth(ast, 2); + MalVal *res = EVAL(a2, env); + env_set(env, a1->val.string, res); + return res; + } else if ((a0->type & MAL_SYMBOL) && + strcmp("let*", a0->val.string) == 0) { + //g_print("eval apply let*\n"); + MalVal *a1 = _nth(ast, 1), + *a2 = _nth(ast, 2), + *key, *val; + assert_type(a1, MAL_LIST|MAL_VECTOR, + "let* bindings must be list or vector"); + len = _count(a1); + assert((len % 2) == 0, "odd number of let* bindings forms"); + Env *let_env = new_env(env, NULL, NULL); + for(i=0; i<len; i+=2) { + key = g_array_index(a1->val.array, MalVal*, i); + val = g_array_index(a1->val.array, MalVal*, i+1); + assert_type(key, MAL_SYMBOL, "let* bind to non-symbol"); + env_set(let_env, key->val.string, EVAL(val, let_env)); + } + return EVAL(a2, let_env); + } else if ((a0->type & MAL_SYMBOL) && + strcmp("quote", a0->val.string) == 0) { + //g_print("eval apply quote\n"); + return _nth(ast, 1); + } else if ((a0->type & MAL_SYMBOL) && + strcmp("quasiquote", a0->val.string) == 0) { + //g_print("eval apply quasiquote\n"); + MalVal *a1 = _nth(ast, 1); + return EVAL(quasiquote(a1), env); + } else if ((a0->type & MAL_SYMBOL) && + strcmp("defmacro!", a0->val.string) == 0) { + //g_print("eval apply defmacro!\n"); + MalVal *a1 = _nth(ast, 1), + *a2 = _nth(ast, 2); + MalVal *res = EVAL(a2, env); + res->ismacro = TRUE; + env_set(env, a1->val.string, res); + return res; + } else if ((a0->type & MAL_SYMBOL) && + strcmp("macroexpand", a0->val.string) == 0) { + //g_print("eval apply macroexpand\n"); + MalVal *a1 = _nth(ast, 1); + return macroexpand(a1, env); + } else if ((a0->type & MAL_SYMBOL) && + strcmp(".", a0->val.string) == 0) { + //g_print("eval apply .\n"); + MalVal *el = eval_ast(_slice(ast, 1, _count(ast)), env); + return invoke_native(el); + } else if ((a0->type & MAL_SYMBOL) && + strcmp("do", a0->val.string) == 0) { + //g_print("eval apply do\n"); + eval_ast(_slice(ast, 1, _count(ast)-1), env); + ast = last(ast); + // Continue loop + } else if ((a0->type & MAL_SYMBOL) && + strcmp("if", a0->val.string) == 0) { + //g_print("eval apply if\n"); + MalVal *a1 = _nth(ast, 1); + MalVal *cond = EVAL(a1, env); + if (!cond || mal_error) return NULL; + if (cond->type & (MAL_FALSE|MAL_NIL)) { + // eval false slot form + ast = _nth(ast, 3); + if (!ast) { + return &mal_nil; + } + } else { + // eval true slot form + ast = _nth(ast, 2); + } + // Continue loop + } else if ((a0->type & MAL_SYMBOL) && + strcmp("fn*", a0->val.string) == 0) { + //g_print("eval apply fn*\n"); + MalVal *mf = malval_new(MAL_FUNCTION_MAL, NULL); + mf->ismacro = FALSE; + mf->val.func.evaluator = EVAL; + mf->val.func.args = _nth(ast, 1); + mf->val.func.body = _nth(ast, 2); + mf->val.func.env = env; + return mf; + } else { + //g_print("eval apply\n"); + MalVal *el = eval_ast(ast, env); + if (!el || mal_error) { return NULL; } + MalVal *f = first(el), + *args = rest(el); + assert_type(f, MAL_FUNCTION_C|MAL_FUNCTION_MAL, + "cannot invoke '%s'", _pr_str(f,1)); + if (f->type & MAL_FUNCTION_MAL) { + ast = f->val.func.body; + env = new_env(f->val.func.env, f->val.func.args, args); + // Continue loop + } else { + return apply(f, args); + } + } + } +} + +// print +char *PRINT(MalVal *exp) { + if (mal_error) { + fprintf(stderr, "Error: %s\n", mal_error->val.string); + malval_free(mal_error); + mal_error = NULL; + return NULL; + } + return _pr_str(exp,1); +} + +// repl + +// read and eval +MalVal *RE(Env *env, char *prompt, char *str) { + MalVal *ast, *exp; + ast = READ(prompt, str); + if (!ast || mal_error) return NULL; + exp = EVAL(ast, env); + if (ast != exp) { + malval_free(ast); // Free input structure + } + return exp; +} + +// Setup the initial REPL environment +Env *repl_env; + +char *slurp_raw(char *path) { + char *data; + struct stat fst; + int fd = open(path, O_RDONLY), + sz; + if (fd < 0) { + abort("slurp failed to open '%s'", path); + } + if (fstat(fd, &fst) < 0) { + abort("slurp failed to stat '%s'", path); + } + data = malloc(fst.st_size+1); + sz = read(fd, data, fst.st_size); + if (sz < fst.st_size) { + abort("slurp failed to read '%s'", path); + } + data[sz] = '\0'; + return data; +} + +void init_repl_env() { + void _ref(char *name, MalVal*(*func)(MalVal*), int arg_cnt) { + void *(*f)(void *) = (void*(*)(void*))func; + env_set(repl_env, name, malval_new_function(f, arg_cnt, NULL)); + } + repl_env = new_env(NULL, NULL, NULL); + + int i; + for(i=0; i< (sizeof(types_ns) / sizeof(types_ns[0])); i++) { + MalVal *(*f)(MalVal *) = (MalVal*(*)(MalVal*))types_ns[i].func; + _ref(types_ns[i].name, f, types_ns[i].arg_cnt); + } + + MalVal *read_string(MalVal *str) { + assert_type(str, MAL_STRING, "read_string of non-string"); + return read_str(str->val.string); + } + _ref("read-string", read_string, 1); + + MalVal *do_eval(MalVal *ast) { + return EVAL(ast, repl_env); + } + _ref("eval", do_eval, 1); + + MalVal *slurp(MalVal *path) { + assert_type(path, MAL_STRING, "slurp of non-string"); + char *data = slurp_raw(path->val.string); + if (!data || mal_error) { return NULL; } + return malval_new_string(data); + } + _ref("slurp", slurp, 1); + + MalVal *slurp_do(MalVal *path) { + assert_type(path, MAL_STRING, "slurp of non-string"); + char *data = slurp_raw(path->val.string), + *wrapped_data; + if (!data || mal_error) { return NULL; } + wrapped_data = g_strdup_printf("(do %s)", data); + free(data); + return malval_new_string(wrapped_data); + } + _ref("slurp-do", slurp_do, 1); + + RE(repl_env, "", "(def! not (fn* (a) (if a false true)))"); + RE(repl_env, "", + "(def! load-file (fn* (f) (eval (read-string (slurp-do f)))))"); +} + +int main(int argc, char *argv[]) +{ + MalVal *exp; + char *output; + char prompt[100]; + + // Set the initial prompt and environment + snprintf(prompt, sizeof(prompt), "user> "); + init_repl_env(); + + if (argc > 1) { + char *cmd = g_strdup_printf("(load-file \"%s\")", argv[1]); + RE(repl_env, "", cmd); + } else { + // REPL loop + for(;;) { + exp = RE(repl_env, prompt, NULL); + if (mal_error && strcmp("EOF", mal_error->val.string) == 0) { + return 0; + } + output = PRINT(exp); + + if (output) { + g_print("%s\n", output); + free(output); // Free output string + } + + //malval_free(exp); // Free evaluated expression + } + } +} diff --git a/c/stepA_more.c b/c/stepA_more.c new file mode 100644 index 0000000..037848a --- /dev/null +++ b/c/stepA_more.c @@ -0,0 +1,393 @@ +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <sys/stat.h> +#include <fcntl.h> +#include "types.h" +#include "readline.h" +#include "reader.h" +#include "interop.h" + +// Declarations +MalVal *EVAL(MalVal *ast, Env *env); +MalVal *macroexpand(MalVal *ast, Env *env); + +// read +MalVal *READ(char prompt[], char *str) { + char *line; + MalVal *ast; + if (str) { + line = str; + } else { + line = _readline(prompt); + if (!line) { + _error("EOF"); + return NULL; + } + } + ast = read_str(line); + if (!str) { free(line); } + return ast; +} + +// eval +int is_pair(MalVal *x) { + return _sequential_Q(x) && (_count(x) > 0); +} + +MalVal *quasiquote(MalVal *ast) { + if (!is_pair(ast)) { + return _list(2, malval_new_symbol("quote"), ast); + } else { + MalVal *a0 = _nth(ast, 0); + if ((a0->type & MAL_SYMBOL) && + strcmp("unquote", a0->val.string) == 0) { + return _nth(ast, 1); + } else if (is_pair(a0)) { + MalVal *a00 = _nth(a0, 0); + if ((a00->type & MAL_SYMBOL) && + strcmp("splice-unquote", a00->val.string) == 0) { + return _list(3, malval_new_symbol("concat"), + _nth(a0, 1), + quasiquote(rest(ast))); + } + } + return _list(3, malval_new_symbol("cons"), + quasiquote(a0), + quasiquote(rest(ast))); + } +} + +int is_macro_call(MalVal *ast, Env *env) { + if (!ast || ast->type != MAL_LIST) { return 0; } + MalVal *a0 = _nth(ast, 0); + return (a0->type & MAL_SYMBOL) && + env_find(env, a0->val.string) && + env_get(env, a0->val.string)->ismacro; +} + +MalVal *macroexpand(MalVal *ast, Env *env) { + if (!ast || mal_error) return NULL; + while (is_macro_call(ast, env)) { + MalVal *a0 = _nth(ast, 0); + MalVal *mac = env_get(env, a0->val.string); + // TODO: this is weird and limits it to 20. FIXME + ast = apply(mac, rest(ast)); + } + return ast; +} + +MalVal *eval_ast(MalVal *ast, Env *env) { + if (!ast || mal_error) return NULL; + if (ast->type == MAL_SYMBOL) { + //g_print("EVAL symbol: %s\n", ast->val.string); + return env_get(env, ast->val.string); + } else if ((ast->type == MAL_LIST) || (ast->type == MAL_VECTOR)) { + //g_print("EVAL sequential: %s\n", _pr_str(ast,1)); + MalVal *el = _map2((MalVal *(*)(void*, void*))EVAL, ast, env); + if (!el || mal_error) return NULL; + el->type = ast->type; + return el; + } else if (ast->type == MAL_HASH_MAP) { + //g_print("EVAL hash_map: %s\n", _pr_str(ast,1)); + GHashTableIter iter; + gpointer key, value; + MalVal *seq = malval_new_list(MAL_LIST, + g_array_sized_new(TRUE, TRUE, sizeof(MalVal*), + _count(ast))); + g_hash_table_iter_init (&iter, ast->val.hash_table); + while (g_hash_table_iter_next (&iter, &key, &value)) { + MalVal *kname = malval_new_string((char *)key); + g_array_append_val(seq->val.array, kname); + MalVal *new_val = EVAL((MalVal *)value, env); + g_array_append_val(seq->val.array, new_val); + } + return hash_map(seq); + } else { + //g_print("EVAL scalar: %s\n", _pr_str(ast,1)); + return ast; + } +} + +MalVal *EVAL(MalVal *ast, Env *env) { + while (TRUE) { + if (!ast || mal_error) return NULL; + //g_print("EVAL: %s\n", _pr_str(ast,1)); + if (ast->type != MAL_LIST) { + return eval_ast(ast, env); + } + if (!ast || mal_error) return NULL; + + // apply list + //g_print("EVAL apply list: %s\n", _pr_str(ast,1)); + ast = macroexpand(ast, env); + if (!ast || mal_error) return NULL; + if (ast->type != MAL_LIST) { return ast; } + if (_count(ast) == 0) { return ast; } + + int i, len; + MalVal *a0 = _nth(ast, 0); + if ((a0->type & MAL_SYMBOL) && + strcmp("def!", a0->val.string) == 0) { + //g_print("eval apply def!\n"); + MalVal *a1 = _nth(ast, 1), + *a2 = _nth(ast, 2); + MalVal *res = EVAL(a2, env); + env_set(env, a1->val.string, res); + return res; + } else if ((a0->type & MAL_SYMBOL) && + strcmp("let*", a0->val.string) == 0) { + //g_print("eval apply let*\n"); + MalVal *a1 = _nth(ast, 1), + *a2 = _nth(ast, 2), + *key, *val; + assert_type(a1, MAL_LIST|MAL_VECTOR, + "let* bindings must be list or vector"); + len = _count(a1); + assert((len % 2) == 0, "odd number of let* bindings forms"); + Env *let_env = new_env(env, NULL, NULL); + for(i=0; i<len; i+=2) { + key = g_array_index(a1->val.array, MalVal*, i); + val = g_array_index(a1->val.array, MalVal*, i+1); + assert_type(key, MAL_SYMBOL, "let* bind to non-symbol"); + env_set(let_env, key->val.string, EVAL(val, let_env)); + } + return EVAL(a2, let_env); + } else if ((a0->type & MAL_SYMBOL) && + strcmp("quote", a0->val.string) == 0) { + //g_print("eval apply quote\n"); + return _nth(ast, 1); + } else if ((a0->type & MAL_SYMBOL) && + strcmp("quasiquote", a0->val.string) == 0) { + //g_print("eval apply quasiquote\n"); + MalVal *a1 = _nth(ast, 1); + return EVAL(quasiquote(a1), env); + } else if ((a0->type & MAL_SYMBOL) && + strcmp("defmacro!", a0->val.string) == 0) { + //g_print("eval apply defmacro!\n"); + MalVal *a1 = _nth(ast, 1), + *a2 = _nth(ast, 2); + MalVal *res = EVAL(a2, env); + res->ismacro = TRUE; + env_set(env, a1->val.string, res); + return res; + } else if ((a0->type & MAL_SYMBOL) && + strcmp("macroexpand", a0->val.string) == 0) { + //g_print("eval apply macroexpand\n"); + MalVal *a1 = _nth(ast, 1); + return macroexpand(a1, env); + } else if ((a0->type & MAL_SYMBOL) && + strcmp(".", a0->val.string) == 0) { + //g_print("eval apply .\n"); + MalVal *el = eval_ast(_slice(ast, 1, _count(ast)), env); + return invoke_native(el); + } else if ((a0->type & MAL_SYMBOL) && + strcmp("try*", a0->val.string) == 0) { + //g_print("eval apply try*\n"); + MalVal *a1 = _nth(ast, 1); + MalVal *a2 = _nth(ast, 2); + MalVal *res = EVAL(a1, env); + if (!mal_error) { return res; } + MalVal *a20 = _nth(a2, 0); + if (strcmp("catch*", a20->val.string) == 0) { + MalVal *a21 = _nth(a2, 1); + MalVal *a22 = _nth(a2, 2); + Env *catch_env = new_env(env, + _list(1, a21), + _list(1, mal_error)); + //malval_free(mal_error); + mal_error = NULL; + res = EVAL(a22, catch_env); + return res; + } else { + return &mal_nil; + } + } else if ((a0->type & MAL_SYMBOL) && + strcmp("do", a0->val.string) == 0) { + //g_print("eval apply do\n"); + eval_ast(_slice(ast, 1, _count(ast)-1), env); + ast = last(ast); + // Continue loop + } else if ((a0->type & MAL_SYMBOL) && + strcmp("if", a0->val.string) == 0) { + //g_print("eval apply if\n"); + MalVal *a1 = _nth(ast, 1); + MalVal *cond = EVAL(a1, env); + if (!cond || mal_error) return NULL; + if (cond->type & (MAL_FALSE|MAL_NIL)) { + // eval false slot form + ast = _nth(ast, 3); + if (!ast) { + return &mal_nil; + } + } else { + // eval true slot form + ast = _nth(ast, 2); + } + // Continue loop + } else if ((a0->type & MAL_SYMBOL) && + strcmp("fn*", a0->val.string) == 0) { + //g_print("eval apply fn*\n"); + MalVal *mf = malval_new(MAL_FUNCTION_MAL, NULL); + mf->ismacro = FALSE; + mf->val.func.evaluator = EVAL; + mf->val.func.args = _nth(ast, 1); + mf->val.func.body = _nth(ast, 2); + mf->val.func.env = env; + return mf; + } else { + //g_print("eval apply\n"); + MalVal *el = eval_ast(ast, env); + if (!el || mal_error) { return NULL; } + MalVal *f = first(el), + *args = rest(el); + assert_type(f, MAL_FUNCTION_C|MAL_FUNCTION_MAL, + "cannot invoke '%s'", _pr_str(f,1)); + if (f->type & MAL_FUNCTION_MAL) { + ast = f->val.func.body; + env = new_env(f->val.func.env, f->val.func.args, args); + // Continue loop + } else { + return apply(f, args); + } + } + } +} + +// print +char *PRINT(MalVal *exp) { + if (mal_error) { + fprintf(stderr, "Error: %s\n", mal_error->val.string); + malval_free(mal_error); + mal_error = NULL; + return NULL; + } + return _pr_str(exp,1); +} + +// repl + +// read and eval +MalVal *RE(Env *env, char *prompt, char *str) { + MalVal *ast, *exp; + ast = READ(prompt, str); + if (!ast || mal_error) return NULL; + exp = EVAL(ast, env); + if (ast != exp) { + malval_free(ast); // Free input structure + } + return exp; +} + +// Setup the initial REPL environment +Env *repl_env; + +char *slurp_raw(char *path) { + char *data; + struct stat fst; + int fd = open(path, O_RDONLY), + sz; + if (fd < 0) { + abort("slurp failed to open '%s'", path); + } + if (fstat(fd, &fst) < 0) { + abort("slurp failed to stat '%s'", path); + } + data = malloc(fst.st_size+1); + sz = read(fd, data, fst.st_size); + if (sz < fst.st_size) { + abort("slurp failed to read '%s'", path); + } + data[sz] = '\0'; + return data; +} + +void init_repl_env() { + void _ref(char *name, MalVal*(*func)(MalVal*), int arg_cnt) { + void *(*f)(void *) = (void*(*)(void*))func; + env_set(repl_env, name, malval_new_function(f, arg_cnt, NULL)); + } + repl_env = new_env(NULL, NULL, NULL); + + int i; + for(i=0; i< (sizeof(types_ns) / sizeof(types_ns[0])); i++) { + MalVal *(*f)(MalVal *) = (MalVal*(*)(MalVal*))types_ns[i].func; + _ref(types_ns[i].name, f, types_ns[i].arg_cnt); + } + + MalVal *readline(MalVal *str) { + assert_type(str, MAL_STRING, "readline of non-string"); + char * line = _readline(str->val.string); + if (line) { return malval_new_string(line); } + else { return &mal_nil; } + } + _ref("readline", readline, 1); + + MalVal *read_string(MalVal *str) { + assert_type(str, MAL_STRING, "read_string of non-string"); + return read_str(str->val.string); + } + _ref("read-string", read_string, 1); + + MalVal *do_eval(MalVal *ast) { + return EVAL(ast, repl_env); + } + _ref("eval", do_eval, 1); + + MalVal *slurp(MalVal *path) { + assert_type(path, MAL_STRING, "slurp of non-string"); + char *data = slurp_raw(path->val.string); + if (!data || mal_error) { return NULL; } + return malval_new_string(data); + } + _ref("slurp", slurp, 1); + + MalVal *slurp_do(MalVal *path) { + assert_type(path, MAL_STRING, "slurp of non-string"); + char *data = slurp_raw(path->val.string), + *wrapped_data; + if (!data || mal_error) { return NULL; } + wrapped_data = g_strdup_printf("(do %s)", data); + free(data); + return malval_new_string(wrapped_data); + } + _ref("slurp-do", slurp_do, 1); + + RE(repl_env, "", "(def! not (fn* (a) (if a false true)))"); + RE(repl_env, "", "(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))"); + RE(repl_env, "", "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))"); + RE(repl_env, "", + "(def! load-file (fn* (f) (eval (read-string (slurp-do f)))))"); +} + +int main(int argc, char *argv[]) +{ + MalVal *exp; + char *output; + char prompt[100]; + + // Set the initial prompt and environment + snprintf(prompt, sizeof(prompt), "user> "); + init_repl_env(); + + if (argc > 1) { + char *cmd = g_strdup_printf("(load-file \"%s\")", argv[1]); + RE(repl_env, "", cmd); + } else { + // REPL loop + for(;;) { + exp = RE(repl_env, prompt, NULL); + if (mal_error && strcmp("EOF", mal_error->val.string) == 0) { + return 0; + } + output = PRINT(exp); + + if (output) { + g_print("%s\n", output); + free(output); // Free output string + } + + //malval_free(exp); // Free evaluated expression + } + } +} diff --git a/c/tests/step9_interop.mal b/c/tests/step9_interop.mal new file mode 100644 index 0000000..657e3e7 --- /dev/null +++ b/c/tests/step9_interop.mal @@ -0,0 +1,23 @@ + +;; Testing FFI of "strlen" +(. nil "int32" "strlen" "string" "abcde") +;=>5 +(. nil "int32" "strlen" "string" "") +;=>0 + +;; Testing FFI of "strcmp" + +(. nil "int32" "strcmp" "string" "abc" "string" "abcA") +;=>-65 +(. nil "int32" "strcmp" "string" "abcA" "string" "abc") +;=>65 +(. nil "int32" "strcmp" "string" "abc" "string" "abc") +;=>0 + + +;; Testing FFI of "pow" (libm.so) + +(. "libm.so" "double" "pow" "double" 2.0 "double" 3.0) +;=>8.000000 +(. "libm.so" "double" "pow" "double" 3.0 "double" 2.0) +;=>9.000000 diff --git a/c/types.c b/c/types.c new file mode 100644 index 0000000..1308aac --- /dev/null +++ b/c/types.c @@ -0,0 +1,1038 @@ +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "types.h" + +// State + +MalVal *mal_error = NULL; + + +// Constant atomic values + +MalVal mal_nil = {MAL_NIL, NULL, {0}, 0}; +MalVal mal_true = {MAL_TRUE, NULL, {0}, 0}; +MalVal mal_false = {MAL_FALSE, NULL, {0}, 0}; + + +// Pre-declarations + +MalVal *cons(MalVal *x, MalVal *seq); + +// General Functions + +// Print a hash table +#include <glib-object.h> +void g_hash_table_print(GHashTable *hash_table) { + GHashTableIter iter; + gpointer key, value; + + g_hash_table_iter_init (&iter, hash_table); + while (g_hash_table_iter_next (&iter, &key, &value)) { + g_print ("%s/%p ", (const char *) key, (void *) value); + //g_print ("%s ", (const char *) key); + } +} + +GHashTable *g_hash_table_copy(GHashTable *src_table) { + GHashTable *new_table = g_hash_table_new(g_str_hash, g_str_equal); + GHashTableIter iter; + gpointer key, value; + + g_hash_table_iter_init (&iter, src_table); + while (g_hash_table_iter_next (&iter, &key, &value)) { + g_hash_table_insert(new_table, key, value); + } + return new_table; +} + +int min(int a, int b) { return a < b ? a : b; } +int max(int a, int b) { return a > b ? a : b; } + +int _count(MalVal *obj) { + switch (obj->type) { + case MAL_NIL: return 0; + case MAL_LIST: return obj->val.array->len; + case MAL_VECTOR: return obj->val.array->len; + case MAL_HASH_MAP: return g_hash_table_size(obj->val.hash_table); + case MAL_STRING: return strlen(obj->val.string); + default: + _error("count unsupported for type %d\n", obj->type); + return 0; + } +} + +// Allocate a malval and set its type and value +MalVal *malval_new(MalType type, MalVal *metadata) { + MalVal *mv = (MalVal*)malloc(sizeof(MalVal)); + mv->type = type; + mv->metadata = metadata; + return mv; +} + +// +int malval_free(MalVal *mv) { + // TODO: free collection items + if (!(mv->type & (MAL_NIL|MAL_TRUE|MAL_FALSE))) { + free(mv); + } +} + +MalVal *malval_new_integer(gint64 val) { + MalVal *mv = malval_new(MAL_INTEGER, NULL); + mv->val.intnum = val; + return mv; +} + +MalVal *malval_new_float(gdouble val) { + MalVal *mv = malval_new(MAL_FLOAT, NULL); + mv->val.floatnum = val; + return mv; +} + +MalVal *malval_new_string(char *val) { + MalVal *mv = malval_new(MAL_STRING, NULL); + mv->val.string = val; + return mv; +} + +MalVal *malval_new_symbol(char *val) { + MalVal *mv = malval_new(MAL_SYMBOL, NULL); + mv->val.string = val; + return mv; +} + +MalVal *malval_new_hash_map(GHashTable *val) { + MalVal *mv = malval_new(MAL_HASH_MAP, NULL); + mv->val.hash_table = val; + return mv; +} + +MalVal *malval_new_list(MalType type, GArray *val) { + MalVal *mv = malval_new(type, NULL); + mv->val.array = val; + return mv; +} + +MalVal *malval_new_atom(MalVal *val) { + MalVal *mv = malval_new(MAL_ATOM, NULL); + mv->val.atom_val = val; + return mv; +} + + +MalVal *malval_new_function(void *(*func)(void *), int arg_cnt, MalVal* metadata) { + MalVal *mv = malval_new(MAL_FUNCTION_C, metadata); + mv->func_arg_cnt = arg_cnt; + assert(mv->func_arg_cnt <= 20, + "native function restricted to 20 args (%d given)", + mv->func_arg_cnt); + mv->ismacro = FALSE; + switch (arg_cnt) { + case -1: mv->val.f1 = (void *(*)(void*))func; break; + case 0: mv->val.f0 = (void *(*)())func; break; + case 1: mv->val.f1 = (void *(*)(void*))func; break; + case 2: mv->val.f2 = (void *(*)(void*,void*))func; break; + case 3: mv->val.f3 = (void *(*)(void*,void*,void*))func; break; + case 4: mv->val.f4 = (void *(*)(void*,void*,void*,void*))func; break; + case 5: mv->val.f5 = (void *(*)(void*,void*,void*,void*,void*))func; break; + case 6: mv->val.f6 = (void *(*)(void*,void*,void*,void*,void*, + void*))func; break; + case 7: mv->val.f7 = (void *(*)(void*,void*,void*,void*,void*, + void*,void*))func; break; + case 8: mv->val.f8 = (void *(*)(void*,void*,void*,void*,void*, + void*,void*,void*))func; break; + case 9: mv->val.f9 = (void *(*)(void*,void*,void*,void*,void*, + void*,void*,void*,void*))func; break; + case 10: mv->val.f10 = (void *(*)(void*,void*,void*,void*,void*, + void*,void*,void*,void*,void*))func; break; + case 11: mv->val.f11 = (void *(*)(void*,void*,void*,void*,void*, + void*,void*,void*,void*,void*, + void*))func; break; + case 12: mv->val.f12 = (void *(*)(void*,void*,void*,void*,void*, + void*,void*,void*,void*,void*, + void*,void*))func; break; + case 13: mv->val.f13 = (void *(*)(void*,void*,void*,void*,void*, + void*,void*,void*,void*,void*, + void*,void*,void*))func; break; + case 14: mv->val.f14 = (void *(*)(void*,void*,void*,void*,void*, + void*,void*,void*,void*,void*, + void*,void*,void*,void*))func; break; + case 15: mv->val.f15 = (void *(*)(void*,void*,void*,void*,void*, + void*,void*,void*,void*,void*, + void*,void*,void*,void*,void*))func; break; + case 16: mv->val.f16 = (void *(*)(void*,void*,void*,void*,void*, + void*,void*,void*,void*,void*, + void*,void*,void*,void*,void*, + void*))func; break; + case 17: mv->val.f17 = (void *(*)(void*,void*,void*,void*,void*, + void*,void*,void*,void*,void*, + void*,void*,void*,void*,void*, + void*,void*))func; break; + case 18: mv->val.f18 = (void *(*)(void*,void*,void*,void*,void*, + void*,void*,void*,void*,void*, + void*,void*,void*,void*,void*, + void*,void*,void*))func; break; + case 19: mv->val.f19 = (void *(*)(void*,void*,void*,void*,void*, + void*,void*,void*,void*,void*, + void*,void*,void*,void*,void*, + void*,void*,void*,void*))func; break; + case 20: mv->val.f20 = (void *(*)(void*,void*,void*,void*,void*, + void*,void*,void*,void*,void*, + void*,void*,void*,void*,void*, + void*,void*,void*,void*,void*))func; break; + } + return mv; +} + +MalVal *apply(MalVal *f, MalVal *args) { + MalVal *res; + assert_type(f, MAL_FUNCTION_C|MAL_FUNCTION_MAL, + "Cannot invoke %s", _pr_str(f,1)); + if (f->type & MAL_FUNCTION_MAL) { + Env *fn_env = new_env(f->val.func.env, f->val.func.args, args); + res = f->val.func.evaluator(f->val.func.body, fn_env); + return res; + } else { + MalVal *a = args; + assert((f->func_arg_cnt == -1) || + (f->func_arg_cnt == _count(args)), + "Length of formal params (%d) does not match actual parameters (%d)", + f->func_arg_cnt, _count(args)); + switch (f->func_arg_cnt) { + case -1: res=f->val.f1 (a); break; + case 0: res=f->val.f0 (); break; + case 1: res=f->val.f1 (_nth(a,0)); break; + case 2: res=f->val.f2 (_nth(a,0),_nth(a,1)); break; + case 3: res=f->val.f3 (_nth(a,0),_nth(a,1),_nth(a,2)); break; + case 4: res=f->val.f4 (_nth(a,0),_nth(a,1),_nth(a,2),_nth(a,3)); break; + case 5: res=f->val.f5 (_nth(a,0),_nth(a,1),_nth(a,2),_nth(a,3),_nth(a,4)); break; + case 6: res=f->val.f6 (_nth(a,0),_nth(a,1),_nth(a,2),_nth(a,3),_nth(a,4), + _nth(a,5)); break; + case 7: res=f->val.f7 (_nth(a,0),_nth(a,1),_nth(a,2),_nth(a,3),_nth(a,4), + _nth(a,5),_nth(a,6)); break; + case 8: res=f->val.f8 (_nth(a,0),_nth(a,1),_nth(a,2),_nth(a,3),_nth(a,4), + _nth(a,5),_nth(a,6),_nth(a,7)); break; + case 9: res=f->val.f9 (_nth(a,0),_nth(a,1),_nth(a,2),_nth(a,3),_nth(a,4), + _nth(a,5),_nth(a,6),_nth(a,7),_nth(a,8)); break; + case 10: res=f->val.f10(_nth(a,0),_nth(a,1),_nth(a,2),_nth(a,3),_nth(a,4), + _nth(a,5),_nth(a,6),_nth(a,7),_nth(a,8),_nth(a,9)); break; + case 11: res=f->val.f11(_nth(a,0),_nth(a,1),_nth(a,2),_nth(a,3),_nth(a,4), + _nth(a,5),_nth(a,6),_nth(a,7),_nth(a,8),_nth(a,9), + _nth(a,10)); break; + case 12: res=f->val.f12(_nth(a,0),_nth(a,1),_nth(a,2),_nth(a,3),_nth(a,4), + _nth(a,5),_nth(a,6),_nth(a,7),_nth(a,8),_nth(a,9), + _nth(a,10),_nth(a,11)); break; + case 13: res=f->val.f13(_nth(a,0),_nth(a,1),_nth(a,2),_nth(a,3),_nth(a,4), + _nth(a,5),_nth(a,6),_nth(a,7),_nth(a,8),_nth(a,9), + _nth(a,10),_nth(a,11),_nth(a,12)); break; + case 14: res=f->val.f14(_nth(a,0),_nth(a,1),_nth(a,2),_nth(a,3),_nth(a,4), + _nth(a,5),_nth(a,6),_nth(a,7),_nth(a,8),_nth(a,9), + _nth(a,10),_nth(a,11),_nth(a,12),_nth(a,13)); break; + case 15: res=f->val.f15(_nth(a,0),_nth(a,1),_nth(a,2),_nth(a,3),_nth(a,4), + _nth(a,5),_nth(a,6),_nth(a,7),_nth(a,8),_nth(a,9), + _nth(a,10),_nth(a,11),_nth(a,12),_nth(a,13),_nth(a,14)); break; + case 16: res=f->val.f16(_nth(a,0),_nth(a,1),_nth(a,2),_nth(a,3),_nth(a,4), + _nth(a,5),_nth(a,6),_nth(a,7),_nth(a,8),_nth(a,9), + _nth(a,10),_nth(a,11),_nth(a,12),_nth(a,13),_nth(a,14), + _nth(a,15)); break; + case 17: res=f->val.f17(_nth(a,0),_nth(a,1),_nth(a,2),_nth(a,3),_nth(a,4), + _nth(a,5),_nth(a,6),_nth(a,7),_nth(a,8),_nth(a,9), + _nth(a,10),_nth(a,11),_nth(a,12),_nth(a,13),_nth(a,14), + _nth(a,15),_nth(a,16)); break; + case 18: res=f->val.f18(_nth(a,0),_nth(a,1),_nth(a,2),_nth(a,3),_nth(a,4), + _nth(a,5),_nth(a,6),_nth(a,7),_nth(a,8),_nth(a,9), + _nth(a,10),_nth(a,11),_nth(a,12),_nth(a,13),_nth(a,14), + _nth(a,15),_nth(a,16),_nth(a,17)); break; + case 19: res=f->val.f19(_nth(a,0),_nth(a,1),_nth(a,2),_nth(a,3),_nth(a,4), + _nth(a,5),_nth(a,6),_nth(a,7),_nth(a,8),_nth(a,9), + _nth(a,10),_nth(a,11),_nth(a,12),_nth(a,13),_nth(a,14), + _nth(a,15),_nth(a,16),_nth(a,17),_nth(a,18)); break; + case 20: res=f->val.f20(_nth(a,0),_nth(a,1),_nth(a,2),_nth(a,3),_nth(a,4), + _nth(a,5),_nth(a,6),_nth(a,7),_nth(a,8),_nth(a,9), + _nth(a,10),_nth(a,11),_nth(a,12),_nth(a,13),_nth(a,14), + _nth(a,15),_nth(a,16),_nth(a,17),_nth(a,18),_nth(a,19)); break; + } + return res; + } +} + + +char *_pr_str_hash_map(MalVal *obj, int print_readably) { + int start = 1; + char *repr = NULL, *repr_tmp1 = NULL, *repr_tmp2 = NULL; + GHashTableIter iter; + gpointer key, value; + + repr = g_strdup_printf("{"); + + g_hash_table_iter_init (&iter, obj->val.hash_table); + while (g_hash_table_iter_next (&iter, &key, &value)) { + //g_print ("%s/%p ", (const char *) key, (void *) value); + + repr_tmp1 = _pr_str((MalVal*)value, print_readably); + if (start) { + start = 0; + repr = g_strdup_printf("{\"%s\" %s", (char *)key, repr_tmp1); + } else { + repr_tmp2 = repr; + repr = g_strdup_printf("%s \"%s\" %s", repr_tmp2, (char *)key, repr_tmp1); + free(repr_tmp2); + } + free(repr_tmp1); + } + repr_tmp2 = repr; + repr = g_strdup_printf("%s}", repr_tmp2); + free(repr_tmp2); + return repr; +} + +char *_pr_str_list(MalVal *obj, int print_readably, char start, char end) { + int i; + char *repr = NULL, *repr_tmp1 = NULL, *repr_tmp2 = NULL; + repr = g_strdup_printf("%c", start); + for (i=0; i<_count(obj); i++) { + repr_tmp1 = _pr_str(g_array_index(obj->val.array, MalVal*, i), + print_readably); + if (i == 0) { + repr = g_strdup_printf("%c%s", start, repr_tmp1); + } else { + repr_tmp2 = repr; + repr = g_strdup_printf("%s %s", repr_tmp2, repr_tmp1); + free(repr_tmp2); + } + free(repr_tmp1); + } + repr_tmp2 = repr; + repr = g_strdup_printf("%s%c", repr_tmp2, end); + free(repr_tmp2); + return repr; +} + +// Return a string representation of the MalVal object. Returned string must +// be freed by caller. +char *_pr_str(MalVal *obj, int print_readably) { + char *repr = NULL; + if (obj == NULL) { return NULL; } + switch (obj->type) { + case MAL_NIL: + repr = g_strdup_printf("nil"); + break; + case MAL_TRUE: + repr = g_strdup_printf("true"); + break; + case MAL_FALSE: + repr = g_strdup_printf("false"); + break; + case MAL_STRING: + if (print_readably) { + char *repr_tmp = g_strescape(obj->val.string, ""); + repr = g_strdup_printf("\"%s\"", repr_tmp); + free(repr_tmp); + } else { + repr = g_strdup_printf("%s", obj->val.string); + } + break; + case MAL_SYMBOL: + repr = g_strdup_printf("%s", obj->val.string); + break; + case MAL_INTEGER: + repr = g_strdup_printf("%" G_GINT64_FORMAT, obj->val.intnum); + break; + case MAL_FLOAT: + repr = g_strdup_printf("%f", obj->val.floatnum); + break; + case MAL_HASH_MAP: + repr = _pr_str_hash_map(obj, print_readably); + break; + case MAL_LIST: + repr = _pr_str_list(obj, print_readably, '(', ')'); + break; + case MAL_VECTOR: + repr = _pr_str_list(obj, print_readably, '[', ']'); + break; + case MAL_ATOM: + repr = g_strdup_printf("(atom %s)", + _pr_str(obj->val.atom_val, print_readably)); + break; + case MAL_FUNCTION_C: + repr = g_strdup_printf("#<function@%p>", obj->val.f0); + break; + case MAL_FUNCTION_MAL: + repr = g_strdup_printf("#<Function: (fn* %s %s)>", + _pr_str(obj->val.func.args, print_readably), + _pr_str(obj->val.func.body, print_readably)); + break; + default: + printf("pr_str unknown type %d\n", obj->type); + repr = g_strdup_printf("<unknown>"); + } + return repr; +} + +// Return a string representation of the MalVal arguments. Returned string must +// be freed by caller. +char *_pr_str_args(MalVal *args, char *sep, int print_readably) { + assert_type(args, MAL_LIST|MAL_VECTOR, + "_pr_str called with non-sequential args"); + int i; + char *repr = g_strdup_printf(""), + *repr2 = NULL; + for (i=0; i<_count(args); i++) { + MalVal *obj = g_array_index(args->val.array, MalVal*, i); + if (i != 0) { + repr2 = repr; + repr = g_strdup_printf("%s%s", repr2, sep); + free(repr2); + } + repr2 = repr; + repr = g_strdup_printf("%s%s", + repr2, _pr_str(obj, print_readably)); + free(repr2); + } + return repr; +} + +// Return a string representation of a MalVal sequence (in a format that can +// be read by the reader). Returned string must be freed by caller. +MalVal *pr_str(MalVal *args) { + assert_type(args, MAL_LIST|MAL_VECTOR, + "pr_str called with non-sequential args"); + return malval_new_string(_pr_str_args(args, " ", 1)); +} + +// Return a string representation of a MalVal sequence with every item +// concatenated together. Returned string must be freed by caller. +MalVal *str(MalVal *args) { + assert_type(args, MAL_LIST|MAL_VECTOR, + "str called with non-sequential args"); + return malval_new_string(_pr_str_args(args, "", 0)); +} + +// Print a string representation of a MalVal sequence (in a format that can +// be read by the reader) followed by a newline. Returns nil. +MalVal *prn(MalVal *args) { + assert_type(args, MAL_LIST|MAL_VECTOR, + "prn called with non-sequential args"); + char *repr = _pr_str_args(args, " ", 1); + g_print("%s\n", repr); + free(repr); + return &mal_nil; +} + +// Print a string representation of a MalVal sequence (for human consumption) +// followed by a newline. Returns nil. +MalVal *println(MalVal *args) { + assert_type(args, MAL_LIST|MAL_VECTOR, + "println called with non-sequential args"); + char *repr = _pr_str_args(args, " ", 0); + g_print("%s\n", repr); + free(repr); + return &mal_nil; +} + +MalVal *with_meta(MalVal *obj, MalVal *meta) { + MalVal *new_obj = malval_new(obj->type, meta); + new_obj->val = obj->val; + return new_obj; +} + +MalVal *meta(MalVal *obj) { + assert_type(obj, MAL_LIST|MAL_VECTOR|MAL_HASH_MAP|MAL_FUNCTION_C|MAL_FUNCTION_MAL, + "attempt to get metadata from non-collection type"); + if (obj->metadata == NULL) { + return &mal_nil; + } else { + return obj->metadata; + } +} + + +int _equal_Q(MalVal *a, MalVal *b) { + if (a == NULL || b == NULL) { return FALSE; } + + // If types are the same or both are sequential then they might be equal + if (!((a->type == b->type) || + (_sequential_Q(a) && _sequential_Q(b)))) { + return FALSE; + } + switch (a->type) { + case MAL_NIL: + case MAL_TRUE: + case MAL_FALSE: + return a->type == b->type; + case MAL_INTEGER: + return a->val.intnum == b->val.intnum; + case MAL_FLOAT: + return a->val.floatnum == b->val.floatnum; + case MAL_SYMBOL: + case MAL_STRING: + if (strcmp(a->val.string, b->val.string) == 0) { + return TRUE; + } else { + return FALSE; + } + case MAL_LIST: + case MAL_VECTOR: + if (a->val.array->len != b->val.array->len) { + return FALSE; + } + int i; + for (i=0; i<a->val.array->len; i++) { + if (! _equal_Q(g_array_index(a->val.array, MalVal*, i), + g_array_index(b->val.array, MalVal*, i))) { + return FALSE; + } + } + return TRUE; + case MAL_HASH_MAP: + _error("_equal_Q does not support hash-maps yet"); + return FALSE; + case MAL_FUNCTION_C: + case MAL_FUNCTION_MAL: + return a->val.f0 == b->val.f0; + default: + _error("_equal_Q unsupported comparison type %d\n", a->type); + return FALSE; + } +} + +MalVal *equal_Q(MalVal *a, MalVal *b) { + if (_equal_Q(a, b)) { return &mal_true; } + else { return &mal_false; } +} + +// +// nil, true, false, string +MalVal *nil_Q(MalVal *seq) { return seq->type & MAL_NIL ? &mal_true : &mal_false; } +MalVal *true_Q(MalVal *seq) { return seq->type & MAL_TRUE ? &mal_true : &mal_false; } +MalVal *false_Q(MalVal *seq) { return seq->type & MAL_FALSE ? &mal_true : &mal_false; } +MalVal *string_Q(MalVal *seq) { return seq->type & MAL_STRING ? &mal_true : &mal_false; } + +// +// Numbers +#define WRAP_INTEGER_OP(name, op) \ + MalVal *int_ ## name(MalVal *a, MalVal *b) { \ + return malval_new_integer(a->val.intnum op b->val.intnum); \ + } +#define WRAP_INTEGER_CMP_OP(name, op) \ + MalVal *int_ ## name(MalVal *a, MalVal *b) { \ + return a->val.intnum op b->val.intnum ? &mal_true : &mal_false; \ + } +WRAP_INTEGER_OP(plus,+) +WRAP_INTEGER_OP(minus,-) +WRAP_INTEGER_OP(multiply,*) +WRAP_INTEGER_OP(divide,/) +WRAP_INTEGER_CMP_OP(gt,>) +WRAP_INTEGER_CMP_OP(gte,>=) +WRAP_INTEGER_CMP_OP(lt,<) +WRAP_INTEGER_CMP_OP(lte,<=) + + +// +// Symbols +MalVal *symbol_Q(MalVal *seq) { return seq->type & MAL_SYMBOL ? &mal_true : &mal_false; } + + +// Hash maps +// +MalVal *_hash_map(int count, ...) { + assert((count % 2) == 0, + "odd number of parameters to hash-map"); + GHashTable *htable = g_hash_table_new(g_str_hash, g_str_equal); + MalVal *hm = malval_new_hash_map(htable); + char *k; + MalVal *v; + va_list ap; + va_start(ap, count); + while (count > 0) { + k = va_arg(ap, char*); + v = va_arg(ap, MalVal*); + g_hash_table_insert(htable, k, v); + count = count - 2; + } + va_end(ap); + return hm; +} + +MalVal *hash_map(MalVal *args) { + assert_type(args, MAL_LIST|MAL_VECTOR, + "hash-map called with non-sequential arguments"); + assert((args->val.array->len % 2) == 0, + "odd number of parameters to hash-map"); + GHashTable *htable = g_hash_table_new(g_str_hash, g_str_equal); + MalVal *hm = malval_new_hash_map(htable); + int i; + MalVal *k, *v; + for(i=0; i< args->val.array->len; i+=2) { + k = g_array_index(args->val.array, MalVal*, i); + assert_type(k, MAL_STRING, + "hash-map called with non-string key"); + v = g_array_index(args->val.array, MalVal*, i+1); + g_hash_table_insert(htable, k->val.string, v); + } + return hm; +} + +int _hash_map_Q(MalVal *seq) { + return seq->type & MAL_HASH_MAP; +} +MalVal *hash_map_Q(MalVal *seq) { return _hash_map_Q(seq) ? &mal_true : &mal_false; } + +// TODO: support multiple key/values +MalVal *assoc(MalVal *hm, MalVal *key, MalVal *val) { + GHashTable *htable = g_hash_table_copy(hm->val.hash_table); + MalVal *new_hm = malval_new_hash_map(htable); + g_hash_table_insert(htable, key->val.string, val); + return new_hm; +} + +// TODO: support multiple keys +MalVal *dissoc(MalVal *hm, MalVal *key) { + GHashTable *htable = g_hash_table_copy(hm->val.hash_table); + MalVal *new_hm = malval_new_hash_map(htable); + g_hash_table_remove(htable, key->val.string); + return new_hm; +} + +MalVal *keys(MalVal *obj) { + assert_type(obj, MAL_HASH_MAP, + "keys called on non-hash-map"); + + GHashTableIter iter; + gpointer key, value; + MalVal *seq = malval_new_list(MAL_LIST, + g_array_sized_new(TRUE, TRUE, sizeof(MalVal*), + _count(obj))); + g_hash_table_iter_init (&iter, obj->val.hash_table); + while (g_hash_table_iter_next (&iter, &key, &value)) { + MalVal *kname = malval_new_string((char *)key); + g_array_append_val(seq->val.array, kname); + } + return seq; +} + +MalVal *vals(MalVal *obj) { + assert_type(obj, MAL_HASH_MAP, + "vals called on non-hash-map"); + + GHashTableIter iter; + gpointer key, value; + MalVal *seq = malval_new_list(MAL_LIST, + g_array_sized_new(TRUE, TRUE, sizeof(MalVal*), + _count(obj))); + g_hash_table_iter_init (&iter, obj->val.hash_table); + while (g_hash_table_iter_next (&iter, &key, &value)) { + g_array_append_val(seq->val.array, value); + } + return seq; +} + + +// Errors/Exceptions +void _error(const char *fmt, ...) { + va_list args; + va_start(args, fmt); + mal_error = malval_new_string(g_strdup_vprintf(fmt, args)); +} +void throw(MalVal *obj) { + mal_error = obj; +} + + +// Lists + +MalVal *_list(int count, ...) { + MalVal *seq = malval_new_list(MAL_LIST, + g_array_sized_new(TRUE, TRUE, sizeof(MalVal*), + count)); + MalVal *v; + va_list ap; + va_start(ap, count); + while (count-- > 0) { + v = va_arg(ap, MalVal*); + g_array_append_val(seq->val.array, v); + } + va_end(ap); + return seq; +} +MalVal *list(MalVal *args) { + assert_type(args, MAL_LIST|MAL_VECTOR, + "list called with invalid arguments"); + args->type = MAL_LIST; + return args; +} + +int _list_Q(MalVal *seq) { + return seq->type & MAL_LIST; +} +MalVal *list_Q(MalVal *seq) { return _list_Q(seq) ? &mal_true : &mal_false; } + + +// Vectors + +MalVal *_vector(int count, ...) { + MalVal *seq = malval_new_list(MAL_VECTOR, + g_array_sized_new(TRUE, TRUE, sizeof(MalVal*), + count)); + MalVal *v; + va_list ap; + va_start(ap, count); + while (count-- > 0) { + v = va_arg(ap, MalVal*); + g_array_append_val(seq->val.array, v); + } + va_end(ap); + return seq; +} +MalVal *vector(MalVal *args) { + assert_type(args, MAL_LIST|MAL_VECTOR, + "vector called with invalid arguments"); + args->type = MAL_VECTOR; + return args; +} + + +int _vector_Q(MalVal *seq) { + return seq->type & MAL_VECTOR; +} +MalVal *vector_Q(MalVal *seq) { return _vector_Q(seq) ? &mal_true : &mal_false; } + + +// hash map and vector functions +MalVal *get(MalVal *obj, MalVal *key) { + MalVal *val; + switch (obj->type) { + case MAL_VECTOR: + return _nth(obj, key->val.intnum); + case MAL_HASH_MAP: + if (g_hash_table_lookup_extended(obj->val.hash_table, + key->val.string, + NULL, (gpointer*)&val)) { + return val; + } else { + return &mal_nil; + } + default: + abort("get called on unsupported type %d", obj->type); + } +} + +MalVal *contains_Q(MalVal *obj, MalVal *key) { + switch (obj->type) { + case MAL_VECTOR: + if (key->val.intnum < obj->val.array->len) { + return &mal_true; + } else { + return &mal_false; + } + case MAL_HASH_MAP: + if (g_hash_table_contains(obj->val.hash_table, key->val.string)) { + return &mal_true; + } else { + return &mal_false; + } + default: + abort("contains? called on unsupported type %d", obj->type); + } +} + + +// Atoms +MalVal *atom(MalVal *val) { + return malval_new_atom(val); +} + +int _atom_Q(MalVal *exp) { + return exp->type & MAL_ATOM; +} +MalVal *atom_Q(MalVal *exp) { return _atom_Q(exp) ? &mal_true : &mal_false; } + +MalVal *deref(MalVal *atm) { + assert_type(atm, MAL_ATOM, + "deref called on non-atom"); + return atm->val.atom_val; +} + +MalVal *reset_BANG(MalVal *atm, MalVal *val) { + assert_type(atm, MAL_ATOM, + "reset! called with non-atom"); + atm->val.atom_val = val; + return val; +} + +MalVal *swap_BANG(MalVal *args) { + assert_type(args, MAL_LIST|MAL_VECTOR, + "swap! called with invalid arguments"); + assert(_count(args) >= 2, + "swap! called with %d args, needs at least 2", _count(args)); + MalVal *atm = _nth(args, 0), + *f = _nth(args, 1), + *sargs = _slice(args, 2, _count(args)), + *fargs = cons(atm->val.atom_val, sargs), + *new_val = apply(f, fargs); + if (mal_error) { return NULL; } + atm->val.atom_val = new_val; + return new_val; +} + + + +// Sequence functions +MalVal *_slice(MalVal *seq, int start, int end) { + int i, new_len = max(0, min(end-start, + _count(seq)-start)); + GArray *new_arr = g_array_sized_new(TRUE, TRUE, sizeof(MalVal*), + new_len); + for (i=start; i<start+new_len; i++) { + g_array_append_val(new_arr, g_array_index(seq->val.array, MalVal*, i)); + } + return malval_new_list(MAL_LIST, new_arr); +} + + +int _sequential_Q(MalVal *seq) { + return seq->type & (MAL_LIST|MAL_VECTOR); +} +MalVal *sequential_Q(MalVal *seq) { + return _sequential_Q(seq) ? &mal_true : &mal_false; +} + +MalVal *cons(MalVal *x, MalVal *seq) { + assert_type(seq, MAL_LIST|MAL_VECTOR, + "second argument to cons is non-sequential"); + int i, len = _count(seq); + GArray *new_arr = g_array_sized_new(TRUE, TRUE, sizeof(MalVal*), + len+1); + g_array_append_val(new_arr, x); + for (i=0; i<len; i++) { + g_array_append_val(new_arr, g_array_index(seq->val.array, MalVal*, i)); + } + return malval_new_list(MAL_LIST, new_arr); +} + +MalVal *count(MalVal *seq) { + return malval_new_integer(_count(seq)); +} + +MalVal *empty_Q(MalVal *seq) { + assert_type(seq, MAL_LIST|MAL_VECTOR, + "empty? called with non-sequential"); + return (seq->val.array->len == 0) ? &mal_true : &mal_false; +} + +MalVal *concat(MalVal *args) { + MalVal *arg, *e, *lst; + int i, j, arg_cnt = _count(args); + lst = malval_new_list(MAL_LIST, + g_array_sized_new(TRUE, TRUE, sizeof(MalVal*), arg_cnt)); + for (i=0; i<arg_cnt; i++) { + arg = g_array_index(args->val.array, MalVal*, i); + assert_type(arg, MAL_LIST|MAL_VECTOR, + "concat called with non-sequential"); + for (j=0; j<_count(arg); j++) { + e = g_array_index(arg->val.array, MalVal*, j); + g_array_append_val(lst->val.array, e); + } + } + + return lst; +} + +MalVal *sconj(MalVal *args) { + assert_type(args, MAL_LIST|MAL_VECTOR, + "conj called with non-sequential"); + MalVal *src_lst = _nth(args, 0); + assert_type(args, MAL_LIST|MAL_VECTOR, + "first argument to conj is non-sequential"); + int i, len = _count(src_lst) + _count(args) - 1; + GArray *new_arr = g_array_sized_new(TRUE, TRUE, sizeof(MalVal*), + len); + for (i=1; i<len; i++) { + g_array_append_val(new_arr, g_array_index(args->val.array, MalVal*, i)); + } + return malval_new_list(MAL_LIST, new_arr); +} + +MalVal *first(MalVal *seq) { + assert_type(seq, MAL_LIST|MAL_VECTOR, + "first called with non-sequential"); + if (_count(seq) == 0) { + return &mal_nil; + } + return g_array_index(seq->val.array, MalVal*, 0); +} + +MalVal *last(MalVal *seq) { + assert_type(seq, MAL_LIST|MAL_VECTOR, + "last called with non-sequential"); + if (_count(seq) == 0) { + return &mal_nil; + } + return g_array_index(seq->val.array, MalVal*, _count(seq)-1); +} + +MalVal *rest(MalVal *seq) { + return _slice(seq, 1, _count(seq)); +} + +MalVal *_nth(MalVal *seq, int idx) { + assert_type(seq, MAL_LIST|MAL_VECTOR, + "nth called with non-sequential"); + if (idx >= _count(seq)) { + return &mal_nil; + } + return g_array_index(seq->val.array, MalVal*, idx); +} +MalVal *nth(MalVal *seq, MalVal *idx) { + return _nth(seq, idx->val.intnum); +} + +MalVal *_map2(MalVal *(*func)(void*, void*), MalVal *lst, void *arg2) { + MalVal *e, *el; + assert_type(lst, MAL_LIST|MAL_VECTOR, + "_map called with non-sequential"); + int i, len = _count(lst); + el = malval_new_list(MAL_LIST, + g_array_sized_new(TRUE, TRUE, sizeof(MalVal*), len)); + for (i=0; i<len; i++) { + e = func(g_array_index(lst->val.array, MalVal*, i), arg2); + if (!e || mal_error) return NULL; + g_array_append_val(el->val.array, e); + } + return el; +} + +MalVal *map(MalVal *mvf, MalVal *lst) { + MalVal *res, *el; + assert_type(mvf, MAL_FUNCTION_C|MAL_FUNCTION_MAL, + "map called with non-function"); + assert_type(lst, MAL_LIST|MAL_VECTOR, + "map called with non-sequential"); + int i, len = _count(lst); + el = malval_new_list(MAL_LIST, + g_array_sized_new(TRUE, TRUE, sizeof(MalVal*), len)); + for (i=0; i<len; i++) { + // TODO: this is replicating some of apply functionality + if (mvf->type & MAL_FUNCTION_MAL) { + Env *fn_env = new_env(mvf->val.func.env, + mvf->val.func.args, + _slice(lst, i, i+1)); + res = mvf->val.func.evaluator(mvf->val.func.body, fn_env); + } else { + res = mvf->val.f1(g_array_index(lst->val.array, MalVal*, i)); + } + if (!res || mal_error) return NULL; + g_array_append_val(el->val.array, res); + } + return el; +} + + +// Env + +Env *new_env(Env *outer, MalVal* binds, MalVal *exprs) { + Env *e = malloc(sizeof(Env)); + e->table = g_hash_table_new(g_str_hash, g_str_equal); + e->outer = outer; + + if (binds && exprs) { + assert_type(binds, MAL_LIST|MAL_VECTOR, + "new_env called with non-sequential bindings"); + assert_type(exprs, MAL_LIST|MAL_VECTOR, + "new_env called with non-sequential expressions"); + int binds_len = _count(binds), + exprs_len = _count(exprs), + varargs = 0, i; + for (i=0; i<binds_len; i++) { + if (i > exprs_len) { break; } + if (_nth(binds, i)->val.string[0] == '&') { + varargs = 1; + env_set(e, _nth(binds, i+1)->val.string, _slice(exprs, i, _count(exprs))); + break; + } else { + env_set(e, _nth(binds, i)->val.string, _nth(exprs, i)); + } + } + assert(varargs || (binds_len == exprs_len), + "Arity mismatch: %d formal params vs %d actual params", + binds_len, exprs_len); + + } + return e; +} + +Env *env_find(Env *env, char *key) { + void *val = g_hash_table_lookup(env->table, key); + if (val) { + return env; + } else if (env->outer) { + return env_find(env->outer, key); + } else { + return NULL; + } +} + +MalVal *env_get(Env *env, char *key) { + Env *e = env_find(env, key); + assert(e, "'%s' not found", key); + return g_hash_table_lookup(e->table, key); +} + +Env *env_set(Env *env, char *key, MalVal *val) { + g_hash_table_insert(env->table, key, val); + return env; +} + +types_ns_entry types_ns[49] = { + {"pr-str", (void*(*)(void*))pr_str, -1}, + {"str", (void*(*)(void*))str, -1}, + {"prn", (void*(*)(void*))prn, -1}, + {"println", (void*(*)(void*))println, -1}, + {"with-meta", (void*(*)(void*))with_meta, 2}, + {"meta", (void*(*)(void*))meta, 1}, + {"=", (void*(*)(void*))equal_Q, 2}, + {"symbol?", (void*(*)(void*))symbol_Q, 1}, + {"nil?", (void*(*)(void*))nil_Q, 1}, + {"true?", (void*(*)(void*))true_Q, 1}, + {"false?", (void*(*)(void*))false_Q, 1}, + {"+", (void*(*)(void*))int_plus, 2}, + {"-", (void*(*)(void*))int_minus, 2}, + {"*", (void*(*)(void*))int_multiply, 2}, + {"/", (void*(*)(void*))int_divide, 2}, + {">", (void*(*)(void*))int_gt, 2}, + {">=", (void*(*)(void*))int_gte, 2}, + {"<", (void*(*)(void*))int_lt, 2}, + {"<=", (void*(*)(void*))int_lte, 2}, + {"hash-map", (void*(*)(void*))hash_map, -1}, + {"map?", (void*(*)(void*))hash_map_Q, 1}, + {"assoc", (void*(*)(void*))assoc, 3}, + {"dissoc", (void*(*)(void*))dissoc, 2}, + {"get", (void*(*)(void*))get, 2}, + {"contains?", (void*(*)(void*))contains_Q, 2}, + {"keys", (void*(*)(void*))keys, 1}, + {"vals", (void*(*)(void*))vals, 1}, + {"throw", (void*(*)(void*))throw, 1}, + {"list", (void*(*)(void*))list, -1}, + {"list?", (void*(*)(void*))list_Q, 1}, + {"vector", (void*(*)(void*))vector, -1}, + {"vector?", (void*(*)(void*))vector_Q, 1}, + {"atom", (void*(*)(void*))atom, 1}, + {"atom?", (void*(*)(void*))atom_Q, 1}, + {"deref", (void*(*)(void*))deref, 1}, + {"reset!", (void*(*)(void*))reset_BANG, 2}, + {"swap!", (void*(*)(void*))swap_BANG, -1}, + {"sequential?", (void*(*)(void*))sequential_Q, 1}, + {"cons", (void*(*)(void*))cons, 2}, + {"count", (void*(*)(void*))count, 1}, + {"empty?", (void*(*)(void*))empty_Q, 1}, + {"concat", (void*(*)(void*))concat, -1}, + {"conj", (void*(*)(void*))sconj, -1}, + {"first", (void*(*)(void*))first, 1}, + {"last", (void*(*)(void*))last, 1}, + {"rest", (void*(*)(void*))rest, 1}, + {"nth", (void*(*)(void*))nth, 2}, + {"apply", (void*(*)(void*))apply, 2}, + {"map", (void*(*)(void*))map, 2}, + }; diff --git a/c/types.h b/c/types.h new file mode 100644 index 0000000..271a899 --- /dev/null +++ b/c/types.h @@ -0,0 +1,162 @@ +#ifndef __MAL_TYPES__ +#define __MAL_TYPES__ + +#include <glib.h> + +// State + +struct MalVal; // pre-declare +extern struct MalVal *mal_error; + +#define abort(format, ...) \ + { _error(format, ##__VA_ARGS__); return NULL; } + +#define assert(test, format, ...) \ + if (!(test)) { \ + _error(format, ##__VA_ARGS__); \ + return NULL; \ + } + +#define assert_type(mv, typ, format, ...) \ + if (!(mv->type & (typ))) { \ + _error(format, ##__VA_ARGS__); \ + return NULL; \ + } + +typedef enum { + MAL_NIL = 1, + MAL_TRUE = 2, + MAL_FALSE = 4, + MAL_INTEGER = 8, + MAL_FLOAT = 16, + MAL_SYMBOL = 32, + MAL_STRING = 64, + MAL_LIST = 128, + MAL_VECTOR = 256, + MAL_HASH_MAP = 512, + MAL_ATOM = 1024, + MAL_FUNCTION_C = 2048, + MAL_FUNCTION_MAL = 4096, +} MalType; + + +// Predeclare Env +typedef struct Env Env; + +typedef struct MalVal { + MalType type; + struct MalVal *metadata; + union { + gint64 intnum; + gdouble floatnum; + char *string; + GArray *array; + GHashTable *hash_table; + struct MalVal *atom_val; + void *(*f0) (); + void *(*f1) (void*); + void *(*f2) (void*,void*); + void *(*f3) (void*,void*,void*); + void *(*f4) (void*,void*,void*,void*); + void *(*f5) (void*,void*,void*,void*,void*); + void *(*f6) (void*,void*,void*,void*,void*,void*); + void *(*f7) (void*,void*,void*,void*,void*,void*,void*); + void *(*f8) (void*,void*,void*,void*,void*,void*,void*,void*); + void *(*f9) (void*,void*,void*,void*,void*,void*,void*,void*,void*); + void *(*f10)(void*,void*,void*,void*,void*,void*,void*,void*,void*,void*); + void *(*f11)(void*,void*,void*,void*,void*,void*,void*,void*,void*,void*, + void*); + void *(*f12)(void*,void*,void*,void*,void*,void*,void*,void*,void*,void*, + void*,void*); + void *(*f13)(void*,void*,void*,void*,void*,void*,void*,void*,void*,void*, + void*,void*,void*); + void *(*f14)(void*,void*,void*,void*,void*,void*,void*,void*,void*,void*, + void*,void*,void*,void*); + void *(*f15)(void*,void*,void*,void*,void*,void*,void*,void*,void*,void*, + void*,void*,void*,void*,void*); + void *(*f16)(void*,void*,void*,void*,void*,void*,void*,void*,void*,void*, + void*,void*,void*,void*,void*,void*); + void *(*f17)(void*,void*,void*,void*,void*,void*,void*,void*,void*,void*, + void*,void*,void*,void*,void*,void*,void*); + void *(*f18)(void*,void*,void*,void*,void*,void*,void*,void*,void*,void*, + void*,void*,void*,void*,void*,void*,void*,void*); + void *(*f19)(void*,void*,void*,void*,void*,void*,void*,void*,void*,void*, + void*,void*,void*,void*,void*,void*,void*,void*,void*); + void *(*f20)(void*,void*,void*,void*,void*,void*,void*,void*,void*,void*, + void*,void*,void*,void*,void*,void*,void*,void*,void*,void*); + struct { + struct MalVal *(*evaluator)(struct MalVal *, Env *); + struct MalVal *args; + struct MalVal *body; + struct Env *env; + } func; + } val; + int func_arg_cnt; + int ismacro; +} MalVal; + +// Constants + +extern MalVal mal_nil; +extern MalVal mal_true; +extern MalVal mal_false; + + +// Declare functions used internally (by other C code). +// Mal visible functions are "exported" in types_ns + +MalVal *malval_new(MalType type, MalVal *metadata); +int malval_free(MalVal *mv); +MalVal *malval_new_integer(gint64 val); +MalVal *malval_new_float(gdouble val); +MalVal *malval_new_string(char *val); +MalVal *malval_new_symbol(char *val); +MalVal *malval_new_list(MalType type, GArray *val); +MalVal *malval_new_function(void *(*func)(void *), int arg_cnt, MalVal* metadata); + +MalVal *hash_map(MalVal *args); +void _error(const char *fmt, ...); +MalVal *_list(int count, ...); + +MalVal *apply(MalVal *f, MalVal *el); + +char *_pr_str(MalVal *args, int print_readably); + +MalVal *first(MalVal* seq); +MalVal *last(MalVal* seq); +MalVal *_slice(MalVal *seq, int start, int end); +MalVal *_nth(MalVal *seq, int idx); +MalVal *rest(MalVal *seq); + +MalVal *_map2(MalVal *(*func)(void*, void*), MalVal *lst, void *arg2); + +// These are just used by step2 and step3 before then type_ns environment is +// imported + +MalVal *int_plus(MalVal *a, MalVal *b); +MalVal *int_minus(MalVal *a, MalVal *b); +MalVal *int_multiply(MalVal *a, MalVal *b); +MalVal *int_divide(MalVal *a, MalVal *b); + +// Env + +typedef struct Env { + struct Env *outer; + GHashTable *table; +} Env; + +Env *new_env(Env *outer, MalVal* binds, MalVal *exprs); +Env *env_find(Env *env, char *key); +MalVal *env_get(Env *env, char *key); +Env *env_set(Env *env, char *key, MalVal *val); + +// namespace of type functions +typedef struct { + char *name; + void *(*func)(void*); + int arg_cnt; +} types_ns_entry; + +extern types_ns_entry types_ns[49]; + +#endif diff --git a/clojure/Makefile b/clojure/Makefile new file mode 100644 index 0000000..d18eb50 --- /dev/null +++ b/clojure/Makefile @@ -0,0 +1,17 @@ + +TESTS = + +SOURCES = src/types.clj src/readline.clj src/reader.clj src/stepA_more.clj + +all: + +.PHONY: stats tests $(TESTS) + +stats: $(SOURCES) + @wc $^ + +tests: $(TESTS) + +$(TESTS): + @echo "Running $@"; \ + lein with-profile XXX$@XXX trampoline run || exit 1; \ diff --git a/clojure/project.clj b/clojure/project.clj new file mode 100644 index 0000000..4e7a15f --- /dev/null +++ b/clojure/project.clj @@ -0,0 +1,25 @@ +(defproject mal "0.0.1-SNAPSHOT" + :description "Make-A-Lisp" + + :dependencies [[org.clojure/clojure "1.5.1"] + [org.clojure/tools.reader "0.8.3"] + [net.n01se/clojure-jna "1.0.0"]] + + ;; To run a step with correct readline behavior: + ;; lein trampoline with-profile stepX run + ;; To load step in repl: + ;; lein with-profile +stepX repl + :profiles {:step0 {:main step0-repl} + :step1 {:main step1-read-print} + :step2 {:main step2-eval} + :step3 {:main step3-env} + :step4 {:main step4-if-fn-do} + :step5 {:main step5-tco} + :step6 {:main step6-file} + :step7 {:main step7-quote} + :step8 {:main step8-macros} + :step9 {:main step9-interop} + :stepA {:main stepA-more}} + + :main stepA-more) + diff --git a/clojure/src/reader.clj b/clojure/src/reader.clj new file mode 100644 index 0000000..8f14767 --- /dev/null +++ b/clojure/src/reader.clj @@ -0,0 +1,32 @@ +(ns reader + (:refer-clojure :exclude [read-string]) + (:require [clojure.tools.reader :as r] + [clojure.tools.reader.reader-types :as rt])) + +;; change tools.reader syntax-quote to quasiquote +(defn- wrap [sym] + (fn [rdr _] (list sym (#'r/read rdr true nil true)))) + +(defn- wrap-with [sym] + (fn [rdr arg _] (list sym (#'r/read rdr true nil true) arg))) + +;; Override some tools.reader reader macros so that we can do our own +;; metadata and quasiquote handling +(alter-var-root #'r/macros + (fn [f] + (fn [ch] + (case ch + \` (wrap 'quasiquote) + \~ (fn [rdr comma] + (if-let [ch (rt/peek-char rdr)] + (if (identical? \@ ch) + ((wrap 'splice-unquote) (doto rdr rt/read-char) \@) + ((wrap 'unquote) rdr \~)))) + \^ (fn [rdr comma] + (let [m (#'r/read rdr)] + ((wrap-with 'with-meta) rdr m \^))) + \@ (wrap 'deref) + (f ch))))) + +(defn read-string [s] + (r/read-string s)) diff --git a/clojure/src/readline.clj b/clojure/src/readline.clj new file mode 100644 index 0000000..dbd4872 --- /dev/null +++ b/clojure/src/readline.clj @@ -0,0 +1,36 @@ +(ns readline + (:require [clojure.string :refer [split]] + [net.n01se.clojure-jna :as jna])) + +(defonce history-loaded (atom nil)) +(def HISTORY-FILE "/home/joelm/.mal-history") + +;; +;; Uncomment one of the following readline libraries +;; + +;; editline (BSD) +#_ +(do + (def readline-call (jna/to-fn String edit/readline)) + (def add-history (jna/to-fn Void edit/add_history)) + (def load-history #(doseq [line (split (slurp %) #"\n")] + (jna/invoke Void edit/add_history line)))) + +;; GNU Readline (GPL) +;; WARNING: distributing your code with GNU readline enabled means you +;; must release your program as GPL +;#_ +(do + (def readline-call (jna/to-fn String readline/readline)) + (def add-history (jna/to-fn Void readline/add_history)) + (def load-history (jna/to-fn Integer readline/read_history))) + +(defn readline [prompt & [lib]] + (if (not @history-loaded) + (load-history HISTORY-FILE)) + (let [line (readline-call prompt)] + (when line + (add-history line) + (spit HISTORY-FILE (str line "\n") :append true)) + line)) diff --git a/clojure/src/step0_repl.clj b/clojure/src/step0_repl.clj new file mode 100644 index 0000000..7a050c7 --- /dev/null +++ b/clojure/src/step0_repl.clj @@ -0,0 +1,26 @@ +(ns step0-repl + (:require [readline])) + + +;; read +(defn READ [& [strng]] + (let [line (if strng strng (read-line))] + strng)) + +;; eval +(defn EVAL [ast env] + (eval (read-string ast))) + +;; print +(defn PRINT [exp] + exp) + +;; repl +(defn rep [strng] (PRINT (EVAL (READ strng), {}))) + +(defn -main [& args] + (loop [] + (let [line (readline/readline "user> ")] + (when line + (println (rep line)) + (recur))))) diff --git a/clojure/src/step1_read_print.clj b/clojure/src/step1_read_print.clj new file mode 100644 index 0000000..a99a0ed --- /dev/null +++ b/clojure/src/step1_read_print.clj @@ -0,0 +1,33 @@ +(ns step1-read-print + (:require [clojure.repl] + [types] + [readline] + [reader])) + +;; read +(defn READ [& [strng]] + (let [line (if strng strng (read-line))] + (reader/read-string strng))) + +;; eval +(defn EVAL [ast env] + ast) + +;; print +(defn PRINT [exp] (pr-str exp)) + +;; repl +(defn rep + [strng] + (PRINT (EVAL (READ strng), {}))) + +(defn -main [& args] + (loop [] + (let [line (readline/readline "user> ")] + (when line + (when-not (re-seq #"^\s*$|^\s*;.*$" line) ; blank/comment + (try + (println (rep line)) + (catch Throwable e + (clojure.repl/pst e)))) + (recur))))) diff --git a/clojure/src/step2_eval.clj b/clojure/src/step2_eval.clj new file mode 100644 index 0000000..6ff9eb3 --- /dev/null +++ b/clojure/src/step2_eval.clj @@ -0,0 +1,61 @@ +(ns step2-eval + (:require [clojure.repl] + [types] + [readline] + [reader])) + +(declare EVAL) + +;; read +(defn READ [& [strng]] + (let [line (if strng strng (read-line))] + (reader/read-string strng))) + +;; eval +(defn eval-ast [ast env] + (cond + (symbol? ast) (or (get env ast) + (throw (Error. (str ast " not found")))) + + (seq? ast) (doall (map #(EVAL % env) ast)) + + (vector? ast) (vec (doall (map #(EVAL % env) ast))) + + (map? ast) (apply hash-map (doall (map #(EVAL % env) + (mapcat identity ast)))) + + :else ast)) + +(defn EVAL [ast env] + ;;(prn "EVAL" ast (keys @env)) (flush) + (if (not (seq? ast)) + (eval-ast ast env) + + ;; apply list + (let [el (eval-ast ast env) + f (first el) + args (rest el)] + (apply f args)))) + +;; print +(defn PRINT [exp] (pr-str exp)) + +;; repl +(def repl-env {'+ + + '- - + '* * + '/ /}) +(defn rep + [strng] + (PRINT (EVAL (READ strng), repl-env))) + +(defn -main [& args] + (loop [] + (let [line (readline/readline "user> ")] + (when line + (when-not (re-seq #"^\s*$|^\s*;.*$" line) ; blank/comment + (try + (println (rep line)) + (catch Throwable e + (clojure.repl/pst e)))) + (recur))))) diff --git a/clojure/src/step3_env.clj b/clojure/src/step3_env.clj new file mode 100644 index 0000000..c0c4e8e --- /dev/null +++ b/clojure/src/step3_env.clj @@ -0,0 +1,76 @@ +(ns step3-env + (:require [clojure.repl] + [types] + [readline] + [reader])) + +(declare EVAL) + +;; read +(defn READ [& [strng]] + (let [line (if strng strng (read-line))] + (reader/read-string strng))) + +;; eval +(defn eval-ast [ast env] + (cond + (symbol? ast) (types/env-get env ast) + + (seq? ast) (doall (map #(EVAL % env) ast)) + + (vector? ast) (vec (doall (map #(EVAL % env) ast))) + + (map? ast) (apply hash-map (doall (map #(EVAL % env) + (mapcat identity ast)))) + + :else ast)) + +(defn EVAL [ast env] + ;;(prn "EVAL" ast (keys @env)) (flush) + (if (not (seq? ast)) + (eval-ast ast env) + + ;; apply list + (let [[a0 a1 a2 a3] ast] + (condp = a0 + 'def! + (types/env-set env a1 (EVAL a2 env)) + + 'let* + (let [let-env (types/env env)] + (doseq [[b e] (partition 2 a1)] + (types/env-set let-env b (EVAL e let-env))) + (EVAL a2 let-env)) + + ;; apply + (let [el (eval-ast ast env) + f (first el) + args (rest el)] + (apply f args)))))) + +;; print +(defn PRINT [exp] (pr-str exp)) + +;; repl +(def repl-env (types/env)) +(defn rep + [strng] + (PRINT (EVAL (READ strng), repl-env))) + +(defn _ref [k,v] (types/env-set repl-env k v)) +(_ref '+ +) +(_ref '- -) +(_ref '* *) +(_ref '/ /) + + +(defn -main [& args] + (loop [] + (let [line (readline/readline "user> ")] + (when line + (when-not (re-seq #"^\s*$|^\s*;.*$" line) ; blank/comment + (try + (println (rep line)) + (catch Throwable e + (clojure.repl/pst e)))) + (recur))))) diff --git a/clojure/src/step4_if_fn_do.clj b/clojure/src/step4_if_fn_do.clj new file mode 100644 index 0000000..4171848 --- /dev/null +++ b/clojure/src/step4_if_fn_do.clj @@ -0,0 +1,92 @@ +(ns step4-if-fn-do + (:require [clojure.repl] + [types] + [readline] + [reader])) + +(declare EVAL) + +;; read +(defn READ [& [strng]] + (let [line (if strng strng (read-line))] + (reader/read-string strng))) + +;; eval +(defn eval-ast [ast env] + (cond + (symbol? ast) (types/env-get env ast) + + (seq? ast) (doall (map #(EVAL % env) ast)) + + (vector? ast) (vec (doall (map #(EVAL % env) ast))) + + (map? ast) (apply hash-map (doall (map #(EVAL % env) + (mapcat identity ast)))) + + :else ast)) + +(defn EVAL [ast env] + ;;(prn "EVAL" ast (keys @env)) (flush) + (if (not (seq? ast)) + (eval-ast ast env) + + ;; apply list + (let [[a0 a1 a2 a3] ast] + (condp = a0 + 'def! + (types/env-set env a1 (EVAL a2 env)) + + 'let* + (let [let-env (types/env env)] + (doseq [[b e] (partition 2 a1)] + (types/env-set let-env b (EVAL e let-env))) + (EVAL a2 let-env)) + + 'do + (last (eval-ast (rest ast) env)) + + 'if + (let [cond (EVAL a1 env)] + (if (or (= cond nil) (= cond false)) + (if (> (count ast) 2) + (EVAL a3 env) + nil) + (EVAL a2 env))) + + 'fn* + (fn [& args] + (EVAL a2 (types/env env a1 args))) + + ;; apply + (let [el (eval-ast ast env) + f (first el) + args (rest el)] + (apply f args)))))) + +;; print +(defn PRINT [exp] (pr-str exp)) + +;; repl +(def repl-env (types/env)) +(defn rep + [strng] + (PRINT (EVAL (READ strng), repl-env))) + +(defn _ref [k,v] (types/env-set repl-env k v)) + +;; Import types related functions +(doseq [[k v] types/types_ns] (_ref k v)) + +;; Defined using the language itself +(rep "(def! not (fn* [a] (if a false true)))") + +(defn -main [& args] + (loop [] + (let [line (readline/readline "user> ")] + (when line + (when-not (re-seq #"^\s*$|^\s*;.*$" line) ; blank/comment + (try + (println (rep line)) + (catch Throwable e + (clojure.repl/pst e)))) + (recur))))) diff --git a/clojure/src/step5_tco.clj b/clojure/src/step5_tco.clj new file mode 100644 index 0000000..2ed07b4 --- /dev/null +++ b/clojure/src/step5_tco.clj @@ -0,0 +1,101 @@ +(ns step5-tco + (:require [clojure.repl] + [types] + [readline] + [reader])) + +(declare EVAL) + +;; read +(defn READ [& [strng]] + (let [line (if strng strng (read-line))] + (reader/read-string strng))) + +;; eval +(defn eval-ast [ast env] + (cond + (symbol? ast) (types/env-get env ast) + + (seq? ast) (doall (map #(EVAL % env) ast)) + + (vector? ast) (vec (doall (map #(EVAL % env) ast))) + + (map? ast) (apply hash-map (doall (map #(EVAL % env) + (mapcat identity ast)))) + + :else ast)) + +(defn EVAL [ast env] + (loop [ast ast + env env] + ;;(prn "EVAL" ast (keys @env)) (flush) + (if (not (seq? ast)) + (eval-ast ast env) + + ;; apply list + (let [[a0 a1 a2 a3] ast] + (condp = a0 + 'def! + (types/env-set env a1 (EVAL a2 env)) + + 'let* + (let [let-env (types/env env)] + (doseq [[b e] (partition 2 a1)] + (types/env-set let-env b (EVAL e let-env))) + (EVAL a2 let-env)) + + 'do + (do (eval-ast (->> ast (drop-last) (drop 1)) env) + (recur (last ast) env)) + + 'if + (let [cond (EVAL a1 env)] + (if (or (= cond nil) (= cond false)) + (if (> (count ast) 2) + (recur a3 env) + nil) + (recur a2 env))) + + 'fn* + ^{:expression a2 + :environment env + :parameters a1} + (fn [& args] + (EVAL a2 (types/env env a1 args))) + + ;; apply + (let [el (eval-ast ast env) + f (first el) + args (rest el) + {:keys [expression environment parameters]} (meta f)] + (if expression + (recur expression (types/env environment parameters args)) + (apply f args)))))))) + +;; print +(defn PRINT [exp] (pr-str exp)) + +;; repl +(def repl-env (types/env)) +(defn rep + [strng] + (PRINT (EVAL (READ strng), repl-env))) + +(defn _ref [k,v] (types/env-set repl-env k v)) + +;; Import types related functions +(doseq [[k v] types/types_ns] (_ref k v)) + +;; Defined using the language itself +(rep "(def! not (fn* [a] (if a false true)))") + +(defn -main [& args] + (loop [] + (let [line (readline/readline "user> ")] + (when line + (when-not (re-seq #"^\s*$|^\s*;.*$" line) ; blank/comment + (try + (println (rep line)) + (catch Throwable e + (clojure.repl/pst e)))) + (recur))))) diff --git a/clojure/src/step6_file.clj b/clojure/src/step6_file.clj new file mode 100644 index 0000000..80eedef --- /dev/null +++ b/clojure/src/step6_file.clj @@ -0,0 +1,109 @@ +(ns step6-file + (:require [clojure.repl] + [types] + [readline] + [reader])) + +(declare EVAL) + +;; read +(defn READ [& [strng]] + (let [line (if strng strng (read-line))] + (reader/read-string strng))) + +;; eval +(defn eval-ast [ast env] + (cond + (symbol? ast) (types/env-get env ast) + + (seq? ast) (doall (map #(EVAL % env) ast)) + + (vector? ast) (vec (doall (map #(EVAL % env) ast))) + + (map? ast) (apply hash-map (doall (map #(EVAL % env) + (mapcat identity ast)))) + + :else ast)) + +(defn EVAL [ast env] + (loop [ast ast + env env] + ;;(prn "EVAL" ast (keys @env)) (flush) + (if (not (seq? ast)) + (eval-ast ast env) + + ;; apply list + (let [[a0 a1 a2 a3] ast] + (condp = a0 + 'def! + (types/env-set env a1 (EVAL a2 env)) + + 'let* + (let [let-env (types/env env)] + (doseq [[b e] (partition 2 a1)] + (types/env-set let-env b (EVAL e let-env))) + (EVAL a2 let-env)) + + 'do + (do (eval-ast (->> ast (drop-last) (drop 1)) env) + (recur (last ast) env)) + + 'if + (let [cond (EVAL a1 env)] + (if (or (= cond nil) (= cond false)) + (if (> (count ast) 2) + (recur a3 env) + nil) + (recur a2 env))) + + 'fn* + ^{:expression a2 + :environment env + :parameters a1} + (fn [& args] + (EVAL a2 (types/env env a1 args))) + + ;; apply + (let [el (eval-ast ast env) + f (first el) + args (rest el) + {:keys [expression environment parameters]} (meta f)] + (if expression + (recur expression (types/env environment parameters args)) + (apply f args)))))))) + +;; print +(defn PRINT [exp] (pr-str exp)) + +;; repl +(def repl-env (types/env)) +(defn rep + [strng] + (PRINT (EVAL (READ strng), repl-env))) + +(defn _ref [k,v] (types/env-set repl-env k v)) + +;; Import types related functions +(doseq [[k v] types/types_ns] (_ref k v)) + +;; Defined using the language itself +(_ref 'read-string reader/read-string) +(_ref 'eval (fn [ast] (EVAL ast repl-env))) +(_ref 'slurp slurp) +(_ref 'slurp-do (fn [f] (str "(do " (slurp f) ")"))) + +(rep "(def! not (fn* [a] (if a false true)))") +(rep "(def! load-file (fn* [f] (eval (read-string (slurp-do f)))))") + +(defn -main [& args] + (if args + (rep (str "(load-file \"" (first args) "\")")) + (loop [] + (let [line (readline/readline "user> ")] + (when line + (when-not (re-seq #"^\s*$|^\s*;.*$" line) ; blank/comment + (try + (println (rep line)) + (catch Throwable e + (clojure.repl/pst e)))) + (recur)))))) diff --git a/clojure/src/step7_quote.clj b/clojure/src/step7_quote.clj new file mode 100644 index 0000000..8f190dd --- /dev/null +++ b/clojure/src/step7_quote.clj @@ -0,0 +1,132 @@ +(ns step7-quote + (:require [clojure.repl] + [types] + [readline] + [reader])) + +(declare EVAL) + +;; read +(defn READ [& [strng]] + (let [line (if strng strng (read-line))] + (reader/read-string strng))) + +;; eval +(defn is-pair [x] + (and (sequential? x) (> (count x) 0))) + +(defn quasiquote [ast] + (cond + (not (is-pair ast)) + (list 'quote ast) + + (= 'unquote (first ast)) + (second ast) + + (and (is-pair (first ast)) (= 'splice-unquote (ffirst ast))) + (list 'concat (-> ast first second) (quasiquote (rest ast))) + + :else + (list 'cons (quasiquote (first ast)) (quasiquote (rest ast))))) + +(defn eval-ast [ast env] + (cond + (symbol? ast) (types/env-get env ast) + + (seq? ast) (doall (map #(EVAL % env) ast)) + + (vector? ast) (vec (doall (map #(EVAL % env) ast))) + + (map? ast) (apply hash-map (doall (map #(EVAL % env) + (mapcat identity ast)))) + + :else ast)) + +(defn EVAL [ast env] + (loop [ast ast + env env] + ;;(prn "EVAL" ast (keys @env)) (flush) + (if (not (seq? ast)) + (eval-ast ast env) + + ;; apply list + (let [[a0 a1 a2 a3] ast] + (condp = a0 + 'def! + (types/env-set env a1 (EVAL a2 env)) + + 'let* + (let [let-env (types/env env)] + (doseq [[b e] (partition 2 a1)] + (types/env-set let-env b (EVAL e let-env))) + (EVAL a2 let-env)) + + 'quote + a1 + + 'quasiquote + (EVAL (quasiquote a1) env) + + 'do + (do (eval-ast (->> ast (drop-last) (drop 1)) env) + (recur (last ast) env)) + + 'if + (let [cond (EVAL a1 env)] + (if (or (= cond nil) (= cond false)) + (if (> (count ast) 2) + (recur a3 env) + nil) + (recur a2 env))) + + 'fn* + ^{:expression a2 + :environment env + :parameters a1} + (fn [& args] + (EVAL a2 (types/env env a1 args))) + + ;; apply + (let [el (eval-ast ast env) + f (first el) + args (rest el) + {:keys [expression environment parameters]} (meta f)] + (if expression + (recur expression (types/env environment parameters args)) + (apply f args)))))))) + +;; print +(defn PRINT [exp] (pr-str exp)) + +;; repl +(def repl-env (types/env)) +(defn rep + [strng] + (PRINT (EVAL (READ strng), repl-env))) + +(defn _ref [k,v] (types/env-set repl-env k v)) + +;; Import types related functions +(doseq [[k v] types/types_ns] (_ref k v)) + +;; Defined using the language itself +(_ref 'read-string reader/read-string) +(_ref 'eval (fn [ast] (EVAL ast repl-env))) +(_ref 'slurp slurp) +(_ref 'slurp-do (fn [f] (str "(do " (slurp f) ")"))) + +(rep "(def! not (fn* [a] (if a false true)))") +(rep "(def! load-file (fn* [f] (eval (read-string (slurp-do f)))))") + +(defn -main [& args] + (if args + (rep (str "(load-file \"" (first args) "\")")) + (loop [] + (let [line (readline/readline "user> ")] + (when line + (when-not (re-seq #"^\s*$|^\s*;.*$" line) ; blank/comment + (try + (println (rep line)) + (catch Throwable e + (clojure.repl/pst e)))) + (recur)))))) diff --git a/clojure/src/step8_macros.clj b/clojure/src/step8_macros.clj new file mode 100644 index 0000000..8b95ba8 --- /dev/null +++ b/clojure/src/step8_macros.clj @@ -0,0 +1,158 @@ +(ns step8-macros + (:refer-clojure :exclude [macroexpand]) + (:require [clojure.repl] + [types] + [readline] + [reader])) + +(declare EVAL) + +;; read +(defn READ [& [strng]] + (let [line (if strng strng (read-line))] + (reader/read-string strng))) + +;; eval +(defn is-pair [x] + (and (sequential? x) (> (count x) 0))) + +(defn quasiquote [ast] + (cond + (not (is-pair ast)) + (list 'quote ast) + + (= 'unquote (first ast)) + (second ast) + + (and (is-pair (first ast)) (= 'splice-unquote (ffirst ast))) + (list 'concat (-> ast first second) (quasiquote (rest ast))) + + :else + (list 'cons (quasiquote (first ast)) (quasiquote (rest ast))))) + +(defn is-macro-call [ast env] + (and (seq? ast) + (symbol? (first ast)) + (types/env-find env (first ast)) + (:ismacro (meta (types/env-get env (first ast)))))) + +(defn macroexpand [ast env] + (loop [ast ast] + (if (is-macro-call ast env) + (let [mac (types/env-get env (first ast))] + (recur (apply mac (rest ast)))) + ast))) + +(defn eval-ast [ast env] + (cond + (symbol? ast) (types/env-get env ast) + + (seq? ast) (doall (map #(EVAL % env) ast)) + + (vector? ast) (vec (doall (map #(EVAL % env) ast))) + + (map? ast) (apply hash-map (doall (map #(EVAL % env) + (mapcat identity ast)))) + + :else ast)) + +(defn EVAL [ast env] + (loop [ast ast + env env] + ;;(prn "EVAL" ast (keys @env)) (flush) + (if (not (seq? ast)) + (eval-ast ast env) + + ;; apply list + (let [ast (macroexpand ast env)] + (if (not (seq? ast)) + ast + + (let [[a0 a1 a2 a3] ast] + (condp = a0 + 'def! + (types/env-set env a1 (EVAL a2 env)) + + 'let* + (let [let-env (types/env env)] + (doseq [[b e] (partition 2 a1)] + (types/env-set let-env b (EVAL e let-env))) + (EVAL a2 let-env)) + + 'quote + a1 + + 'quasiquote + (EVAL (quasiquote a1) env) + + 'defmacro! + (let [func (with-meta (EVAL a2 env) + {:ismacro true})] + (types/env-set env a1 func)) + + 'macroexpand + (macroexpand a1 env) + + 'do + (do (eval-ast (->> ast (drop-last) (drop 1)) env) + (recur (last ast) env)) + + 'if + (let [cond (EVAL a1 env)] + (if (or (= cond nil) (= cond false)) + (if (> (count ast) 2) + (recur a3 env) + nil) + (recur a2 env))) + + 'fn* + ^{:expression a2 + :environment env + :parameters a1} + (fn [& args] + (EVAL a2 (types/env env a1 args))) + + ;; apply + (let [el (eval-ast ast env) + f (first el) + args (rest el) + {:keys [expression environment parameters]} (meta f)] + (if expression + (recur expression (types/env environment parameters args)) + (apply f args)))))))))) + +;; print +(defn PRINT [exp] (pr-str exp)) + +;; repl +(def repl-env (types/env)) +(defn rep + [strng] + (PRINT (EVAL (READ strng) repl-env))) + +(defn _ref [k,v] (types/env-set repl-env k v)) + +;; Import types related functions +(doseq [[k v] types/types_ns] (_ref k v)) + +;; Defined using the language itself +(_ref 'read-string reader/read-string) +(_ref 'eval (fn [ast] (EVAL ast repl-env))) +(_ref 'slurp slurp) +(_ref 'slurp-do (fn [f] (str "(do " (slurp f) ")"))) + +(rep "(def! not (fn* [a] (if a false true)))") +(rep "(def! load-file (fn* [f] (eval (read-string (slurp-do f)))))") + +(defn -main [& args] + (if args + (rep (str "(load-file \"" (first args) "\")")) + (loop [] + (let [line (readline/readline "user> ")] + (when line + (when-not (re-seq #"^\s*$|^\s*;.*$" line) ; blank/comment + (try + (println (rep line)) + (catch Throwable e + (clojure.repl/pst e)))) + (recur)))))) diff --git a/clojure/src/step9_interop.clj b/clojure/src/step9_interop.clj new file mode 100644 index 0000000..48ae687 --- /dev/null +++ b/clojure/src/step9_interop.clj @@ -0,0 +1,161 @@ +(ns step9-interop + (:refer-clojure :exclude [macroexpand]) + (:require [clojure.repl] + [types] + [readline] + [reader])) + +(declare EVAL) + +;; read +(defn READ [& [strng]] + (let [line (if strng strng (read-line))] + (reader/read-string strng))) + +;; eval +(defn is-pair [x] + (and (sequential? x) (> (count x) 0))) + +(defn quasiquote [ast] + (cond + (not (is-pair ast)) + (list 'quote ast) + + (= 'unquote (first ast)) + (second ast) + + (and (is-pair (first ast)) (= 'splice-unquote (ffirst ast))) + (list 'concat (-> ast first second) (quasiquote (rest ast))) + + :else + (list 'cons (quasiquote (first ast)) (quasiquote (rest ast))))) + +(defn is-macro-call [ast env] + (and (seq? ast) + (symbol? (first ast)) + (types/env-find env (first ast)) + (:ismacro (meta (types/env-get env (first ast)))))) + +(defn macroexpand [ast env] + (loop [ast ast] + (if (is-macro-call ast env) + (let [mac (types/env-get env (first ast))] + (recur (apply mac (rest ast)))) + ast))) + +(defn eval-ast [ast env] + (cond + (symbol? ast) (types/env-get env ast) + + (seq? ast) (doall (map #(EVAL % env) ast)) + + (vector? ast) (vec (doall (map #(EVAL % env) ast))) + + (map? ast) (apply hash-map (doall (map #(EVAL % env) + (mapcat identity ast)))) + + :else ast)) + +(defn EVAL [ast env] + (loop [ast ast + env env] + ;;(prn "EVAL" ast (keys @env)) (flush) + (if (not (seq? ast)) + (eval-ast ast env) + + ;; apply list + (let [ast (macroexpand ast env)] + (if (not (seq? ast)) + ast + + (let [[a0 a1 a2 a3] ast] + (condp = a0 + 'def! + (types/env-set env a1 (EVAL a2 env)) + + 'let* + (let [let-env (types/env env)] + (doseq [[b e] (partition 2 a1)] + (types/env-set let-env b (EVAL e let-env))) + (EVAL a2 let-env)) + + 'quote + a1 + + 'quasiquote + (EVAL (quasiquote a1) env) + + 'defmacro! + (let [func (with-meta (EVAL a2 env) + {:ismacro true})] + (types/env-set env a1 func)) + + 'macroexpand + (macroexpand a1 env) + + 'clj* + (eval (reader/read-string a1)) + + 'do + (do (eval-ast (->> ast (drop-last) (drop 1)) env) + (recur (last ast) env)) + + 'if + (let [cond (EVAL a1 env)] + (if (or (= cond nil) (= cond false)) + (if (> (count ast) 2) + (recur a3 env) + nil) + (recur a2 env))) + + 'fn* + ^{:expression a2 + :environment env + :parameters a1} + (fn [& args] + (EVAL a2 (types/env env a1 args))) + + ;; apply + (let [el (eval-ast ast env) + f (first el) + args (rest el) + {:keys [expression environment parameters]} (meta f)] + (if expression + (recur expression (types/env environment parameters args)) + (apply f args)))))))))) + +;; print +(defn PRINT [exp] (pr-str exp)) + +;; repl +(def repl-env (types/env)) +(defn rep + [strng] + (PRINT (EVAL (READ strng) repl-env))) + +(defn _ref [k,v] (types/env-set repl-env k v)) + +;; Import types related functions +(doseq [[k v] types/types_ns] (_ref k v)) + +;; Defined using the language itself +(_ref 'read-string reader/read-string) +(_ref 'eval (fn [ast] (EVAL ast repl-env))) +(_ref 'slurp slurp) +(_ref 'slurp-do (fn [f] (str "(do " (slurp f) ")"))) + +(rep "(def! not (fn* [a] (if a false true)))") +(rep "(def! load-file (fn* [f] (eval (read-string (slurp-do f)))))") + +(defn -main [& args] + (if args + (rep (str "(load-file \"" (first args) "\")")) + (loop [] + (let [line (readline/readline "user> ")] + (when line + (when-not (re-seq #"^\s*$|^\s*;.*$" line) ; blank/comment + (try + (println (rep line)) + (catch Throwable e + (clojure.repl/pst e)))) + (recur)))))) diff --git a/clojure/src/stepA_more.clj b/clojure/src/stepA_more.clj new file mode 100644 index 0000000..19a0c36 --- /dev/null +++ b/clojure/src/stepA_more.clj @@ -0,0 +1,178 @@ +(ns stepA-more + (:refer-clojure :exclude [macroexpand]) + (:require [clojure.repl] + [types] + [readline] + [reader])) + +(declare EVAL) + +;; read +(defn READ [& [strng]] + (let [line (if strng strng (read-line))] + (reader/read-string strng))) + +;; eval +(defn is-pair [x] + (and (sequential? x) (> (count x) 0))) + +(defn quasiquote [ast] + (cond + (not (is-pair ast)) + (list 'quote ast) + + (= 'unquote (first ast)) + (second ast) + + (and (is-pair (first ast)) (= 'splice-unquote (ffirst ast))) + (list 'concat (-> ast first second) (quasiquote (rest ast))) + + :else + (list 'cons (quasiquote (first ast)) (quasiquote (rest ast))))) + +(defn is-macro-call [ast env] + (and (seq? ast) + (symbol? (first ast)) + (types/env-find env (first ast)) + (:ismacro (meta (types/env-get env (first ast)))))) + +(defn macroexpand [ast env] + (loop [ast ast] + (if (is-macro-call ast env) + (let [mac (types/env-get env (first ast))] + (recur (apply mac (rest ast)))) + ast))) + +(defn eval-ast [ast env] + (cond + (symbol? ast) (types/env-get env ast) + + (seq? ast) (doall (map #(EVAL % env) ast)) + + (vector? ast) (vec (doall (map #(EVAL % env) ast))) + + (map? ast) (apply hash-map (doall (map #(EVAL % env) + (mapcat identity ast)))) + + :else ast)) + +(defn EVAL [ast env] + (loop [ast ast + env env] + ;;(prn "EVAL" ast (keys @env)) (flush) + (if (not (seq? ast)) + (eval-ast ast env) + + ;; apply list + (let [ast (macroexpand ast env)] + (if (not (seq? ast)) + ast + + (let [[a0 a1 a2 a3] ast] + (condp = a0 + 'def! + (types/env-set env a1 (EVAL a2 env)) + + 'let* + (let [let-env (types/env env)] + (doseq [[b e] (partition 2 a1)] + (types/env-set let-env b (EVAL e let-env))) + (EVAL a2 let-env)) + + 'quote + a1 + + 'quasiquote + (EVAL (quasiquote a1) env) + + 'defmacro! + (let [func (with-meta (EVAL a2 env) + {:ismacro true})] + (types/env-set env a1 func)) + + 'macroexpand + (macroexpand a1 env) + + 'clj* + (eval (reader/read-string a1)) + + 'try* + (if (= 'catch* (nth a2 0)) + (try + (EVAL a1 env) + (catch clojure.lang.ExceptionInfo ei + (EVAL (nth a2 2) (types/env env + [(nth a2 1)] + [(:data (ex-data ei))]))) + (catch Throwable t + (EVAL (nth a2 2) (types/env env + [(nth a2 1)] + [(.getMessage t)])))) + (EVAL a1 env)) + + 'do + (do (eval-ast (->> ast (drop-last) (drop 1)) env) + (recur (last ast) env)) + + 'if + (let [cond (EVAL a1 env)] + (if (or (= cond nil) (= cond false)) + (if (> (count ast) 2) + (recur a3 env) + nil) + (recur a2 env))) + + 'fn* + ^{:expression a2 + :environment env + :parameters a1} + (fn [& args] + (EVAL a2 (types/env env a1 args))) + + ;; apply + (let [el (eval-ast ast env) + f (first el) + args (rest el) + {:keys [expression environment parameters]} (meta f)] + (if expression + (recur expression (types/env environment parameters args)) + (apply f args)))))))))) + +;; print +(defn PRINT [exp] (pr-str exp)) + +;; repl +(def repl-env (types/env)) +(defn rep + [strng] + (PRINT (EVAL (READ strng) repl-env))) + +(defn _ref [k,v] (types/env-set repl-env k v)) + +;; Import types related functions +(doseq [[k v] types/types_ns] (_ref k v)) + +;; Defined using the language itself +(_ref 'readline readline/readline) +(_ref 'read-string reader/read-string) +(_ref 'eval (fn [ast] (EVAL ast repl-env))) +(_ref 'slurp slurp) +(_ref 'slurp-do (fn [f] (str "(do " (slurp f) ")"))) + +(rep "(def! not (fn* [a] (if a false true)))") +(rep "(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))") +(rep "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))") +(rep "(def! load-file (fn* [f] (eval (read-string (slurp-do f)))))") + +(defn -main [& args] + (if args + (rep (str "(load-file \"" (first args) "\")")) + (loop [] + (let [line (readline/readline "user> ")] + (when line + (when-not (re-seq #"^\s*$|^\s*;.*$" line) ; blank/comment + (try + (println (rep line)) + (catch Throwable e + (clojure.repl/pst e)))) + (recur)))))) diff --git a/clojure/src/types.clj b/clojure/src/types.clj new file mode 100644 index 0000000..922cf79 --- /dev/null +++ b/clojure/src/types.clj @@ -0,0 +1,71 @@ +(ns types) + +;; Custom printing + +(defmethod clojure.core/print-method clojure.lang.Atom [a writer] + (.write writer "(atom ") + (.write writer (pr-str @a)) + (.write writer ")")) + +;; Errors/exceptions +(defn mal_throw [obj] + (throw (ex-info "mal exception" {:data obj}))) + + +;; Atoms +(defn atom? [atm] + (= (type atm) clojure.lang.Atom)) + + +;; env + +(defn env [& [outer binds exprs]] + ;;(prn "env" binds exprs) + ;; (when (not= (count binds) (count exprs)) + ;; (throw (Exception. "Arity mistmatch in env call"))) + (atom + (loop [env {:outer outer} + b binds + e exprs] + (cond + (= nil b) + env + + (= '& (first b)) + (assoc env (nth b 1) e) + + :else + (recur (assoc env (first b) (first e)) (next b) (next e)))))) + +(defn env-find [env k] + (cond + (contains? @env k) env + (:outer @env) (env-find (:outer @env) k) + :else nil)) + +(defn env-get [env k] + (let [e (env-find env k)] + (when-not e + (throw (Exception. (str "'" k "' not found")))) + (get @e k))) + +(defn env-set [env k v] + (swap! env assoc k v) + v) + +(def types_ns + [['pr-str pr-str] ['str str] ['prn prn] ['println println] + ['with-meta with-meta] ['meta meta] ['= =] + ['nil? nil?] ['true? true?] ['false? false?] ['symbol? symbol?] + ['> >] ['>= >=] ['< <] ['<= <=] ['+ +] ['- -] ['* *] ['/ /] + ['hash-map hash-map] ['map? map?] + ['assoc assoc] ['dissoc dissoc] ['get get] + ['contains? contains?] ['keys keys] ['vals vals] + ['throw mal_throw] + ['list list] ['list? seq?] ['vector vector] ['vector? vector?] + ['atom atom] ['atom? atom?] ['deref deref] + ['reset! reset!] ['swap! swap!] + ['sequential? sequential?] ['cons cons] ['nth nth] + ['empty? empty?] ['count count] ['concat concat] + ['conj conj] ['first first] ['rest rest] + ['apply apply] ['map #(doall (map %1 %2))]]) diff --git a/core.mal b/core.mal new file mode 100644 index 0000000..2896dcc --- /dev/null +++ b/core.mal @@ -0,0 +1,83 @@ +(def! inc (fn* (a) (+ a 1))) + +(def! dec (fn* (a) (- a 1))) + +(def! zero? (fn* (n) (= 0 n))) + +(def! reduce + (fn* (f init xs) + (if (> (count xs) 0) + (reduce f (f init (first xs)) (rest xs)) + init))) + +(def! identity (fn* (x) x)) + +(def! every? + (fn* (pred xs) + (if (> (count xs) 0) + (if (pred (first xs)) + (every? pred (rest xs)) + false) + true))) + +(def! not (fn* (x) (if x false true))) + +(def! some + (fn* (pred xs) + (if (> (count xs) 0) + (let* (res (pred (first xs))) + (if (pred (first xs)) + res + (some pred (rest xs)))) + nil))) + +(defmacro! and + (fn* (& xs) + (if (empty? xs) + true + (if (= 1 (count xs)) + (first xs) + `(let* (and_FIXME ~(first xs)) + (if and_FIXME (and ~@(rest xs)) and_FIXME)))))) + +(defmacro! or + (fn* (& xs) + (if (empty? xs) + nil + (if (= 1 (count xs)) + (first xs) + `(let* (or_FIXME ~(first xs)) + (if or_FIXME or_FIXME (or ~@(rest xs)))))))) + +(defmacro! cond + (fn* (& clauses) + (if (> (count clauses) 0) + (list 'if (first clauses) + (if (> (count clauses) 1) + (nth clauses 1) + (throw "cond requires an even number of forms")) + (cons 'cond (rest (rest clauses))))))) + +(defmacro! -> + (fn* (x & xs) + (if (empty? xs) + x + (let* (form (first xs) + more (rest xs)) + (if (empty? more) + (if (list? form) + `(~(first form) ~x ~@(rest form)) + (list form x)) + `(-> (-> ~x ~form) ~@more)))))) + +(defmacro! ->> + (fn* (x & xs) + (if (empty? xs) + x + (let* (form (first xs) + more (rest xs)) + (if (empty? more) + (if (list? form) + `(~(first form) ~@(rest form) ~x) + (list form x)) + `(->> (->> ~x ~form) ~@more)))))) diff --git a/docs/TODO b/docs/TODO new file mode 100644 index 0000000..d8f17b7 --- /dev/null +++ b/docs/TODO @@ -0,0 +1,95 @@ +All: + - multi-line read + - loop/recur ? + - hash-maps with non-string keys + - gensym reader inside quasiquote + - "intern" symbols, strings and numbers. Simplify equality + comparision. + - Contact Peter Norvig about license + + - synchronize function/definitions order/names in files + - move Env into separate file (maybe)? + - more metadata tests + - more hash_map tests + - hash-map with space in key string (make) + - more interop tests + - support metadata on symbol, hash-map, list, vector, function, atom + + - unindent tco while loop for step5-A + +--------------------------------------------- + +JS: + +Python: + +Clojure: + +C: + - come up with better way to do 20 vararg code + +Bash: + +PHP: + +Make: + - Norvig2: TCO/recur? + - allow '_' in make variable names + - errors should propagate up from within load-file + + +Mal: + - line numbers in errors + - step6: command line arguments + - step 5 + - step 9 + +Java: + - vectors, hash-maps, metadata + - step 9 + - mvn exec:java -Dexec.mainClass="mal.step6_file" -Dexec.args="incC.mal" + +Rust: + - http://www.rustforrubyists.com/book/index.html + - http://static.rust-lang.org/doc/0.9/complement-cheatsheet.html + - http://pzol.github.io/getting_rusty/ + - readline: + - http://redbrain.co.uk/2013/11/09/rust-and-readline-c-ffi/ + - http://www.reddit.com/r/rust/comments/1q9pqc/rust_cffi_and_readline/ + - https://github.com/dbp/rustrepl + - hash-map: + - http://static.rust-lang.org/doc/master/std/hashmap/index.html + - http://static.rust-lang.org/doc/master/std/hashmap/struct.HashMap.html + - vector/list: + - http://static.rust-lang.org/doc/master/std/vec/index.html + - steps 2-A + + +Others (based on redmonk languages from Jan 2014): ? + http://sogrady-media.redmonk.com/sogrady/files/2014/01/lang-rank-114-wm.png + + - Tier 1 + * JavaScript + * Java + * PHP + ? C# + * Python + ? C++ + - Ruby + * C + - Objective-C + - Perl + * Shell + + - Tier 2 + * Clojure + ? Go + ? Assembly + ? Fortan + ? Dart + ? D + + - Tier 3 + ? Pascal + - Rust + - diff --git a/docs/step_notes.txt b/docs/step_notes.txt new file mode 100644 index 0000000..768167d --- /dev/null +++ b/docs/step_notes.txt @@ -0,0 +1,181 @@ +Step Notes: + +- step0_repl + - prompt, input, READ, EVAL, PRINT, output + - readline module + - display prompt, read line of input + +- use native eval in EVAL if available + +- libedit/GNU readline: + - use existing lib, wrap shell call or implement + - load history file on first call + - add non-blank lines to history + - append to history file + +- step1_read_print + - types module: + - add boxed types if no language equivalent: + - nil, true, false, symbol, integer, string, list + - pr_str: + - stringify boxed types to their Mal representations + - list/array is recursive + - reader module: + - stateful reader object + - alternative: mutate token list + - tokenize (use regex if available) + - standard regex pattern: "/[\s,]*(~@|[\[\]{}()'`~^@]|\"(?:\\.|[^\\\"])*\"|;.*|[^\s\[\]{}('\"`,;)]*)/" + - read_str + - read_form(new Reader(tokenize(str))) + - read_form + - detect errors + - call read_list or read_atom + - read_list + - read_form until ')' + - return array (boxed) + - read_atom + - return scalar boxed type: + - nil, true, false, symbol, integer, string + +- vectors + - Basically: two array types that retain their boxed types, can be + challenging depending on the language (e.g. JS, PHP: no clean + way to derive new array types). + - types module: + - add vector boxed type + - derived from array if possible + - pr_str: + - vector is recursive + - sequential? + - reader module: + - read_vector: + - re-use read_list but with different constructor, delims + +- hash-maps + - reader module: + - re-use read_list function and apply that using hash-map + constructor + - types module: + - pr_str addition + - hash-map, map?, assoc, dissoc, get, contains?, keys, + vals (probably assoc! and dissoc! for internal) + - eval_map: eval the keys and values of hash_maps + - EVAL: + - if hash_map, call eval_map on it + +- step2_eval + - types module: + - first, rest, nth on list + - eval_ast: + - if symbol, return value of looking up in env + - if list, eval each item, return new list + - if vector support, eval each item, return new vector + - if hash_map support, eval each value, return new hash_map + - otherwise, just return unchanged ast + - EVAL/apply: + - if not a list, call eval_ast + - otherwise, apply first item to eval_ast of (rest ast) + - repl_env as simple one level assoc. array (or hash_map) + - store function as hash_map value + +- step3_env + - types module: + - Env type: + - find, set, get (no binds/exprs in constructor yet) + - may need function type if HashMap is strongly typed (e.g. Java) + - EVAL/apply: + - def! - mutate current environment + - let* - create new environment with bindings + - _ref sugar + +- step4_if_fn_do + - types module: + - function type (closure) + - add function printing to pr_str + - add binds/exprs handling to Env constructor with variable arity + - functions (exported via types_ns): + - move arith operations here + - comparison operations (including =) + - prn, pr_str, = (recursive) + - list, list?, count, empty? + - EVAL: + - do: + - if: + - fn*: + - simple if language supports closures + - define not using rep() + + +- metadata + - + +- step5_tco + - types module: + - function type: + - stores: func, exp, env, params + - func is EVAL in native mal case, otherwise reference to + platform function + - if metadata support, then store exp, env, params as + metadata + - update function printer to show function types + - EVAL: + - while loop around whole thing + - cases where we directly return result of EVAL, instead set + ast and env to what would be put in the EVAL, then loop. + - for apply case, set env to new Env based on properties + on the function + +- step6_file + - add read-string, eval, slurp, slurp-do platform wrappers + - define load-file function + - if files on command line, use load-file to run + +- step7_quote + - reader module: + - add reader macros to read_form for quote, unquote, + splice-unquote and quasiquote + - types module: + - add cons and concat functions + - add is_pair and quasiquote functions + - rewrite ast using cons/concat functions + - if vectors, use sequential? instead of list? in is_pair + - EVAL: + - add 'quote', 'quasiquote' cases + +- step8_macros + - types module: + - add first, rest functions + - add is_macro_call and macroexpand + - recursively macroexpand lists + - if applying a macro function, run it on the ast first before + continuing + - call macroexpand apply in EVAL + - EVAL: + - add 'defmacro!' and 'macroexpand' + - store ismacro property on function metadata + +- stepA_more + - types module: + - throw function + - map, apply functions + - symbol?, nil?, true?, false? + - conj, first, rest + - EVAL: + - try*/catch*: for normal exceptions, extracts string + otherwise extracts full value + - define cond and or macros using rep() + +- atoms + - reader module: + - @a reader macro -> (deref a) + - types module: + - pr_str case + - atom type, atom, atom?, deref, reset!, swap! + +- metadata + - types module: + - support meta property on symbols, hash-maps, lists, vectors, + functions, atoms + - add with-meta, meta functions + - reader module: + - ^ reader macro reads ^meta obj -> (with-meta obj meta) diff --git a/java/Makefile b/java/Makefile new file mode 100644 index 0000000..ccbd6c5 --- /dev/null +++ b/java/Makefile @@ -0,0 +1,19 @@ + +TESTS = + + +SOURCES = src/main/java/mal/types.java src/main/java/mal/readline.java \ + src/main/java/mal/reader.java src/main/java/mal/Env.java \ + src/main/java/mal/step5_tco.java + +#.PHONY: stats tests $(TESTS) +.PHONY: stats + +stats: $(SOURCES) + @wc $^ + +#tests: $(TESTS) +# +#$(TESTS): +# @echo "Running $@"; \ +# python $@ || exit 1; \ diff --git a/java/pom.xml b/java/pom.xml new file mode 100644 index 0000000..2f0a4fd --- /dev/null +++ b/java/pom.xml @@ -0,0 +1,81 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + <groupId>org.martintribe</groupId> + <artifactId>mal</artifactId> + <packaging>jar</packaging> + <version>0.0.1</version> + + <dependencies> + <dependency> + <groupId>com.google.guava</groupId> + <artifactId>guava</artifactId> + <version>16.0.1</version> + </dependency> + <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-lang3</artifactId> + <version>3.3</version> + </dependency> + <dependency> + <groupId>net.java.dev.jna</groupId> + <artifactId>jna</artifactId> + <version>4.0.0</version> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <artifactId>maven-compiler-plugin</artifactId> + <configuration> + <source>1.7</source> + <target>1.7</target> + </configuration> + </plugin> + <plugin> + <groupId>org.codehaus.mojo</groupId> + <artifactId>exec-maven-plugin</artifactId> + <version>1.2.1</version> + <executions> + <execution> + <goals> + <goal>java</goal> + </goals> + </execution> + </executions> + <configuration> + <!-- + <mainClass>mal.stepA_more</mainClass> + <arguments> + <argument>foo</argument> + <argument>bar</argument> + </arguments> + --> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-shade-plugin</artifactId> + <version>1.7.1</version> + <executions> + <execution> + <phase>package</phase> + <goals> + <goal>shade</goal> + </goals> + <configuration> + <transformers> + <transformer + implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> + <mainClass>mal.stepA_more</mainClass> + </transformer> + </transformers> + </configuration> + </execution> + </executions> + </plugin> + </plugins> + </build> +</project> diff --git a/java/src/main/java/mal/reader.java b/java/src/main/java/mal/reader.java new file mode 100644 index 0000000..6bae506 --- /dev/null +++ b/java/src/main/java/mal/reader.java @@ -0,0 +1,147 @@ +package mal; + +import java.util.ArrayList; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.apache.commons.lang3.StringEscapeUtils; +import mal.types.*; + +public class reader { + public static class ParseError extends MalThrowable { + public ParseError(String msg) { + super(msg); + } + } + + public static class Reader { + ArrayList<String> tokens; + Integer position; + public Reader(ArrayList<String> t) { + tokens = t; + position = 0; + } + + public String peek() { + if (position >= tokens.size()) { + return null; + } else { + return tokens.get(position); + } + } + public String next() { + return tokens.get(position++); + } + } + + public static ArrayList<String> tokenize(String str) { + ArrayList<String> tokens = new ArrayList<String>(); + Pattern pattern = Pattern.compile("[\\s ,]*(~@|[\\[\\]{}()'`~@]|\"(?:[\\\\].|[^\\\\\"])*\"|;.*|[^\\s \\[\\]{}()'\"`~@,;]*)"); + Matcher matcher = pattern.matcher(str); + while (matcher.find()) { + String token = matcher.group(1); + if (token != null && + !token.equals("") && + !(token.charAt(0) == ';')) { + tokens.add(token); + } + } + return tokens; + } + + public static MalVal read_atom(Reader rdr) + throws ParseError { + String token = rdr.next(); + Pattern pattern = Pattern.compile("(^-?[0-9]+$)|(^-?[0-9][0-9.]*$)|(^nil$)|(^true$)|(^false$)|^\"(.*)\"$|(^[^\"]*$)"); + Matcher matcher = pattern.matcher(token); + if (!matcher.find()) { + throw new ParseError("unrecognized token '" + token + "'"); + } + if (matcher.group(1) != null) { + return new MalInteger(Integer.parseInt(matcher.group(1))); + } else if (matcher.group(3) != null) { + return types.Nil; + } else if (matcher.group(4) != null) { + return types.True; + } else if (matcher.group(5) != null) { + return types.False; + } else if (matcher.group(6) != null) { + return new MalString(StringEscapeUtils.unescapeJson(matcher.group(6))); + } else if (matcher.group(7) != null) { + return new MalSymbol(matcher.group(7)); + } else { + throw new ParseError("unrecognized '" + matcher.group(0) + "'"); + } + } + + public static MalVal read_list(Reader rdr, MalList lst, char start, char end) + throws MalContinue, ParseError { + String token = rdr.next(); + if (token.charAt(0) != start) { + throw new ParseError("expected '" + start + "'"); + } + + while ((token = rdr.peek()) != null && token.charAt(0) != end) { + lst.conj_BANG(read_form(rdr)); + } + + if (token == null) { + throw new ParseError("expected '" + end + "', got EOF"); + } + rdr.next(); + + return lst; + } + + public static MalVal read_hash_map(Reader rdr) + throws MalContinue, ParseError { + MalList lst = (MalList)read_list(rdr, new MalList(), '{', '}'); + return new MalHashMap(lst); + } + + public static MalVal read_form(Reader rdr) + throws MalContinue, ParseError { + String token = rdr.peek(); + if (token == null) { throw new MalContinue(); } + MalVal form; + + switch (token.charAt(0)) { + case '\'': rdr.next(); + return new MalList(new MalSymbol("quote"), + read_form(rdr)); + case '`': rdr.next(); + return new MalList(new MalSymbol("quasiquote"), + read_form(rdr)); + case '~': + if (token.equals("~")) { + rdr.next(); + return new MalList(new MalSymbol("unquote"), + read_form(rdr)); + } else { + rdr.next(); + return new MalList(new MalSymbol("splice-unquote"), + read_form(rdr)); + } + case '^': rdr.next(); + MalVal meta = read_form(rdr); + return new MalList(new MalSymbol("with-meta"), + read_form(rdr), + meta); + case '@': rdr.next(); + return new MalList(new MalSymbol("deref"), + read_form(rdr)); + case '(': form = read_list(rdr, new MalList(), '(' , ')'); break; + case ')': throw new ParseError("unexpected ')'"); + case '[': form = read_list(rdr, new MalVector(), '[' , ']'); break; + case ']': throw new ParseError("unexpected ']'"); + case '{': form = read_hash_map(rdr); break; + case '}': throw new ParseError("unexpected '}'"); + default: form = read_atom(rdr); + } + return form; + } + + public static MalVal read_str(String str) + throws MalContinue, ParseError { + return read_form(new Reader(tokenize(str))); + } +} diff --git a/java/src/main/java/mal/readline.java b/java/src/main/java/mal/readline.java new file mode 100644 index 0000000..1705f39 --- /dev/null +++ b/java/src/main/java/mal/readline.java @@ -0,0 +1,101 @@ +package mal; + +import java.io.IOException; +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.BufferedWriter; +import java.io.FileWriter; + +import java.io.File; +import com.google.common.io.Files; +import java.nio.charset.StandardCharsets; +import java.util.List; + +import com.sun.jna.Library; +import com.sun.jna.Native; +import com.sun.jna.Platform; + +class readline { + public enum Mode { JNA, JAVA } + static Mode mode = Mode.JNA; + + static String HISTORY_FILE = "/home/joelm/.mal-history"; + static Boolean historyLoaded = false; + + public static class EOFException extends Exception { + } + + public interface RLLibrary extends Library { + // Select a library to use. + // WARNING: GNU readline is GPL. + + // GNU readline (GPL) + RLLibrary INSTANCE = (RLLibrary) + Native.loadLibrary("readline", RLLibrary.class); + // Libedit (BSD) +// RLLibrary INSTANCE = (RLLibrary) +// Native.loadLibrary("edit", RLLibrary.class); + + String readline(String prompt); + void add_history(String line); + } + + public static void loadHistory(String filename) { + File file = new File(filename); + try { + List<String> lines = Files.readLines(file, + StandardCharsets.UTF_8); + for (String line : lines) { + RLLibrary.INSTANCE.add_history(line); + } + } catch (IOException e) { + // ignore + } + } + + public static void appendHistory(String filename, String line) { + try { + BufferedWriter w; + w = new BufferedWriter(new FileWriter(filename, true)); + w.append(line + "\n"); + w.close(); + } catch (IOException e) { + // ignore + } + } + + public static String jna_readline(String prompt) + throws EOFException, IOException { + if (!historyLoaded) { + loadHistory(HISTORY_FILE); + } + String line = RLLibrary.INSTANCE.readline(prompt); + if (line == null) { + throw new EOFException(); + } + RLLibrary.INSTANCE.add_history(line); + appendHistory(HISTORY_FILE, line); + return line; + } + + // Just java readline (no history, or line editing) + public static String java_readline(String prompt) + throws EOFException, IOException { + System.out.print(prompt); + BufferedReader buffer=new BufferedReader(new InputStreamReader(System.in)); + String line=buffer.readLine(); + if (line == null) { + throw new EOFException(); + } + return line; + } + + public static String readline(String prompt) + throws EOFException, IOException { + if (mode == Mode.JNA) { + return jna_readline(prompt); + } else { + return java_readline(prompt); + } + } +} diff --git a/java/src/main/java/mal/step0_repl.java b/java/src/main/java/mal/step0_repl.java new file mode 100644 index 0000000..9095aca --- /dev/null +++ b/java/src/main/java/mal/step0_repl.java @@ -0,0 +1,48 @@ +package mal; + +import java.io.IOException; + +import mal.readline; + +public class step0_repl { + // read + public static String READ(String str) { + return str; + } + + // eval + public static String EVAL(String ast, String env) { + return ast; + } + + // print + public static String PRINT(String exp) { + return exp; + } + + // REPL + public static String RE(String env, String str) { + return EVAL(READ(str), env); + } + + public static void main(String[] args) { + String prompt = "user> "; + + if (args[0].equals("--raw")) { + readline.mode = readline.Mode.JAVA; + } + while (true) { + String line; + try { + line = readline.readline(prompt); + if (line == null) { continue; } + } catch (readline.EOFException e) { + break; + } catch (IOException e) { + System.out.println("IOException: " + e.getMessage()); + break; + } + System.out.println(PRINT(RE(null, line))); + } + } +} diff --git a/java/src/main/java/mal/step1_read_print.java b/java/src/main/java/mal/step1_read_print.java new file mode 100644 index 0000000..447afc5 --- /dev/null +++ b/java/src/main/java/mal/step1_read_print.java @@ -0,0 +1,60 @@ +package mal; + +import java.io.IOException; + +import mal.types.*; +import mal.readline; +import mal.reader; + +public class step1_read_print { + // read + public static MalVal READ(String str) throws MalThrowable { + return reader.read_str(str); + } + + // eval + public static MalVal EVAL(MalVal ast, String env) { + return ast; + } + + // print + public static String PRINT(MalVal exp) { + return types._pr_str(exp, true); + } + + // REPL + public static MalVal RE(String env, String str) throws MalThrowable { + return EVAL(READ(str), env); + } + + public static void main(String[] args) throws MalThrowable { + String prompt = "user> "; + + if (args[0].equals("--raw")) { + readline.mode = readline.Mode.JAVA; + } + while (true) { + String line; + try { + line = readline.readline(prompt); + if (line == null) { continue; } + } catch (readline.EOFException e) { + break; + } catch (IOException e) { + System.out.println("IOException: " + e.getMessage()); + break; + } + try { + System.out.println(PRINT(RE(null, line))); + } catch (MalContinue e) { + continue; + } catch (MalError e) { + System.out.println("Error: " + e.getMessage()); + continue; + } catch (reader.ParseError e) { + System.out.println(e.getMessage()); + continue; + } + } + } +} diff --git a/java/src/main/java/mal/step2_eval.java b/java/src/main/java/mal/step2_eval.java new file mode 100644 index 0000000..e1b30a9 --- /dev/null +++ b/java/src/main/java/mal/step2_eval.java @@ -0,0 +1,140 @@ +package mal; + +import java.io.IOException; + +import java.util.List; +import java.util.Map; +import java.util.HashMap; +import java.util.Iterator; +import mal.types.*; +import mal.readline; +import mal.reader; + +public class step2_eval { + // read + public static MalVal READ(String str) throws MalThrowable { + return reader.read_str(str); + } + + // eval + public static MalVal eval_ast(MalVal ast, HashMap env) throws MalThrowable { + if (ast instanceof MalSymbol) { + MalSymbol sym = (MalSymbol)ast; + return (MalVal)env.get(sym.getName()); + } else if (ast instanceof MalList) { + MalList old_lst = (MalList)ast; + MalList new_lst = types._list_Q(ast) ? new MalList() + : (MalList)new MalVector(); + for (MalVal mv : (List<MalVal>)old_lst.value) { + new_lst.conj_BANG(EVAL(mv, env)); + } + return new_lst; + } else if (ast instanceof MalHashMap) { + MalHashMap new_hm = new MalHashMap(); + Iterator it = ((MalHashMap)ast).value.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry entry = (Map.Entry)it.next(); + new_hm.value.put(entry.getKey(), EVAL((MalVal)entry.getValue(), env)); + } + return new_hm; + } else { + return ast; + } + } + + public static MalVal EVAL(MalVal orig_ast, HashMap env) throws MalThrowable { + MalVal a0; + //System.out.println("EVAL: " + types._pr_str(orig_ast, true)); + if (!(types._list_Q(orig_ast))) { + return eval_ast(orig_ast, env); + } + + // apply list + MalList ast = (MalList)orig_ast; + if (ast.size() == 0) { return ast; } + a0 = ast.nth(0); + if (!(a0 instanceof MalSymbol)) { + throw new MalError("attempt to apply on non-symbol '" + + types._pr_str(a0,true) + "'"); + } + MalVal args = eval_ast(types._rest(ast), env); + MalSymbol fsym = (MalSymbol)a0; + ILambda f = (ILambda)env.get(fsym.getName()); + return f.apply((MalList)args); + } + + // print + public static String PRINT(MalVal exp) { + return types._pr_str(exp, true); + } + + // REPL + public static MalVal RE(HashMap env, String str) throws MalThrowable { + return EVAL(READ(str), env); + } + + static interface ILambda { + public MalVal apply(MalList args); + } + static class plus implements ILambda { + public MalVal apply(MalList args) { + return ((MalInteger)args.nth(0)).add( + ((MalInteger)args.nth(1))); + } + } + static class minus implements ILambda { + public MalVal apply(MalList args) { + return ((MalInteger)args.nth(0)).subtract( + ((MalInteger)args.nth(1))); + } + } + static class multiply implements ILambda { + public MalVal apply(MalList args) { + return ((MalInteger)args.nth(0)).multiply( + ((MalInteger)args.nth(1))); + } + } + static class divide implements ILambda { + public MalVal apply(MalList args) { + return ((MalInteger)args.nth(0)).divide( + ((MalInteger)args.nth(1))); + } + } + + public static void main(String[] args) throws MalThrowable { + String prompt = "user> "; + + HashMap repl_env = new HashMap(); + repl_env.put("+", new plus()); + repl_env.put("-", new minus()); + repl_env.put("*", new multiply()); + repl_env.put("/", new divide()); + + if (args[0].equals("--raw")) { + readline.mode = readline.Mode.JAVA; + } + while (true) { + String line; + try { + line = readline.readline(prompt); + if (line == null) { continue; } + } catch (readline.EOFException e) { + break; + } catch (IOException e) { + System.out.println("IOException: " + e.getMessage()); + break; + } + try { + System.out.println(PRINT(RE(repl_env, line))); + } catch (MalContinue e) { + continue; + } catch (MalError e) { + System.out.println("Error: " + e.getMessage()); + continue; + } catch (reader.ParseError e) { + System.out.println(e.getMessage()); + continue; + } + } + } +} diff --git a/java/src/main/java/mal/step3_env.java b/java/src/main/java/mal/step3_env.java new file mode 100644 index 0000000..867dba1 --- /dev/null +++ b/java/src/main/java/mal/step3_env.java @@ -0,0 +1,137 @@ +package mal; + +import java.io.IOException; + +import java.util.List; +import java.util.Map; +import java.util.HashMap; +import java.util.Iterator; +import mal.types.*; +import mal.readline; +import mal.reader; + +public class step3_env { + // read + public static MalVal READ(String str) throws MalThrowable { + return reader.read_str(str); + } + + // eval + public static MalVal eval_ast(MalVal ast, Env env) throws MalThrowable { + if (ast instanceof MalSymbol) { + MalSymbol sym = (MalSymbol)ast; + return env.get(sym.getName()); + } else if (ast instanceof MalList) { + MalList old_lst = (MalList)ast; + MalList new_lst = types._list_Q(ast) ? new MalList() + : (MalList)new MalVector(); + for (MalVal mv : (List<MalVal>)old_lst.value) { + new_lst.conj_BANG(EVAL(mv, env)); + } + return new_lst; + } else if (ast instanceof MalHashMap) { + MalHashMap new_hm = new MalHashMap(); + Iterator it = ((MalHashMap)ast).value.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry entry = (Map.Entry)it.next(); + new_hm.value.put(entry.getKey(), EVAL((MalVal)entry.getValue(), env)); + } + return new_hm; + } else { + return ast; + } + } + + public static MalVal EVAL(MalVal orig_ast, Env env) throws MalThrowable { + MalVal a0, a1,a2, res; + //System.out.println("EVAL: " + types._pr_str(orig_ast, true)); + if (!(types._list_Q(orig_ast))) { + return eval_ast(orig_ast, env); + } + + // apply list + MalList ast = (MalList)orig_ast; + if (ast.size() == 0) { return ast; } + a0 = ast.nth(0); + if (!(a0 instanceof MalSymbol)) { + throw new MalError("attempt to apply on non-symbol '" + + types._pr_str(a0,true) + "'"); + } + + switch (((MalSymbol)a0).getName()) { + case "def!": + a1 = ast.nth(1); + a2 = ast.nth(2); + res = EVAL(a2, env); + env.set(((MalSymbol)a1).getName(), res); + return res; + case "let*": + a1 = ast.nth(1); + a2 = ast.nth(2); + MalSymbol key; + MalVal val; + Env let_env = new Env(env); + for(int i=0; i<((MalList)a1).size(); i+=2) { + key = (MalSymbol)((MalList)a1).nth(i); + val = ((MalList)a1).nth(i+1); + let_env.set(key.getName(), EVAL(val, let_env)); + } + return EVAL(a2, let_env); + default: + MalVal args = eval_ast(types._rest(ast), env); + MalSymbol fsym = (MalSymbol)a0; + ILambda f = (ILambda)env.get(fsym.getName()); + return f.apply((MalList)args); + } + } + + // print + public static String PRINT(MalVal exp) { + return types._pr_str(exp, true); + } + + // REPL + public static MalVal RE(Env env, String str) throws MalThrowable { + return EVAL(READ(str), env); + } + public static Env _ref(Env env, String name, MalVal mv) { + return env.set(name, mv); + } + + public static void main(String[] args) throws MalThrowable { + String prompt = "user> "; + + Env repl_env = new Env(null); + _ref(repl_env, "+", types.add); + _ref(repl_env, "-", types.subtract); + _ref(repl_env, "*", types.multiply); + _ref(repl_env, "/", types.divide); + + if (args[0].equals("--raw")) { + readline.mode = readline.Mode.JAVA; + } + while (true) { + String line; + try { + line = readline.readline(prompt); + if (line == null) { continue; } + } catch (readline.EOFException e) { + break; + } catch (IOException e) { + System.out.println("IOException: " + e.getMessage()); + break; + } + try { + System.out.println(PRINT(RE(repl_env, line))); + } catch (MalContinue e) { + continue; + } catch (MalError e) { + System.out.println("Error: " + e.getMessage()); + continue; + } catch (reader.ParseError e) { + System.out.println(e.getMessage()); + continue; + } + } + } +} diff --git a/java/src/main/java/mal/step4_if_fn_do.java b/java/src/main/java/mal/step4_if_fn_do.java new file mode 100644 index 0000000..7501b50 --- /dev/null +++ b/java/src/main/java/mal/step4_if_fn_do.java @@ -0,0 +1,163 @@ +package mal; + +import java.io.IOException; + +import java.util.List; +import java.util.Map; +import java.util.HashMap; +import java.util.Iterator; +import mal.types.*; +import mal.readline; +import mal.reader; + +public class step4_if_fn_do { + // read + public static MalVal READ(String str) throws MalThrowable { + return reader.read_str(str); + } + + // eval + public static MalVal eval_ast(MalVal ast, Env env) throws MalThrowable { + if (ast instanceof MalSymbol) { + MalSymbol sym = (MalSymbol)ast; + return env.get(sym.getName()); + } else if (ast instanceof MalList) { + MalList old_lst = (MalList)ast; + MalList new_lst = types._list_Q(ast) ? new MalList() + : (MalList)new MalVector(); + for (MalVal mv : (List<MalVal>)old_lst.value) { + new_lst.conj_BANG(EVAL(mv, env)); + } + return new_lst; + } else if (ast instanceof MalHashMap) { + MalHashMap new_hm = new MalHashMap(); + Iterator it = ((MalHashMap)ast).value.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry entry = (Map.Entry)it.next(); + new_hm.value.put(entry.getKey(), EVAL((MalVal)entry.getValue(), env)); + } + return new_hm; + } else { + return ast; + } + } + + public static MalVal EVAL(MalVal orig_ast, Env env) throws MalThrowable { + MalVal a0, a1,a2, a3, res; + MalList el; + //System.out.println("EVAL: " + types._pr_str(orig_ast, true)); + if (!(types._list_Q(orig_ast))) { + return eval_ast(orig_ast, env); + } + + // apply list + MalList ast = (MalList)orig_ast; + if (ast.size() == 0) { return ast; } + a0 = ast.nth(0); + String a0sym = a0 instanceof MalSymbol ? ((MalSymbol)a0).getName() + : "__<*fn*>__"; + switch (a0sym) { + case "def!": + a1 = ast.nth(1); + a2 = ast.nth(2); + res = EVAL(a2, env); + env.set(((MalSymbol)a1).getName(), res); + return res; + case "let*": + a1 = ast.nth(1); + a2 = ast.nth(2); + MalSymbol key; + MalVal val; + Env let_env = new Env(env); + for(int i=0; i<((MalList)a1).size(); i+=2) { + key = (MalSymbol)((MalList)a1).nth(i); + val = ((MalList)a1).nth(i+1); + let_env.set(key.getName(), EVAL(val, let_env)); + } + return EVAL(a2, let_env); + case "do": + el = (MalList)eval_ast(types._rest(ast), env); + return el.nth(el.size()-1); + case "if": + a1 = ast.nth(1); + MalVal cond = EVAL(a1, env); + if (cond == types.Nil || cond == types.False) { + // eval false slot form + if (ast.size() > 3) { + a3 = ast.nth(3); + return EVAL(a3, env); + } else { + return types.Nil; + } + } else { + // eval true slot form + a2 = ast.nth(2); + return EVAL(a2, env); + } + case "fn*": + final MalList a1f = (MalList)ast.nth(1); + final MalVal a2f = ast.nth(2); + final Env cur_env = env; + return new MalFunction () { + public MalVal apply(MalList args) throws MalThrowable { + return EVAL(a2f, new Env(cur_env, a1f, args)); + } + }; + default: + el = (MalList)eval_ast(ast, env); + MalFunction f = (MalFunction)el.nth(0); + return f.apply(types._rest(el)); + } + } + + // print + public static String PRINT(MalVal exp) { + return types._pr_str(exp, true); + } + + // REPL + public static MalVal RE(Env env, String str) throws MalThrowable { + return EVAL(READ(str), env); + } + public static Env _ref(Env env, String name, MalVal mv) { + return env.set(name, mv); + } + + public static void main(String[] args) throws MalThrowable { + String prompt = "user> "; + + Env repl_env = new Env(null); + for (String key : types.types_ns.keySet()) { + _ref(repl_env, key, types.types_ns.get(key)); + } + + RE(repl_env, "(def! not (fn* (a) (if a false true)))"); + + if (args[0].equals("--raw")) { + readline.mode = readline.Mode.JAVA; + } + while (true) { + String line; + try { + line = readline.readline(prompt); + if (line == null) { continue; } + } catch (readline.EOFException e) { + break; + } catch (IOException e) { + System.out.println("IOException: " + e.getMessage()); + break; + } + try { + System.out.println(PRINT(RE(repl_env, line))); + } catch (MalContinue e) { + continue; + } catch (reader.ParseError e) { + System.out.println(e.getMessage()); + continue; + } catch (MalThrowable t) { + System.out.println("Error: " + t.getMessage()); + continue; + } + } + } +} diff --git a/java/src/main/java/mal/step5_tco.java b/java/src/main/java/mal/step5_tco.java new file mode 100644 index 0000000..41b295b --- /dev/null +++ b/java/src/main/java/mal/step5_tco.java @@ -0,0 +1,174 @@ +package mal; + +import java.io.IOException; + +import java.util.List; +import java.util.Map; +import java.util.HashMap; +import java.util.Iterator; +import mal.types.*; +import mal.readline; +import mal.reader; + +public class step5_tco { + // read + public static MalVal READ(String str) throws MalThrowable { + return reader.read_str(str); + } + + // eval + public static MalVal eval_ast(MalVal ast, Env env) throws MalThrowable { + if (ast instanceof MalSymbol) { + MalSymbol sym = (MalSymbol)ast; + return env.get(sym.getName()); + } else if (ast instanceof MalList) { + MalList old_lst = (MalList)ast; + MalList new_lst = types._list_Q(ast) ? new MalList() + : (MalList)new MalVector(); + for (MalVal mv : (List<MalVal>)old_lst.value) { + new_lst.conj_BANG(EVAL(mv, env)); + } + return new_lst; + } else if (ast instanceof MalHashMap) { + MalHashMap new_hm = new MalHashMap(); + Iterator it = ((MalHashMap)ast).value.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry entry = (Map.Entry)it.next(); + new_hm.value.put(entry.getKey(), EVAL((MalVal)entry.getValue(), env)); + } + return new_hm; + } else { + return ast; + } + } + + public static MalVal EVAL(MalVal orig_ast, Env env) throws MalThrowable { + MalVal a1,a2, a3, res; + MalList el; + + while (true) { + + //System.out.println("EVAL: " + types._pr_str(orig_ast, true)); + if (!(types._list_Q(orig_ast))) { + return eval_ast(orig_ast, env); + } + + // apply list + MalList ast = (MalList)orig_ast; + if (ast.size() == 0) { return ast; } + MalVal a0 = ast.nth(0); + String a0sym = a0 instanceof MalSymbol ? ((MalSymbol)a0).getName() + : "__<*fn*>__"; + switch (a0sym) { + case "def!": + a1 = ast.nth(1); + a2 = ast.nth(2); + res = EVAL(a2, env); + env.set(((MalSymbol)a1).getName(), res); + return res; + case "let*": + a1 = ast.nth(1); + a2 = ast.nth(2); + MalSymbol key; + MalVal val; + Env let_env = new Env(env); + for(int i=0; i<((MalList)a1).size(); i+=2) { + key = (MalSymbol)((MalList)a1).nth(i); + val = ((MalList)a1).nth(i+1); + let_env.set(key.getName(), EVAL(val, let_env)); + } + return EVAL(a2, let_env); + case "do": + eval_ast(ast.slice(1, ast.size()-1), env); + orig_ast = ast.nth(ast.size()-1); + break; + case "if": + a1 = ast.nth(1); + MalVal cond = EVAL(a1, env); + if (cond == types.Nil || cond == types.False) { + // eval false slot form + if (ast.size() > 3) { + orig_ast = ast.nth(3); + } else { + return types.Nil; + } + } else { + // eval true slot form + orig_ast = ast.nth(2); + } + break; + case "fn*": + final MalList a1f = (MalList)ast.nth(1); + final MalVal a2f = ast.nth(2); + final Env cur_env = env; + return new MalFunction (a2f, (mal.types.Env)env, a1f) { + public MalVal apply(MalList args) throws MalThrowable { + return EVAL(a2f, new Env(cur_env, a1f, args)); + } + }; + default: + el = (MalList)eval_ast(ast, env); + MalFunction f = (MalFunction)el.nth(0); + MalVal fnast = f.getAst(); + if (fnast != null) { + orig_ast = fnast; + env = new Env(f.getEnv(), f.getParams(), el.slice(1)); + } else { + return f.apply(types._rest(el)); + } + } + + } + } + + // print + public static String PRINT(MalVal exp) { + return types._pr_str(exp, true); + } + + // REPL + public static MalVal RE(Env env, String str) throws MalThrowable { + return EVAL(READ(str), env); + } + public static Env _ref(Env env, String name, MalVal mv) { + return env.set(name, mv); + } + + public static void main(String[] args) throws MalThrowable { + String prompt = "user> "; + + Env repl_env = new Env(null); + for (String key : types.types_ns.keySet()) { + _ref(repl_env, key, types.types_ns.get(key)); + } + + RE(repl_env, "(def! not (fn* (a) (if a false true)))"); + + if (args[0].equals("--raw")) { + readline.mode = readline.Mode.JAVA; + } + while (true) { + String line; + try { + line = readline.readline(prompt); + if (line == null) { continue; } + } catch (readline.EOFException e) { + break; + } catch (IOException e) { + System.out.println("IOException: " + e.getMessage()); + break; + } + try { + System.out.println(PRINT(RE(repl_env, line))); + } catch (MalContinue e) { + continue; + } catch (reader.ParseError e) { + System.out.println(e.getMessage()); + continue; + } catch (MalThrowable t) { + System.out.println("Error: " + t.getMessage()); + continue; + } + } + } +} diff --git a/java/src/main/java/mal/step6_file.java b/java/src/main/java/mal/step6_file.java new file mode 100644 index 0000000..95bfd1c --- /dev/null +++ b/java/src/main/java/mal/step6_file.java @@ -0,0 +1,216 @@ +package mal; + +import java.io.IOException; +import java.io.FileNotFoundException; + +import java.util.Scanner; +import java.io.File; +import java.util.List; +import java.util.Map; +import java.util.HashMap; +import java.util.Iterator; +import mal.types.*; +import mal.readline; +import mal.reader; + +public class step6_file { + // read + public static MalVal READ(String str) throws MalThrowable { + return reader.read_str(str); + } + + // eval + public static MalVal eval_ast(MalVal ast, Env env) throws MalThrowable { + if (ast instanceof MalSymbol) { + MalSymbol sym = (MalSymbol)ast; + return env.get(sym.getName()); + } else if (ast instanceof MalList) { + MalList old_lst = (MalList)ast; + MalList new_lst = types._list_Q(ast) ? new MalList() + : (MalList)new MalVector(); + for (MalVal mv : (List<MalVal>)old_lst.value) { + new_lst.conj_BANG(EVAL(mv, env)); + } + return new_lst; + } else if (ast instanceof MalHashMap) { + MalHashMap new_hm = new MalHashMap(); + Iterator it = ((MalHashMap)ast).value.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry entry = (Map.Entry)it.next(); + new_hm.value.put(entry.getKey(), EVAL((MalVal)entry.getValue(), env)); + } + return new_hm; + } else { + return ast; + } + } + + public static MalVal EVAL(MalVal orig_ast, Env env) throws MalThrowable { + MalVal a1,a2, a3, res; + MalList el; + + while (true) { + + //System.out.println("EVAL: " + types._pr_str(orig_ast, true)); + if (!(types._list_Q(orig_ast))) { + return eval_ast(orig_ast, env); + } + + // apply list + MalList ast = (MalList)orig_ast; + if (ast.size() == 0) { return ast; } + MalVal a0 = ast.nth(0); + String a0sym = a0 instanceof MalSymbol ? ((MalSymbol)a0).getName() + : "__<*fn*>__"; + switch (a0sym) { + case "def!": + a1 = ast.nth(1); + a2 = ast.nth(2); + res = EVAL(a2, env); + env.set(((MalSymbol)a1).getName(), res); + return res; + case "let*": + a1 = ast.nth(1); + a2 = ast.nth(2); + MalSymbol key; + MalVal val; + Env let_env = new Env(env); + for(int i=0; i<((MalList)a1).size(); i+=2) { + key = (MalSymbol)((MalList)a1).nth(i); + val = ((MalList)a1).nth(i+1); + let_env.set(key.getName(), EVAL(val, let_env)); + } + return EVAL(a2, let_env); + case "do": + eval_ast(ast.slice(1, ast.size()-1), env); + orig_ast = ast.nth(ast.size()-1); + break; + case "if": + a1 = ast.nth(1); + MalVal cond = EVAL(a1, env); + if (cond == types.Nil || cond == types.False) { + // eval false slot form + if (ast.size() > 3) { + orig_ast = ast.nth(3); + } else { + return types.Nil; + } + } else { + // eval true slot form + orig_ast = ast.nth(2); + } + break; + case "fn*": + final MalList a1f = (MalList)ast.nth(1); + final MalVal a2f = ast.nth(2); + final Env cur_env = env; + return new MalFunction (a2f, (mal.types.Env)env, a1f) { + public MalVal apply(MalList args) throws MalThrowable { + return EVAL(a2f, new Env(cur_env, a1f, args)); + } + }; + default: + el = (MalList)eval_ast(ast, env); + MalFunction f = (MalFunction)el.nth(0); + MalVal fnast = f.getAst(); + if (fnast != null) { + orig_ast = fnast; + env = new Env(f.getEnv(), f.getParams(), el.slice(1)); + } else { + return f.apply(types._rest(el)); + } + } + + } + } + + // print + public static String PRINT(MalVal exp) { + return types._pr_str(exp, true); + } + + // REPL + public static MalVal RE(Env env, String str) throws MalThrowable { + return EVAL(READ(str), env); + } + public static Env _ref(Env env, String name, MalVal mv) { + return env.set(name, mv); + } + public static String slurp(String fname) throws MalThrowable { + try { + return new Scanner(new File(fname)) + .useDelimiter("\\Z").next(); + } catch (FileNotFoundException e) { + throw new MalError(e.getMessage()); + } + } + + public static void main(String[] args) throws MalThrowable { + String prompt = "user> "; + + final Env repl_env = new Env(null); + for (String key : types.types_ns.keySet()) { + _ref(repl_env, key, types.types_ns.get(key)); + } + _ref(repl_env, "read-string", new MalFunction() { + public MalVal apply(MalList args) throws MalThrowable { + return reader.read_str(((MalString)args.nth(0)).getValue()); + } + }); + _ref(repl_env, "eval", new MalFunction() { + public MalVal apply(MalList args) throws MalThrowable { + return EVAL(args.nth(0), repl_env); + } + }); + _ref(repl_env, "slurp", new MalFunction() { + public MalVal apply(MalList args) throws MalThrowable { + String fname = ((MalString)args.nth(0)).getValue(); + return new MalString(slurp(fname)); + } + }); + _ref(repl_env, "slurp-do", new MalFunction() { + public MalVal apply(MalList args) throws MalThrowable { + String fname = ((MalString)args.nth(0)).getValue(); + return new MalString("(do " + slurp(fname) + ")"); + } + }); + + RE(repl_env, "(def! not (fn* (a) (if a false true)))"); + RE(repl_env, "(def! load-file (fn* (f) (eval (read-string (slurp-do f)))))"); + + Integer fileIdx = 0; + if (args.length > 0 && args[0].equals("--raw")) { + readline.mode = readline.Mode.JAVA; + fileIdx = 1; + } + if (args.length > fileIdx) { + for(Integer i=fileIdx; i<args.length; i++) { + RE(repl_env, "(load-file \"" + args[i] + "\")"); + } + return; + } + while (true) { + String line; + try { + line = readline.readline(prompt); + if (line == null) { continue; } + } catch (readline.EOFException e) { + break; + } catch (IOException e) { + System.out.println("IOException: " + e.getMessage()); + break; + } + try { + System.out.println(PRINT(RE(repl_env, line))); + } catch (MalContinue e) { + continue; + } catch (reader.ParseError e) { + System.out.println(e.getMessage()); + continue; + } catch (MalThrowable t) { + System.out.println("Error: " + t.getMessage()); + continue; + } + } + } +} diff --git a/java/src/main/java/mal/step7_quote.java b/java/src/main/java/mal/step7_quote.java new file mode 100644 index 0000000..49f395e --- /dev/null +++ b/java/src/main/java/mal/step7_quote.java @@ -0,0 +1,247 @@ +package mal; + +import java.io.IOException; +import java.io.FileNotFoundException; + +import java.util.Scanner; +import java.io.File; +import java.util.List; +import java.util.Map; +import java.util.HashMap; +import java.util.Iterator; +import mal.types.*; +import mal.readline; +import mal.reader; + +public class step7_quote { + // read + public static MalVal READ(String str) throws MalThrowable { + return reader.read_str(str); + } + + // eval + public static Boolean is_pair(MalVal x) { + return x instanceof MalList && ((MalList)x).size() > 0; + } + + public static MalVal quasiquote(MalVal ast) { + if (!is_pair(ast)) { + return new MalList(new MalSymbol("quote"), ast); + } else { + MalVal a0 = ((MalList)ast).nth(0); + if ((a0 instanceof MalSymbol) && + (((MalSymbol)a0).getName() == "unquote")) { + return ((MalList)ast).nth(1); + } else if (is_pair(a0)) { + MalVal a00 = ((MalList)a0).nth(0); + if ((a00 instanceof MalSymbol) && + (((MalSymbol)a00).getName() == "splice-unquote")) { + return new MalList(new MalSymbol("concat"), + ((MalList)a0).nth(1), + quasiquote(types._rest((MalList)ast))); + } + } + return new MalList(new MalSymbol("cons"), + quasiquote(a0), + quasiquote(types._rest((MalList)ast))); + } + } + + public static MalVal eval_ast(MalVal ast, Env env) throws MalThrowable { + if (ast instanceof MalSymbol) { + MalSymbol sym = (MalSymbol)ast; + return env.get(sym.getName()); + } else if (ast instanceof MalList) { + MalList old_lst = (MalList)ast; + MalList new_lst = types._list_Q(ast) ? new MalList() + : (MalList)new MalVector(); + for (MalVal mv : (List<MalVal>)old_lst.value) { + new_lst.conj_BANG(EVAL(mv, env)); + } + return new_lst; + } else if (ast instanceof MalHashMap) { + MalHashMap new_hm = new MalHashMap(); + Iterator it = ((MalHashMap)ast).value.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry entry = (Map.Entry)it.next(); + new_hm.value.put(entry.getKey(), EVAL((MalVal)entry.getValue(), env)); + } + return new_hm; + } else { + return ast; + } + } + + public static MalVal EVAL(MalVal orig_ast, Env env) throws MalThrowable { + MalVal a1,a2, a3, res; + MalList el; + + while (true) { + + //System.out.println("EVAL: " + types._pr_str(orig_ast, true)); + if (!(types._list_Q(orig_ast))) { + return eval_ast(orig_ast, env); + } + + // apply list + MalList ast = (MalList)orig_ast; + if (ast.size() == 0) { return ast; } + MalVal a0 = ast.nth(0); + String a0sym = a0 instanceof MalSymbol ? ((MalSymbol)a0).getName() + : "__<*fn*>__"; + switch (a0sym) { + case "def!": + a1 = ast.nth(1); + a2 = ast.nth(2); + res = EVAL(a2, env); + env.set(((MalSymbol)a1).getName(), res); + return res; + case "let*": + a1 = ast.nth(1); + a2 = ast.nth(2); + MalSymbol key; + MalVal val; + Env let_env = new Env(env); + for(int i=0; i<((MalList)a1).size(); i+=2) { + key = (MalSymbol)((MalList)a1).nth(i); + val = ((MalList)a1).nth(i+1); + let_env.set(key.getName(), EVAL(val, let_env)); + } + return EVAL(a2, let_env); + case "quote": + return ast.nth(1); + case "quasiquote": + return EVAL(quasiquote(ast.nth(1)), env); + case "do": + eval_ast(ast.slice(1, ast.size()-1), env); + orig_ast = ast.nth(ast.size()-1); + break; + case "if": + a1 = ast.nth(1); + MalVal cond = EVAL(a1, env); + if (cond == types.Nil || cond == types.False) { + // eval false slot form + if (ast.size() > 3) { + orig_ast = ast.nth(3); + } else { + return types.Nil; + } + } else { + // eval true slot form + orig_ast = ast.nth(2); + } + break; + case "fn*": + final MalList a1f = (MalList)ast.nth(1); + final MalVal a2f = ast.nth(2); + final Env cur_env = env; + return new MalFunction (a2f, (mal.types.Env)env, a1f) { + public MalVal apply(MalList args) throws MalThrowable { + return EVAL(a2f, new Env(cur_env, a1f, args)); + } + }; + default: + el = (MalList)eval_ast(ast, env); + MalFunction f = (MalFunction)el.nth(0); + MalVal fnast = f.getAst(); + if (fnast != null) { + orig_ast = fnast; + env = new Env(f.getEnv(), f.getParams(), el.slice(1)); + } else { + return f.apply(types._rest(el)); + } + } + + } + } + + // print + public static String PRINT(MalVal exp) { + return types._pr_str(exp, true); + } + + // REPL + public static MalVal RE(Env env, String str) throws MalThrowable { + return EVAL(READ(str), env); + } + public static Env _ref(Env env, String name, MalVal mv) { + return env.set(name, mv); + } + public static String slurp(String fname) throws MalThrowable { + try { + return new Scanner(new File(fname)) + .useDelimiter("\\Z").next(); + } catch (FileNotFoundException e) { + throw new MalError(e.getMessage()); + } + } + + public static void main(String[] args) throws MalThrowable { + String prompt = "user> "; + + final Env repl_env = new Env(null); + for (String key : types.types_ns.keySet()) { + _ref(repl_env, key, types.types_ns.get(key)); + } + _ref(repl_env, "read-string", new MalFunction() { + public MalVal apply(MalList args) throws MalThrowable { + return reader.read_str(((MalString)args.nth(0)).getValue()); + } + }); + _ref(repl_env, "eval", new MalFunction() { + public MalVal apply(MalList args) throws MalThrowable { + return EVAL(args.nth(0), repl_env); + } + }); + _ref(repl_env, "slurp", new MalFunction() { + public MalVal apply(MalList args) throws MalThrowable { + String fname = ((MalString)args.nth(0)).getValue(); + return new MalString(slurp(fname)); + } + }); + _ref(repl_env, "slurp-do", new MalFunction() { + public MalVal apply(MalList args) throws MalThrowable { + String fname = ((MalString)args.nth(0)).getValue(); + return new MalString("(do " + slurp(fname) + ")"); + } + }); + + RE(repl_env, "(def! not (fn* (a) (if a false true)))"); + RE(repl_env, "(def! load-file (fn* (f) (eval (read-string (slurp-do f)))))"); + + Integer fileIdx = 0; + if (args.length > 0 && args[0].equals("--raw")) { + readline.mode = readline.Mode.JAVA; + fileIdx = 1; + } + if (args.length > fileIdx) { + for(Integer i=fileIdx; i<args.length; i++) { + RE(repl_env, "(load-file \"" + args[i] + "\")"); + } + return; + } + while (true) { + String line; + try { + line = readline.readline(prompt); + if (line == null) { continue; } + } catch (readline.EOFException e) { + break; + } catch (IOException e) { + System.out.println("IOException: " + e.getMessage()); + break; + } + try { + System.out.println(PRINT(RE(repl_env, line))); + } catch (MalContinue e) { + continue; + } catch (reader.ParseError e) { + System.out.println(e.getMessage()); + continue; + } catch (MalThrowable t) { + System.out.println("Error: " + t.getMessage()); + continue; + } + } + } +} diff --git a/java/src/main/java/mal/step8_macros.java b/java/src/main/java/mal/step8_macros.java new file mode 100644 index 0000000..c632987 --- /dev/null +++ b/java/src/main/java/mal/step8_macros.java @@ -0,0 +1,285 @@ +package mal; + +import java.io.IOException; +import java.io.FileNotFoundException; + +import java.util.Scanner; +import java.io.File; +import java.util.List; +import java.util.Map; +import java.util.HashMap; +import java.util.Iterator; +import mal.types.*; +import mal.readline; +import mal.reader; + +public class step8_macros { + // read + public static MalVal READ(String str) throws MalThrowable { + return reader.read_str(str); + } + + // eval + public static Boolean is_pair(MalVal x) { + return x instanceof MalList && ((MalList)x).size() > 0; + } + + public static MalVal quasiquote(MalVal ast) { + if (!is_pair(ast)) { + return new MalList(new MalSymbol("quote"), ast); + } else { + MalVal a0 = ((MalList)ast).nth(0); + if ((a0 instanceof MalSymbol) && + (((MalSymbol)a0).getName() == "unquote")) { + return ((MalList)ast).nth(1); + } else if (is_pair(a0)) { + MalVal a00 = ((MalList)a0).nth(0); + if ((a00 instanceof MalSymbol) && + (((MalSymbol)a00).getName() == "splice-unquote")) { + return new MalList(new MalSymbol("concat"), + ((MalList)a0).nth(1), + quasiquote(types._rest((MalList)ast))); + } + } + return new MalList(new MalSymbol("cons"), + quasiquote(a0), + quasiquote(types._rest((MalList)ast))); + } + } + + public static Boolean is_macro_call(MalVal ast, Env env) + throws MalThrowable { + if (ast instanceof MalList) { + MalVal a0 = ((MalList)ast).nth(0); + if (a0 instanceof MalSymbol && + env.find(((MalSymbol)a0).getName()) != null) { + MalVal mac = env.get(((MalSymbol)a0).getName()); + if (mac instanceof MalFunction && + ((MalFunction)mac).isMacro()) { + return true; + } + } + } + return false; + } + + public static MalVal macroexpand(MalVal ast, Env env) + throws MalThrowable { + while (is_macro_call(ast, env)) { + MalSymbol a0 = (MalSymbol)((MalList)ast).nth(0); + MalFunction mac = (MalFunction) env.get(a0.getName()); + ast = mac.apply(types._rest((MalList)ast)); + } + return ast; + } + + public static MalVal eval_ast(MalVal ast, Env env) throws MalThrowable { + if (ast instanceof MalSymbol) { + MalSymbol sym = (MalSymbol)ast; + return env.get(sym.getName()); + } else if (ast instanceof MalList) { + MalList old_lst = (MalList)ast; + MalList new_lst = types._list_Q(ast) ? new MalList() + : (MalList)new MalVector(); + for (MalVal mv : (List<MalVal>)old_lst.value) { + new_lst.conj_BANG(EVAL(mv, env)); + } + return new_lst; + } else if (ast instanceof MalHashMap) { + MalHashMap new_hm = new MalHashMap(); + Iterator it = ((MalHashMap)ast).value.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry entry = (Map.Entry)it.next(); + new_hm.value.put(entry.getKey(), EVAL((MalVal)entry.getValue(), env)); + } + return new_hm; + } else { + return ast; + } + } + + public static MalVal EVAL(MalVal orig_ast, Env env) throws MalThrowable { + MalVal a1,a2, a3, res; + MalList el; + + while (true) { + + //System.out.println("EVAL: " + types._pr_str(orig_ast, true)); + if (!(types._list_Q(orig_ast))) { + return eval_ast(orig_ast, env); + } + + // apply list + MalVal expanded = macroexpand(orig_ast, env); + if (!types._list_Q(expanded)) { return expanded; } + MalList ast = (MalList) expanded; + if (ast.size() == 0) { return ast; } + MalVal a0 = ast.nth(0); + String a0sym = a0 instanceof MalSymbol ? ((MalSymbol)a0).getName() + : "__<*fn*>__"; + switch (a0sym) { + case "def!": + a1 = ast.nth(1); + a2 = ast.nth(2); + res = EVAL(a2, env); + env.set(((MalSymbol)a1).getName(), res); + return res; + case "let*": + a1 = ast.nth(1); + a2 = ast.nth(2); + MalSymbol key; + MalVal val; + Env let_env = new Env(env); + for(int i=0; i<((MalList)a1).size(); i+=2) { + key = (MalSymbol)((MalList)a1).nth(i); + val = ((MalList)a1).nth(i+1); + let_env.set(key.getName(), EVAL(val, let_env)); + } + return EVAL(a2, let_env); + case "quote": + return ast.nth(1); + case "quasiquote": + return EVAL(quasiquote(ast.nth(1)), env); + case "defmacro!": + a1 = ast.nth(1); + a2 = ast.nth(2); + res = EVAL(a2, env); + ((MalFunction)res).setMacro(); + env.set(((MalSymbol)a1).getName(), res); + return res; + case "macroexpand": + a1 = ast.nth(1); + return macroexpand(a1, env); + case "do": + eval_ast(ast.slice(1, ast.size()-1), env); + orig_ast = ast.nth(ast.size()-1); + break; + case "if": + a1 = ast.nth(1); + MalVal cond = EVAL(a1, env); + if (cond == types.Nil || cond == types.False) { + // eval false slot form + if (ast.size() > 3) { + orig_ast = ast.nth(3); + } else { + return types.Nil; + } + } else { + // eval true slot form + orig_ast = ast.nth(2); + } + break; + case "fn*": + final MalList a1f = (MalList)ast.nth(1); + final MalVal a2f = ast.nth(2); + final Env cur_env = env; + return new MalFunction (a2f, (mal.types.Env)env, a1f) { + public MalVal apply(MalList args) throws MalThrowable { + return EVAL(a2f, new Env(cur_env, a1f, args)); + } + }; + default: + el = (MalList)eval_ast(ast, env); + MalFunction f = (MalFunction)el.nth(0); + MalVal fnast = f.getAst(); + if (fnast != null) { + orig_ast = fnast; + env = new Env(f.getEnv(), f.getParams(), el.slice(1)); + } else { + return f.apply(types._rest(el)); + } + } + + } + } + + // print + public static String PRINT(MalVal exp) { + return types._pr_str(exp, true); + } + + // REPL + public static MalVal RE(Env env, String str) throws MalThrowable { + return EVAL(READ(str), env); + } + public static Env _ref(Env env, String name, MalVal mv) { + return env.set(name, mv); + } + public static String slurp(String fname) throws MalThrowable { + try { + return new Scanner(new File(fname)) + .useDelimiter("\\Z").next(); + } catch (FileNotFoundException e) { + throw new MalError(e.getMessage()); + } + } + + public static void main(String[] args) throws MalThrowable { + String prompt = "user> "; + + final Env repl_env = new Env(null); + for (String key : types.types_ns.keySet()) { + _ref(repl_env, key, types.types_ns.get(key)); + } + _ref(repl_env, "read-string", new MalFunction() { + public MalVal apply(MalList args) throws MalThrowable { + return reader.read_str(((MalString)args.nth(0)).getValue()); + } + }); + _ref(repl_env, "eval", new MalFunction() { + public MalVal apply(MalList args) throws MalThrowable { + return EVAL(args.nth(0), repl_env); + } + }); + _ref(repl_env, "slurp", new MalFunction() { + public MalVal apply(MalList args) throws MalThrowable { + String fname = ((MalString)args.nth(0)).getValue(); + return new MalString(slurp(fname)); + } + }); + _ref(repl_env, "slurp-do", new MalFunction() { + public MalVal apply(MalList args) throws MalThrowable { + String fname = ((MalString)args.nth(0)).getValue(); + return new MalString("(do " + slurp(fname) + ")"); + } + }); + + RE(repl_env, "(def! not (fn* (a) (if a false true)))"); + RE(repl_env, "(def! load-file (fn* (f) (eval (read-string (slurp-do f)))))"); + + Integer fileIdx = 0; + if (args.length > 0 && args[0].equals("--raw")) { + readline.mode = readline.Mode.JAVA; + fileIdx = 1; + } + if (args.length > fileIdx) { + for(Integer i=fileIdx; i<args.length; i++) { + RE(repl_env, "(load-file \"" + args[i] + "\")"); + } + return; + } + while (true) { + String line; + try { + line = readline.readline(prompt); + if (line == null) { continue; } + } catch (readline.EOFException e) { + break; + } catch (IOException e) { + System.out.println("IOException: " + e.getMessage()); + break; + } + try { + System.out.println(PRINT(RE(repl_env, line))); + } catch (MalContinue e) { + continue; + } catch (reader.ParseError e) { + System.out.println(e.getMessage()); + continue; + } catch (MalThrowable t) { + System.out.println("Error: " + t.getMessage()); + continue; + } + } + } +} diff --git a/java/src/main/java/mal/stepA_more.java b/java/src/main/java/mal/stepA_more.java new file mode 100644 index 0000000..ff09aff --- /dev/null +++ b/java/src/main/java/mal/stepA_more.java @@ -0,0 +1,333 @@ +package mal; + +import java.io.IOException; +import java.io.FileNotFoundException; + +import java.util.Scanner; +import java.io.File; +import java.io.StringWriter; +import java.io.PrintWriter; +import java.util.List; +import java.util.Map; +import java.util.HashMap; +import java.util.Iterator; +import mal.types.*; +import mal.readline; +import mal.reader; + +public class stepA_more { + // read + public static MalVal READ(String str) throws MalThrowable { + return reader.read_str(str); + } + + // eval + public static Boolean is_pair(MalVal x) { + return x instanceof MalList && ((MalList)x).size() > 0; + } + + public static MalVal quasiquote(MalVal ast) { + if (!is_pair(ast)) { + return new MalList(new MalSymbol("quote"), ast); + } else { + MalVal a0 = ((MalList)ast).nth(0); + if ((a0 instanceof MalSymbol) && + (((MalSymbol)a0).getName() == "unquote")) { + return ((MalList)ast).nth(1); + } else if (is_pair(a0)) { + MalVal a00 = ((MalList)a0).nth(0); + if ((a00 instanceof MalSymbol) && + (((MalSymbol)a00).getName() == "splice-unquote")) { + return new MalList(new MalSymbol("concat"), + ((MalList)a0).nth(1), + quasiquote(types._rest((MalList)ast))); + } + } + return new MalList(new MalSymbol("cons"), + quasiquote(a0), + quasiquote(types._rest((MalList)ast))); + } + } + + public static Boolean is_macro_call(MalVal ast, Env env) + throws MalThrowable { + if (ast instanceof MalList) { + MalVal a0 = ((MalList)ast).nth(0); + if (a0 instanceof MalSymbol && + env.find(((MalSymbol)a0).getName()) != null) { + MalVal mac = env.get(((MalSymbol)a0).getName()); + if (mac instanceof MalFunction && + ((MalFunction)mac).isMacro()) { + return true; + } + } + } + return false; + } + + public static MalVal macroexpand(MalVal ast, Env env) + throws MalThrowable { + while (is_macro_call(ast, env)) { + MalSymbol a0 = (MalSymbol)((MalList)ast).nth(0); + MalFunction mac = (MalFunction) env.get(a0.getName()); + ast = mac.apply(types._rest((MalList)ast)); + } + return ast; + } + + public static MalVal eval_ast(MalVal ast, Env env) throws MalThrowable { + if (ast instanceof MalSymbol) { + MalSymbol sym = (MalSymbol)ast; + return env.get(sym.getName()); + } else if (ast instanceof MalList) { + MalList old_lst = (MalList)ast; + MalList new_lst = types._list_Q(ast) ? new MalList() + : (MalList)new MalVector(); + for (MalVal mv : (List<MalVal>)old_lst.value) { + new_lst.conj_BANG(EVAL(mv, env)); + } + return new_lst; + } else if (ast instanceof MalHashMap) { + MalHashMap new_hm = new MalHashMap(); + Iterator it = ((MalHashMap)ast).value.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry entry = (Map.Entry)it.next(); + new_hm.value.put(entry.getKey(), EVAL((MalVal)entry.getValue(), env)); + } + return new_hm; + } else { + return ast; + } + } + + public static MalVal EVAL(MalVal orig_ast, Env env) throws MalThrowable { + MalVal a1,a2, a3, res; + MalList el; + + while (true) { + + //System.out.println("EVAL: " + types._pr_str(orig_ast, true)); + if (!(types._list_Q(orig_ast))) { + return eval_ast(orig_ast, env); + } + + // apply list + MalVal expanded = macroexpand(orig_ast, env); + if (!types._list_Q(expanded)) { return expanded; } + MalList ast = (MalList) expanded; + if (ast.size() == 0) { return ast; } + MalVal a0 = ast.nth(0); + String a0sym = a0 instanceof MalSymbol ? ((MalSymbol)a0).getName() + : "__<*fn*>__"; + switch (a0sym) { + case "def!": + a1 = ast.nth(1); + a2 = ast.nth(2); + res = EVAL(a2, env); + env.set(((MalSymbol)a1).getName(), res); + return res; + case "let*": + a1 = ast.nth(1); + a2 = ast.nth(2); + MalSymbol key; + MalVal val; + Env let_env = new Env(env); + for(int i=0; i<((MalList)a1).size(); i+=2) { + key = (MalSymbol)((MalList)a1).nth(i); + val = ((MalList)a1).nth(i+1); + let_env.set(key.getName(), EVAL(val, let_env)); + } + return EVAL(a2, let_env); + case "quote": + return ast.nth(1); + case "quasiquote": + return EVAL(quasiquote(ast.nth(1)), env); + case "defmacro!": + a1 = ast.nth(1); + a2 = ast.nth(2); + res = EVAL(a2, env); + ((MalFunction)res).setMacro(); + env.set(((MalSymbol)a1).getName(), res); + return res; + case "macroexpand": + a1 = ast.nth(1); + return macroexpand(a1, env); + case "try*": + try { + return EVAL(ast.nth(1), env); + } catch (Throwable t) { + if (ast.size() > 2) { + MalVal exc; + a2 = ast.nth(2); + MalVal a20 = ((MalList)a2).nth(0); + if (((MalSymbol)a20).getName().equals("catch*")) { + if (t instanceof MalException) { + exc = ((MalException)t).getValue(); + } else { + StringWriter sw = new StringWriter(); + t.printStackTrace(new PrintWriter(sw)); + String tstr = sw.toString(); + exc = new MalString(t.getMessage() + ": " + tstr); + } + return EVAL(((MalList)a2).nth(2), + new Env(env, ((MalList)a2).slice(1,2), + new MalList(exc))); + } + } + throw t; + } + case "do": + eval_ast(ast.slice(1, ast.size()-1), env); + orig_ast = ast.nth(ast.size()-1); + break; + case "if": + a1 = ast.nth(1); + MalVal cond = EVAL(a1, env); + if (cond == types.Nil || cond == types.False) { + // eval false slot form + if (ast.size() > 3) { + orig_ast = ast.nth(3); + } else { + return types.Nil; + } + } else { + // eval true slot form + orig_ast = ast.nth(2); + } + break; + case "fn*": + final MalList a1f = (MalList)ast.nth(1); + final MalVal a2f = ast.nth(2); + final Env cur_env = env; + return new MalFunction (a2f, (mal.types.Env)env, a1f) { + public MalVal apply(MalList args) throws MalThrowable { + return EVAL(a2f, new Env(cur_env, a1f, args)); + } + }; + default: + el = (MalList)eval_ast(ast, env); + MalFunction f = (MalFunction)el.nth(0); + MalVal fnast = f.getAst(); + if (fnast != null) { + orig_ast = fnast; + env = new Env(f.getEnv(), f.getParams(), el.slice(1)); + } else { + return f.apply(types._rest(el)); + } + } + + } + } + + // print + public static String PRINT(MalVal exp) { + return types._pr_str(exp, true); + } + + // REPL + public static MalVal RE(Env env, String str) throws MalThrowable { + return EVAL(READ(str), env); + } + public static Env _ref(Env env, String name, MalVal mv) { + return env.set(name, mv); + } + public static String slurp(String fname) throws MalThrowable { + try { + return new Scanner(new File(fname)) + .useDelimiter("\\Z").next(); + } catch (FileNotFoundException e) { + throw new MalError(e.getMessage()); + } + } + + public static void main(String[] args) throws MalThrowable { + String prompt = "user> "; + + final Env repl_env = new Env(null); + for (String key : types.types_ns.keySet()) { + _ref(repl_env, key, types.types_ns.get(key)); + } + _ref(repl_env, "readline", new MalFunction() { + public MalVal apply(MalList args) throws MalThrowable { + String prompt = ((MalString)args.nth(0)).getValue(); + try { + return new MalString(readline.readline(prompt)); + } catch (IOException e) { + throw new MalException(new MalString(e.getMessage())); + } catch (readline.EOFException e) { + throw new MalException(new MalString(e.getMessage())); + } + } + }); + _ref(repl_env, "read-string", new MalFunction() { + public MalVal apply(MalList args) throws MalThrowable { + try { + return reader.read_str(((MalString)args.nth(0)).getValue()); + } catch (MalContinue c) { + return types.Nil; + } + } + }); + _ref(repl_env, "eval", new MalFunction() { + public MalVal apply(MalList args) throws MalThrowable { + return EVAL(args.nth(0), repl_env); + } + }); + _ref(repl_env, "slurp", new MalFunction() { + public MalVal apply(MalList args) throws MalThrowable { + String fname = ((MalString)args.nth(0)).getValue(); + return new MalString(slurp(fname)); + } + }); + _ref(repl_env, "slurp-do", new MalFunction() { + public MalVal apply(MalList args) throws MalThrowable { + String fname = ((MalString)args.nth(0)).getValue(); + return new MalString("(do " + slurp(fname) + ")"); + } + }); + + RE(repl_env, "(def! not (fn* (a) (if a false true)))"); + RE(repl_env, "(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))"); + RE(repl_env, "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))"); + + RE(repl_env, "(def! load-file (fn* (f) (eval (read-string (slurp-do f)))))"); + + Integer fileIdx = 0; + if (args.length > 0 && args[0].equals("--raw")) { + readline.mode = readline.Mode.JAVA; + fileIdx = 1; + } + if (args.length > fileIdx) { + for(Integer i=fileIdx; i<args.length; i++) { + RE(repl_env, "(load-file \"" + args[i] + "\")"); + } + return; + } + while (true) { + String line; + try { + line = readline.readline(prompt); + if (line == null) { continue; } + } catch (readline.EOFException e) { + break; + } catch (IOException e) { + System.out.println("IOException: " + e.getMessage()); + break; + } + try { + System.out.println(PRINT(RE(repl_env, line))); + } catch (MalContinue e) { + continue; + } catch (reader.ParseError e) { + System.out.println(e.getMessage()); + continue; + } catch (MalException e) { + System.out.println("Error: " + types._pr_str(e.getValue(), false)); + continue; + } catch (MalThrowable t) { + System.out.println("Error: " + t.getMessage()); + continue; + } + } + } +} diff --git a/java/src/main/java/mal/types.java b/java/src/main/java/mal/types.java new file mode 100644 index 0000000..1e9bb34 --- /dev/null +++ b/java/src/main/java/mal/types.java @@ -0,0 +1,882 @@ +package mal; + +import java.util.List; +import java.util.ArrayList; +import com.google.common.base.Joiner; +import java.util.Set; +import java.util.Map; +import java.util.HashMap; +import com.google.common.collect.ImmutableMap; +import org.apache.commons.lang3.StringEscapeUtils; + +public class types { + // + // Exceptions/Errors + // + public static class MalThrowable extends Exception { + public MalThrowable() { } + public MalThrowable(String msg) { super(msg); } + } + public static class MalError extends MalThrowable { + public MalError(String msg) { super(msg); } + } + public static class MalContinue extends MalThrowable { } + + // Thrown by throw function + public static class MalException extends MalThrowable { + MalVal value; + public MalException(MalVal value) { + this.value = value; + } + public MalException(String value) { + this.value = new MalString(value); + } + public MalVal getValue() { return value; } + } + + + // + // Mal boxed types + // + abstract public static class MalVal { + MalVal meta = Nil; + abstract public MalVal copy() throws MalThrowable; + + // Default is just to call regular toString() + public String toString(Boolean print_readably) { + return this.toString(); + } + public MalVal getMeta() { return meta; } + public void setMeta(MalVal m) { meta = m; } + } + public static class MalConstant extends MalVal { + String value; + public MalConstant(String name) { value = name; } + public MalConstant copy() throws MalThrowable { return this; } + + public String toString() { return value; } + } + static MalConstant Nil = new MalConstant("nil"); + static MalConstant True = new MalConstant("true"); + static MalConstant False = new MalConstant("false"); + + public static class MalInteger extends MalVal { + Integer value; + public MalInteger(Integer v) { value = v; } + public MalInteger copy() throws MalThrowable { return this; } + + public Integer getValue() { return value; } + @Override public String toString() { + return value.toString(); + } + public MalInteger add(MalInteger other) { + return new MalInteger(value + other.getValue()); + } + public MalInteger subtract(MalInteger other) { + return new MalInteger(value - other.getValue()); + } + public MalInteger multiply(MalInteger other) { + return new MalInteger(value * other.getValue()); + } + public MalInteger divide(MalInteger other) { + return new MalInteger(value / other.getValue()); + } + public MalConstant lt(MalInteger other) { + return (value < other.getValue()) ? True : False; + } + public MalConstant lte(MalInteger other) { + return (value <= other.getValue()) ? True : False; + } + public MalConstant gt(MalInteger other) { + return (value > other.getValue()) ? True : False; + } + public MalConstant gte(MalInteger other) { + return (value >= other.getValue()) ? True : False; + } + } + + public static class MalSymbol extends MalVal { + String value; + public MalSymbol(String v) { value = v; } + public MalSymbol copy() throws MalThrowable { return this; } + + public String getName() { return value; } + @Override public String toString() { + return value; + } + } + + public static class MalString extends MalVal { + String value; + public MalString(String v) { value = v; } + public MalString copy() throws MalThrowable { return this; } + + public String getValue() { return value; } + @Override public String toString() { + return "\"" + value + "\""; + } + public String toString(Boolean print_readably) { + if (print_readably) { + return "\"" + StringEscapeUtils.escapeJson(value) + "\""; + } else { + return value; + } + } + } + + public static class MalList extends MalVal { + String start = "(", end = ")"; + List value; + public MalList(List val) { + value = val; + } + public MalList(MalVal... mvs) { + value = new ArrayList<MalVal>(); + conj_BANG(mvs); + } + public MalList copy() throws MalThrowable { + MalList new_ml = new MalList(); + new_ml.value.addAll(value); + new_ml.meta = meta; + return new_ml; + } + + String _join(String delim, Boolean print_readably) { + ArrayList<String> strs = new ArrayList<String>(); + for (MalVal mv : (List<MalVal>)value) { + strs.add(mv.toString(print_readably)); + } + return Joiner.on(delim).join(strs); + } + @Override public String toString() { + return start + _join(" ", true) + end; + } + public String toString(Boolean print_readably) { + return start + _join(" ", print_readably) + end; + } + + public MalList conj_BANG(MalVal... mvs) { + for (MalVal mv : mvs) { + value.add(mv); + } + return this; + } + + public Integer size() { + return value.size(); + } + + public MalVal nth(Integer idx) { + return (MalVal)value.get(idx); + } + + public MalList slice(Integer start, Integer end) { + return new MalList(value.subList(start, end)); + } + public MalList slice(Integer start) { + return slice(start, value.size()); + } + } + + public static class MalVector extends MalList { + // Same implementation except for instantiation methods + public MalVector(List val) { + value = val; + start = "["; + end = "]"; + } + public MalVector(MalVal... mvs) { + super(mvs); + start = "["; + end = "]"; + } + public MalVector copy() throws MalThrowable { + MalVector new_mv = new MalVector(); + new_mv.value.addAll(value); + new_mv.meta = meta; + return new_mv; + } + + public MalVector slice(Integer start, Integer end) { + return new MalVector(value.subList(start, end)); + } + } + + public static class MalHashMap extends MalVal { + Map value; + public MalHashMap(Map val) { + value = val; + } + public MalHashMap(MalList lst) { + value = new HashMap<String, MalVal>(); + assoc_BANG(lst); + } + public MalHashMap(MalVal... mvs) { + value = new HashMap<String, MalVal>(); + assoc_BANG(mvs); + } + public MalHashMap copy() throws MalThrowable { + Map<String,MalVal> shallowCopy = new HashMap<String,MalVal>(); + shallowCopy.putAll(value); + MalHashMap new_hm = new MalHashMap(shallowCopy); + new_hm.meta = meta; + return new_hm; + } + + String _join(Boolean print_readably) { + ArrayList<String> strs = new ArrayList<String>(); + for (Map.Entry<String, MalVal> entry : + ((Map<String,MalVal>)value).entrySet()) { + if (print_readably) { + strs.add("\"" + entry.getKey().toString() + "\""); + } else { + strs.add(entry.getKey().toString()); + } + strs.add(entry.getValue().toString(print_readably)); + } + return Joiner.on(" ").join(strs); + } + @Override public String toString() { + return "{" + _join(true) + "}"; + } + public String toString(Boolean print_readably) { + return "{" + _join(print_readably) + "}"; + } + + public Set _entries() { + return value.entrySet(); + } + + public MalHashMap assoc_BANG(MalVal... mvs) { + for (Integer i=0; i<mvs.length; i+=2) { + value.put(((MalSymbol)mvs[i]).getName(), + mvs[i+1]); + } + return this; + } + + public MalHashMap assoc_BANG(MalList lst) { + for (Integer i=0; i<lst.value.size(); i+=2) { + value.put(((MalString)lst.nth(i)).getValue(), + lst.nth(i+1)); + } + return this; + } + + public MalHashMap dissoc_BANG(MalList lst) { + for (Integer i=0; i<lst.value.size(); i++) { + value.remove(((MalString)lst.nth(i)).getValue()); + } + return this; + } + + public Integer size() { + return value.size(); + } + } + + public static class MalAtom extends MalVal { + MalVal value; + public MalAtom(MalVal value) { this.value = value; } + public MalAtom copy() throws MalThrowable { return new MalAtom(value); } + @Override public String toString() { + return "(atom " + _pr_str(value, true) + ")"; + } + public String toString(Boolean print_readably) { + return "(atom " + _pr_str(value, print_readably) + ")"; + } + } + + public static interface ILambda { + public MalVal apply(MalList args) throws MalThrowable; + } + + public static abstract class MalFunction extends MalVal + implements ILambda, java.lang.Cloneable { + public MalVal ast = null; + public Env env = null; + public MalList params = null; + public Boolean macro = false; + public MalFunction() { } + public MalFunction(MalVal ast, Env env, MalList params) { + this.ast = ast; + this.env = env; + this.params = params; + } + public MalFunction copy() throws MalThrowable { + try { + // WARNING: clone() is broken: + // http://www.artima.com/intv/bloch13.html + // However, this doesn't work: + // MalFunction new_mf = this.getClass().newInstance(); + // So for now it's clone. + MalFunction new_mf = (MalFunction) this.clone(); + new_mf.ast = ast; + new_mf.env = env; + new_mf.params = params; + new_mf.macro = macro; + return new_mf; + } catch (Throwable t) { + // not much we can do + t.printStackTrace(); + throw new MalError("Could not copy MalFunction: " + this); + } + } + + public MalVal getAst() { return ast; } + public Env getEnv() { return env; } + public MalList getParams() { return params; } + public Boolean isMacro() { return macro; } + public void setMacro() { macro = true; } + } + + + // + // General functions + // + public static String _pr_str(MalVal mv, Boolean print_readably) { + return mv.toString(print_readably); + } + + public static String _pr_str_args(MalList args, String sep, Boolean print_readably) { + return args._join(sep, print_readably); + } + + static MalFunction pr_str = new MalFunction() { + public MalVal apply(MalList args) throws MalThrowable { + return new MalString(_pr_str_args(args, " ", true)); + } + }; + + static MalFunction str = new MalFunction() { + public MalVal apply(MalList args) throws MalThrowable { + return new MalString(_pr_str_args(args, "", false)); + } + }; + + static MalFunction prn = new MalFunction() { + public MalVal apply(MalList args) throws MalThrowable { + System.out.println(_pr_str_args(args, " ", true)); + return Nil; + } + }; + + static MalFunction println = new MalFunction() { + public MalVal apply(MalList args) throws MalThrowable { + System.out.println(_pr_str_args(args, " ", false)); + return Nil; + } + }; + + + static MalFunction meta = new MalFunction() { + public MalVal apply(MalList args) throws MalThrowable { + return args.nth(0).getMeta(); + } + }; + + static MalFunction with_meta = new MalFunction() { + public MalVal apply(MalList args) throws MalThrowable { + MalVal new_mv = ((MalVal)args.nth(0)).copy(); + new_mv.setMeta(args.nth(1)); + return new_mv; + } + }; + + + public static Boolean _equal_Q(MalVal a, MalVal b) { + Class ota = a.getClass(), otb = b.getClass(); + if (!((ota == otb) || + (a instanceof MalList && b instanceof MalList))) { + return false; + } else { + if (a instanceof MalInteger) { + return ((MalInteger)a).getValue() == + ((MalInteger)b).getValue(); + } else if (a instanceof MalSymbol) { + return ((MalSymbol)a).getName().equals( + ((MalSymbol)b).getName()); + } else if (a instanceof MalString) { + return ((MalString)a).getValue().equals( + ((MalString)b).getValue()); + } else if (a instanceof MalList) { + if (((MalList)a).size() != ((MalList)b).size()) { + return false; + } + for (Integer i=0; i<((MalList)a).size(); i++) { + if (! _equal_Q(((MalList)a).nth(i), + ((MalList)b).nth(i))) { + return false; + } + } + return true; + } else { + return a == b; + } + } + } + + static MalFunction equal_Q = new MalFunction() { + public MalVal apply(MalList args) throws MalThrowable { + return _equal_Q(args.nth(0), args.nth(1)) ? True : False; + } + }; + + + // + // Constants operations + // + static MalFunction symbol_Q = new MalFunction() { + public MalVal apply(MalList args) throws MalThrowable { + return args.nth(0) instanceof MalSymbol ? True : False; + } + }; + + static MalFunction nil_Q = new MalFunction() { + public MalVal apply(MalList args) throws MalThrowable { + return args.nth(0) == Nil ? True : False; + } + }; + + static MalFunction true_Q = new MalFunction() { + public MalVal apply(MalList args) throws MalThrowable { + return args.nth(0) == True ? True : False; + } + }; + + static MalFunction false_Q = new MalFunction() { + public MalVal apply(MalList args) throws MalThrowable { + return args.nth(0) == False ? True : False; + } + }; + + + // + // Number operations + // + static MalFunction add = new MalFunction() { + public MalVal apply(MalList a) throws MalThrowable { + return ((MalInteger)a.nth(0)).add((MalInteger)a.nth(1)); + } + }; + static MalFunction subtract = new MalFunction() { + public MalVal apply(MalList a) throws MalThrowable { + return ((MalInteger)a.nth(0)).subtract((MalInteger)a.nth(1)); + } + }; + static MalFunction multiply = new MalFunction() { + public MalVal apply(MalList a) throws MalThrowable { + return ((MalInteger)a.nth(0)).multiply((MalInteger)a.nth(1)); + } + }; + static MalFunction divide = new MalFunction() { + public MalVal apply(MalList a) throws MalThrowable { + return ((MalInteger)a.nth(0)).divide((MalInteger)a.nth(1)); + } + }; + + static MalFunction lt = new MalFunction() { + public MalVal apply(MalList a) throws MalThrowable { + return ((MalInteger)a.nth(0)).lt((MalInteger)a.nth(1)); + } + }; + static MalFunction lte = new MalFunction() { + public MalVal apply(MalList a) throws MalThrowable { + return ((MalInteger)a.nth(0)).lte((MalInteger)a.nth(1)); + } + }; + static MalFunction gt = new MalFunction() { + public MalVal apply(MalList a) throws MalThrowable { + return ((MalInteger)a.nth(0)).gt((MalInteger)a.nth(1)); + } + }; + static MalFunction gte = new MalFunction() { + public MalVal apply(MalList a) throws MalThrowable { + return ((MalInteger)a.nth(0)).gte((MalInteger)a.nth(1)); + } + }; + + // + // Errors/Exceptions + // + static MalFunction mal_throw = new MalFunction() { + public MalVal apply(MalList a) throws MalThrowable { + throw new MalException(a.nth(0)); + } + }; + + // + // List operations + // + static MalFunction new_list = new MalFunction() { + public MalVal apply(MalList a) throws MalThrowable { + return new MalList(a.value); + } + }; + + static public Boolean _list_Q(MalVal mv) { + return mv.getClass().equals(MalList.class); + } + static MalFunction list_Q = new MalFunction() { + public MalVal apply(MalList a) throws MalThrowable { + return _list_Q(a.nth(0)) ? True : False; + } + }; + + // + // Vector operations + // + static MalFunction new_vector = new MalFunction() { + public MalVal apply(MalList a) throws MalThrowable { + return new MalVector(a.value); + } + }; + + static public Boolean _vector_Q(MalVal mv) { + return mv.getClass().equals(MalVector.class); + } + static MalFunction vector_Q = new MalFunction() { + public MalVal apply(MalList a) throws MalThrowable { + return _vector_Q(a.nth(0)) ? True : False; + } + }; + + // + // Hash map operations + // + static MalFunction new_hash_map = new MalFunction() { + public MalVal apply(MalList a) throws MalThrowable { + return new MalHashMap(a); + } + }; + + static MalFunction hash_map_Q = new MalFunction() { + public MalVal apply(MalList a) throws MalThrowable { + return a.nth(0) instanceof MalHashMap ? True : False; + } + }; + + static MalFunction contains_Q = new MalFunction() { + public MalVal apply(MalList a) throws MalThrowable { + String key = ((MalString)a.nth(1)).getValue(); + MalHashMap mhm = (MalHashMap)a.nth(0); + HashMap<String,MalVal> hm = (HashMap<String,MalVal>)mhm.value; + return hm.containsKey(key) ? True : False; + } + }; + + static MalFunction assoc = new MalFunction() { + public MalVal apply(MalList a) throws MalThrowable { + MalHashMap mhm = (MalHashMap)a.nth(0); + HashMap<String,MalVal> hm = (HashMap<String,MalVal>)mhm.value; + MalHashMap new_mhm = new MalHashMap((Map)hm.clone()); + new_mhm.assoc_BANG((MalList)a.slice(1)); + return new_mhm; + } + }; + + static MalFunction dissoc = new MalFunction() { + public MalVal apply(MalList a) throws MalThrowable { + MalHashMap mhm = (MalHashMap)a.nth(0); + HashMap<String,MalVal> hm = (HashMap<String,MalVal>)mhm.value; + MalHashMap new_mhm = new MalHashMap((Map)hm.clone()); + new_mhm.dissoc_BANG((MalList)a.slice(1)); + return new_mhm; + } + }; + + static MalFunction get = new MalFunction() { + public MalVal apply(MalList a) throws MalThrowable { + String key = ((MalString)a.nth(1)).getValue(); + MalHashMap mhm = (MalHashMap)a.nth(0); + HashMap<String,MalVal> hm = (HashMap<String,MalVal>)mhm.value; + if (hm.containsKey(key)) { + return hm.get(key); + } else { + return Nil; + } + } + }; + + static MalFunction keys = new MalFunction() { + public MalVal apply(MalList a) throws MalThrowable { + MalHashMap mhm = (MalHashMap)a.nth(0); + HashMap<String,MalVal> hm = (HashMap<String,MalVal>)mhm.value; + MalList key_lst = new MalList(); + for (String key : hm.keySet()) { + key_lst.conj_BANG(new MalString(key)); + } + return key_lst; + } + }; + + static MalFunction vals = new MalFunction() { + public MalVal apply(MalList a) throws MalThrowable { + MalHashMap mhm = (MalHashMap)a.nth(0); + HashMap<String,MalVal> hm = (HashMap<String,MalVal>)mhm.value; + //return new ArrayList<MalVal>(((HashMap<String,MalVal>)hm).values()); + MalList val_lst = new MalList(); + for (MalVal val : hm.values()) { + val_lst.conj_BANG(val); + } + return val_lst; + } + }; + + + // + // Atoms + // + static MalFunction new_atom = new MalFunction() { + public MalVal apply(MalList a) throws MalThrowable { + return new MalAtom(a.nth(0)); + } + }; + + static MalFunction atom_Q = new MalFunction() { + public MalVal apply(MalList a) throws MalThrowable { + return a.nth(0) instanceof MalAtom ? True : False; + } + }; + + static MalFunction deref = new MalFunction() { + public MalVal apply(MalList a) throws MalThrowable { + return ((MalAtom)a.nth(0)).value; + } + }; + + static MalFunction reset_BANG = new MalFunction() { + public MalVal apply(MalList a) throws MalThrowable { + return ((MalAtom)a.nth(0)).value = a.nth(1); + } + }; + + static MalFunction swap_BANG = new MalFunction() { + public MalVal apply(MalList a) throws MalThrowable { + MalAtom atm = (MalAtom)a.nth(0); + MalFunction f = (MalFunction)a.nth(1); + MalList new_args = new MalList(); + new_args.value.addAll(((MalList)a.slice(2)).value); + new_args.value.add(0, atm.value); + atm.value = f.apply(new_args); + return atm.value; + } + }; + + + + + // + // Sequence operations + // + static MalFunction sequential_Q = new MalFunction() { + public MalVal apply(MalList a) throws MalThrowable { + return a.nth(0) instanceof MalList ? True : False; + } + }; + + static MalFunction count = new MalFunction() { + public MalVal apply(MalList a) throws MalThrowable { + return new MalInteger(((MalList)a.nth(0)).size()); + } + }; + + static MalFunction empty_Q = new MalFunction() { + public MalVal apply(MalList a) throws MalThrowable { + MalVal exp = a.nth(0); + if (exp == Nil || (exp instanceof MalList && + ((MalList)exp).size() == 0)) { + return True; + } else { + return False; + } + } + }; + + static MalFunction cons = new MalFunction() { + public MalVal apply(MalList a) throws MalThrowable { + MalList lst = new MalList(); + lst.value.addAll(((MalList)a.nth(1)).value); + lst.value.add(0, a.nth(0)); + return (MalVal) lst; + } + }; + + static MalFunction concat = new MalFunction() { + public MalVal apply(MalList a) throws MalThrowable { + if (a.size() == 0) { return new MalList(); } + MalList lst = new MalList(); + lst.value.addAll(((MalList)a.nth(0)).value); + for(Integer i=1; i<a.size(); i++) { + lst.value.addAll(((MalList)a.nth(i)).value); + } + return (MalVal) lst; + } + }; + + static MalFunction conj = new MalFunction() { + public MalVal apply(MalList a) throws MalThrowable { + MalList lst = new MalList(); + lst.value.addAll(((MalList)a.nth(0)).value); + for(Integer i=1; i<a.size(); i++) { + lst.value.add(a.nth(i)); + } + return (MalVal) lst; + } + }; + + static MalFunction first = new MalFunction() { + public MalVal apply(MalList a) throws MalThrowable { + MalList ml = ((MalList)a.nth(0)); + return ml.size() > 0 ? ml.nth(0) : Nil; + } + }; + + static MalList _rest (MalList ml) { + if (ml.size() > 0) { + return new MalList(ml.value.subList(1, ml.value.size())); + } else { + return new MalList(); + } + } + + static MalFunction rest = new MalFunction() { + public MalVal apply(MalList a) throws MalThrowable { + MalList ml = ((MalList)a.nth(0)); + return _rest(ml); + } + }; + + static MalFunction nth = new MalFunction() { + public MalVal apply(MalList a) throws MalThrowable { + Integer idx = ((MalInteger)a.nth(1)).getValue(); + return ((MalList)a.nth(0)).nth(idx); + } + }; + + // General list related functions + static MalFunction apply = new MalFunction() { + public MalVal apply(MalList a) throws MalThrowable { + MalFunction f = (MalFunction)a.nth(0); + MalList args = a.slice(1,a.size()-1); + args.value.addAll( ((MalList)a.nth(a.size()-1)).value); + return f.apply(args); + } + }; + + static MalFunction map = new MalFunction() { + public MalVal apply(MalList a) throws MalThrowable { + MalFunction f = (MalFunction) a.nth(0); + MalList src_lst = (MalList) a.nth(1); + MalList new_lst = new MalList(); + for(Integer i=0; i<src_lst.size(); i++) { + new_lst.value.add( + f.apply(new MalList(src_lst.nth(i)))); + } + return new_lst; + } + }; + + + + // + // Env implementation + // + public static class Env { + Env outer = null; + HashMap<String,MalVal> data = new HashMap<String,MalVal>(); + + public Env(Env outer) { + this.outer = outer; + } + public Env(Env outer, MalList binds, MalList exprs) { + this.outer = outer; + for (Integer i=0; i<binds.size(); i++) { + String sym = ((MalSymbol)binds.nth(i)).getName(); + if (sym.equals("&")) { + data.put(((MalSymbol)binds.nth(i+1)).getName(), + exprs.slice(i)); + break; + } else { + data.put(sym, exprs.nth(i)); + } + } + } + + public Env find(String key) { + if (data.containsKey(key)) { + return this; + } else if (outer != null) { + return outer.find(key); + } else { + return null; + } + } + + public MalVal get(String key) throws MalThrowable { + Env e = find(key); + if (e == null) { + throw new MalException("'" + key + "' not found"); + } else { + return e.data.get(key); + } + } + + public Env set(String key, MalVal value) { + data.put(key, value); + return this; + } + } + + // types_ns is namespace of type functions + static Map<String, MalVal> types_ns = ImmutableMap.<String, MalVal>builder() + .put("pr-str", pr_str) + .put("str", str) + .put("prn", prn) + .put("println", println) + .put("meta", meta) + .put("with-meta", with_meta) + .put("=", equal_Q) + .put("symbol?", symbol_Q) + .put("nil?", nil_Q) + .put("true?", true_Q) + .put("false?", false_Q) + .put("<", lt) + .put("<=", lte) + .put(">", gt) + .put(">=", gte) + .put("+", add) + .put("-", subtract) + .put("*", multiply) + .put("/", divide) + .put("throw", mal_throw) + .put("list", new_list) + .put("list?", list_Q) + .put("vector", new_vector) + .put("vector?", vector_Q) + .put("hash-map", new_hash_map) + .put("map?", hash_map_Q) + .put("assoc", assoc) + .put("dissoc", dissoc) + .put("contains?", contains_Q) + .put("get", get) + .put("keys", keys) + .put("vals", vals) + .put("atom", new_atom) + .put("atom?", atom_Q) + .put("deref", deref) + .put("reset!", reset_BANG) + .put("swap!", swap_BANG) + .put("sequential?", sequential_Q) + .put("cons", cons) + .put("concat", concat) + .put("conj", conj) + .put("first", first) + .put("rest", rest) + .put("nth", nth) + .put("count", count) + .put("empty?", empty_Q) + .put("apply", apply) + .put("map", map) + .build(); +} diff --git a/js/Makefile b/js/Makefile new file mode 100644 index 0000000..cb57644 --- /dev/null +++ b/js/Makefile @@ -0,0 +1,29 @@ + +TESTS = tests/types.js tests/reader.js tests/step5_tco.js + +SOURCES = node_readline.js types.js reader.js stepA_more.js +WEB_SOURCES = $(SOURCES:node_readline.js=josh_readline.js) + +all: mal.js mal_web.js + +mal.js: $(SOURCES) + echo "#!/usr/bin/env node" > $@ + cat $+ | grep -v "= *require('./" >> $@ + chmod +x $@ + +mal_web.js: $(WEB_SOURCES) + cat $+ | grep -v "= *require('./" > $@ + +clean: + rm -f mal.js mal_web.js + +.PHONY: stats tests $(TESTS) + +stats: $(SOURCES) + @wc $^ + +tests: $(TESTS) + +$(TESTS): + @echo "Running $@"; \ + node $@ || exit 1; \ diff --git a/js/josh_readline.js b/js/josh_readline.js new file mode 100644 index 0000000..ff4d201 --- /dev/null +++ b/js/josh_readline.js @@ -0,0 +1,402 @@ +/* ------------------------------------------------------------------------* + * Copyright 2013 Arne F. Claassen + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *-------------------------------------------------------------------------*/ + +var Josh = Josh || {}; +(function(root, $, _) { + Josh.Shell = function(config) { + config = config || {}; + + // instance fields + var _console = config.console || (Josh.Debug && root.console ? root.console : { + log: function() { + } + }); + var _prompt = config.prompt || 'jsh$'; + var _action = config.action || function(str) { + return "<div>No action defined for: " + str + "</div>"; + }; + var _shell_view_id = config.shell_view_id || 'shell-view'; + var _shell_panel_id = config.shell_panel_id || 'shell-panel'; + var _input_id = config.input_id || 'shell-cli'; + var _blinktime = config.blinktime || 500; + var _history = config.history || new Josh.History(); + var _readline = config.readline || new Josh.ReadLine({history: _history, console: _console}); + var _active = false; + var _cursor_visible = false; + var _activationHandler; + var _deactivationHandler; + var _cmdHandlers = { + clear: { + exec: function(cmd, args, callback) { + $(id(_input_id)).parent().empty(); + callback(); + } + }, + help: { + exec: function(cmd, args, callback) { + callback(self.templates.help({commands: commands()})); + } + }, + history: { + exec: function(cmd, args, callback) { + if(args[0] == "-c") { + _history.clear(); + callback(); + return; + } + callback(self.templates.history({items: _history.items()})); + } + }, + _default: { + exec: function(cmd, args, callback) { + callback(self.templates.bad_command({cmd: cmd})); + }, + completion: function(cmd, arg, line, callback) { + if(!arg) { + arg = cmd; + } + return callback(self.bestMatch(arg, self.commands())) + } + } + }; + var _line = { + text: '', + cursor: 0 + }; + var _searchMatch = ''; + var _view, _panel; + var _promptHandler; + var _initializationHandler; + var _initialized; + + // public methods + var self = { + commands: commands, + templates: { + history: _.template("<div><% _.each(items, function(cmd, i) { %><div><%- i %> <%- cmd %></div><% }); %></div>"), + help: _.template("<div><div><strong>Commands:</strong></div><% _.each(commands, function(cmd) { %><div> <%- cmd %></div><% }); %></div>"), + bad_command: _.template('<div><strong>Unrecognized command: </strong><%=cmd%></div>'), + input_cmd: _.template('<div id="<%- id %>"><span class="prompt"></span> <span class="input"><span class="left"/><span class="cursor"/><span class="right"/></span></div>'), + input_search: _.template('<div id="<%- id %>">(reverse-i-search)`<span class="searchterm"></span>\': <span class="input"><span class="left"/><span class="cursor"/><span class="right"/></span></div>'), + suggest: _.template("<div><% _.each(suggestions, function(suggestion) { %><div><%- suggestion %></div><% }); %></div>") + }, + isActive: function() { + return _readline.isActive(); + }, + activate: function() { + if($(id(_shell_view_id)).length == 0) { + _active = false; + return; + } + _readline.activate(); + }, + deactivate: function() { + _console.log("deactivating"); + _active = false; + _readline.deactivate(); + }, + setCommandHandler: function(cmd, cmdHandler) { + _cmdHandlers[cmd] = cmdHandler; + }, + getCommandHandler: function(cmd) { + return _cmdHandlers[cmd]; + }, + setPrompt: function(prompt) { + _prompt = prompt; + if(!_active) { + return; + } + self.refresh(); + }, + onEOT: function(completionHandler) { + _readline.onEOT(completionHandler); + }, + onCancel: function(completionHandler) { + _readline.onCancel(completionHandler); + }, + onInitialize: function(completionHandler) { + _initializationHandler = completionHandler; + }, + onActivate: function(completionHandler) { + _activationHandler = completionHandler; + }, + onDeactivate: function(completionHandler) { + _deactivationHandler = completionHandler; + }, + onNewPrompt: function(completionHandler) { + _promptHandler = completionHandler; + }, + render: function() { + var text = _line.text || ''; + var cursorIdx = _line.cursor || 0; + if(_searchMatch) { + cursorIdx = _searchMatch.cursoridx || 0; + text = _searchMatch.text || ''; + $(id(_input_id) + ' .searchterm').text(_searchMatch.term); + } + var left = _.escape(text.substr(0, cursorIdx)).replace(/ /g, ' '); + var cursor = text.substr(cursorIdx, 1); + var right = _.escape(text.substr(cursorIdx + 1)).replace(/ /g, ' '); + $(id(_input_id) + ' .prompt').html(_prompt); + $(id(_input_id) + ' .input .left').html(left); + if(!cursor) { + $(id(_input_id) + ' .input .cursor').html(' ').css('textDecoration', 'underline'); + } else { + $(id(_input_id) + ' .input .cursor').text(cursor).css('textDecoration', 'underline'); + } + $(id(_input_id) + ' .input .right').html(right); + _cursor_visible = true; + self.scrollToBottom(); + _console.log('rendered "' + text + '" w/ cursor at ' + cursorIdx); + }, + refresh: function() { + $(id(_input_id)).replaceWith(self.templates.input_cmd({id:_input_id})); + self.render(); + _console.log('refreshed ' + _input_id); + + }, + scrollToBottom: function() { + _panel.animate({scrollTop: _view.height()}, 0); + }, + bestMatch: function(partial, possible) { + _console.log("bestMatch on partial '" + partial + "'"); + var result = { + completion: null, + suggestions: [] + }; + if(!possible || possible.length == 0) { + return result; + } + var common = ''; + if(!partial) { + if(possible.length == 1) { + result.completion = possible[0]; + result.suggestions = possible; + return result; + } + if(!_.every(possible, function(x) { + return possible[0][0] == x[0] + })) { + result.suggestions = possible; + return result; + } + } + for(var i = 0; i < possible.length; i++) { + var option = possible[i]; + if(option.slice(0, partial.length) == partial) { + result.suggestions.push(option); + if(!common) { + common = option; + _console.log("initial common:" + common); + } else if(option.slice(0, common.length) != common) { + _console.log("find common stem for '" + common + "' and '" + option + "'"); + var j = partial.length; + while(j < common.length && j < option.length) { + if(common[j] != option[j]) { + common = common.substr(0, j); + break; + } + j++; + } + } + } + } + result.completion = common.substr(partial.length); + return result; + } + }; + + function id(id) { + return "#"+id; + } + + function commands() { + return _.chain(_cmdHandlers).keys().filter(function(x) { + return x[0] != "_" + }).value(); + } + + function blinkCursor() { + if(!_active) { + return; + } + root.setTimeout(function() { + if(!_active) { + return; + } + _cursor_visible = !_cursor_visible; + if(_cursor_visible) { + $(id(_input_id) + ' .input .cursor').css('textDecoration', 'underline'); + } else { + $(id(_input_id) + ' .input .cursor').css('textDecoration', ''); + } + blinkCursor(); + }, _blinktime); + } + + function split(str) { + return _.filter(str.split(/\s+/), function(x) { + return x; + }); + } + + function getHandler(cmd) { + return _cmdHandlers[cmd] || _cmdHandlers._default; + } + + function renderOutput(output, callback) { + if(output) { + $(id(_input_id)).after(output); + } + $(id(_input_id) + ' .input .cursor').css('textDecoration', ''); + $(id(_input_id)).removeAttr('id'); + $(id(_shell_view_id)).append(self.templates.input_cmd({id:_input_id})); + if(_promptHandler) { + return _promptHandler(function(prompt) { + self.setPrompt(prompt); + return callback(); + }); + } + return callback(); + } + + function activate() { + _console.log("activating shell"); + if(!_view) { + _view = $(id(_shell_view_id)); + } + if(!_panel) { + _panel = $(id(_shell_panel_id)); + } + if($(id(_input_id)).length == 0) { + _view.append(self.templates.input_cmd({id:_input_id})); + } + self.refresh(); + _active = true; + blinkCursor(); + if(_promptHandler) { + _promptHandler(function(prompt) { + self.setPrompt(prompt); + }) + } + if(_activationHandler) { + _activationHandler(); + } + } + + // init + _readline.onActivate(function() { + if(!_initialized) { + _initialized = true; + if(_initializationHandler) { + return _initializationHandler(activate); + } + } + return activate(); + }); + _readline.onDeactivate(function() { + if(_deactivationHandler) { + _deactivationHandler(); + } + }); + _readline.onChange(function(line) { + _line = line; + self.render(); + }); + _readline.onClear(function() { + _cmdHandlers.clear.exec(null, null, function() { + renderOutput(null, function() { + }); + }); + }); + _readline.onSearchStart(function() { + $(id(_input_id)).replaceWith(self.templates.input_search({id:_input_id})); + _console.log('started search'); + }); + _readline.onSearchEnd(function() { + $(id(_input_id)).replaceWith(self.templates.input_cmd({id:_input_id})); + _searchMatch = null; + self.render(); + _console.log("ended search"); + }); + _readline.onSearchChange(function(match) { + _searchMatch = match; + self.render(); + }); + _readline.onEnter(function(cmdtext, callback) { + _console.log("got command: " + cmdtext); + var result; + try { + result = "<div>" + _action(cmdtext) + "</div>"; + } catch (e) { + result = "<div>" + e.stack + "</div>"; + } + renderOutput(result, function() { + callback(""); + }); + }); + _readline.onCompletion(function(line, callback) { + if(!line) { + return callback(); + } + var text = line.text.substr(0, line.cursor); + var parts = split(text); + + var cmd = parts.shift() || ''; + var arg = parts.pop() || ''; + _console.log("getting completion handler for " + cmd); + var handler = getHandler(cmd); + if(handler != _cmdHandlers._default && cmd && cmd == text) { + + _console.log("valid cmd, no args: append space"); + // the text to complete is just a valid command, append a space + return callback(' '); + } + if(!handler.completion) { + // handler has no completion function, so we can't complete + return callback(); + } + _console.log("calling completion handler for " + cmd); + return handler.completion(cmd, arg, line, function(match) { + _console.log("completion: " + JSON.stringify(match)); + if(!match) { + return callback(); + } + if(match.suggestions && match.suggestions.length > 1) { + return renderOutput(self.templates.suggest({suggestions: match.suggestions}), function() { + callback(match.completion); + }); + } + return callback(match.completion); + }); + }); + return self; + } +})(this, $, _); + +var readline = {}; +readline.rlwrap = function(action) { + var history = new Josh.History({ key: 'josh.helloworld'}); + var shell = Josh.Shell({history: history, + action: action}); + var promptCounter = 0; + shell.onNewPrompt(function(callback) { + promptCounter++; + //callback("[" + promptCounter + "] $"); + callback("user>"); + }); + shell.activate(); +} diff --git a/js/node_readline.js b/js/node_readline.js new file mode 100644 index 0000000..bfd1982 --- /dev/null +++ b/js/node_readline.js @@ -0,0 +1,38 @@ +// IMPORTANT: choose one +var RL_LIB = "libreadline"; // NOTE: libreadline is GPL +//var RL_LIB = "libedit"; + +var HISTORY_FILE = require('path').join(process.env.HOME, '.mal-history'); + +var rlwrap = {}; // namespace for this module in web context + +var ffi = require('ffi'), + fs = require('fs'); + +var rllib = ffi.Library(RL_LIB, { + 'readline': [ 'string', [ 'string' ] ], + 'add_history': [ 'int', [ 'string' ] ]}); + +var rl_history_loaded = false; + +exports.readline = rlwrap.readline = function(prompt) { + prompt = prompt || "user> "; + + if (!rl_history_loaded) { + rl_history_loaded = true; + var lines = fs.readFileSync(HISTORY_FILE).toString().split("\n"); + // Max of 2000 lines + lines = lines.slice(Math.max(lines.length - 2000, 0)); + for (var i=0; i<lines.length; i++) { + if (lines[i]) { rllib.add_history(lines[i]); } + } + } + + var line = rllib.readline(prompt); + if (line) { + rllib.add_history(line); + fs.appendFileSync(HISTORY_FILE, line + "\n"); + } + + return line; +} diff --git a/js/package.json b/js/package.json new file mode 100644 index 0000000..976e6ae --- /dev/null +++ b/js/package.json @@ -0,0 +1,8 @@ +{ + "name": "mal", + "version": "0.0.1", + "description": "Make a Lisp (mal) language implemented in Javascript", + "dependencies": { + "ffi": "1.2.x" + } +} diff --git a/js/reader.js b/js/reader.js new file mode 100644 index 0000000..da51088 --- /dev/null +++ b/js/reader.js @@ -0,0 +1,127 @@ +// Node vs browser behavior +var reader = {}; +if (typeof module !== 'undefined') { + var types = require('./types'); +} else { + var exports = reader; +} + +function Reader(tokens) { + // copy + this.tokens = tokens.map(function (a) { return a; }); + this.position = 0; +} +Reader.prototype.next = function() { return this.tokens[this.position++]; } +Reader.prototype.peek = function() { return this.tokens[this.position]; } + +function tokenize(str) { + var re = /[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"`,;)]*)/g; + var results = []; + while ((match = re.exec(str)[1]) != '') { + if (match[0] === ';') { continue; } + results.push(match); + } + return results; +} + +function read_atom (reader) { + var token = reader.next(); + //console.log("read_atom:", token); + if (token.match(/^-?[0-9]+$/)) { + return parseInt(token,10) // integer + } else if (token.match(/^-?[0-9][0-9.]*$/)) { + return parseFloat(token,10); // float + } else if (token[0] === "\"") { + return token.slice(1,token.length-1).replace(/\\"/g, '"'); // string + } else if (token === "nil") { + return null; + } else if (token === "true") { + return true; + } else if (token === "false") { + return false; + } else { + return types.symbol(token); // symbol + } +} + +// read list of tokens +function read_list(reader, start, end) { + start = start || '('; + end = end || ')'; + var ast = []; + var token = reader.next(); + if (token !== start) { + throw new Error("expected '" + start + "'"); + } + while ((token = reader.peek()) !== end) { + if (!token) { + throw new Error("expected '" + end + "', got EOF"); + } + ast.push(read_form(reader)); + } + reader.next(); + return ast; +} + +// read vector of tokens +function read_vector(reader) { + var lst = read_list(reader, '[', ']'); + return types.vector.apply(types.vector, lst); +} + +// read hash-map key/value pairs +function read_hash_map(reader) { + var lst = read_list(reader, '{', '}'); + return types.hash_map.apply(types.hash_map, lst); +} + +function read_form(reader) { + var token = reader.peek(); + switch (token) { + // reader macros/transforms + case ';': return null; // Ignore comments + case '\'': reader.next(); + return [types.symbol('quote'), read_form(reader)]; + case '`': reader.next(); + return [types.symbol('quasiquote'), read_form(reader)]; + case '~': reader.next(); + return [types.symbol('unquote'), read_form(reader)]; + case '~@': reader.next(); + return [types.symbol('splice-unquote'), read_form(reader)]; + case '^': reader.next(); + var meta = read_form(reader); + return [types.symbol('with-meta'), read_form(reader), meta]; + case '@': reader.next(); + return [types.symbol('deref'), read_form(reader)]; + + // list + case ')': throw new Error("unexpected ')'"); + case '(': return read_list(reader); + + // vector + case ']': throw new Error("unexpected ']'"); + case '[': return read_vector(reader); + + // hash-map + case '}': throw new Error("unexpected '}'"); + case '{': return read_hash_map(reader); + + // atom + default: return read_atom(reader); + } +} + +function BlankException(msg) { +} + +function read_str(str) { + var tokens = tokenize(str); + if (tokens.length === 0) { throw new BlankException(); } + return read_form(new Reader(tokens)) +} + +exports.Reader = reader.Reader = Reader; +exports.BlankException = reader.BlankException = BlankException; +exports.tokenize = reader.tokenize = tokenize; +exports.read_form = reader.read_form = read_form; +exports.read_str = reader.read_str = read_str; diff --git a/js/step0_repl.js b/js/step0_repl.js new file mode 100644 index 0000000..5fa10f2 --- /dev/null +++ b/js/step0_repl.js @@ -0,0 +1,42 @@ +if (typeof module !== 'undefined') { + var readline = require('./node_readline'); +} + +// read +function READ(str) { + return str; +} + +// eval +function EVAL(ast, env) { + return eval(ast); +} + +// print +function PRINT(exp) { + return exp; +} + +// repl +var rep = function(str) { return PRINT(EVAL(READ(str), {})); }; + +if (typeof require === 'undefined') { + // Asynchronous browser mode + readline.rlwrap(function(line) { return rep(line); }, + function(exc) { + if (exc.stack) { console.log(exc.stack); } else { console.log(exc); } + }); +} else if (require.main === module) { + // Synchronous node.js commandline mode + while (true) { + var line = readline.readline("user> "); + if (line === null) { break; } + try { + if (line) { console.log(rep(line)); } + } catch (exc) { + if (exc.stack) { console.log(exc.stack); } else { console.log(exc); } + } + } +} else { + exports.rep = rep; +} diff --git a/js/step1_read_print.js b/js/step1_read_print.js new file mode 100644 index 0000000..ee027d7 --- /dev/null +++ b/js/step1_read_print.js @@ -0,0 +1,47 @@ +var types = require('./types'); +var reader = require('./reader'); +if (typeof module !== 'undefined') { + var readline = require('./node_readline'); +} + +// read +function READ(str) { + return reader.read_str(str); +} + +// eval +function EVAL(ast, env) { + return ast; +} + +// print +function PRINT(exp) { + return types._pr_str(exp, true); +} + +// repl +var re = function(str) { return EVAL(READ(str), {}); }; +var rep = function(str) { return PRINT(EVAL(READ(str), {})); }; + +if (typeof require === 'undefined') { + // Asynchronous browser mode + readline.rlwrap(function(line) { return rep(line); }, + function(exc) { + if (exc instanceof reader.BlankException) { return; } + if (exc.stack) { console.log(exc.stack); } else { console.log(exc); } + }); +} else if (require.main === module) { + // Synchronous node.js commandline mode + while (true) { + var line = readline.readline("user> "); + if (line === null) { break; } + try { + if (line) { console.log(rep(line)); } + } catch (exc) { + if (exc instanceof reader.BlankException) { continue; } + if (exc.stack) { console.log(exc.stack); } else { console.log(exc); } + } + } +} else { + exports.rep = rep; +} diff --git a/js/step2_eval.js b/js/step2_eval.js new file mode 100644 index 0000000..f2cb8b1 --- /dev/null +++ b/js/step2_eval.js @@ -0,0 +1,83 @@ +var types = require('./types'); +var reader = require('./reader'); +if (typeof module !== 'undefined') { + var readline = require('./node_readline'); +} + +// read +function READ(str) { + return reader.read_str(str); +} + +// eval +function eval_ast(ast, env) { + if (types.symbol_Q(ast)) { + return env[ast]; + } else if (types.list_Q(ast)) { + return ast.map(function(a) { return EVAL(a, env); }); + } else if (types.vector_Q(ast)) { + var v = ast.map(function(a) { return EVAL(a, env); }); + v.__isvector__ = true; + return v; + } else if (types.hash_map_Q(ast)) { + var new_hm = {}; + for (k in ast) { + new_hm[EVAL(k, env)] = EVAL(ast[k], env); + } + return new_hm; + } else { + return ast; + } +} + +function _EVAL(ast, env) { + if (!types.list_Q(ast)) { + return eval_ast(ast, env); + } + + // apply list + var el = eval_ast(ast, env), f = el[0]; + return f.apply(f, el.slice(1)); +} + +function EVAL(ast, env) { + var result = _EVAL(ast, env); + return (typeof result !== "undefined") ? result : null; +} + +// print +function PRINT(exp) { + return types._pr_str(exp, true); +} + +// repl +repl_env = {}; +var rep = function(str) { return PRINT(EVAL(READ(str), repl_env)); }; + +repl_env['+'] = function(a,b){return a+b;}; +repl_env['-'] = function(a,b){return a-b;}; +repl_env['*'] = function(a,b){return a*b;}; +repl_env['/'] = function(a,b){return a/b;}; + +if (typeof require === 'undefined') { + // Asynchronous browser mode + readline.rlwrap(function(line) { return rep(line); }, + function(exc) { + if (exc instanceof reader.BlankException) { return; } + if (exc.stack) { console.log(exc.stack); } else { console.log(exc); } + }); +} else if (require.main === module) { + // Synchronous node.js commandline mode + while (true) { + var line = readline.readline("user> "); + if (line === null) { break; } + try { + if (line) { console.log(rep(line)); } + } catch (exc) { + if (exc instanceof reader.BlankException) { continue; } + if (exc.stack) { console.log(exc.stack); } else { console.log(exc); } + } + } +} else { + exports.rep = rep; +} diff --git a/js/step3_env.js b/js/step3_env.js new file mode 100644 index 0000000..5b6e802 --- /dev/null +++ b/js/step3_env.js @@ -0,0 +1,97 @@ +var types = require('./types'); +var reader = require('./reader'); +if (typeof module !== 'undefined') { + var readline = require('./node_readline'); +} + +// read +function READ(str) { + return reader.read_str(str); +} + +// eval +function eval_ast(ast, env) { + if (types.symbol_Q(ast)) { + return env.get(ast); + } else if (types.list_Q(ast)) { + return ast.map(function(a) { return EVAL(a, env); }); + } else if (types.vector_Q(ast)) { + var v = ast.map(function(a) { return EVAL(a, env); }); + v.__isvector__ = true; + return v; + } else if (types.hash_map_Q(ast)) { + var new_hm = {}; + for (k in ast) { + new_hm[EVAL(k, env)] = EVAL(ast[k], env); + } + return new_hm; + } else { + return ast; + } +} + +function _EVAL(ast, env) { + if (!types.list_Q(ast)) { + return eval_ast(ast, env); + } + + // apply list + var a0 = ast[0], a1 = ast[1], a2 = ast[2], a3 = ast[3]; + switch (a0.value) { + case "def!": + var res = EVAL(a2, env); + return env.set(a1, res); + case "let*": + var let_env = new types.Env(env); + for (var i=0; i < a1.length; i+=2) { + let_env.set(a1[i].value, EVAL(a1[i+1], let_env)); + } + return EVAL(a2, let_env); + default: + var el = eval_ast(ast, env), f = el[0]; + return f.apply(f, el.slice(1)); + } +} + +function EVAL(ast, env) { + var result = _EVAL(ast, env); + return (typeof result !== "undefined") ? result : null; +} + +// print +function PRINT(exp) { + return types._pr_str(exp, true); +} + +// repl +var repl_env = new types.Env(); +var rep = function(str) { return PRINT(EVAL(READ(str), repl_env)); }; +_ref = function (k,v) { repl_env.set(k, v); } + +_ref('+', function(a,b){return a+b;}); +_ref('-', function(a,b){return a-b;}); +_ref('*', function(a,b){return a*b;}); +_ref('/', function(a,b){return a/b;}); + +if (typeof require === 'undefined') { + // Asynchronous browser mode + readline.rlwrap(function(line) { return rep(line); }, + function(exc) { + if (exc instanceof reader.BlankException) { return; } + if (exc.stack) { console.log(exc.stack); } else { console.log(exc); } + }); +} else if (require.main === module) { + // Synchronous node.js commandline mode + while (true) { + var line = readline.readline("user> "); + if (line === null) { break; } + try { + if (line) { console.log(rep(line)); } + } catch (exc) { + if (exc instanceof reader.BlankException) { continue; } + if (exc.stack) { console.log(exc.stack); } else { console.log(exc); } + } + } +} else { + exports.rep = rep; +} diff --git a/js/step4_if_fn_do.js b/js/step4_if_fn_do.js new file mode 100644 index 0000000..d33ec04 --- /dev/null +++ b/js/step4_if_fn_do.js @@ -0,0 +1,112 @@ +var types = require('./types'); +var reader = require('./reader'); +if (typeof module !== 'undefined') { + var readline = require('./node_readline'); +} + +// read +function READ(str) { + return reader.read_str(str); +} + +// eval +function eval_ast(ast, env) { + if (types.symbol_Q(ast)) { + return env.get(ast); + } else if (types.list_Q(ast)) { + return ast.map(function(a) { return EVAL(a, env); }); + } else if (types.vector_Q(ast)) { + var v = ast.map(function(a) { return EVAL(a, env); }); + v.__isvector__ = true; + return v; + } else if (types.hash_map_Q(ast)) { + var new_hm = {}; + for (k in ast) { + new_hm[EVAL(k, env)] = EVAL(ast[k], env); + } + return new_hm; + } else { + return ast; + } +} + +function _EVAL(ast, env) { + if (!types.list_Q(ast)) { + return eval_ast(ast, env); + } + + // apply list + var a0 = ast[0], a1 = ast[1], a2 = ast[2], a3 = ast[3]; + switch (a0.value) { + case "def!": + var res = EVAL(a2, env); + return env.set(a1, res); + case "let*": + var let_env = new types.Env(env); + for (var i=0; i < a1.length; i+=2) { + let_env.set(a1[i].value, EVAL(a1[i+1], let_env)); + } + return EVAL(a2, let_env); + case "do": + var el = eval_ast(ast.slice(1), env); + return el[el.length-1]; + case "if": + var cond = EVAL(a1, env); + if (cond === null || cond === false) { + return typeof a3 !== "undefined" ? EVAL(a3, env) : null; + } else { + return EVAL(a2, env); + } + case "fn*": + return function() { + return EVAL(a2, new types.Env(env, a1, arguments)); + }; + default: + var el = eval_ast(ast, env), f = el[0]; + return f.apply(f, el.slice(1)); + } +} + +function EVAL(ast, env) { + var result = _EVAL(ast, env); + return (typeof result !== "undefined") ? result : null; +} + +// print +function PRINT(exp) { + return types._pr_str(exp, true); +} + +// repl +var repl_env = new types.Env(); +var rep = function(str) { return PRINT(EVAL(READ(str), repl_env)); }; +_ref = function (k,v) { repl_env.set(k, v); } + +// Import types functions +for (var n in types.ns) { repl_env.set(n, types.ns[n]); } + +// Defined using the language itself +rep("(def! not (fn* (a) (if a false true)))"); + +if (typeof require === 'undefined') { + // Asynchronous browser mode + readline.rlwrap(function(line) { return rep(line); }, + function(exc) { + if (exc instanceof reader.BlankException) { return; } + if (exc.stack) { console.log(exc.stack); } else { console.log(exc); } + }); +} else if (require.main === module) { + // Synchronous node.js commandline mode + while (true) { + var line = readline.readline("user> "); + if (line === null) { break; } + try { + if (line) { console.log(rep(line)); } + } catch (exc) { + if (exc instanceof reader.BlankException) { continue; } + if (exc.stack) { console.log(exc.stack); } else { console.log(exc); } + } + } +} else { + exports.rep = rep; +} diff --git a/js/step5_tco.js b/js/step5_tco.js new file mode 100644 index 0000000..20a9583 --- /dev/null +++ b/js/step5_tco.js @@ -0,0 +1,119 @@ +var types = require('./types'); +var reader = require('./reader'); +if (typeof module !== 'undefined') { + var readline = require('./node_readline'); +} + +// read +function READ(str) { + return reader.read_str(str); +} + +// eval +function eval_ast(ast, env) { + if (types.symbol_Q(ast)) { + return env.get(ast); + } else if (types.list_Q(ast)) { + return ast.map(function(a) { return EVAL(a, env); }); + } else if (types.vector_Q(ast)) { + var v = ast.map(function(a) { return EVAL(a, env); }); + v.__isvector__ = true; + return v; + } else if (types.hash_map_Q(ast)) { + var new_hm = {}; + for (k in ast) { + new_hm[EVAL(k, env)] = EVAL(ast[k], env); + } + return new_hm; + } else { + return ast; + } +} + +function _EVAL(ast, env) { + while (true) { + if (!types.list_Q(ast)) { + return eval_ast(ast, env); + } + + // apply list + var a0 = ast[0], a1 = ast[1], a2 = ast[2], a3 = ast[3]; + switch (a0.value) { + case "def!": + var res = EVAL(a2, env); + return env.set(a1, res); + case "let*": + var let_env = new types.Env(env); + for (var i=0; i < a1.length; i+=2) { + let_env.set(a1[i].value, EVAL(a1[i+1], let_env)); + } + return EVAL(a2, let_env); + case "do": + eval_ast(ast.slice(1, -1), env); + ast = ast[ast.length-1]; + break; + case "if": + var cond = EVAL(a1, env); + if (cond === null || cond === false) { + ast = (typeof a3 !== "undefined") ? a3 : null; + } else { + ast = a2; + } + break; + case "fn*": + return types.new_function(EVAL, a2, env, a1); + default: + var el = eval_ast(ast, env), f = el[0], meta = f.__meta__; + if (meta && meta.exp) { + ast = meta.exp; + env = new types.Env(meta.env, meta.params, el.slice(1)); + } else { + return f.apply(f, el.slice(1)); + } + } + } +} + +function EVAL(ast, env) { + var result = _EVAL(ast, env); + return (typeof result !== "undefined") ? result : null; +} + +// print +function PRINT(exp) { + return types._pr_str(exp, true); +} + +// repl +var repl_env = new types.Env(); +var rep = function(str) { return PRINT(EVAL(READ(str), repl_env)); }; +_ref = function (k,v) { repl_env.set(k, v); } + +// Import types functions +for (var n in types.ns) { repl_env.set(n, types.ns[n]); } + +// Defined using the language itself +rep("(def! not (fn* (a) (if a false true)))"); + +if (typeof require === 'undefined') { + // Asynchronous browser mode + readline.rlwrap(function(line) { return rep(line); }, + function(exc) { + if (exc instanceof reader.BlankException) { return; } + if (exc.stack) { console.log(exc.stack); } else { console.log(exc); } + }); +} else if (require.main === module) { + // Synchronous node.js commandline mode + while (true) { + var line = readline.readline("user> "); + if (line === null) { break; } + try { + if (line) { console.log(rep(line)); } + } catch (exc) { + if (exc instanceof reader.BlankException) { continue; } + if (exc.stack) { console.log(exc.stack); } else { console.log(exc); } + } + } +} else { + exports.rep = rep; +} diff --git a/js/step6_file.js b/js/step6_file.js new file mode 100644 index 0000000..b9ec187 --- /dev/null +++ b/js/step6_file.js @@ -0,0 +1,133 @@ +var types = require('./types'); +var reader = require('./reader'); +if (typeof module !== 'undefined') { + var readline = require('./node_readline'); +} + +// read +function READ(str) { + return reader.read_str(str); +} + +// eval +function eval_ast(ast, env) { + if (types.symbol_Q(ast)) { + return env.get(ast); + } else if (types.list_Q(ast)) { + return ast.map(function(a) { return EVAL(a, env); }); + } else if (types.vector_Q(ast)) { + var v = ast.map(function(a) { return EVAL(a, env); }); + v.__isvector__ = true; + return v; + } else if (types.hash_map_Q(ast)) { + var new_hm = {}; + for (k in ast) { + new_hm[EVAL(k, env)] = EVAL(ast[k], env); + } + return new_hm; + } else { + return ast; + } +} + +function _EVAL(ast, env) { + while (true) { + if (!types.list_Q(ast)) { + return eval_ast(ast, env); + } + + // apply list + var a0 = ast[0], a1 = ast[1], a2 = ast[2], a3 = ast[3]; + switch (a0.value) { + case "def!": + var res = EVAL(a2, env); + return env.set(a1, res); + case "let*": + var let_env = new types.Env(env); + for (var i=0; i < a1.length; i+=2) { + let_env.set(a1[i].value, EVAL(a1[i+1], let_env)); + } + return EVAL(a2, let_env); + case "do": + eval_ast(ast.slice(1, -1), env); + ast = ast[ast.length-1]; + break; + case "if": + var cond = EVAL(a1, env); + if (cond === null || cond === false) { + ast = (typeof a3 !== "undefined") ? a3 : null; + } else { + ast = a2; + } + break; + case "fn*": + return types.new_function(EVAL, a2, env, a1); + default: + var el = eval_ast(ast, env), f = el[0], meta = f.__meta__; + if (meta && meta.exp) { + ast = meta.exp; + env = new types.Env(meta.env, meta.params, el.slice(1)); + } else { + return f.apply(f, el.slice(1)); + } + } + } +} + +function EVAL(ast, env) { + var result = _EVAL(ast, env); + return (typeof result !== "undefined") ? result : null; +} + +// print +function PRINT(exp) { + return types._pr_str(exp, true); +} + +// repl +var repl_env = new types.Env(); +var rep = function(str) { return PRINT(EVAL(READ(str), repl_env)); }; +_ref = function (k,v) { repl_env.set(k, v); } + +// Import types functions +for (var n in types.ns) { repl_env.set(n, types.ns[n]); } + +_ref('read-string', reader.read_str); +_ref('eval', function(ast) { return EVAL(ast, repl_env); }); +_ref('slurp', function(f) { + return require('fs').readFileSync(f, 'utf-8'); +}); +_ref('slurp-do', function(f) { + return '(do ' + require('fs').readFileSync(f, 'utf-8') + ')'; +}); + +// Defined using the language itself +rep("(def! not (fn* (a) (if a false true)))"); +rep("(def! load-file (fn* (f) (eval (read-string (slurp-do f)))))"); + +if (typeof process !== 'undefined' && process.argv.length > 2) { + for (var i=2; i < process.argv.length; i++) { + rep('(load-file "' + process.argv[i] + '")'); + } +} else if (typeof require === 'undefined') { + // Asynchronous browser mode + readline.rlwrap(function(line) { return rep(line); }, + function(exc) { + if (exc instanceof reader.BlankException) { return; } + if (exc.stack) { console.log(exc.stack); } else { console.log(exc); } + }); +} else if (require.main === module) { + // Synchronous node.js commandline mode + while (true) { + var line = readline.readline("user> "); + if (line === null) { break; } + try { + if (line) { console.log(rep(line)); } + } catch (exc) { + if (exc instanceof reader.BlankException) { continue; } + if (exc.stack) { console.log(exc.stack); } else { console.log(exc); } + } + } +} else { + exports.rep = rep; +} diff --git a/js/step7_quote.js b/js/step7_quote.js new file mode 100644 index 0000000..832e47b --- /dev/null +++ b/js/step7_quote.js @@ -0,0 +1,154 @@ +var types = require('./types'); +var reader = require('./reader'); +if (typeof module !== 'undefined') { + var readline = require('./node_readline'); +} + +// read +function READ(str) { + return reader.read_str(str); +} + +// eval +function is_pair(x) { + return types.sequential_Q(x) && x.length > 0; +} + +function quasiquote(ast) { + if (!is_pair(ast)) { + return [types.symbol("quote"), ast]; + } else if (ast[0].value === 'unquote') { + return ast[1]; + } else if (is_pair(ast[0]) && ast[0][0].value === 'splice-unquote') { + return [types.symbol("concat"), ast[0][1], quasiquote(ast.slice(1))]; + } else { + return [types.symbol("cons"), quasiquote(ast[0]), quasiquote(ast.slice(1))]; + } +} + +function eval_ast(ast, env) { + if (types.symbol_Q(ast)) { + return env.get(ast); + } else if (types.list_Q(ast)) { + return ast.map(function(a) { return EVAL(a, env); }); + } else if (types.vector_Q(ast)) { + var v = ast.map(function(a) { return EVAL(a, env); }); + v.__isvector__ = true; + return v; + } else if (types.hash_map_Q(ast)) { + var new_hm = {}; + for (k in ast) { + new_hm[EVAL(k, env)] = EVAL(ast[k], env); + } + return new_hm; + } else { + return ast; + } +} + +function _EVAL(ast, env) { + while (true) { + //console.log("EVAL:", types._pr_str(ast, true)); + if (!types.list_Q(ast)) { + return eval_ast(ast, env); + } + + // apply list + var a0 = ast[0], a1 = ast[1], a2 = ast[2], a3 = ast[3]; + switch (a0.value) { + case "def!": + var res = EVAL(a2, env); + return env.set(a1, res); + case "let*": + var let_env = new types.Env(env); + for (var i=0; i < a1.length; i+=2) { + let_env.set(a1[i].value, EVAL(a1[i+1], let_env)); + } + return EVAL(a2, let_env); + case "quote": + return a1; + case "quasiquote": + return EVAL(quasiquote(a1), env); + case "do": + eval_ast(ast.slice(1, -1), env); + ast = ast[ast.length-1]; + break; + case "if": + var cond = EVAL(a1, env); + if (cond === null || cond === false) { + ast = (typeof a3 !== "undefined") ? a3 : null; + } else { + ast = a2; + } + break; + case "fn*": + return types.new_function(EVAL, a2, env, a1); + default: + var el = eval_ast(ast, env), f = el[0], meta = f.__meta__; + if (meta && meta.exp) { + ast = meta.exp; + env = new types.Env(meta.env, meta.params, el.slice(1)); + } else { + return f.apply(f, el.slice(1)); + } + } + } +} + +function EVAL(ast, env) { + var result = _EVAL(ast, env); + return (typeof result !== "undefined") ? result : null; +} + +// print +function PRINT(exp) { + return types._pr_str(exp, true); +} + +// repl +var repl_env = new types.Env(); +var rep = function(str) { return PRINT(EVAL(READ(str), repl_env)); }; +_ref = function (k,v) { repl_env.set(k, v); } + +// Import types functions +for (var n in types.ns) { repl_env.set(n, types.ns[n]); } + +_ref('read-string', reader.read_str); +_ref('eval', function(ast) { return EVAL(ast, repl_env); }); +_ref('slurp', function(f) { + return require('fs').readFileSync(f, 'utf-8'); +}); +_ref('slurp-do', function(f) { + return '(do ' + require('fs').readFileSync(f, 'utf-8') + ')'; +}); + +// Defined using the language itself +rep("(def! not (fn* (a) (if a false true)))"); +rep("(def! load-file (fn* (f) (eval (read-string (slurp-do f)))))"); + +if (typeof process !== 'undefined' && process.argv.length > 2) { + for (var i=2; i < process.argv.length; i++) { + rep('(load-file "' + process.argv[i] + '")'); + } +} else if (typeof require === 'undefined') { + // Asynchronous browser mode + readline.rlwrap(function(line) { return rep(line); }, + function(exc) { + if (exc instanceof reader.BlankException) { return; } + if (exc.stack) { console.log(exc.stack); } else { console.log(exc); } + }); +} else if (require.main === module) { + // Synchronous node.js commandline mode + while (true) { + var line = readline.readline("user> "); + if (line === null) { break; } + try { + if (line) { console.log(rep(line)); } + } catch (exc) { + if (exc instanceof reader.BlankException) { continue; } + if (exc.stack) { console.log(exc.stack); } else { console.log(exc); } + } + } +} else { + exports.rep = rep; +} diff --git a/js/step8_macros.js b/js/step8_macros.js new file mode 100644 index 0000000..766f750 --- /dev/null +++ b/js/step8_macros.js @@ -0,0 +1,178 @@ +var types = require('./types'); +var reader = require('./reader'); +if (typeof module !== 'undefined') { + var readline = require('./node_readline'); +} + +// read +function READ(str) { + return reader.read_str(str); +} + +// eval +function is_pair(x) { + return types.sequential_Q(x) && x.length > 0; +} + +function quasiquote(ast) { + if (!is_pair(ast)) { + return [types.symbol("quote"), ast]; + } else if (ast[0].value === 'unquote') { + return ast[1]; + } else if (is_pair(ast[0]) && ast[0][0].value === 'splice-unquote') { + return [types.symbol("concat"), ast[0][1], quasiquote(ast.slice(1))]; + } else { + return [types.symbol("cons"), quasiquote(ast[0]), quasiquote(ast.slice(1))]; + } +} + +function is_macro_call(ast, env) { + return types.list_Q(ast) && + types.symbol_Q(ast[0]) && + env.find(ast[0].value) && + env.get(ast[0].value)._ismacro_; +} + +function macroexpand(ast, env) { + while (is_macro_call(ast, env)) { + var mac = env.get(ast[0]); + ast = mac.apply(mac, ast.slice(1)); + } + return ast; +} + +function eval_ast(ast, env) { + if (types.symbol_Q(ast)) { + return env.get(ast); + } else if (types.list_Q(ast)) { + return ast.map(function(a) { return EVAL(a, env); }); + } else if (types.vector_Q(ast)) { + var v = ast.map(function(a) { return EVAL(a, env); }); + v.__isvector__ = true; + return v; + } else if (types.hash_map_Q(ast)) { + var new_hm = {}; + for (k in ast) { + new_hm[EVAL(k, env)] = EVAL(ast[k], env); + } + return new_hm; + } else { + return ast; + } +} + +function _EVAL(ast, env) { + while (true) { + //console.log("EVAL:", types._pr_str(ast, true)); + if (!types.list_Q(ast)) { + return eval_ast(ast, env); + } + + // apply list + ast = macroexpand(ast, env); + if (!types.list_Q(ast)) { return ast; } + + var a0 = ast[0], a1 = ast[1], a2 = ast[2], a3 = ast[3]; + switch (a0.value) { + case "def!": + var res = EVAL(a2, env); + return env.set(a1, res); + case "let*": + var let_env = new types.Env(env); + for (var i=0; i < a1.length; i+=2) { + let_env.set(a1[i].value, EVAL(a1[i+1], let_env)); + } + return EVAL(a2, let_env); + case "quote": + return a1; + case "quasiquote": + return EVAL(quasiquote(a1), env); + case 'defmacro!': + var func = EVAL(a2, env); + func._ismacro_ = true; + return env.set(a1, func); + case 'macroexpand': + return macroexpand(a1, env); + case "do": + eval_ast(ast.slice(1, -1), env); + ast = ast[ast.length-1]; + break; + case "if": + var cond = EVAL(a1, env); + if (cond === null || cond === false) { + ast = (typeof a3 !== "undefined") ? a3 : null; + } else { + ast = a2; + } + break; + case "fn*": + return types.new_function(EVAL, a2, env, a1); + default: + var el = eval_ast(ast, env), f = el[0], meta = f.__meta__; + if (meta && meta.exp) { + ast = meta.exp; + env = new types.Env(meta.env, meta.params, el.slice(1)); + } else { + return f.apply(f, el.slice(1)); + } + } + } +} + +function EVAL(ast, env) { + var result = _EVAL(ast, env); + return (typeof result !== "undefined") ? result : null; +} + +// print +function PRINT(exp) { + return types._pr_str(exp, true); +} + +// repl +var repl_env = new types.Env(); +var rep = function(str) { return PRINT(EVAL(READ(str), repl_env)); }; +_ref = function (k,v) { repl_env.set(k, v); } + +// Import types functions +for (var n in types.ns) { repl_env.set(n, types.ns[n]); } + +_ref('read-string', reader.read_str); +_ref('eval', function(ast) { return EVAL(ast, repl_env); }); +_ref('slurp', function(f) { + return require('fs').readFileSync(f, 'utf-8'); +}); +_ref('slurp-do', function(f) { + return '(do ' + require('fs').readFileSync(f, 'utf-8') + ')'; +}); + +// Defined using the language itself +rep("(def! not (fn* (a) (if a false true)))"); +rep("(def! load-file (fn* (f) (eval (read-string (slurp-do f)))))"); + +if (typeof process !== 'undefined' && process.argv.length > 2) { + for (var i=2; i < process.argv.length; i++) { + rep('(load-file "' + process.argv[i] + '")'); + } +} else if (typeof require === 'undefined') { + // Asynchronous browser mode + readline.rlwrap(function(line) { return rep(line); }, + function(exc) { + if (exc instanceof reader.BlankException) { return; } + if (exc.stack) { console.log(exc.stack); } else { console.log(exc); } + }); +} else if (require.main === module) { + // Synchronous node.js commandline mode + while (true) { + var line = readline.readline("user> "); + if (line === null) { break; } + try { + if (line) { console.log(rep(line)); } + } catch (exc) { + if (exc instanceof reader.BlankException) { continue; } + if (exc.stack) { console.log(exc.stack); } else { console.log(exc); } + } + } +} else { + exports.rep = rep; +} diff --git a/js/step9_interop.js b/js/step9_interop.js new file mode 100644 index 0000000..a811c52 --- /dev/null +++ b/js/step9_interop.js @@ -0,0 +1,184 @@ +var types = require('./types'); +var reader = require('./reader'); +if (typeof module !== 'undefined') { + var readline = require('./node_readline'); +} + +// read +function READ(str) { + return reader.read_str(str); +} + +// eval +function is_pair(x) { + return types.sequential_Q(x) && x.length > 0; +} + +function quasiquote(ast) { + if (!is_pair(ast)) { + return [types.symbol("quote"), ast]; + } else if (ast[0].value === 'unquote') { + return ast[1]; + } else if (is_pair(ast[0]) && ast[0][0].value === 'splice-unquote') { + return [types.symbol("concat"), ast[0][1], quasiquote(ast.slice(1))]; + } else { + return [types.symbol("cons"), quasiquote(ast[0]), quasiquote(ast.slice(1))]; + } +} + +function is_macro_call(ast, env) { + return types.list_Q(ast) && + types.symbol_Q(ast[0]) && + env.find(ast[0].value) && + env.get(ast[0].value)._ismacro_; +} + +function macroexpand(ast, env) { + while (is_macro_call(ast, env)) { + var mac = env.get(ast[0]); + ast = mac.apply(mac, ast.slice(1)); + } + return ast; +} + +function eval_ast(ast, env) { + if (types.symbol_Q(ast)) { + return env.get(ast); + } else if (types.list_Q(ast)) { + return ast.map(function(a) { return EVAL(a, env); }); + } else if (types.vector_Q(ast)) { + var v = ast.map(function(a) { return EVAL(a, env); }); + v.__isvector__ = true; + return v; + } else if (types.hash_map_Q(ast)) { + var new_hm = {}; + for (k in ast) { + new_hm[EVAL(k, env)] = EVAL(ast[k], env); + } + return new_hm; + } else { + return ast; + } +} + +function _EVAL(ast, env) { + while (true) { + //console.log("EVAL:", types._pr_str(ast, true)); + if (!types.list_Q(ast)) { + return eval_ast(ast, env); + } + + // apply list + ast = macroexpand(ast, env); + if (!types.list_Q(ast)) { return ast; } + + var a0 = ast[0], a1 = ast[1], a2 = ast[2], a3 = ast[3]; + switch (a0.value) { + case "def!": + var res = EVAL(a2, env); + return env.set(a1, res); + case "let*": + var let_env = new types.Env(env); + for (var i=0; i < a1.length; i+=2) { + let_env.set(a1[i].value, EVAL(a1[i+1], let_env)); + } + return EVAL(a2, let_env); + case "quote": + return a1; + case "quasiquote": + return EVAL(quasiquote(a1), env); + case 'defmacro!': + var func = EVAL(a2, env); + func._ismacro_ = true; + return env.set(a1, func); + case 'macroexpand': + return macroexpand(a1, env); + case "js*": + return eval(a1.toString()); + case ".": + var el = eval_ast(ast.slice(2), env), + f = eval(a1.toString()); + return f.apply(f, el); + case "do": + eval_ast(ast.slice(1, -1), env); + ast = ast[ast.length-1]; + break; + case "if": + var cond = EVAL(a1, env); + if (cond === null || cond === false) { + ast = (typeof a3 !== "undefined") ? a3 : null; + } else { + ast = a2; + } + break; + case "fn*": + return types.new_function(EVAL, a2, env, a1); + default: + var el = eval_ast(ast, env), f = el[0], meta = f.__meta__; + if (meta && meta.exp) { + ast = meta.exp; + env = new types.Env(meta.env, meta.params, el.slice(1)); + } else { + return f.apply(f, el.slice(1)); + } + } + } +} + +function EVAL(ast, env) { + var result = _EVAL(ast, env); + return (typeof result !== "undefined") ? result : null; +} + +// print +function PRINT(exp) { + return types._pr_str(exp, true); +} + +// repl +var repl_env = new types.Env(); +var rep = function(str) { return PRINT(EVAL(READ(str), repl_env)); }; +_ref = function (k,v) { repl_env.set(k, v); } + +// Import types functions +for (var n in types.ns) { repl_env.set(n, types.ns[n]); } + +_ref('read-string', reader.read_str); +_ref('eval', function(ast) { return EVAL(ast, repl_env); }); +_ref('slurp', function(f) { + return require('fs').readFileSync(f, 'utf-8'); +}); +_ref('slurp-do', function(f) { + return '(do ' + require('fs').readFileSync(f, 'utf-8') + ')'; +}); + +// Defined using the language itself +rep("(def! not (fn* (a) (if a false true)))"); +rep("(def! load-file (fn* (f) (eval (read-string (slurp-do f)))))"); + +if (typeof process !== 'undefined' && process.argv.length > 2) { + for (var i=2; i < process.argv.length; i++) { + rep('(load-file "' + process.argv[i] + '")'); + } +} else if (typeof require === 'undefined') { + // Asynchronous browser mode + readline.rlwrap(function(line) { return rep(line); }, + function(exc) { + if (exc instanceof reader.BlankException) { return; } + if (exc.stack) { console.log(exc.stack); } else { console.log(exc); } + }); +} else if (require.main === module) { + // Synchronous node.js commandline mode + while (true) { + var line = readline.readline("user> "); + if (line === null) { break; } + try { + if (line) { console.log(rep(line)); } + } catch (exc) { + if (exc instanceof reader.BlankException) { continue; } + if (exc.stack) { console.log(exc.stack); } else { console.log(exc); } + } + } +} else { + exports.rep = rep; +} diff --git a/js/stepA_more.js b/js/stepA_more.js new file mode 100644 index 0000000..fca3744 --- /dev/null +++ b/js/stepA_more.js @@ -0,0 +1,198 @@ +var types = require('./types'); +var reader = require('./reader'); +if (typeof module !== 'undefined') { + var readline = require('./node_readline'); +} + +// read +function READ(str) { + return reader.read_str(str); +} + +// eval +function is_pair(x) { + return types.sequential_Q(x) && x.length > 0; +} + +function quasiquote(ast) { + if (!is_pair(ast)) { + return [types.symbol("quote"), ast]; + } else if (ast[0].value === 'unquote') { + return ast[1]; + } else if (is_pair(ast[0]) && ast[0][0].value === 'splice-unquote') { + return [types.symbol("concat"), ast[0][1], quasiquote(ast.slice(1))]; + } else { + return [types.symbol("cons"), quasiquote(ast[0]), quasiquote(ast.slice(1))]; + } +} + +function is_macro_call(ast, env) { + return types.list_Q(ast) && + types.symbol_Q(ast[0]) && + env.find(ast[0].value) && + env.get(ast[0].value)._ismacro_; +} + +function macroexpand(ast, env) { + while (is_macro_call(ast, env)) { + var mac = env.get(ast[0]); + ast = mac.apply(mac, ast.slice(1)); + } + return ast; +} + +function eval_ast(ast, env) { + if (types.symbol_Q(ast)) { + return env.get(ast); + } else if (types.list_Q(ast)) { + return ast.map(function(a) { return EVAL(a, env); }); + } else if (types.vector_Q(ast)) { + var v = ast.map(function(a) { return EVAL(a, env); }); + v.__isvector__ = true; + return v; + } else if (types.hash_map_Q(ast)) { + var new_hm = {}; + for (k in ast) { + new_hm[EVAL(k, env)] = EVAL(ast[k], env); + } + return new_hm; + } else { + return ast; + } +} + +function _EVAL(ast, env) { + while (true) { + //console.log("EVAL:", types._pr_str(ast, true)); + if (!types.list_Q(ast)) { + return eval_ast(ast, env); + } + + // apply list + ast = macroexpand(ast, env); + if (!types.list_Q(ast)) { return ast; } + + var a0 = ast[0], a1 = ast[1], a2 = ast[2], a3 = ast[3]; + switch (a0.value) { + case "def!": + var res = EVAL(a2, env); + return env.set(a1, res); + case "let*": + var let_env = new types.Env(env); + for (var i=0; i < a1.length; i+=2) { + let_env.set(a1[i].value, EVAL(a1[i+1], let_env)); + } + return EVAL(a2, let_env); + case "quote": + return a1; + case "quasiquote": + return EVAL(quasiquote(a1), env); + case 'defmacro!': + var func = EVAL(a2, env); + func._ismacro_ = true; + return env.set(a1, func); + case 'macroexpand': + return macroexpand(a1, env); + case "js*": + return eval(a1.toString()); + case ".": + var el = eval_ast(ast.slice(2), env), + f = eval(a1.toString()); + return f.apply(f, el); + case "try*": + try { + return EVAL(a1, env); + } catch (exc) { + if (a2 && a2[0].value === "catch*") { + if (exc instanceof Error) { exc = exc.message; } + return EVAL(a2[2], new types.Env(env, [a2[1]], [exc])); + } else { + throw exc; + } + } + case "do": + eval_ast(ast.slice(1, -1), env); + ast = ast[ast.length-1]; + break; + case "if": + var cond = EVAL(a1, env); + if (cond === null || cond === false) { + ast = (typeof a3 !== "undefined") ? a3 : null; + } else { + ast = a2; + } + break; + case "fn*": + return types.new_function(EVAL, a2, env, a1); + default: + var el = eval_ast(ast, env), f = el[0], meta = f.__meta__; + if (meta && meta.exp) { + ast = meta.exp; + env = new types.Env(meta.env, meta.params, el.slice(1)); + } else { + return f.apply(f, el.slice(1)); + } + } + } +} + +function EVAL(ast, env) { + var result = _EVAL(ast, env); + return (typeof result !== "undefined") ? result : null; +} + +// print +function PRINT(exp) { + return types._pr_str(exp, true); +} + +// repl +var repl_env = new types.Env(); +var rep = function(str) { return PRINT(EVAL(READ(str), repl_env)); }; +_ref = function (k,v) { repl_env.set(k, v); } + +// Import types functions +for (var n in types.ns) { repl_env.set(n, types.ns[n]); } + +_ref('readline', readline.readline) +_ref('read-string', reader.read_str); +_ref('eval', function(ast) { return EVAL(ast, repl_env); }); +_ref('slurp', function(f) { + return require('fs').readFileSync(f, 'utf-8'); +}); +_ref('slurp-do', function(f) { + return '(do ' + require('fs').readFileSync(f, 'utf-8') + ')'; +}); + +// Defined using the language itself +rep("(def! not (fn* (a) (if a false true)))"); +rep("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))"); +rep("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))"); +rep("(def! load-file (fn* (f) (eval (read-string (slurp-do f)))))"); + +if (typeof process !== 'undefined' && process.argv.length > 2) { + for (var i=2; i < process.argv.length; i++) { + rep('(load-file "' + process.argv[i] + '")'); + } +} else if (typeof require === 'undefined') { + // Asynchronous browser mode + readline.rlwrap(function(line) { return rep(line); }, + function(exc) { + if (exc instanceof reader.BlankException) { return; } + if (exc.stack) { console.log(exc.stack); } else { console.log(exc); } + }); +} else if (require.main === module) { + // Synchronous node.js commandline mode + while (true) { + var line = readline.readline("user> "); + if (line === null) { break; } + try { + if (line) { console.log(rep(line)); } + } catch (exc) { + if (exc instanceof reader.BlankException) { continue; } + if (exc.stack) { console.log(exc.stack); } else { console.log(exc); } + } + } +} else { + exports.rep = rep; +} diff --git a/js/tests/common.js b/js/tests/common.js new file mode 100644 index 0000000..a95d79b --- /dev/null +++ b/js/tests/common.js @@ -0,0 +1,15 @@ +fs = require('fs'); +assert = require('assert'); + +function assert_eq(a, b) { + GLOBAL.assert.deepEqual(a, b, a + " !== " + b); +} + +function load(file) { + console.log(process.cwd()); + //process.chdir('../'); + eval(fs.readFileSync(file,'utf8')); +} + +exports.assert_eq = assert_eq; +exports.load = load; diff --git a/js/tests/node_modules b/js/tests/node_modules new file mode 120000 index 0000000..b870225 --- /dev/null +++ b/js/tests/node_modules @@ -0,0 +1 @@ +../
\ No newline at end of file diff --git a/js/tests/reader.js b/js/tests/reader.js new file mode 100644 index 0000000..2aa81c6 --- /dev/null +++ b/js/tests/reader.js @@ -0,0 +1,68 @@ +common = require('./common.js'); +types = require('../types'); +reader = require('../reader'); +var assert_eq = common.assert_eq, + read_str = reader.read_str, + nth = types.ns.nth; + +console.log("Testing read of constants/strings"); +assert_eq(2,read_str('2')); +assert_eq(12345,read_str('12345')); +assert_eq(12345,read_str('12345 "abc"')); +assert_eq('abc',read_str('"abc"')); +assert_eq('a string (with parens)',read_str('"a string (with parens)"')); + +console.log("Testing read of symbols"); +assert(types.symbol_Q(read_str('abc'))); +assert_eq('abc',read_str('abc').value); +assert_eq('.',read_str('.').value); + +console.log("Testing READ_STR of strings"); +assert_eq('a string',read_str('"a string"')); +assert_eq('a string (with parens)',read_str('"a string (with parens)"')); +assert_eq('a string',read_str('"a string"()')); +assert_eq('a string',read_str('"a string"123')); +assert_eq('a string',read_str('"a string"abc')); +assert_eq('',read_str('""')); +assert_eq('abc ',read_str('"abc "')); +assert_eq(' abc',read_str('" abc"')); +assert_eq('$abc',read_str('"$abc"')); +assert_eq('abc$()',read_str('"abc$()"')); +assert_eq('"xyz"',read_str('"\\"xyz\\""')); + + +console.log("Testing READ_STR of lists"); +assert_eq(2,types.ns.count(read_str('(2 3)'))); +assert_eq(2,types.ns.first(read_str('(2 3)'))); +assert_eq(3,types.ns.first(types.ns.rest(read_str('(2 3)')))); +L = read_str('(+ 1 2 "str1" "string (with parens) and \'single quotes\'")'); +assert_eq(5,types.ns.count(L)); +assert_eq('str1',nth(L,3)); +assert_eq('string (with parens) and \'single quotes\'',nth(L,4)); +assert_eq([2,3],read_str('(2 3)')); +assert_eq([2,3, 'string (with parens)'],read_str('(2 3 "string (with parens)")')); + + +console.log("Testing READ_STR of quote/quasiquote"); +assert_eq('quote',nth(read_str('\'1'),0).value); +assert_eq(1,nth(read_str('\'1'),1)); +assert_eq('quote',nth(read_str('\'(1 2 3)'),0).value); +assert_eq(3,nth(nth(read_str('\'(1 2 3)'),1),2)); + +assert_eq('quasiquote',nth(read_str('`1'),0).value); +assert_eq(1,nth(read_str('`1'),1)); +assert_eq('quasiquote',nth(read_str('`(1 2 3)'),0).value); +assert_eq(3,nth(nth(read_str('`(1 2 3)'),1),2)); + +assert_eq('unquote',nth(read_str('~1'),0).value); +assert_eq(1,nth(read_str('~1'),1)); +assert_eq('unquote',nth(read_str('~(1 2 3)'),0).value); +assert_eq(3,nth(nth(read_str('~(1 2 3)'),1),2)); + +assert_eq('splice-unquote',nth(read_str('~@1'),0).value); +assert_eq(1,nth(read_str('~@1'),1)); +assert_eq('splice-unquote',nth(read_str('~@(1 2 3)'),0).value); +assert_eq(3,nth(nth(read_str('~@(1 2 3)'),1),2)); + + +console.log("All tests completed"); diff --git a/js/tests/step5_tco.js b/js/tests/step5_tco.js new file mode 100644 index 0000000..60c0576 --- /dev/null +++ b/js/tests/step5_tco.js @@ -0,0 +1,22 @@ +common = require('./common.js'); +var assert_eq = common.assert_eq; +var rep = require('../step5_tco.js').rep; + +console.log("Testing Stack Exhaustion Function"); +rep('(def! sum-to (fn* (n) (if (= n 0) 0 (+ n (sum-to (- n 1))))))'); +try { + rep('(sum-to 10000)'); + throw new Error("Did not get expected stack exhaustion"); +} catch (e) { + if (e.toString().match(/RangeError/)) { + console.log("Got expected stack exhaustion"); + } else { + throw new Error("Unexpected error: " + e); + } +} + +console.log("Testing Tail Call Optimization/Elimination"); +rep('(def! sum2 (fn* (n acc) (if (= n 0) acc (sum2 (- n 1) (+ n acc)))))'); +rep('(sum2 10000 0)'); + +console.log("All tests completed"); diff --git a/js/tests/types.js b/js/tests/types.js new file mode 100644 index 0000000..71b276f --- /dev/null +++ b/js/tests/types.js @@ -0,0 +1,94 @@ +common = require('./common.js'); +var assert_eq = common.assert_eq; +var types = require('../types.js'); +var symbol = types.symbol, + hash_map = types.ns['hash-map'], + hash_map_Q = types.ns['map?'], + assoc = types.ns['assoc'], + dissoc = types.ns['dissoc'], + get = types.ns['get'], + contains_Q = types.ns['contains?'], + count = types.ns['count'], + equal_Q = types.ns['=']; + + +console.log("Testing hash_maps"); +X = hash_map(); +assert_eq(true, hash_map_Q(X)); + +assert_eq(null, get(X,'a')); +assert_eq(false, contains_Q(X, 'a')); +X1 = assoc(X, 'a', "value of X a"); +assert_eq(null, get(X,'a')); +assert_eq(false, contains_Q(X, 'a')); +assert_eq("value of X a", get(X1, 'a')); +assert_eq(true, contains_Q(X1, 'a')); + +Y = hash_map(); +assert_eq(0, count(Y)); +Y1 = assoc(Y, 'a', "value of Y a"); +assert_eq(1, count(Y1)); +Y2 = assoc(Y1, 'b', "value of Y b"); +assert_eq(2, count(Y2)); +assert_eq("value of Y a", get(Y2, 'a')); +assert_eq("value of Y b", get(Y2, 'b')); + +X2 = assoc(X1, 'b', Y2); +assert_eq(2, count(Y2)); + +assert_eq(true, hash_map_Q(get(X2,'b'))); + +assert_eq('value of Y a', get(get(X2,'b'),'a')); +assert_eq('value of Y b', get(get(X2,'b'),'b')); + +Y3 = dissoc(Y2, 'a'); +assert_eq(2, count(Y2)); +assert_eq(1, count(Y3)); +assert_eq(null, get(Y3, 'a')); +Y4 = dissoc(Y3, 'b'); +assert_eq(0, count(Y4)); +assert_eq(null, get(Y4, 'b')); + + +console.log("Testing equal? function"); +assert_eq(true, equal_Q(2,2)); +assert_eq(false, equal_Q(2,3)); +assert_eq(false, equal_Q(2,3)); +assert_eq(true, equal_Q("abc","abc")); +assert_eq(false, equal_Q("abc","abz")); +assert_eq(false, equal_Q("zbc","abc")); +assert_eq(true, equal_Q(symbol("abc"),symbol("abc"))); +assert_eq(false, equal_Q(symbol("abc"),symbol("abz"))); +assert_eq(false, equal_Q(symbol("zbc"),symbol("abc"))); +L6 = [1, 2, 3]; +L7 = [1, 2, 3]; +L8 = [1, 2, "Z"]; +L9 = ["Z", 2, 3]; +L10 = [1, 2]; +assert_eq(true, equal_Q(L6, L7)); +assert_eq(false, equal_Q(L6, L8)); +assert_eq(false, equal_Q(L6, L9)); +assert_eq(false, equal_Q(L6, L10)); +assert_eq(false, equal_Q(L10, L6)); + + +console.log("Testing ENV (1 level)") +env1 = new types.Env(); +assert_eq('val_a',env1.set('a','val_a')); +assert_eq('val_b',env1.set('b','val_b')); +assert_eq('val_eq',env1.set('=','val_eq')); +assert_eq('val_a',env1.get('a')); +assert_eq('val_b',env1.get('b')); +assert_eq('val_eq',env1.get('=')); + +console.log("Testing ENV (2 levels)"); +env2 = new types.Env(env1); +assert_eq('val_b2',env2.set('b','val_b2')); +assert_eq('val_c',env2.set('c','val_c')); +assert_eq(env1,env2.find('a')); +assert_eq(env2,env2.find('b')); +assert_eq(env2,env2.find('c')); +assert_eq('val_a', env2.get('a')); +assert_eq('val_b2',env2.get('b')); +assert_eq('val_c', env2.get('c')); + diff --git a/js/types.js b/js/types.js new file mode 100644 index 0000000..062b0dd --- /dev/null +++ b/js/types.js @@ -0,0 +1,429 @@ +// Node vs browser behavior +var types = {}; +if (typeof module === 'undefined') { + var exports = types; +} + +// General utility functions + +// Clone a function +Function.prototype.clone = function() { + var that = this; + var temp = function () { return that.apply(this, arguments); }; + for( key in this ) { + temp[key] = this[key]; + } + return temp; +}; + +function _clone (obj) { + var new_obj; + switch (obj_type(obj)) { + case 'list': + new_obj = obj.slice(0); + break; + case 'vector': + new_obj = obj.slice(0); + new_obj.__isvector__ = true; + break; + case 'hash-map': + new_obj = {}; + for (var k in obj) { + if (obj.hasOwnProperty(k)) { new_obj[k] = obj[k]; } + } + break; + case 'function': + new_obj = obj.clone(); + break; + default: + throw new Error("clone of non-collection: " + obj_type(obj)); + } + return new_obj; +} + + + + +function nil_Q(a) { return a === null ? true : false; } +function true_Q(a) { return a === true ? true : false; } +function false_Q(a) { return a === false ? true : false; } + +function obj_type(obj) { + if (symbol_Q(obj)) { return 'symbol'; } + else if (list_Q(obj)) { return 'list'; } + else if (vector_Q(obj)) { return 'vector'; } + else if (hash_map_Q(obj)) { return 'hash-map'; } + else if (nil_Q(obj)) { return 'nil'; } + else if (true_Q(obj)) { return 'true'; } + else if (false_Q(obj)) { return 'false'; } + else if (atom_Q(obj)) { return 'atom'; } + else { + switch (typeof(obj)) { + case 'number': return 'number'; + case 'function': return 'function'; + case 'string': return 'string'; + default: throw new Error("Unknown type '" + typeof(obj) + "'"); + } + } +} + +function _pr_str(obj, print_readably) { + if (typeof print_readably === 'undefined') { print_readably = true; } + var _r = print_readably; + var ot = obj_type(obj); + switch (ot) { + case 'list': + var ret = obj.map(function(e) { return _pr_str(e,_r); }); + return "(" + ret.join(' ') + ")"; + case 'vector': + var ret = obj.map(function(e) { return _pr_str(e,_r); }); + return "[" + ret.join(' ') + "]"; + case 'hash-map': + var ret = []; + for (var k in obj) { + ret.push(_pr_str(k,_r), _pr_str(obj[k],_r)); + } + return "{" + ret.join(' ') + "}"; + case 'string': + if (print_readably) { + return '"' + obj.replace(/\\/, "\\\\").replace(/"/g, '\\"') + '"'; + } else { + return obj; + } + case 'nil': + return "nil"; + case 'atom': + return "(atom " + _pr_str(obj.val,_r) + ")"; + default: + return obj.toString(); + } +} + +function pr_str() { + return Array.prototype.map.call(arguments,function(exp) { + return _pr_str(exp, true); + }).join(" "); +} + +function str() { + return Array.prototype.map.call(arguments,function(exp) { + return _pr_str(exp, false); + }).join(""); +} + +function prn() { + console.log.apply(console, Array.prototype.map.call(arguments,function(exp) { + return _pr_str(exp, true); + })); +} + +function println() { + console.log.apply(console, Array.prototype.map.call(arguments,function(exp) { + return _pr_str(exp, false); + })); +} + +function with_meta(obj, m) { + var new_obj = _clone(obj); + new_obj.__meta__ = m; + return new_obj; +} + +function meta(obj) { + // TODO: support symbols and atoms + if ((!sequential_Q(obj)) && + (!(hash_map_Q(obj))) && + (!(function_Q(obj)))) { + throw new Error("attempt to get metadata from: " + obj_type(obj)); + } + return obj.__meta__; +} + + +function equal_Q (a, b) { + var ota = obj_type(a), otb = obj_type(b); + if (!(ota === otb || (sequential_Q(a) && sequential_Q(b)))) { + return false; + } + switch (ota) { + case 'symbol': return a.value === b.value; + case 'list': + case 'vector': + if (a.length !== b.length) { return false; } + for (var i=0; i<a.length; i++) { + if (! equal_Q(a[i], b[i])) { return false; } + } + return true; + case 'hash-map': + var akeys = Object.keys(a).sort(), + bkeys = Object.keys(b).sort(); + if (akeys.length !== bkeys.length) { return false; } + for (var i=0; i<akeys.length; i++) { + if (akeys[i] !== bkeys[i]) { return false; } + if (! equal_Q(a[akeys[i]], b[bkeys[i]])) { return false; } + } + return true; + default: + return a === b; + } +} + + + +// Symbols +function Symbol(name) { + this.value = name; + return this; +} +Symbol.prototype.toString = function() { return this.value; } + +function symbol(name) { return new Symbol(name); } + +function symbol_Q(obj) { return obj instanceof Symbol; } + + +// Functions +function new_function(func, exp, env, params) { + var f = function() { + // TODO: figure out why this throws with 'and' macro + //throw new Error("Attempt to invoke mal function directly"); + return func(exp, new Env(env, params, arguments)); + }; + f.__meta__ = {exp: exp, env: env, params: params}; + return f; + +} +function function_Q(f) { return typeof f == "function"; } + + + +// Errors/Exceptions +function mal_throw(exc) { throw exc; } + + +// Vectors +function vector() { + var v = Array.prototype.slice.call(arguments, 0); + v.__isvector__ = true; + return v; +} + +function vector_Q(v) { return Array.isArray(v) && v.__isvector__; } + + +// Lists + +function list() { + return Array.prototype.slice.call(arguments, 0); +} + +function list_Q(lst) { return Array.isArray(lst) && !lst.__isvector__; } + + +// Hash Maps + +function hash_map() { + if (arguments.length % 2 === 1) { + throw new Error("Odd number of hash map arguments"); + } + var args = [{}].concat(Array.prototype.slice.call(arguments, 0)); + return assoc_BANG.apply(null, args); +} + +function hash_map_Q(hm) { + return typeof hm === "object" && + !Array.isArray(hm) && + !(hm === null) && + !(hm instanceof Atom); +} + +function assoc_BANG(hm) { + if (arguments.length % 2 !== 1) { + throw new Error("Odd number of assoc arguments"); + } + for (var i=1; i<arguments.length; i+=2) { + var ktoken = arguments[i], + vtoken = arguments[i+1]; + // TODO: support more than string keys + //if (list_Q(ktoken) && hash_map_Q(ktoken)) { + // throw new Error("expected hash-map key atom, got collection"); + //} + if (typeof ktoken !== "string") { + throw new Error("expected hash-map key string, got: " + (typeof ktoken)); + } + hm[ktoken] = vtoken; + } + return hm; +} + +function assoc(src_hm) { + var hm = _clone(src_hm); + var args = [hm].concat(Array.prototype.slice.call(arguments, 1)); + return assoc_BANG.apply(null, args); +} + +function dissoc_BANG(hm) { + for (var i=1; i<arguments.length; i++) { + var ktoken = arguments[i]; + delete hm[ktoken]; + } + return hm; +} + +function dissoc(src_hm) { + var hm = _clone(src_hm); + var args = [hm].concat(Array.prototype.slice.call(arguments, 1)); + return dissoc_BANG.apply(null, args); +} + +function get(hm, key) { + if (key in hm) { + return hm[key]; + } else { + return null; + } +} + +function contains_Q(hm, key) { + if (key in hm) { return true; } else { return false; } +} + +function keys(hm) { return Object.keys(hm); } +function vals(hm) { return Object.keys(hm).map(function(k) { return hm[k]; }); } + + +// Atoms +function Atom(val) { this.val = val; } +function atom(val) { return new Atom(val); } +function atom_Q(atm) { return atm instanceof Atom; } +function deref(atm) { return atm.val; } +function reset_BANG(atm, val) { return atm.val = val; } +function swap_BANG(atm, f) { + var args = [atm.val].concat(Array.prototype.slice.call(arguments, 2)); + atm.val = f.apply(f, args); + return atm.val; +} + + +// Sequence operations +function sequential_Q(lst) { return list_Q(lst) || vector_Q(lst); } + +function nth(lst, idx) { return lst[idx]; } + +function count(s) { + if (Array.isArray(s)) { return s.length; } + else { return Object.keys(s).length; } +} + +function empty_Q(lst) { return lst.length === 0; } + +function cons(a, b) { return [a].concat(b); } + +function concat(lst) { + lst = lst || []; + return lst.concat.apply(lst, Array.prototype.slice.call(arguments, 1)); +} + +function conj(lst) { + return lst.concat(Array.prototype.slice.call(arguments, 1)); +} + +function first(lst) { return lst[0]; } + +function rest(lst) { return lst.slice(1); } + + + +// General list related functions +function apply(f) { + var args = Array.prototype.slice.call(arguments, 1); + return f.apply(f, args.slice(0, args.length-1).concat(args[args.length-1])); +} + +function map(f, lst) { + return lst.map(function(el){ return f(el); }); +} + + +// Env implementation +function Env(outer, binds, exprs) { + this.data = {}; + this.outer = outer || null; + + if (binds && exprs) { + // Returns a new Env with symbols in binds bound to + // corresponding values in exprs + // TODO: check types of binds and exprs and compare lengths + for (var i=0; i<binds.length;i++) { + if (binds[i].value === "&") { + // variable length arguments + this.data[binds[i+1].value] = Array.prototype.slice.call(exprs, i); + break; + } else { + this.data[binds[i].value] = exprs[i]; + } + } + } + return this; +} +Env.prototype.find = function (key) { + if (key in this.data) { return this; } + else if (this.outer) { return this.outer.find(key); } + else { return null; } +}; +Env.prototype.set = function(key, value) { this.data[key] = value; return value; }, +Env.prototype.get = function(key) { + var env = this.find(key); + if (!env) { throw new Error("'" + key + "' not found"); } + return env.data[key]; +}; + +// types.ns is namespace of type functions +var ns = {'pr-str': pr_str, 'str': str, 'prn': prn, 'println': println, + 'with-meta': with_meta, 'meta': meta, + type: obj_type, '=': equal_Q, + symbol: symbol, 'symbol?': symbol_Q, + 'nil?': nil_Q, 'true?': true_Q, 'false?': false_Q, + '<' : function(a,b){return a<b;}, + '<=' : function(a,b){return a<=b;}, + '>' : function(a,b){return a>b;}, + '>=' : function(a,b){return a>=b;}, + '+' : function(a,b){return a+b;}, + '-' : function(a,b){return a-b;}, + '*' : function(a,b){return a*b;}, + '/' : function(a,b){return a/b;}, + 'throw': mal_throw, + 'list': list, 'list?': list_Q, + 'vector': vector, 'vector?': vector_Q, + 'hash-map': hash_map, 'map?': hash_map_Q, + 'assoc': assoc, 'dissoc': dissoc, 'get': get, + 'contains?': contains_Q, 'keys': keys, 'vals': vals, + 'atom': atom, 'atom?': atom_Q, + "deref": deref, "reset!": reset_BANG, "swap!": swap_BANG, + 'sequential?': sequential_Q, 'cons': cons, 'nth': nth, + 'empty?': empty_Q, 'count': count, 'concat': concat, + 'conj': conj, 'first': first, 'rest': rest, + 'apply': apply, 'map': map}; + +exports.ns = types.ns = ns; +exports._pr_str = types._pr_str = _pr_str; +exports.prn = types.prn = prn; +exports.Env = types.Env = Env; + +exports.symbol = types.symbol = symbol; +exports.symbol_Q = types.symbol_Q = symbol_Q; +exports.hash_map = types.hash_map = hash_map; +exports.hash_map_Q = types.hash_map_Q = hash_map_Q; +exports.new_function = types.new_function = new_function; +exports.list = types.list = list; +exports.list_Q = types.list_Q = list_Q; +exports.vector = types.vector = vector; +exports.vector_Q = types.vector_Q = vector_Q; + +exports.sequential_Q = types.sequential_Q = sequential_Q; +exports.cons = types.cons = cons; +exports.concat = types.concat = concat; +exports.first = types.first = first; +exports.rest = types.rest = rest; +exports.apply = types.apply = apply; +exports.map = types.map = map; diff --git a/make/Makefile b/make/Makefile new file mode 100644 index 0000000..1110397 --- /dev/null +++ b/make/Makefile @@ -0,0 +1,23 @@ + +TESTS = tests/types.mk tests/reader.mk tests/step9_interop.mk + +SOURCES = util.mk readline.mk gmsl.mk types.mk reader.mk stepA_more.mk + +mal.mk: $(SOURCES) + echo "#!/usr/bin/make -f" > $@ + cat $+ | grep -v "^include " >> $@ + chmod +x $@ + +clean: + rm -f mal.mk + +.PHONY: stats tests $(TESTS) + +stats: $(SOURCES) + @wc $^ + +tests: $(TESTS) + +$(TESTS): + @echo "Running $@"; \ + make -f $@ || exit 1; \ diff --git a/make/gmsl.mk b/make/gmsl.mk new file mode 100644 index 0000000..e988d2c --- /dev/null +++ b/make/gmsl.mk @@ -0,0 +1,115 @@ +# +# mal (Make Lisp) trimmed and namespaced GMSL functions/definitions +# - derived from the GMSL 1.1.3 +# + +ifndef __mal_gmsl_included +__mal_gmsl_included := true + +# ---------------------------------------------------------------------------- +# +# GNU Make Standard Library (GMSL) +# +# A library of functions to be used with GNU Make's $(call) that +# provides functionality not available in standard GNU Make. +# +# Copyright (c) 2005-2013 John Graham-Cumming +# +# This file is part of GMSL +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# Neither the name of the John Graham-Cumming nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +# ---------------------------------------------------------------------------- + + +# Numbers +__gmsl_sixteen := x x x x x x x x x x x x x x x x +__gmsl_input_int := $(foreach a,$(__gmsl_sixteen), \ + $(foreach b,$(__gmsl_sixteen), \ + $(foreach c,$(__gmsl_sixteen), \ + $(__gmsl_sixteen))))) + +int_decode = $(words $1) +int_encode = $(wordlist 1,$1,$(__gmsl_input_int)) + +__gmsl_int_wrap = $(call int_decode,$(call $1,$(call int_encode,$2),$(call int_encode,$3))) + +int_plus = $(strip $1 $2) +int_subtract = $(strip $(if $(call int_gte,$1,$2), \ + $(filter-out xx,$(join $1,$2)), \ + $(warning Subtraction underflow))) +int_multiply = $(strip $(foreach a,$1,$2)) +# _error function must be provided to report/catch division by zero +int_divide = $(strip $(if $2, \ + $(if $(call int_gte,$1,$2), \ + x $(call int_divide,$(call int_subtract,$1,$2),$2),), \ + $(call _error,Division by zero))) + +int_max = $(subst xx,x,$(join $1,$2)) +int_min = $(subst xx,x,$(filter xx,$(join $1,$2))) +int_gt = $(strip $(filter-out $(words $2),$(words $(call int_max,$1,$2)))) +int_gte = $(strip $(call int_gt,$1,$2)$(call int_eq,$1,$2)) +int_lt = $(strip $(filter-out $(words $1),$(words $(call int_max,$1,$2)))) +int_lte = $(strip $(call int_lt,$1,$2)$(call int_eq,$1,$2)) +int_eq = $(strip $(filter $(words $1),$(words $2))) +int_ne = $(strip $(filter-out $(words $1),$(words $2))) + +gmsl_plus = $(call __gmsl_int_wrap,int_plus,$1,$2) +gmsl_subtract = $(call __gmsl_int_wrap,int_subtract,$1,$2) +gmsl_multiply = $(call __gmsl_int_wrap,int_multiply,$1,$2) +gmsl_divide = $(call __gmsl_int_wrap,int_divide,$1,$2) + + +# Strings + +__gmsl_characters := A B C D E F G H I J K L M N O P Q R S T U V W X Y Z +__gmsl_characters += a b c d e f g h i j k l m n o p q r s t u v w x y z +__gmsl_characters += 0 1 2 3 4 5 6 7 8 9 +__gmsl_characters += ` ~ ! @ \# $$ % ^ & * ( ) - _ = + +__gmsl_characters += { } [ ] \ : ; ' " < > , . / ? | +__syntax_highlight_protect = #"'` + + +__gmsl_space := +__gmsl_space += + +gmsl_strlen = $(strip $(eval __temp := $(subst $(__gmsl_space),x,$1)) \ + $(foreach a,$(__gmsl_characters),$(eval __temp := $$(subst $$a,x,$(__temp)))) \ + $(eval __temp := $(subst x,x ,$(__temp))) \ + $(words $(__temp))) + +gmsl_merge = $(strip $(if $2, \ + $(if $(call _EQ,1,$(words $2)), \ + $2,$(firstword $2)$1$(call gmsl_merge,$1,$(wordlist 2,$(words $2),$2))))) + +gmsl_pairmap = $(strip \ + $(if $2$3,$(call $1,$(word 1,$2),$(word 1,$3)) \ + $(call gmsl_pairmap,$1,$(wordlist 2,$(words $2),$2),$(wordlist 2,$(words $3),$3)))) + +endif diff --git a/make/reader.mk b/make/reader.mk new file mode 100755 index 0000000..ce3b078 --- /dev/null +++ b/make/reader.mk @@ -0,0 +1,170 @@ +# +# mal (Make Lisp) Parser/Reader +# + +ifndef __mal_reader_included +__mal_reader_included := true + +_TOP_DIR := $(dir $(lastword $(MAKEFILE_LIST))) +include $(_TOP_DIR)util.mk +include $(_TOP_DIR)types.mk +include $(_TOP_DIR)readline.mk + +READER_DEBUG ?= + +_TOKEN_DELIMS := $(SEMI) $(COMMA) $(DQUOTE) $(QQUOTE) $(_SP) $(_NL) $(_LC) $(_RC) $(_LP) $(_RP) $(LBRACKET) $(RBRACKET) + +define READ_NUMBER +$(foreach ch,$(word 1,$($(1))),\ + $(if $(ch),\ + $(if $(filter $(_TOKEN_DELIMS),$(ch)),\ + ,\ + $(if $(filter-out $(NUMBERS),$(ch)),\ + $(call _error,Invalid number character '$(ch)'),\ + $(eval $(1) := $(wordlist 2,$(words $($(1))),$($(1))))\ + $(and $(READER_DEBUG),$(info READ_NUMBER ch: $(ch) | $($(1))))\ + $(ch)$(strip $(call READ_NUMBER,$(1))))),\ + )) +endef + +define READ_STRING +$(foreach ch,$(word 1,$($(1))),\ + $(if $(ch),\ + $(if $(and $(filter \,$(ch)),$(filter $(DQUOTE),$(word 2,$($(1))))),\ + $(eval $(1) := $(wordlist 3,$(words $($(1))),$($(1))))\ + $(and $(READER_DEBUG),$(info READ_STRING ch: \$(word 1,$($(1))) | $($(1))))\ + $(DQUOTE) $(strip $(call READ_STRING,$(1))),\ + $(if $(filter $(DQUOTE),$(ch)),\ + ,\ + $(eval $(1) := $(wordlist 2,$(words $($(1))),$($(1))))\ + $(and $(READER_DEBUG),$(info READ_STRING ch: $(ch) | $($(1))))\ + $(ch) $(strip $(call READ_STRING,$(1))))),)) +endef + +define READ_SYMBOL +$(foreach ch,$(word 1,$($(1))),\ + $(if $(ch),\ + $(if $(filter $(_TOKEN_DELIMS),$(ch)),\ + ,\ + $(eval $(1) := $(wordlist 2,$(words $($(1))),$($(1))))\ + $(and $(READER_DEBUG),$(info READ_SYMBOL ch: $(ch) | $($(1))))\ + $(ch)$(strip $(call READ_SYMBOL,$(1)))),\ + )) +endef + +define READ_ATOM +$(foreach ch,$(word 1,$($(1))),\ + $(if $(filter $(NUMBERS),$(ch)),\ + $(call number,$(call READ_NUMBER,$(1))),\ + $(if $(filter $(DQUOTE),$(ch)),\ + $(eval $(1) := $(wordlist 2,$(words $($(1))),$($(1))))\ + $(call _string,$(strip $(call READ_STRING,$(1))))\ + $(eval $(if $(filter $(DQUOTE),$(word 1,$($(1)))),\ + $(eval $(1) := $(wordlist 2,$(words $($(1))),$($(1)))),\ + $(call _error,Expected '$(DQUOTE)' in; $($(1))))),\ + $(foreach sym,$(call READ_SYMBOL,$(1)),\ + $(if $(call _EQ,nil,$(sym)),\ + $(__nil),\ + $(if $(call _EQ,true,$(sym)),\ + $(__true),\ + $(if $(call _EQ,false,$(sym)),\ + $(__false),\ + $(call symbol,$(sym))))))))) +endef + +# read and return tokens until $(2) found +define READ_UNTIL +$(and $(READER_DEBUG),$(info READ_UNTIL: $($(1)) [$(2) $(3)])) +$(foreach ch,$(word 1,$($(1))),\ + $(if $(ch),\ + $(if $(filter $(2),$(ch)),\ + ,\ + $(call READ_FORM,$(1))\ + $(call READ_UNTIL,$(1),$(2),$(3))),\ + $(call _error,Expected '$(3)'))) +endef + +define DROP_UNTIL +$(and $(READER_DEBUG),$(info DROP_UNTIL: $($(1)) [$(2)])) +$(foreach ch,$(word 1,$($(1))),\ + $(if $(ch),\ + $(if $(filter $(2),$(ch)),\ + ,\ + $(eval $(1) := $(wordlist 2,$(words $($(1))),$($(1))))\ + $(call DROP_UNTIL,$(1),$(2),$(3))),\ + )) +endef + +define READ_SPACES +$(and $(READER_DEBUG),$(info READ_SPACES: $($(1)))) +$(foreach ch,$(word 1,$($(1))),\ + $(if $(filter $(_SP) $(_NL) $(COMMA),$(ch)),\ + $(eval $(1) := $(wordlist 2,$(words $($(1))),$($(1))))\ + $(call READ_SPACES,$(1)),)) +endef + +define READ_FORM +$(and $(READER_DEBUG),$(info READ_FORM: $($(1)))) +$(call READ_SPACES,$(1)) +$(foreach ch,$(word 1,$($(1))),\ + $(if $(filter $(SEMI),$(ch)),\ + $(call DROP_UNTIL,$(1),$(_NL)),\ + $(if $(filter $(SQUOTE),$(ch)),\ + $(eval $(1) := $(wordlist 2,$(words $($(1))),$($(1))))\ + $(call list,$(call symbol,quote) $(strip $(call READ_FORM,$(1)))),\ + $(if $(filter $(QQUOTE),$(ch)),\ + $(eval $(1) := $(wordlist 2,$(words $($(1))),$($(1))))\ + $(call list,$(call symbol,quasiquote) $(strip $(call READ_FORM,$(1)))),\ + $(if $(filter $(UNQUOTE),$(ch)),\ + $(eval $(1) := $(wordlist 2,$(words $($(1))),$($(1))))\ + $(call list,$(call symbol,unquote) $(strip $(call READ_FORM,$(1)))),\ + $(if $(filter $(_SUQ),$(ch)),\ + $(eval $(1) := $(wordlist 2,$(words $($(1))),$($(1))))\ + $(call list,$(call symbol,splice-unquote) $(strip $(call READ_FORM,$(1)))),\ + $(if $(filter $(CARET),$(ch)),\ + $(eval $(1) := $(wordlist 2,$(words $($(1))),$($(1))))\ + $(foreach meta,$(strip $(call READ_FORM,$(1))),\ + $(call list,$(call symbol,with-meta) $(strip $(call READ_FORM,$(1))) $(meta))),\ + $(if $(filter $(ATSIGN),$(ch)),\ + $(eval $(1) := $(wordlist 2,$(words $($(1))),$($(1))))\ + $(call list,$(call symbol,deref) $(strip $(call READ_FORM,$(1)))),\ + $(if $(filter $(_RC),$(ch)),\ + $(call _error,Unexpected '$(RCURLY)'),\ + $(if $(filter $(_LC),$(ch)),\ + $(eval $(1) := $(wordlist 2,$(words $($(1))),$($(1))))\ + $(foreach thm,$(call hash_map),\ + $(call do,$(call _assoc_seq!,$(thm),$(strip $(call READ_UNTIL,$(1),$(_RC),$(RCURLY)))))\ + $(eval $(if $(filter $(_RC),$(word 1,$($(1)))),\ + $(eval $(1) := $(wordlist 2,$(words $($(1))),$($(1)))),\ + $(call _error,Expected '$(RCURLY)')))\ + $(thm)),\ + $(if $(filter $(_RP),$(ch)),\ + $(call _error,Unexpected '$(RPAREN)'),\ + $(if $(filter $(_LP),$(ch)),\ + $(eval $(1) := $(wordlist 2,$(words $($(1))),$($(1))))\ + $(foreach tlist,$(call _list),\ + $(eval $(foreach item,$(strip $(call READ_UNTIL,$(1),$(_RP),$(RPAREN))),\ + $(call do,$(call _conj!,$(tlist),$(item)))))\ + $(eval $(if $(filter $(_RP),$(word 1,$($(1)))),\ + $(eval $(1) := $(wordlist 2,$(words $($(1))),$($(1)))),\ + $(call _error,Expected '$(RPAREN)')))\ + $(tlist)),\ + $(if $(filter $(RBRACKET),$(ch)),\ + $(call _error,Unexpected '$(RBRACKET)'),\ + $(if $(filter $(LBRACKET),$(ch)),\ + $(eval $(1) := $(wordlist 2,$(words $($(1))),$($(1))))\ + $(foreach tvec,$(call _vector),\ + $(eval $(foreach item,$(strip $(call READ_UNTIL,$(1),$(RBRACKET),$(RBRACKET))),\ + $(call do,$(call _conj!,$(tvec),$(item)))))\ + $(eval $(if $(filter $(RBRACKET),$(word 1,$($(1)))),\ + $(eval $(1) := $(wordlist 2,$(words $($(1))),$($(1)))),\ + $(call _error,Expected '$(RBRACKET)')))\ + $(tvec)),\ + $(call READ_ATOM,$(1)))))))))))))))) +$(call READ_SPACES,$(1)) +endef + +# read-str from a raw "string" or from a string object +READ_STR = $(strip $(eval __reader_temp := $(call str_encode,$(if $(call _string?,$(1)),$(call str_decode,$($(1)_value)),$(1))))$(call READ_FORM,__reader_temp)) + +endif diff --git a/make/readline.mk b/make/readline.mk new file mode 100644 index 0000000..1208f5c --- /dev/null +++ b/make/readline.mk @@ -0,0 +1,15 @@ +# +# mal (Make Lisp) shell readline wrapper +# + +ifndef __mal_readline_included +__mal_readline_included := true + +# Call bash read/readline. Since each call is in a separate shell +# instance we need to restore and save after each call in order to +# have readline history. +READLINE_EOF := +READLINE_HISTORY_FILE := $${HOME}/.mal-history +READLINE = $(eval __readline_temp := $(shell history -r $(READLINE_HISTORY_FILE); read -u 0 -r -e -p $(if $(1),$(1),"user> ") line && history -s -- "$${line}" && history -a $(READLINE_HISTORY_FILE) && echo "$${line}" || echo "__||EOF||__"))$(if $(filter __||EOF||__,$(__readline_temp)),$(eval READLINE_EOF := yes),$(__readline_temp)) + +endif diff --git a/make/step0_repl.mk b/make/step0_repl.mk new file mode 100644 index 0000000..b8b1309 --- /dev/null +++ b/make/step0_repl.mk @@ -0,0 +1,26 @@ +# +# mal (Make Lisp) +# +_TOP_DIR := $(dir $(lastword $(MAKEFILE_LIST))) +include $(_TOP_DIR)readline.mk + +SHELL := /bin/bash + +define READ +$(call READLINE) +endef + +define EVAL +$(if $(READLINE_EOF),,\ + $(if $(findstring =,$(1)),$(eval $(1))$($(word 1,$(1))),$(eval __return := $(1))$(__return))) +endef + +define PRINT +$(1) +endef + +REP = $(call PRINT,$(strip $(call EVAL,$(strip $(call READ))))) +REPL = $(info $(call REP))$(if $(READLINE_EOF),,$(call REPL)) + +# Call the read-eval-print loop +$(call REPL) diff --git a/make/step1_read_print.mk b/make/step1_read_print.mk new file mode 100644 index 0000000..710cd1d --- /dev/null +++ b/make/step1_read_print.mk @@ -0,0 +1,31 @@ +# +# mal (Make Lisp) +# +_TOP_DIR := $(dir $(lastword $(MAKEFILE_LIST))) +include $(_TOP_DIR)types.mk +include $(_TOP_DIR)reader.mk + +SHELL := /bin/bash +INTERACTIVE ?= yes + +# READ: read and parse input +define READ +$(if $(READLINE_EOF)$(__ERROR),,$(call READ_STR,$(if $(1),$(1),$(call READLINE,"user> ")))) +endef + +# EVAL: just return the input +define EVAL +$(if $(READLINE_EOF)$(__ERROR),,$(1)) +endef + +# PRINT: +define PRINT +$(if $(__ERROR),Error: $(call _pr_str,$(__ERROR),yes),$(if $(1),$(call _pr_str,$(1),yes)))$(if $(__ERROR),$(eval __ERROR :=),) +endef + +# REPL: read, eval, print, loop +REP = $(call PRINT,$(strip $(call EVAL,$(strip $(call READ,$(1))),$(REPL_ENV)))) +REPL = $(info $(call REP,$(call READLINE,"user> ")))$(if $(READLINE_EOF),,$(call REPL)) + +# Call the read-eval-print loop +$(if $(strip $(INTERACTIVE)),$(call REPL)) diff --git a/make/step2_eval.mk b/make/step2_eval.mk new file mode 100644 index 0000000..62cd415 --- /dev/null +++ b/make/step2_eval.mk @@ -0,0 +1,71 @@ +# +# mal (Make Lisp) +# +_TOP_DIR := $(dir $(lastword $(MAKEFILE_LIST))) +include $(_TOP_DIR)types.mk +include $(_TOP_DIR)reader.mk + +SHELL := /bin/bash +INTERACTIVE ?= yes +EVAL_DEBUG ?= + +# READ: read and parse input +define READ +$(if $(READLINE_EOF)$(__ERROR),,$(call READ_STR,$(if $(1),$(1),$(call READLINE,"user> ")))) +endef + +# EVAL: evaluate the parameter +define EVAL_AST +$(strip \ + $(and $(EVAL_DEBUG),$(info EVAL_AST: $(call _pr_str,$(1))))\ + $(if $(call _symbol?,$(1)),\ + $(foreach key,$($(1)_value),\ + $(if $(call _contains?,$(2),$(key)),\ + $(call _get,$(2),$(key)),\ + $(call _error,'$(key)' not found in REPL_ENV ($(2))))),\ + $(if $(call _list?,$(1)),\ + $(call _smap,EVAL,$(1),$(2)),\ + $(if $(call _vector?,$(1)),\ + $(call _smap_vec,EVAL,$(1),$(2)),\ + $(if $(call _hash_map?,$(1)),\ + $(foreach new_hmap,$(call __new_obj,hmap),\ + $(foreach v,$(call __get_obj_values,$(1)),\ + $(eval $(v:$(1)_%=$(new_hmap)_%) := $(call EVAL,$($(v)),$(2))))\ + $(eval $(new_hmap)_size := $($(1)_size))\ + $(new_hmap)),\ + $(1)))))) +endef + +define EVAL_INVOKE +$(if $(__ERROR),,\ + $(and $(EVAL_DEBUG),$(info EVAL_INVOKE: $(call _pr_str,$(1))))\ + $(foreach el,$(call EVAL_AST,$(1),$(2)),\ + $(call _apply,$(call sfirst,$(el)),$(call srest,$(el))))) +endef + +define EVAL +$(strip $(if $(__ERROR),,\ + $(and $(EVAL_DEBUG),$(info EVAL: $(call _pr_str,$(1))))\ + $(if $(call _list?,$(1)),\ + $(strip $(call EVAL_INVOKE,$(1),$(2))),\ + $(call EVAL_AST,$(1),$(2))))) +endef + + +# PRINT: +define PRINT +$(if $(__ERROR),Error: $(call _pr_str,$(__ERROR),yes),$(if $(1),$(call _pr_str,$(1),yes)))$(if $(__ERROR),$(eval __ERROR :=),) +endef + +# REPL: +REPL_ENV := $(call _hash_map) +REP = $(call PRINT,$(strip $(call EVAL,$(strip $(call READ,$(1))),$(REPL_ENV)))) +REPL = $(info $(call REP,$(call READLINE,"user> ")))$(if $(READLINE_EOF),,$(call REPL)) + +$(call do,$(call _assoc!,$(REPL_ENV),+,number_plus)) +$(call do,$(call _assoc!,$(REPL_ENV),-,number_subtract)) +$(call do,$(call _assoc!,$(REPL_ENV),*,number_multiply)) +$(call do,$(call _assoc!,$(REPL_ENV),/,number_divide)) + +# Call the read-eval-print loop +$(if $(strip $(INTERACTIVE)),$(call REPL)) diff --git a/make/step3_env.mk b/make/step3_env.mk new file mode 100644 index 0000000..4f3f070 --- /dev/null +++ b/make/step3_env.mk @@ -0,0 +1,93 @@ +# +# mal (Make Lisp) +# +_TOP_DIR := $(dir $(lastword $(MAKEFILE_LIST))) +include $(_TOP_DIR)types.mk +include $(_TOP_DIR)reader.mk + +SHELL := /bin/bash +INTERACTIVE ?= yes +EVAL_DEBUG ?= + +# READ: read and parse input +define READ +$(if $(READLINE_EOF)$(__ERROR),,$(call READ_STR,$(if $(1),$(1),$(call READLINE,"user> ")))) +endef + +# EVAL: evaluate the parameter +define LET +$(strip \ + $(word 1,$(2) \ + $(foreach var,$(call _nth,$(1),0),\ + $(foreach val,$(call _nth,$(1),1),\ + $(call ENV_SET,$(2),$($(var)_value),$(call EVAL,$(val),$(2)))\ + $(foreach left,$(call srest,$(call srest,$(1))), + $(if $(call _EQ,0,$(call _count,$(left))),\ + ,\ + $(call LET,$(left),$(2)))))))) +endef + +define EVAL_AST +$(strip \ + $(and $(EVAL_DEBUG),$(info EVAL_AST: $(call _pr_str,$(1))))\ + $(if $(call _symbol?,$(1)),\ + $(foreach key,$($(1)_value),\ + $(call ENV_GET,$(2),$(key))),\ + $(if $(call _list?,$(1)),\ + $(call _smap,EVAL,$(1),$(2)),\ + $(if $(call _vector?,$(1)),\ + $(call _smap_vec,EVAL,$(1),$(2)),\ + $(if $(call _hash_map?,$(1)),\ + $(foreach new_hmap,$(call __new_obj,hmap),\ + $(foreach v,$(call __get_obj_values,$(1)),\ + $(eval $(v:$(1)_%=$(new_hmap)_%) := $(call EVAL,$($(v)),$(2))))\ + $(eval $(new_hmap)_size := $($(1)_size))\ + $(new_hmap)),\ + $(1)))))) +endef + +define EVAL_INVOKE +$(if $(__ERROR),,\ + $(and $(EVAL_DEBUG),$(info EVAL_INVOKE: $(call _pr_str,$(1)))) + $(foreach a0,$(call _nth,$(1),0),\ + $(if $(call _EQ,def!,$($(a0)_value)),\ + $(foreach a1,$(call _nth,$(1),1),\ + $(foreach a2,$(call _nth,$(1),2),\ + $(foreach res,$(call EVAL,$(a2),$(2)),\ + $(if $(call ENV_SET,$(2),$($(a1)_value),$(res)),$(res),)))),\ + $(if $(call _EQ,let*,$($(a0)_value)),\ + $(foreach a1,$(call _nth,$(1),1),\ + $(foreach a2,$(call _nth,$(1),2),\ + $(call EVAL,$(a2),$(call LET,$(a1),$(call ENV,$(2)))))),\ + $(foreach el,$(call EVAL_AST,$(1),$(2)),\ + $(call _apply,$(call sfirst,$(el)),$(call srest,$(el)))))))) +endef + +define EVAL +$(strip $(if $(__ERROR),,\ + $(and $(EVAL_DEBUG),$(info EVAL: $(call _pr_str,$(1))))\ + $(if $(call _list?,$(1)),\ + $(strip $(call EVAL_INVOKE,$(1),$(2))),\ + $(call EVAL_AST,$(1),$(2))))) +endef + + +# PRINT: +define PRINT +$(if $(__ERROR),Error: $(call _pr_str,$(__ERROR),yes),$(if $(1),$(call _pr_str,$(1),yes)))$(if $(__ERROR),$(eval __ERROR :=),) +endef + +# REPL: +REPL_ENV := $(call ENV) +REP = $(call PRINT,$(strip $(call EVAL,$(strip $(call READ,$(1))),$(REPL_ENV)))) +REPL = $(info $(call REP,$(call READLINE,"user> ")))$(if $(READLINE_EOF),,$(call REPL)) + +# Setup the environment +_ref = $(eval REPL_ENV := $(call ENV_SET,$(REPL_ENV),$(1),$(if $(2),$(2),$(1)))) +$(call _ref,+,number_plus) +$(call _ref,-,number_subtract) +$(call _ref,*,number_multiply) +$(call _ref,/,number_divide) + +# Call the read-eval-print loop +$(if $(strip $(INTERACTIVE)),$(call REPL)) diff --git a/make/step4_if_fn_do.mk b/make/step4_if_fn_do.mk new file mode 100644 index 0000000..d08998d --- /dev/null +++ b/make/step4_if_fn_do.mk @@ -0,0 +1,112 @@ +# +# mal (Make Lisp) +# +_TOP_DIR := $(dir $(lastword $(MAKEFILE_LIST))) +include $(_TOP_DIR)types.mk +include $(_TOP_DIR)reader.mk + +SHELL := /bin/bash +INTERACTIVE ?= yes +EVAL_DEBUG ?= + +# READ: read and parse input +define READ +$(if $(READLINE_EOF)$(__ERROR),,$(call READ_STR,$(if $(1),$(1),$(call READLINE,"user> ")))) +endef + +# EVAL: evaluate the parameter +define LET +$(strip \ + $(word 1,$(2) \ + $(foreach var,$(call _nth,$(1),0),\ + $(foreach val,$(call _nth,$(1),1),\ + $(call ENV_SET,$(2),$($(var)_value),$(call EVAL,$(val),$(2)))\ + $(foreach left,$(call srest,$(call srest,$(1))), + $(if $(call _EQ,0,$(call _count,$(left))),\ + ,\ + $(call LET,$(left),$(2)))))))) +endef + +define EVAL_AST +$(strip \ + $(and $(EVAL_DEBUG),$(info EVAL_AST: $(call _pr_str,$(1))))\ + $(if $(call _symbol?,$(1)),\ + $(foreach key,$($(1)_value),\ + $(call ENV_GET,$(2),$(key))),\ + $(if $(call _list?,$(1)),\ + $(call _smap,EVAL,$(1),$(2)),\ + $(if $(call _vector?,$(1)),\ + $(call _smap_vec,EVAL,$(1),$(2)),\ + $(if $(call _hash_map?,$(1)),\ + $(foreach new_hmap,$(call __new_obj,hmap),\ + $(foreach v,$(call __get_obj_values,$(1)),\ + $(eval $(v:$(1)_%=$(new_hmap)_%) := $(call EVAL,$($(v)),$(2))))\ + $(eval $(new_hmap)_size := $($(1)_size))\ + $(new_hmap)),\ + $(1)))))) +endef + +define EVAL_INVOKE +$(if $(__ERROR),,\ + $(and $(EVAL_DEBUG),$(info EVAL_INVOKE: $(call _pr_str,$(1)))) + $(foreach a0,$(call _nth,$(1),0),\ + $(if $(call _EQ,def!,$($(a0)_value)),\ + $(foreach a1,$(call _nth,$(1),1),\ + $(foreach a2,$(call _nth,$(1),2),\ + $(foreach res,$(call EVAL,$(a2),$(2)),\ + $(if $(call ENV_SET,$(2),$($(a1)_value),$(res)),$(res),)))),\ + $(if $(call _EQ,let*,$($(a0)_value)),\ + $(foreach a1,$(call _nth,$(1),1),\ + $(foreach a2,$(call _nth,$(1),2),\ + $(call EVAL,$(a2),$(call LET,$(a1),$(call ENV,$(2)))))),\ + $(if $(call _EQ,do,$($(a0)_value)),\ + $(call slast,$(call EVAL_AST,$(call srest,$(1)),$(2))),\ + $(if $(call _EQ,if,$($(a0)_value)),\ + $(foreach a1,$(call _nth,$(1),1),\ + $(foreach a2,$(call _nth,$(1),2),\ + $(foreach cond,$(call EVAL,$(a1),$(2)),\ + $(if $(or $(call _EQ,$(__nil),$(cond)),$(call _EQ,$(__false),$(cond))),\ + $(foreach a3,$(call _nth,$(1),3),$(call EVAL,$(a3),$(2))),\ + $(call EVAL,$(a2),$(2)))))),\ + $(if $(call _EQ,fn*,$($(a0)_value)),\ + $(foreach a1,$(call _nth,$(1),1),\ + $(foreach a2,$(call _nth,$(1),2),\ + $(call function,$$(call EVAL,$(a2),$$(call ENV,$(2),$(a1),$$1))))),\ + $(foreach el,$(call EVAL_AST,$(1),$(2)),\ + $(and $(EVAL_DEBUG),$(info invoke: $(call _pr_str,$(el))))\ + $(foreach f,$(call sfirst,$(el)),\ + $(foreach args,$(call srest,$(el)),\ + $(call apply,$(f),$(args)))))))))))) +endef + +define EVAL +$(strip $(if $(__ERROR),,\ + $(and $(EVAL_DEBUG),$(info EVAL: $(call _pr_str,$(1))))\ + $(if $(call _list?,$(1)),\ + $(word 1,$(strip $(call EVAL_INVOKE,$(1),$(2)) $(__nil))),\ + $(call EVAL_AST,$(1),$(2))))) +endef + + +# PRINT: +define PRINT +$(if $(__ERROR),Error: $(call _pr_str,$(__ERROR),yes),$(if $(1),$(call _pr_str,$(1),yes)))$(if $(__ERROR),$(eval __ERROR :=),) +endef + +# REPL: +REPL_ENV := $(call ENV) +REP = $(call PRINT,$(strip $(call EVAL,$(strip $(call READ,$(1))),$(REPL_ENV)))) +REPL = $(info $(call REP,$(call READLINE,"user> ")))$(if $(READLINE_EOF),,$(call REPL)) + +# Setup the environment +_fref = $(eval REPL_ENV := $(call ENV_SET,$(REPL_ENV),$(1),$(call function,$$(call $(2),$$1)))) + +# Import types functions +_import_types = $(if $(strip $(1)),$(call _fref,$(word 1,$(1)),$(word 2,$(1)))$(call _import_types,$(wordlist 3,$(words $(1)),$(1))),) +$(call _import_types,$(types_ns)) + +# Defined in terms of the language itself +$(call do,$(call REP, (def! not (fn* (a) (if a false true))) )) + +# Call the read-eval-print loop +$(if $(strip $(INTERACTIVE)),$(call REPL)) diff --git a/make/step6_file.mk b/make/step6_file.mk new file mode 100644 index 0000000..da6de04 --- /dev/null +++ b/make/step6_file.mk @@ -0,0 +1,130 @@ +# +# mal (Make Lisp) +# +_TOP_DIR := $(dir $(lastword $(MAKEFILE_LIST))) +include $(_TOP_DIR)types.mk +include $(_TOP_DIR)reader.mk + +SHELL := /bin/bash +INTERACTIVE ?= yes +EVAL_DEBUG ?= + +# READ: read and parse input +define READ +$(if $(READLINE_EOF)$(__ERROR),,$(call READ_STR,$(if $(1),$(1),$(call READLINE,"user> ")))) +endef + +# EVAL: evaluate the parameter +define LET +$(strip \ + $(word 1,$(2) \ + $(foreach var,$(call _nth,$(1),0),\ + $(foreach val,$(call _nth,$(1),1),\ + $(call ENV_SET,$(2),$($(var)_value),$(call EVAL,$(val),$(2)))\ + $(foreach left,$(call srest,$(call srest,$(1))), + $(if $(call _EQ,0,$(call _count,$(left))),\ + ,\ + $(call LET,$(left),$(2)))))))) +endef + +define EVAL_AST +$(strip \ + $(and $(EVAL_DEBUG),$(info EVAL_AST: $(call _pr_str,$(1))))\ + $(if $(call _symbol?,$(1)),\ + $(foreach key,$($(1)_value),\ + $(call ENV_GET,$(2),$(key))),\ + $(if $(call _list?,$(1)),\ + $(call _smap,EVAL,$(1),$(2)),\ + $(if $(call _vector?,$(1)),\ + $(call _smap_vec,EVAL,$(1),$(2)),\ + $(if $(call _hash_map?,$(1)),\ + $(foreach new_hmap,$(call __new_obj,hmap),\ + $(foreach v,$(call __get_obj_values,$(1)),\ + $(eval $(v:$(1)_%=$(new_hmap)_%) := $(call EVAL,$($(v)),$(2))))\ + $(eval $(new_hmap)_size := $($(1)_size))\ + $(new_hmap)),\ + $(1)))))) +endef + +define EVAL_INVOKE +$(if $(__ERROR),,\ + $(and $(EVAL_DEBUG),$(info EVAL_INVOKE: $(call _pr_str,$(1)))) + $(foreach a0,$(call _nth,$(1),0),\ + $(if $(call _EQ,def!,$($(a0)_value)),\ + $(foreach a1,$(call _nth,$(1),1),\ + $(foreach a2,$(call _nth,$(1),2),\ + $(foreach res,$(call EVAL,$(a2),$(2)),\ + $(if $(call ENV_SET,$(2),$($(a1)_value),$(res)),$(res),)))),\ + $(if $(call _EQ,let*,$($(a0)_value)),\ + $(foreach a1,$(call _nth,$(1),1),\ + $(foreach a2,$(call _nth,$(1),2),\ + $(call EVAL,$(a2),$(call LET,$(a1),$(call ENV,$(2)))))),\ + $(if $(call _EQ,do,$($(a0)_value)),\ + $(call slast,$(call EVAL_AST,$(call srest,$(1)),$(2))),\ + $(if $(call _EQ,if,$($(a0)_value)),\ + $(foreach a1,$(call _nth,$(1),1),\ + $(foreach a2,$(call _nth,$(1),2),\ + $(foreach cond,$(call EVAL,$(a1),$(2)),\ + $(if $(or $(call _EQ,$(__nil),$(cond)),$(call _EQ,$(__false),$(cond))),\ + $(foreach a3,$(call _nth,$(1),3),$(call EVAL,$(a3),$(2))),\ + $(call EVAL,$(a2),$(2)))))),\ + $(if $(call _EQ,fn*,$($(a0)_value)),\ + $(foreach a1,$(call _nth,$(1),1),\ + $(foreach a2,$(call _nth,$(1),2),\ + $(call function,$$(call EVAL,$(a2),$$(call ENV,$(2),$(a1),$$1))))),\ + $(foreach el,$(call EVAL_AST,$(1),$(2)),\ + $(and $(EVAL_DEBUG),$(info invoke: $(call _pr_str,$(el))))\ + $(foreach f,$(call sfirst,$(el)),\ + $(foreach args,$(call srest,$(el)),\ + $(call apply,$(f),$(args)))))))))))) +endef + +define EVAL +$(strip $(if $(__ERROR),,\ + $(and $(EVAL_DEBUG),$(info EVAL: $(call _pr_str,$(1))))\ + $(if $(call _list?,$(1)),\ + $(word 1,$(strip $(call EVAL_INVOKE,$(1),$(2)) $(__nil))),\ + $(call EVAL_AST,$(1),$(2))))) +endef + + +# PRINT: +define PRINT +$(if $(__ERROR),Error: $(call _pr_str,$(__ERROR),yes),$(if $(1),$(call _pr_str,$(1),yes)))$(if $(__ERROR),$(eval __ERROR :=),) +endef + +# REPL: +REPL_ENV := $(call ENV) +REP = $(call PRINT,$(strip $(call EVAL,$(strip $(call READ,$(1))),$(REPL_ENV)))) +REPL = $(info $(call REP,$(call READLINE,"user> ")))$(if $(READLINE_EOF),,$(call REPL)) + +# Setup the environment +_ref = $(eval REPL_ENV := $(call ENV_SET,$(REPL_ENV),$(1),$(if $(2),$(2),$(1)))) +_fref = $(eval REPL_ENV := $(call ENV_SET,$(REPL_ENV),$(1),$(call function,$$(call $(2),$$1)))) + +# Import types functions +_import_types = $(if $(strip $(1)),$(call _fref,$(word 1,$(1)),$(word 2,$(1)))$(call _import_types,$(wordlist 3,$(words $(1)),$(1))),) +$(call _import_types,$(types_ns)) + +$(call _ref,read-string,$(call function,$$(call READ_STR,$$(1)))) +$(call _ref,eval,$(call function,$$(call EVAL,$$(1),$$(REPL_ENV)))) + +_slurp = $(call string,$(call _read_file,$(1))) +_slurp_do = $(call string,(do $(call _read_file,$(1)))) +$(call _ref,slurp,$(call function,$$(call _slurp,$$(call str_decode,$$($$(1)_value))))) +$(call _ref,slurp-do,$(call function,$$(call _slurp_do,$$(call str_decode,$$($$(1)_value))))) + +# Defined in terms of the language itself +$(call do,$(call REP, (def! not (fn* (a) (if a false true))) )) +$(call do,$(call REP, (def! load-file (fn* (f) (eval (read-string (slurp-do f))))) )) + +# Load and eval any files specified on the command line +$(if $(MAKECMDGOALS),\ + $(foreach file,$(MAKECMDGOALS),$(call do,$(call REP, (load-file "$(file)") )))\ + $(eval INTERACTIVE :=),) +.PHONY: none $(MAKECMDGOALS) +none $(MAKECMDGOALS): + @true + +# Call the read-eval-print loop +$(if $(strip $(INTERACTIVE)),$(call REPL)) diff --git a/make/step7_quote.mk b/make/step7_quote.mk new file mode 100644 index 0000000..a8695da --- /dev/null +++ b/make/step7_quote.mk @@ -0,0 +1,147 @@ +# +# mal (Make Lisp) +# +_TOP_DIR := $(dir $(lastword $(MAKEFILE_LIST))) +include $(_TOP_DIR)types.mk +include $(_TOP_DIR)reader.mk + +SHELL := /bin/bash +INTERACTIVE ?= yes +EVAL_DEBUG ?= + +# READ: read and parse input +define READ +$(if $(READLINE_EOF)$(__ERROR),,$(call READ_STR,$(if $(1),$(1),$(call READLINE,"user> ")))) +endef + +# EVAL: evaluate the parameter +IS_PAIR = $(if $(call _EQ,list,$(call _obj_type,$(1))),$(if $(call _EQ,0,$(call _count,$(1))),,true),) + +define QUASIQUOTE +$(strip \ + $(if $(call _NOT,$(call IS_PAIR,$(1))),\ + $(call list,$(call symbol,quote) $(1)),\ + $(if $(call _EQ,unquote,$($(call _nth,$(1),0)_value)),\ + $(call _nth,$(1),1),\ + $(if $(and $(call IS_PAIR,$(call _nth,$(1),0)),$(call _EQ,splice-unquote,$($(call _nth,$(call _nth,$(1),0),0)_value))),\ + $(call list,$(call symbol,concat) $(call _nth,$(call _nth,$(1),0),1) $(call QUASIQUOTE,$(call srest,$(1)))),\ + $(call list,$(call symbol,cons) $(call QUASIQUOTE,$(call _nth,$(1),0)) $(call QUASIQUOTE,$(call srest,$(1)))))))) +endef + +define LET +$(strip \ + $(word 1,$(2) \ + $(foreach var,$(call _nth,$(1),0),\ + $(foreach val,$(call _nth,$(1),1),\ + $(call ENV_SET,$(2),$($(var)_value),$(call EVAL,$(val),$(2)))\ + $(foreach left,$(call srest,$(call srest,$(1))), + $(if $(call _EQ,0,$(call _count,$(left))),\ + ,\ + $(call LET,$(left),$(2)))))))) +endef + +define EVAL_AST +$(strip \ + $(and $(EVAL_DEBUG),$(info EVAL_AST: $(call _pr_str,$(1))))\ + $(if $(call _symbol?,$(1)),\ + $(foreach key,$($(1)_value),\ + $(call ENV_GET,$(2),$(key))),\ + $(if $(call _list?,$(1)),\ + $(call _smap,EVAL,$(1),$(2)),\ + $(if $(call _vector?,$(1)),\ + $(call _smap_vec,EVAL,$(1),$(2)),\ + $(if $(call _hash_map?,$(1)),\ + $(foreach new_hmap,$(call __new_obj,hmap),\ + $(foreach v,$(call __get_obj_values,$(1)),\ + $(eval $(v:$(1)_%=$(new_hmap)_%) := $(call EVAL,$($(v)),$(2))))\ + $(eval $(new_hmap)_size := $($(1)_size))\ + $(new_hmap)),\ + $(1)))))) +endef + +define EVAL_INVOKE +$(if $(__ERROR),,\ + $(and $(EVAL_DEBUG),$(info EVAL_INVOKE: $(call _pr_str,$(1)))) + $(foreach a0,$(call _nth,$(1),0),\ + $(if $(call _EQ,def!,$($(a0)_value)),\ + $(foreach a1,$(call _nth,$(1),1),\ + $(foreach a2,$(call _nth,$(1),2),\ + $(foreach res,$(call EVAL,$(a2),$(2)),\ + $(if $(call ENV_SET,$(2),$($(a1)_value),$(res)),$(res),)))),\ + $(if $(call _EQ,let*,$($(a0)_value)),\ + $(foreach a1,$(call _nth,$(1),1),\ + $(foreach a2,$(call _nth,$(1),2),\ + $(call EVAL,$(a2),$(call LET,$(a1),$(call ENV,$(2)))))),\ + $(if $(call _EQ,quote,$($(a0)_value)),\ + $(call _nth,$(1),1),\ + $(if $(call _EQ,quasiquote,$($(a0)_value)),\ + $(call EVAL,$(call QUASIQUOTE,$(call _nth,$(1),1)),$(2)),\ + $(if $(call _EQ,do,$($(a0)_value)),\ + $(call slast,$(call EVAL_AST,$(call srest,$(1)),$(2))),\ + $(if $(call _EQ,if,$($(a0)_value)),\ + $(foreach a1,$(call _nth,$(1),1),\ + $(foreach a2,$(call _nth,$(1),2),\ + $(foreach cond,$(call EVAL,$(a1),$(2)),\ + $(if $(or $(call _EQ,$(__nil),$(cond)),$(call _EQ,$(__false),$(cond))),\ + $(foreach a3,$(call _nth,$(1),3),$(call EVAL,$(a3),$(2))),\ + $(call EVAL,$(a2),$(2)))))),\ + $(if $(call _EQ,fn*,$($(a0)_value)),\ + $(foreach a1,$(call _nth,$(1),1),\ + $(foreach a2,$(call _nth,$(1),2),\ + $(call function,$$(call EVAL,$(a2),$$(call ENV,$(2),$(a1),$$1))))),\ + $(foreach el,$(call EVAL_AST,$(1),$(2)),\ + $(and $(EVAL_DEBUG),$(info invoke: $(call _pr_str,$(el))))\ + $(foreach f,$(call sfirst,$(el)),\ + $(foreach args,$(call srest,$(el)),\ + $(call apply,$(f),$(args)))))))))))))) +endef + +define EVAL +$(strip $(if $(__ERROR),,\ + $(and $(EVAL_DEBUG),$(info EVAL: $(call _pr_str,$(1))))\ + $(if $(call _list?,$(1)),\ + $(word 1,$(strip $(call EVAL_INVOKE,$(1),$(2)) $(__nil))),\ + $(call EVAL_AST,$(1),$(2))))) +endef + + +# PRINT: +define PRINT +$(if $(__ERROR),Error: $(call _pr_str,$(__ERROR),yes),$(if $(1),$(call _pr_str,$(1),yes)))$(if $(__ERROR),$(eval __ERROR :=),) +endef + +# REPL: +REPL_ENV := $(call ENV) +REP = $(call PRINT,$(strip $(call EVAL,$(strip $(call READ,$(1))),$(REPL_ENV)))) +REPL = $(info $(call REP,$(call READLINE,"user> ")))$(if $(READLINE_EOF),,$(call REPL)) + +# Setup the environment +_ref = $(eval REPL_ENV := $(call ENV_SET,$(REPL_ENV),$(1),$(if $(2),$(2),$(1)))) +_fref = $(eval REPL_ENV := $(call ENV_SET,$(REPL_ENV),$(1),$(call function,$$(call $(2),$$1)))) + +# Import types functions +_import_types = $(if $(strip $(1)),$(call _fref,$(word 1,$(1)),$(word 2,$(1)))$(call _import_types,$(wordlist 3,$(words $(1)),$(1))),) +$(call _import_types,$(types_ns)) + +$(call _ref,read-string,$(call function,$$(call READ_STR,$$(1)))) +$(call _ref,eval,$(call function,$$(call EVAL,$$(1),$$(REPL_ENV)))) + +_slurp = $(call string,$(call _read_file,$(1))) +_slurp_do = $(call string,(do $(call _read_file,$(1)))) +$(call _ref,slurp,$(call function,$$(call _slurp,$$(call str_decode,$$($$(1)_value))))) +$(call _ref,slurp-do,$(call function,$$(call _slurp_do,$$(call str_decode,$$($$(1)_value))))) + +# Defined in terms of the language itself +$(call do,$(call REP, (def! not (fn* (a) (if a false true))) )) +$(call do,$(call REP, (def! load-file (fn* (f) (eval (read-string (slurp-do f))))) )) + +# Load and eval any files specified on the command line +$(if $(MAKECMDGOALS),\ + $(foreach file,$(MAKECMDGOALS),$(call do,$(call REP, (load-file "$(file)") )))\ + $(eval INTERACTIVE :=),) +.PHONY: none $(MAKECMDGOALS) +none $(MAKECMDGOALS): + @true + +# Call the read-eval-print loop +$(if $(strip $(INTERACTIVE)),$(call REPL)) diff --git a/make/step8_macros.mk b/make/step8_macros.mk new file mode 100644 index 0000000..2b4e33b --- /dev/null +++ b/make/step8_macros.mk @@ -0,0 +1,170 @@ +# +# mal (Make Lisp) +# +_TOP_DIR := $(dir $(lastword $(MAKEFILE_LIST))) +include $(_TOP_DIR)types.mk +include $(_TOP_DIR)reader.mk + +SHELL := /bin/bash +INTERACTIVE ?= yes +EVAL_DEBUG ?= + +# READ: read and parse input +define READ +$(if $(READLINE_EOF)$(__ERROR),,$(call READ_STR,$(if $(1),$(1),$(call READLINE,"user> ")))) +endef + +# EVAL: evaluate the parameter +IS_PAIR = $(if $(call _EQ,list,$(call _obj_type,$(1))),$(if $(call _EQ,0,$(call _count,$(1))),,true),) + +define QUASIQUOTE +$(strip \ + $(if $(call _NOT,$(call IS_PAIR,$(1))),\ + $(call list,$(call symbol,quote) $(1)),\ + $(if $(call _EQ,unquote,$($(call _nth,$(1),0)_value)),\ + $(call _nth,$(1),1),\ + $(if $(and $(call IS_PAIR,$(call _nth,$(1),0)),$(call _EQ,splice-unquote,$($(call _nth,$(call _nth,$(1),0),0)_value))),\ + $(call list,$(call symbol,concat) $(call _nth,$(call _nth,$(1),0),1) $(call QUASIQUOTE,$(call srest,$(1)))),\ + $(call list,$(call symbol,cons) $(call QUASIQUOTE,$(call _nth,$(1),0)) $(call QUASIQUOTE,$(call srest,$(1)))))))) +endef + +define IS_MACRO_CALL +$(if $(call _list?,$(1)),$(call ENV_FIND,$(2),_macro_$($(call _nth,$(1),0)_value)),) +endef + +define MACROEXPAND +$(strip $(if $(__ERROR),,\ + $(if $(call IS_MACRO_CALL,$(1),$(2)),\ + $(foreach mac,$(call ENV_GET,$(2),$($(call _nth,$(1),0)_value)),\ + $(call MACROEXPAND,$(call apply,$(mac),$(call srest,$(1))),$(2))),\ + $(1)))) +endef + +define LET +$(strip \ + $(word 1,$(2) \ + $(foreach var,$(call _nth,$(1),0),\ + $(foreach val,$(call _nth,$(1),1),\ + $(call ENV_SET,$(2),$($(var)_value),$(call EVAL,$(val),$(2)))\ + $(foreach left,$(call srest,$(call srest,$(1))), + $(if $(call _EQ,0,$(call _count,$(left))),\ + ,\ + $(call LET,$(left),$(2)))))))) +endef + +define EVAL_AST +$(strip \ + $(and $(EVAL_DEBUG),$(info EVAL_AST: $(call _pr_str,$(1))))\ + $(if $(call _symbol?,$(1)),\ + $(foreach key,$($(1)_value),\ + $(call ENV_GET,$(2),$(key))),\ + $(if $(call _list?,$(1)),\ + $(call _smap,EVAL,$(1),$(2)),\ + $(if $(call _vector?,$(1)),\ + $(call _smap_vec,EVAL,$(1),$(2)),\ + $(if $(call _hash_map?,$(1)),\ + $(foreach new_hmap,$(call __new_obj,hmap),\ + $(foreach v,$(call __get_obj_values,$(1)),\ + $(eval $(v:$(1)_%=$(new_hmap)_%) := $(call EVAL,$($(v)),$(2))))\ + $(eval $(new_hmap)_size := $($(1)_size))\ + $(new_hmap)),\ + $(1)))))) +endef + +define EVAL_INVOKE +$(if $(__ERROR),,\ + $(and $(EVAL_DEBUG),$(info EVAL_INVOKE: $(call _pr_str,$(1)))) + $(foreach a0,$(call _nth,$(1),0),\ + $(if $(call _EQ,def!,$($(a0)_value)),\ + $(foreach a1,$(call _nth,$(1),1),\ + $(foreach a2,$(call _nth,$(1),2),\ + $(foreach res,$(call EVAL,$(a2),$(2)),\ + $(if $(call ENV_SET,$(2),$($(a1)_value),$(res)),$(res),)))),\ + $(if $(call _EQ,let*,$($(a0)_value)),\ + $(foreach a1,$(call _nth,$(1),1),\ + $(foreach a2,$(call _nth,$(1),2),\ + $(call EVAL,$(a2),$(call LET,$(a1),$(call ENV,$(2)))))),\ + $(if $(call _EQ,quote,$($(a0)_value)),\ + $(call _nth,$(1),1),\ + $(if $(call _EQ,quasiquote,$($(a0)_value)),\ + $(call EVAL,$(call QUASIQUOTE,$(call _nth,$(1),1)),$(2)),\ + $(if $(call _EQ,defmacro!,$($(a0)_value)),\ + $(foreach a1,$(call _nth,$(1),1),\ + $(foreach a2,$(call _nth,$(1),2),\ + $(foreach res,$(call EVAL,$(a2),$(2)),\ + $(if $(call ENV_SET,$(2),_macro_$($(a1)_value),true),,)\ + $(if $(call ENV_SET,$(2),$($(a1)_value),$(res)),$(res),)))),\ + $(if $(call _EQ,macroexpand,$($(a0)_value)),\ + $(call MACROEXPAND,$(call _nth,$(1),1),$(2)),\ + $(if $(call _EQ,do,$($(a0)_value)),\ + $(call slast,$(call EVAL_AST,$(call srest,$(1)),$(2))),\ + $(if $(call _EQ,if,$($(a0)_value)),\ + $(foreach a1,$(call _nth,$(1),1),\ + $(foreach a2,$(call _nth,$(1),2),\ + $(foreach cond,$(call EVAL,$(a1),$(2)),\ + $(if $(or $(call _EQ,$(__nil),$(cond)),$(call _EQ,$(__false),$(cond))),\ + $(foreach a3,$(call _nth,$(1),3),$(call EVAL,$(a3),$(2))),\ + $(call EVAL,$(a2),$(2)))))),\ + $(if $(call _EQ,fn*,$($(a0)_value)),\ + $(foreach a1,$(call _nth,$(1),1),\ + $(foreach a2,$(call _nth,$(1),2),\ + $(call function,$$(call EVAL,$(a2),$$(call ENV,$(2),$(a1),$$1))))),\ + $(foreach el,$(call EVAL_AST,$(1),$(2)),\ + $(and $(EVAL_DEBUG),$(info invoke: $(call _pr_str,$(el))))\ + $(foreach f,$(call sfirst,$(el)),\ + $(foreach args,$(call srest,$(el)),\ + $(call apply,$(f),$(args)))))))))))))))) +endef + +define EVAL +$(strip $(if $(__ERROR),,\ + $(and $(EVAL_DEBUG),$(info EVAL: $(call _pr_str,$(1))))\ + $(if $(call _list?,$(1)),\ + $(foreach ast,$(call MACROEXPAND,$(1),$(2)), + $(if $(call _list?,$(ast)),\ + $(word 1,$(strip $(call EVAL_INVOKE,$(ast),$(2)) $(__nil))),\ + $(ast))),\ + $(call EVAL_AST,$(1),$(2))))) +endef + + +# PRINT: +define PRINT +$(if $(__ERROR),Error: $(call _pr_str,$(__ERROR),yes),$(if $(1),$(call _pr_str,$(1),yes)))$(if $(__ERROR),$(eval __ERROR :=),) +endef + +# REPL: +REPL_ENV := $(call ENV) +REP = $(call PRINT,$(strip $(call EVAL,$(strip $(call READ,$(1))),$(REPL_ENV)))) +REPL = $(info $(call REP,$(call READLINE,"user> ")))$(if $(READLINE_EOF),,$(call REPL)) + +# Setup the environment +_ref = $(eval REPL_ENV := $(call ENV_SET,$(REPL_ENV),$(1),$(if $(2),$(2),$(1)))) +_fref = $(eval REPL_ENV := $(call ENV_SET,$(REPL_ENV),$(1),$(call function,$$(call $(2),$$1)))) + +# Import types functions +_import_types = $(if $(strip $(1)),$(call _fref,$(word 1,$(1)),$(word 2,$(1)))$(call _import_types,$(wordlist 3,$(words $(1)),$(1))),) +$(call _import_types,$(types_ns)) + +$(call _ref,read-string,$(call function,$$(call READ_STR,$$(1)))) +$(call _ref,eval,$(call function,$$(call EVAL,$$(1),$$(REPL_ENV)))) + +_slurp = $(call string,$(call _read_file,$(1))) +_slurp_do = $(call string,(do $(call _read_file,$(1)))) +$(call _ref,slurp,$(call function,$$(call _slurp,$$(call str_decode,$$($$(1)_value))))) +$(call _ref,slurp-do,$(call function,$$(call _slurp_do,$$(call str_decode,$$($$(1)_value))))) + +# Defined in terms of the language itself +$(call do,$(call REP, (def! not (fn* (a) (if a false true))) )) +$(call do,$(call REP, (def! load-file (fn* (f) (eval (read-string (slurp-do f))))) )) + +# Load and eval any files specified on the command line +$(if $(MAKECMDGOALS),\ + $(foreach file,$(MAKECMDGOALS),$(call do,$(call REP, (load-file "$(file)") )))\ + $(eval INTERACTIVE :=),) +.PHONY: none $(MAKECMDGOALS) +none $(MAKECMDGOALS): + @true + +# Call the read-eval-print loop +$(if $(strip $(INTERACTIVE)),$(call REPL)) diff --git a/make/step9_interop.mk b/make/step9_interop.mk new file mode 100644 index 0000000..a3d2b5e --- /dev/null +++ b/make/step9_interop.mk @@ -0,0 +1,174 @@ +# +# mal (Make Lisp) +# +_TOP_DIR := $(dir $(lastword $(MAKEFILE_LIST))) +include $(_TOP_DIR)types.mk +include $(_TOP_DIR)reader.mk + +SHELL := /bin/bash +INTERACTIVE ?= yes +EVAL_DEBUG ?= + +# READ: read and parse input +define READ +$(if $(READLINE_EOF)$(__ERROR),,$(call READ_STR,$(if $(1),$(1),$(call READLINE,"user> ")))) +endef + +# EVAL: evaluate the parameter +IS_PAIR = $(if $(call _EQ,list,$(call _obj_type,$(1))),$(if $(call _EQ,0,$(call _count,$(1))),,true),) + +define QUASIQUOTE +$(strip \ + $(if $(call _NOT,$(call IS_PAIR,$(1))),\ + $(call list,$(call symbol,quote) $(1)),\ + $(if $(call _EQ,unquote,$($(call _nth,$(1),0)_value)),\ + $(call _nth,$(1),1),\ + $(if $(and $(call IS_PAIR,$(call _nth,$(1),0)),$(call _EQ,splice-unquote,$($(call _nth,$(call _nth,$(1),0),0)_value))),\ + $(call list,$(call symbol,concat) $(call _nth,$(call _nth,$(1),0),1) $(call QUASIQUOTE,$(call srest,$(1)))),\ + $(call list,$(call symbol,cons) $(call QUASIQUOTE,$(call _nth,$(1),0)) $(call QUASIQUOTE,$(call srest,$(1)))))))) +endef + +define IS_MACRO_CALL +$(if $(call _list?,$(1)),$(call ENV_FIND,$(2),_macro_$($(call _nth,$(1),0)_value)),) +endef + +define MACROEXPAND +$(strip $(if $(__ERROR),,\ + $(if $(call IS_MACRO_CALL,$(1),$(2)),\ + $(foreach mac,$(call ENV_GET,$(2),$($(call _nth,$(1),0)_value)),\ + $(call MACROEXPAND,$(call apply,$(mac),$(call srest,$(1))),$(2))),\ + $(1)))) +endef + +define LET +$(strip \ + $(word 1,$(2) \ + $(foreach var,$(call _nth,$(1),0),\ + $(foreach val,$(call _nth,$(1),1),\ + $(call ENV_SET,$(2),$($(var)_value),$(call EVAL,$(val),$(2)))\ + $(foreach left,$(call srest,$(call srest,$(1))), + $(if $(call _EQ,0,$(call _count,$(left))),\ + ,\ + $(call LET,$(left),$(2)))))))) +endef + +define EVAL_AST +$(strip \ + $(and $(EVAL_DEBUG),$(info EVAL_AST: $(call _pr_str,$(1))))\ + $(if $(call _symbol?,$(1)),\ + $(foreach key,$($(1)_value),\ + $(call ENV_GET,$(2),$(key))),\ + $(if $(call _list?,$(1)),\ + $(call _smap,EVAL,$(1),$(2)),\ + $(if $(call _vector?,$(1)),\ + $(call _smap_vec,EVAL,$(1),$(2)),\ + $(if $(call _hash_map?,$(1)),\ + $(foreach new_hmap,$(call __new_obj,hmap),\ + $(foreach v,$(call __get_obj_values,$(1)),\ + $(eval $(v:$(1)_%=$(new_hmap)_%) := $(call EVAL,$($(v)),$(2))))\ + $(eval $(new_hmap)_size := $($(1)_size))\ + $(new_hmap)),\ + $(1)))))) +endef + +define EVAL_INVOKE +$(if $(__ERROR),,\ + $(and $(EVAL_DEBUG),$(info EVAL_INVOKE: $(call _pr_str,$(1)))) + $(foreach a0,$(call _nth,$(1),0),\ + $(if $(call _EQ,def!,$($(a0)_value)),\ + $(foreach a1,$(call _nth,$(1),1),\ + $(foreach a2,$(call _nth,$(1),2),\ + $(foreach res,$(call EVAL,$(a2),$(2)),\ + $(if $(call ENV_SET,$(2),$($(a1)_value),$(res)),$(res),)))),\ + $(if $(call _EQ,let*,$($(a0)_value)),\ + $(foreach a1,$(call _nth,$(1),1),\ + $(foreach a2,$(call _nth,$(1),2),\ + $(call EVAL,$(a2),$(call LET,$(a1),$(call ENV,$(2)))))),\ + $(if $(call _EQ,quote,$($(a0)_value)),\ + $(call _nth,$(1),1),\ + $(if $(call _EQ,quasiquote,$($(a0)_value)),\ + $(call EVAL,$(call QUASIQUOTE,$(call _nth,$(1),1)),$(2)),\ + $(if $(call _EQ,defmacro!,$($(a0)_value)),\ + $(foreach a1,$(call _nth,$(1),1),\ + $(foreach a2,$(call _nth,$(1),2),\ + $(foreach res,$(call EVAL,$(a2),$(2)),\ + $(if $(call ENV_SET,$(2),_macro_$($(a1)_value),true),,)\ + $(if $(call ENV_SET,$(2),$($(a1)_value),$(res)),$(res),)))),\ + $(if $(call _EQ,macroexpand,$($(a0)_value)),\ + $(call MACROEXPAND,$(call _nth,$(1),1),$(2)),\ + $(if $(call _EQ,make*,$($(a0)_value)),\ + $(foreach a1,$(call _nth,$(1),1),\ + $(and $(EVAL_DEBUG),$(info make*: $$(eval __result := $(call str_decode,$(value $(a1)_value)))))\ + $(eval __result := $(call str_decode,$(value $(a1)_value)))$(call READ_STR,$(__result))),\ + $(if $(call _EQ,do,$($(a0)_value)),\ + $(call slast,$(call EVAL_AST,$(call srest,$(1)),$(2))),\ + $(if $(call _EQ,if,$($(a0)_value)),\ + $(foreach a1,$(call _nth,$(1),1),\ + $(foreach a2,$(call _nth,$(1),2),\ + $(foreach cond,$(call EVAL,$(a1),$(2)),\ + $(if $(or $(call _EQ,$(__nil),$(cond)),$(call _EQ,$(__false),$(cond))),\ + $(foreach a3,$(call _nth,$(1),3),$(call EVAL,$(a3),$(2))),\ + $(call EVAL,$(a2),$(2)))))),\ + $(if $(call _EQ,fn*,$($(a0)_value)),\ + $(foreach a1,$(call _nth,$(1),1),\ + $(foreach a2,$(call _nth,$(1),2),\ + $(call function,$$(call EVAL,$(a2),$$(call ENV,$(2),$(a1),$$1))))),\ + $(foreach el,$(call EVAL_AST,$(1),$(2)),\ + $(and $(EVAL_DEBUG),$(info invoke: $(call _pr_str,$(el))))\ + $(foreach f,$(call sfirst,$(el)),\ + $(foreach args,$(call srest,$(el)),\ + $(call apply,$(f),$(args))))))))))))))))) +endef + +define EVAL +$(strip $(if $(__ERROR),,\ + $(and $(EVAL_DEBUG),$(info EVAL: $(call _pr_str,$(1))))\ + $(if $(call _list?,$(1)),\ + $(foreach ast,$(call MACROEXPAND,$(1),$(2)), + $(if $(call _list?,$(ast)),\ + $(word 1,$(strip $(call EVAL_INVOKE,$(ast),$(2)) $(__nil))),\ + $(ast))),\ + $(call EVAL_AST,$(1),$(2))))) +endef + + +# PRINT: +define PRINT +$(if $(__ERROR),Error: $(call _pr_str,$(__ERROR),yes),$(if $(1),$(call _pr_str,$(1),yes)))$(if $(__ERROR),$(eval __ERROR :=),) +endef + +# REPL: +REPL_ENV := $(call ENV) +REP = $(call PRINT,$(strip $(call EVAL,$(strip $(call READ,$(1))),$(REPL_ENV)))) +REPL = $(info $(call REP,$(call READLINE,"user> ")))$(if $(READLINE_EOF),,$(call REPL)) + +# Setup the environment +_ref = $(eval REPL_ENV := $(call ENV_SET,$(REPL_ENV),$(1),$(if $(2),$(2),$(1)))) +_fref = $(eval REPL_ENV := $(call ENV_SET,$(REPL_ENV),$(1),$(call function,$$(call $(2),$$1)))) + +# Import types functions +_import_types = $(if $(strip $(1)),$(call _fref,$(word 1,$(1)),$(word 2,$(1)))$(call _import_types,$(wordlist 3,$(words $(1)),$(1))),) +$(call _import_types,$(types_ns)) + +$(call _ref,read-string,$(call function,$$(call READ_STR,$$(1)))) +$(call _ref,eval,$(call function,$$(call EVAL,$$(1),$$(REPL_ENV)))) + +_slurp = $(call string,$(call _read_file,$(1))) +_slurp_do = $(call string,(do $(call _read_file,$(1)))) +$(call _ref,slurp,$(call function,$$(call _slurp,$$(call str_decode,$$($$(1)_value))))) +$(call _ref,slurp-do,$(call function,$$(call _slurp_do,$$(call str_decode,$$($$(1)_value))))) + +# Defined in terms of the language itself +$(call do,$(call REP, (def! not (fn* (a) (if a false true))) )) +$(call do,$(call REP, (def! load-file (fn* (f) (eval (read-string (slurp-do f))))) )) + +# Load and eval any files specified on the command line +$(if $(MAKECMDGOALS),\ + $(foreach file,$(MAKECMDGOALS),$(call do,$(call REP, (load-file "$(file)") )))\ + $(eval INTERACTIVE :=),) +.PHONY: none $(MAKECMDGOALS) +none $(MAKECMDGOALS): + @true + +# Call the read-eval-print loop +$(if $(strip $(INTERACTIVE)),$(call REPL)) diff --git a/make/stepA_more.mk b/make/stepA_more.mk new file mode 100644 index 0000000..ec32d85 --- /dev/null +++ b/make/stepA_more.mk @@ -0,0 +1,192 @@ +# +# mal (Make Lisp) +# +_TOP_DIR := $(dir $(lastword $(MAKEFILE_LIST))) +include $(_TOP_DIR)types.mk +include $(_TOP_DIR)reader.mk + +SHELL := /bin/bash +INTERACTIVE ?= yes +EVAL_DEBUG ?= + +# READ: read and parse input +define READ +$(if $(READLINE_EOF)$(__ERROR),,$(call READ_STR,$(if $(1),$(1),$(call READLINE,"user> ")))) +endef + +# EVAL: evaluate the parameter +IS_PAIR = $(if $(call _EQ,list,$(call _obj_type,$(1))),$(if $(call _EQ,0,$(call _count,$(1))),,true),) + +define QUASIQUOTE +$(strip \ + $(if $(call _NOT,$(call IS_PAIR,$(1))),\ + $(call list,$(call symbol,quote) $(1)),\ + $(if $(call _EQ,unquote,$($(call _nth,$(1),0)_value)),\ + $(call _nth,$(1),1),\ + $(if $(and $(call IS_PAIR,$(call _nth,$(1),0)),$(call _EQ,splice-unquote,$($(call _nth,$(call _nth,$(1),0),0)_value))),\ + $(call list,$(call symbol,concat) $(call _nth,$(call _nth,$(1),0),1) $(call QUASIQUOTE,$(call srest,$(1)))),\ + $(call list,$(call symbol,cons) $(call QUASIQUOTE,$(call _nth,$(1),0)) $(call QUASIQUOTE,$(call srest,$(1)))))))) +endef + +define IS_MACRO_CALL +$(if $(call _list?,$(1)),$(call ENV_FIND,$(2),_macro_$($(call _nth,$(1),0)_value)),) +endef + +define MACROEXPAND +$(strip $(if $(__ERROR),,\ + $(if $(call IS_MACRO_CALL,$(1),$(2)),\ + $(foreach mac,$(call ENV_GET,$(2),$($(call _nth,$(1),0)_value)),\ + $(call MACROEXPAND,$(call apply,$(mac),$(call srest,$(1))),$(2))),\ + $(1)))) +endef + +define LET +$(strip \ + $(word 1,$(2) \ + $(foreach var,$(call _nth,$(1),0),\ + $(foreach val,$(call _nth,$(1),1),\ + $(call ENV_SET,$(2),$($(var)_value),$(call EVAL,$(val),$(2)))\ + $(foreach left,$(call srest,$(call srest,$(1))), + $(if $(call _EQ,0,$(call _count,$(left))),\ + ,\ + $(call LET,$(left),$(2)))))))) +endef + +define EVAL_AST +$(strip \ + $(and $(EVAL_DEBUG),$(info EVAL_AST: $(call _pr_str,$(1))))\ + $(if $(call _symbol?,$(1)),\ + $(foreach key,$($(1)_value),\ + $(call ENV_GET,$(2),$(key))),\ + $(if $(call _list?,$(1)),\ + $(call _smap,EVAL,$(1),$(2)),\ + $(if $(call _vector?,$(1)),\ + $(call _smap_vec,EVAL,$(1),$(2)),\ + $(if $(call _hash_map?,$(1)),\ + $(foreach new_hmap,$(call __new_obj,hmap),\ + $(foreach v,$(call __get_obj_values,$(1)),\ + $(eval $(v:$(1)_%=$(new_hmap)_%) := $(call EVAL,$($(v)),$(2))))\ + $(eval $(new_hmap)_size := $($(1)_size))\ + $(new_hmap)),\ + $(1)))))) +endef + +define EVAL_INVOKE +$(if $(__ERROR),,\ + $(and $(EVAL_DEBUG),$(info EVAL_INVOKE: $(call _pr_str,$(1)))) + $(foreach a0,$(call _nth,$(1),0),\ + $(if $(call _EQ,def!,$($(a0)_value)),\ + $(foreach a1,$(call _nth,$(1),1),\ + $(foreach a2,$(call _nth,$(1),2),\ + $(foreach res,$(call EVAL,$(a2),$(2)),\ + $(if $(call ENV_SET,$(2),$($(a1)_value),$(res)),$(res),)))),\ + $(if $(call _EQ,let*,$($(a0)_value)),\ + $(foreach a1,$(call _nth,$(1),1),\ + $(foreach a2,$(call _nth,$(1),2),\ + $(call EVAL,$(a2),$(call LET,$(a1),$(call ENV,$(2)))))),\ + $(if $(call _EQ,quote,$($(a0)_value)),\ + $(call _nth,$(1),1),\ + $(if $(call _EQ,quasiquote,$($(a0)_value)),\ + $(call EVAL,$(call QUASIQUOTE,$(call _nth,$(1),1)),$(2)),\ + $(if $(call _EQ,defmacro!,$($(a0)_value)),\ + $(foreach a1,$(call _nth,$(1),1),\ + $(foreach a2,$(call _nth,$(1),2),\ + $(foreach res,$(call EVAL,$(a2),$(2)),\ + $(if $(call ENV_SET,$(2),_macro_$($(a1)_value),true),,)\ + $(if $(call ENV_SET,$(2),$($(a1)_value),$(res)),$(res),)))),\ + $(if $(call _EQ,macroexpand,$($(a0)_value)),\ + $(call MACROEXPAND,$(call _nth,$(1),1),$(2)),\ + $(if $(call _EQ,make*,$($(a0)_value)),\ + $(foreach a1,$(call _nth,$(1),1),\ + $(and $(EVAL_DEBUG),$(info make*: $$(eval __result := $(call str_decode,$(value $(a1)_value)))))\ + $(eval __result := $(call str_decode,$(value $(a1)_value)))$(call READ_STR,$(__result))),\ + $(if $(call _EQ,try*,$($(a0)_value)),\ + $(foreach a1,$(call _nth,$(1),1),\ + $(foreach res,$(call EVAL,$(a1),$(2)),\ + $(if $(__ERROR),\ + $(foreach a2,$(call _nth,$(1),2),\ + $(foreach a20,$(call _nth,$(a2),0),\ + $(if $(call _EQ,catch*,$($(a20)_value)),\ + $(foreach a21,$(call _nth,$(a2),1),\ + $(foreach a22,$(call _nth,$(a2),2),\ + $(foreach binds,$(call list,$(a21)),\ + $(foreach catch_env,$(call ENV,$(2),$(binds),$(__ERROR)),\ + $(eval __ERROR :=)\ + $(call EVAL,$(a22),$(catch_env)))))),\ + $(res)))),\ + $(res)))),\ + $(if $(call _EQ,do,$($(a0)_value)),\ + $(call slast,$(call EVAL_AST,$(call srest,$(1)),$(2))),\ + $(if $(call _EQ,if,$($(a0)_value)),\ + $(foreach a1,$(call _nth,$(1),1),\ + $(foreach a2,$(call _nth,$(1),2),\ + $(foreach cond,$(call EVAL,$(a1),$(2)),\ + $(if $(or $(call _EQ,$(__nil),$(cond)),$(call _EQ,$(__false),$(cond))),\ + $(foreach a3,$(call _nth,$(1),3),$(call EVAL,$(a3),$(2))),\ + $(call EVAL,$(a2),$(2)))))),\ + $(if $(call _EQ,fn*,$($(a0)_value)),\ + $(foreach a1,$(call _nth,$(1),1),\ + $(foreach a2,$(call _nth,$(1),2),\ + $(call function,$$(call EVAL,$(a2),$$(call ENV,$(2),$(a1),$$1))))),\ + $(foreach el,$(call EVAL_AST,$(1),$(2)),\ + $(and $(EVAL_DEBUG),$(info invoke: $(call _pr_str,$(el))))\ + $(foreach f,$(call sfirst,$(el)),\ + $(foreach args,$(call srest,$(el)),\ + $(call apply,$(f),$(args)))))))))))))))))) +endef + +define EVAL +$(strip $(if $(__ERROR),,\ + $(and $(EVAL_DEBUG),$(info EVAL: $(call _pr_str,$(1))))\ + $(if $(call _list?,$(1)),\ + $(foreach ast,$(call MACROEXPAND,$(1),$(2)), + $(if $(call _list?,$(ast)),\ + $(word 1,$(strip $(call EVAL_INVOKE,$(ast),$(2)) $(__nil))),\ + $(ast))),\ + $(call EVAL_AST,$(1),$(2))))) +endef + + +# PRINT: +define PRINT +$(if $(__ERROR),Error: $(call _pr_str,$(__ERROR),yes),$(if $(1),$(call _pr_str,$(1),yes)))$(if $(__ERROR),$(eval __ERROR :=),) +endef + +# REPL: +REPL_ENV := $(call ENV) +REP = $(call PRINT,$(strip $(call EVAL,$(strip $(call READ,$(1))),$(REPL_ENV)))) +REPL = $(info $(call REP,$(call READLINE,"user> ")))$(if $(READLINE_EOF),,$(call REPL)) + +# Setup the environment +_ref = $(eval REPL_ENV := $(call ENV_SET,$(REPL_ENV),$(1),$(if $(2),$(2),$(1)))) +_fref = $(eval REPL_ENV := $(call ENV_SET,$(REPL_ENV),$(1),$(call function,$$(call $(2),$$1)))) + +# Import types functions +_import_types = $(if $(strip $(1)),$(call _fref,$(word 1,$(1)),$(word 2,$(1)))$(call _import_types,$(wordlist 3,$(words $(1)),$(1))),) +$(call _import_types,$(types_ns)) + +$(call _ref,readline,$(call function,$$(foreach res,$$(call string,$$(call READLINE,"$$(call str_decode,$$($$(1)_value))")),$$(if $$(READLINE_EOF),$$(__nil),$$(res))))) +$(call _ref,read-string,$(call function,$$(call READ_STR,$$(1)))) +$(call _ref,eval,$(call function,$$(call EVAL,$$(1),$$(REPL_ENV)))) + +_slurp = $(call string,$(call _read_file,$(1))) +_slurp_do = $(call string,(do $(call _read_file,$(1)))) +$(call _ref,slurp,$(call function,$$(call _slurp,$$(call str_decode,$$($$(1)_value))))) +$(call _ref,slurp-do,$(call function,$$(call _slurp_do,$$(call str_decode,$$($$(1)_value))))) + +# Defined in terms of the language itself +$(call do,$(call REP, (def! not (fn* (a) (if a false true))) )) +$(call do,$(call REP, (defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw "odd number of forms to cond")) (cons 'cond (rest (rest xs))))))) )) +$(call do,$(call REP, (defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs)))))))) )) +$(call do,$(call REP, (def! load-file (fn* (f) (eval (read-string (slurp-do f))))) )) + +# Load and eval any files specified on the command line +$(if $(MAKECMDGOALS),\ + $(foreach file,$(MAKECMDGOALS),$(call do,$(call REP, (load-file "$(file)") )))\ + $(eval INTERACTIVE :=),) +.PHONY: none $(MAKECMDGOALS) +none $(MAKECMDGOALS): + @true + +# Call the read-eval-print loop +$(if $(strip $(INTERACTIVE)),$(call REPL)) diff --git a/make/tests/common.mk b/make/tests/common.mk new file mode 100644 index 0000000..55b931d --- /dev/null +++ b/make/tests/common.mk @@ -0,0 +1,18 @@ + +# assert macros +assert = $(if $1,,$(error assert failure: $2)) +assert_not = $(if $1,$(error assert_not: $2),) +assert_eq = $(if $(call _EQ,$(1),$(2)),,$(error assert_eq failure: $(1) != $(2): $(3))) +# With debug: +#assert_eq = $(info 1: $(1))$(info 2: $(2))$(info 3: $(3))$(if $(call _EQ,$(1),$(2)),,$(error assert_eq failure: $(3))) + + +# REPL related wrappers +test_read = $(call READ_STR,$(1)) +ifndef MACROEXPAND +define MACROEXPAND +$(1) +endef +endif +test_re = $(strip $(call EVAL,$(call MACROEXPAND,$(strip $(call test_read,$(1))),$(REPL_ENV)),$(REPL_ENV))) +test_rep = $(call PRINT,$(strip $(call EVAL,$(call MACROEXPAND,$(strip $(call test_read,$(1))),$(REPL_ENV)),$(REPL_ENV)))) diff --git a/make/tests/reader.mk b/make/tests/reader.mk new file mode 100644 index 0000000..672d27b --- /dev/null +++ b/make/tests/reader.mk @@ -0,0 +1,76 @@ +INTERACTIVE = no + +include tests/common.mk +include reader.mk + +_tonum = $(call int_decode,$($(1)_value)) + +$(info Testing READ_STR of numbers) +$(call assert_eq,2,$(call _tonum,$(call READ_STR,2))) +$(call assert_eq,12345,$(call _tonum,$(call READ_STR,12345))) +$(call assert_eq,12345,$(call _tonum,$(call READ_STR,12345 "abc"))) +$(call assert_eq,12345,$(call _tonum,$(call READ_STR,12345"abc"))) +$(call assert_eq,12345,$(call _tonum,$(call READ_STR,12345(1)))) + +$(info Testing READ_STR of symbols) +$(call assert_eq,abc,$($(call READ_STR,abc)_value)) +$(call assert_eq,abc,$($(call READ_STR,abc )_value)) +$(call assert_eq,abc,$($(call READ_STR,abc"a str")_value)) +$(call assert_eq,abc,$($(call READ_STR,abc(2 3))_value)) + +$(info Testing READ_STR of strings) +$(call assert_eq,a string,$(call str_decode,$($(call READ_STR,"a string")_value))) +$(call assert_eq,a string (with parens),$(call str_decode,$($(call READ_STR,"a string (with parens)")_value))) +$(call assert_eq,a string,$(call str_decode,$($(call READ_STR,"a string"())_value))) +$(call assert_eq,a string,$(call str_decode,$($(call READ_STR,"a string"123)_value))) +$(call assert_eq,a string,$(call str_decode,$($(call READ_STR,"a string"abc)_value))) +$(call assert_eq,,$(call str_decode,$($(call READ_STR,"")_value))) +$(call assert_eq,abc ,$(call str_decode,$($(call READ_STR,"abc ")_value))) +$(call assert_eq, abc,$(call str_decode,$($(call READ_STR," abc")_value))) +$(call assert_eq,$$abc,$(call str_decode,$($(call READ_STR,"$$abc")_value))) +$(call assert_eq,abc$$(),$(call str_decode,$($(call READ_STR,"abc$$()")_value))) +$(call assert_eq,"xyz",$(call str_decode,$($(call READ_STR,"\"xyz\"")_value))) + +$(info Testing READ_STR of lists) +$(call assert_eq,2,$(call _count,$(call READ_STR,(2 3)))) +$(call assert_eq,2,$(call _tonum,$(call sfirst,$(call READ_STR,(2 3))))) +$(call assert_eq,3,$(call _tonum,$(call sfirst,$(call srest,$(call READ_STR,(2 3)))))) +L := $(strip $(call READ_STR,(+ 1 2 "str1" "string (with parens) and 'single quotes'"))) +$(call assert_eq,5,$(call _count,$(L))) +$(call assert_eq,str1,$(call str_decode,$($(call _nth,$(L),3)_value))) +$(call assert_eq,string (with parens) and 'single quotes',$(call str_decode,$($(call _nth,$(L),4)_value))) + +$(info Testing READ_STR of vectors) +$(call assert_eq,2,$(call _count,$(call READ_STR,[2 3]))) +$(call assert_eq,2,$(call _tonum,$(call sfirst,$(call READ_STR,[2 3])))) +$(call assert_eq,3,$(call _tonum,$(call sfirst,$(call srest,$(call READ_STR,[2 3]))))) +L := $(strip $(call READ_STR,[+ 1 2 "str1" "string (with parens) and 'single quotes'"])) +$(call assert_eq,5,$(call _count,$(L))) +$(call assert_eq,str1,$(call str_decode,$($(call _nth,$(L),3)_value))) +$(call assert_eq,string (with parens) and 'single quotes',$(call str_decode,$($(call _nth,$(L),4)_value))) + +$(info Testing READ_STR of quote/quasiquote) +$(call assert_eq,quote,$($(call _nth,$(call READ_STR,'1),0)_value)) #' +$(call assert_eq,1,$(call _tonum,$(call _nth,$(call READ_STR,'1),1))) #' +$(call assert_eq,quote,$($(call _nth,$(call READ_STR,'(1 2 3)),0)_value)) #' +$(call assert_eq,3,$(call _tonum,$(call _nth,$(call _nth,$(call READ_STR,'(1 2 3)),1),2))) #' + +$(call assert_eq,quasiquote,$($(call _nth,$(call READ_STR,`1),0)_value)) +$(call assert_eq,1,$(call _tonum,$(call _nth,$(call READ_STR,`1),1))) +$(call assert_eq,quasiquote,$($(call _nth,$(call READ_STR,`(1 2 3)),0)_value)) +$(call assert_eq,3,$(call _tonum,$(call _nth,$(call _nth,$(call READ_STR,`(1 2 3)),1),2))) + +$(call assert_eq,unquote,$($(call _nth,$(call READ_STR,~1),0)_value)) +$(call assert_eq,1,$(call _tonum,$(call _nth,$(call READ_STR,~1),1))) +$(call assert_eq,unquote,$($(call _nth,$(call READ_STR,~(1 2 3)),0)_value)) +$(call assert_eq,3,$(call _tonum,$(call _nth,$(call _nth,$(call READ_STR,~(1 2 3)),1),2))) + +$(call assert_eq,splice-unquote,$($(call _nth,$(call READ_STR,~@1),0)_value)) +$(call assert_eq,1,$(call _tonum,$(call _nth,$(call READ_STR,~@1),1))) +$(call assert_eq,splice-unquote,$($(call _nth,$(call READ_STR,~@(1 2 3)),0)_value)) +$(call assert_eq,3,$(call _tonum,$(call _nth,$(call _nth,$(call READ_STR,~@(1 2 3)),1),2))) + + +.PHONY: all +all: + @echo "All tests completed" diff --git a/make/tests/step9_interop.mk b/make/tests/step9_interop.mk new file mode 100644 index 0000000..0e44a88 --- /dev/null +++ b/make/tests/step9_interop.mk @@ -0,0 +1,14 @@ +INTERACTIVE = + +include tests/common.mk +include step9_interop.mk + +$(info Testing trivial macros) +$(call assert_eq,7,$(call test_rep, (make* "7") )) +$(call assert_eq,"XaY XbY XcY",$(call test_rep, (make* "\"$(foreach v,a b c,X$(v)Y)\"") )) +$(call assert_eq,(2 3 4),$(call test_rep, (make* "($(foreach v,1 2 3,$(call gmsl_plus,1,$(v))))") )) + + +.PHONY: all +all: + @echo "All tests completed" diff --git a/make/tests/types.mk b/make/tests/types.mk new file mode 100644 index 0000000..c1c1849 --- /dev/null +++ b/make/tests/types.mk @@ -0,0 +1,304 @@ +include tests/common.mk +include types.mk + +# treat an expression as a statement +do = $(eval __tmp := $(1)) + + +$(info Testing foreach as a let form) + +$(call assert_eq,XbX,$(foreach local_var,b,X$(local_var)X),\ + Using foreach as 'let' failed) + + +$(info Testing type function) +$(call assert_eq,make,$(call _obj_type,xyz),\ + (type xyz) is not 'make') +$(call assert_eq,nil,$(call _obj_type,$(__nil)),\ + (type $$(__nil)) is not 'nil') +$(call assert_eq,true,$(call _obj_type,$(__true)),\ + (type $$(__true)) is not 'true') +$(call assert_eq,false,$(call _obj_type,$(__false)),\ + (type $$(__false)) is not 'false') + + +$(info Testing number? function) + +$(call assert_eq,number,$(call _obj_type,$(call number,1))) +$(call assert_eq,number,$(call _obj_type,$(call number,10))) +$(call assert_eq,number,$(call _obj_type,$(call number,12345))) + + +$(info Testing symbols) + +$(call assert_eq,symbol,$(call _obj_type,$(call symbol,abc)),\ + (type (symbol abc)) is not 'symbol') +SYM1 := $(call symbol,a sym value) +$(call assert_eq,a sym value,$($(SYM1)_value)) +$(call assert_eq,$(__true),$(call symbol?,$(SYM1))) + + +$(info Testing strings) + +$(call assert_eq,string,$(call _obj_type,$(call string,abc)),\ + (type (string abc)) is not string) + +STR1 := $(call string,a string value) +$(call assert_eq,a string value,$(call str_decode,$($(STR1)_value))) +$(call assert_eq,$(__true),$(call string?,$(STR1))) +$(call assert_eq,14,$(call _count,$(STR1))) + +STR2 := $(call string,a string (with parens)) +$(call assert_eq,a string (with parens),$(call str_decode,$($(STR2)_value))) +$(call assert_eq,$(__true),$(call string?,$(STR2))) +$(call assert_eq,22,$(call _count,$(STR2))) + +$(info Testing strings (subs)) +$(call assert_eq,a string (with parens),$(call str_decode,$($(call subs,$(STR2),$(call number,2))_value))) +$(call assert_eq,a string,$(call str_decode,$($(call subs,$(STR2),$(call number,0),$(call number,8))_value))) + +$(info Testing strings (str)) +$(call assert_eq,a string value - a string (with parens),$(call str_decode,$($(call str,$(STR1) $(call string, - ) $(STR2))_value))) + + +$(info Testing function objects) + +$(call assert_eq,function,$(call _obj_type,$(call function,abc)),\ + (type (function abc)) is not 'function') +FN1 := $(call function,arg1:'$$(word 1,$$(1))' arg2:'$$(word 2,$$(1))') +$(call assert_eq,$(__true),$(call function?,$(FN1))) +$(call assert_eq,arg1:'A' arg2:'B',$(call apply,$(FN1),$(call list,A B))) + + +$(info Testing lists) + +$(call assert_eq,list,$(call _obj_type,$(call list)),\ + (type (list)) is not 'list') + +$(info Testing lists (cons)) +L1 := $(call cons,P $(call list)) +L2 := $(call cons,Q $(L1)) +$(call assert_eq,$(__true),$(call list?,$(L1))) +$(call assert_eq,$(__true),$(call list?,$(L2))) +$(call assert_eq,P,$(call sfirst,$(L1))) +$(call assert_eq,2,$(call _count,$(L2))) +$(call assert_eq,Q,$(call sfirst,$(L2))) +$(call assert_eq,P,$(call _nth,$(L2),1)) +$(call assert_eq,$(__true),$(call equal?,$(L1) $(call srest,$(L2)))) + +$(info Testing lists (concat)) +L1_2 := $(call concat,$(L1) $(L2)) +$(call assert_eq,3,$(call _count,$(L1_2))) +$(call assert_eq,P,$(call sfirst,$(L1_2))) +$(call assert_eq,Q,$(call _nth,$(L1_2),1)) +$(call assert_eq,P,$(call _nth,$(L1_2),2)) +$(call assert_eq,$(__true),$(call equal?,$(L2) $(call srest,$(L1_2)))) + +$(info Testing lists (conj)) +L3 := $(call _conj!,$(call list),A B) +L4 := $(call _conj!,$(call list),X $(L3)) +$(call assert_eq,$(__true),$(call list?,$(L3)),\ + (list? $$(L3))) +$(call assert_eq,$(__true),$(call list?,$(L4)),\ + (list? $$(L3))) +$(call assert_eq,A,$(call sfirst,$(L3)),\ + (sfirst $$(L3)) is not 'A') +$(call assert_eq,X,$(call sfirst,$(L4)),\ + (sfirst $$(L4)) is not 'X') +$(call assert_eq,$(__true),$(call list?,$(call _nth,$(L4),1)),\ + (_nth $$(L4),1) is not a list) +$(call assert_eq,A,$(call sfirst,$(call _nth,$(L4),1)),\ + (first (_nth $$(L4),1)) is not 'A') + + +$(info Testing hash_maps) + +X := $(call hash_map) +$(call assert_eq,$(__true),$(call hash_map?,$(X)),\ + (hash_map? $$(X))) +$(call assert_eq,$(__false),$(call vector?,$(X)),\ + (vector? $$(X))) + +mykey := $(call _string,a) +$(call assert_not,$(call _get,$(X),a),\ + (get $$(X),a)) +$(call assert_eq,$(__false),$(call contains?,$(X),$(mykey)),\ + (contains? $$(X),a)) +$(call do,$(call _assoc!,$(X),a,value of X a)) +$(call assert_eq,value of X a,$(call _get,$(X),a),\ + (get $$(X),a) is not 'value of Xa') +$(call assert_eq,$(__true),$(call contains?,$(X) $(mykey)),\ + (contains? $$(X),a)) + +Y := $(call hash_map) +$(call assert_eq,0,$(call _count,$(Y)),\ + (_count $$(Y))) +$(call do,$(call _assoc!,$(Y),a,value of Y a)) +$(call assert_eq,1,$(call _count,$(Y)),\ + (_count $$(Y))) +$(call do,$(call _assoc!,$(Y),b,value of Y b)) +$(call assert_eq,2,$(call _count,$(Y)),\ + (_count $$(Y))) +$(call assert_eq,value of Y a,$(call _get,$(Y),a),\ + (get $$(Y),a) is not 'value of Y a') +$(call assert_eq,value of Y b,$(call _get,$(Y),b),\ + (get $$(Y),b) is not 'value of Y b') +$(call assert_eq,value of Y a value of Y b,$(call raw_flat,$(Y),b),\ + (raw_flat $(Y)) is not 'value of Y a value of Y b') + +$(call do,$(call _assoc!,$(X),b,$(Y))) +$(call assert_eq,2,$(call _count,$(Y),a),\ + (_count $$(Y)) should still be 2) + +$(call assert_eq,$(__true),$(call hash_map?,$(call _get,$(X),b)),\ + (hash_map? (get $$(X),b))) + +$(call assert_eq,$(call _get,$(call _get,$(X),b),a),value of Y a,\ + (get (get $(X),b),a) is not 'value of Y a') +$(call assert_eq,$(call _get,$(call _get,$(X),b),b),value of Y b,\ + (get (get $(X),b),b) is not 'value of Y b') + +$(call do,$(call _dissoc!,$(Y),a)) +$(call assert_eq,1,$(call _count,$(Y)),\ + (_count $$(Y)) should now be 1) +$(call assert_not,$(call _get,$(Y),a),\ + (get $$(Y),a)) +$(call do,$(call _dissoc!,$(Y),b)) +$(call assert_eq,0,$(call _count,$(Y)),\ + (_count $$(Y)) should now be 0) +$(call assert_not,$(call _get,$(Y),b),\ + (get $$(Y),b)) + + +$(info Testing vectors) + +V1 := $(call _conj!,$(call vector),first.vector.value second.vector.value third.vector.value) +$(call assert_eq,$(__true),$(call vector?,$(V1)),\ + (vector? $$(V1))) +$(call assert_eq,first.vector.value,$(call _nth,$(V1),0)) +$(call assert_eq,second.vector.value,$(call _nth,$(V1),1)) +$(call assert_eq,third.vector.value,$(call _nth,$(V1),2)) +$(call assert_eq,third.vector.value,$(call slast,$(V1))) +$(call assert_eq,3,$(call _count,$(V1))) + +V2 := $(call _conj!,$(call vector),A B C) +$(call assert_eq,3,$(call _count,$(V2)),\ + (_count $$(V2)) is not 3) +$(call assert_eq,A B C,$($(V2)_value)) +$(call assert_eq,A,$(call sfirst,$(V2)),\ + (first $$(V2)) is not 'A') +$(call assert_eq,$(__true),$(call list?,$(call srest,$(V2))),\ + (rest $$(V2)) is not a vector) +$(call assert_eq,B C,$($(call srest,$(V2))_value)) +$(call assert_eq,B,$(call sfirst,$(call srest,$(V2))),\ + (first (rest $$(V2))) is not 'B') +$(call assert_eq,C,$(call sfirst,$(call srest,$(call srest,$(V2)))),\ + (first (rest (rest $$(V2)))) is not 'C') +$(call assert_eq,C,$(call _nth,$(V2),2),\ + (_nth $$(V2),2) is not 'C') + +V2_1 := $(call _conj!,$(V2),$(V1)) +$(call assert_eq,4,$(call _count,$(V2_1)),\ + (_count $$(V2_1)) is not 4) +$(call assert_eq,C,$(call _nth,$(V2_1),2),\ + (_nth $$(V2_1),2) is no longer 'C') +$(call assert_eq,$(__true),$(call vector?,$(call _nth,$(V2_1),3)),\ + (_nth $$(V2_1),3) is not a vector) +$(call assert_eq,second.vector.value,$(call _nth,$(call _nth,$(V2_1),3),1),\ + (_nth (_nth $$(V2_1),3),1) is not 'second.vector.value') + +$(info Testing vectors (rest)) + +V3 := $(call srest,$(V2_1)) +$(call assert_eq,3,$(call _count,$(V3)),\ + (_count $$(V3)) is not 3) +$(call assert_eq,B,$(call sfirst,$(V3)),\ + (first $$(V3)) is not 'B') +$(call assert_eq,$(__true),$(call vector?,$(call _nth,$(V3),2)),\ + (_nth $$(V3),2) is not a vector) +$(call assert_eq,second.vector.value,$(call _nth,$(call _nth,$(V3),2),1),\ + (_nth (_nth $$(V3),2),1) is not 'second.vector.value') + +$(info Testing vectors (contains?)) + +$(call assert_eq,$(__true),$(call _contains?,$(V2_1),0),\ + (contains? $$(V2_1),0)) +$(call assert_eq,,$(call _contains?,$(V2_1),7),\ + (contains? $$(V2_1),7)) + + +$(info Testing _apply function) + +label_args = $(word 1,$(1))$(word 2,$(1))$(word 3,$(1))$(word 4,$(1)) +$(call assert_eq,,$(call _apply,label_args,$(call list))) +$(call assert_eq,A,$(call _apply,label_args,$(call list,A))) +$(call assert_eq,AB,$(call _apply,label_args,$(call list,A B))) +$(call assert_eq,ABCD,$(call _apply,label_args,$(call list,A B C D))) + + +$(info Testing smap function) + +L5 := $(call _conj!,$(call list),$(call number,1) $(call number,2) $(call number,3)) +inc = $(call number_plus,$(call number,1) $(1)) +$(call assert_eq,(2 3 4),$(call _pr_str,$(call _smap,inc,$(L5)))) +inc_func := $(call function,$$(call number_plus,$$(call number,1) $$(1))) +$(call assert_eq,(2 3 4),$(call _pr_str,$(call smap,$(inc_func) $(L5)))) + + +$(info Testing equal? function) +$(call assert_eq,$(__true),$(call equal?,2 2)) +$(call assert_eq,$(__false),$(call equal?,2 3)) +$(call assert_eq,$(__false),$(call equal?,2 3)) +$(call assert_eq,$(__true),$(call equal?,abc abc)) +$(call assert_eq,$(__false),$(call equal?,abc abz)) +$(call assert_eq,$(__false),$(call equal?,zbc abc)) +$(call assert_eq,$(__true),$(call equal?,$(call string,abc) $(call string,abc))) +$(call assert_eq,$(__false),$(call equal?,$(call string,abc) $(call string,abz))) +$(call assert_eq,$(__false),$(call equal?,$(call string,zbc) $(call string,abc))) +$(call assert_eq,$(__true),$(call equal?,$(call symbol,abc) $(call symbol,abc))) +$(call assert_eq,$(__false),$(call equal?,$(call symbol,abc) $(call symbol,abz))) +$(call assert_eq,$(__false),$(call equal?,$(call symbol,zbc) $(call symbol,abc))) +L6 := $(call _conj!,$(call list),1 2 3) +L7 := $(call _conj!,$(call list),1 2 3) +L8 := $(call _conj!,$(call list),1 2 Z) +L9 := $(call _conj!,$(call list),Z 2 3) +L10 := $(call _conj!,$(call list),1 2) +$(call assert_eq,$(__true),$(call equal?,$(L6) $(L7))) +$(call assert_eq,$(__false),$(call equal?,$(L6) $(L8))) +$(call assert_eq,$(__false),$(call equal?,$(L6) $(L9))) +$(call assert_eq,$(__false),$(call equal?,$(L6) $(L10))) +$(call assert_eq,$(__false),$(call equal?,$(L10) $(L6))) + + +$(info Testing empty? function) +$(call assert_eq,$(__true),$(call empty?,$(call list))) +$(call assert_eq,$(__false),$(call empty?,$(call list,1))) + + +$(info Testing ENV (1 level)) +env1 := $(call ENV) +$(call assert_eq,,$(call ENV_GET,$(env1),a)) +$(call assert_eq,$(env1),$(call ENV_SET,$(env1),a,val_a)) +$(call assert_eq,$(env1),$(call ENV_SET,$(env1),b,val_b)) +$(call assert_eq,$(env1),$(call ENV_SET,$(env1),=,val_eq)) +$(call assert_eq,val_a,$(call ENV_GET,$(env1),a)) +$(call assert_eq,val_b,$(call ENV_GET,$(env1),b)) +$(call assert_eq,val_eq,$(call ENV_GET,$(env1),=)) +$(call assert_eq,hash_map,$(call _obj_type,$(call ENV_FIND,$(env1),a))) +$(call assert_eq,val_a,$(call _get,$(call ENV_FIND,$(env1),a),a)) + +$(info Testing ENV (2 levels)) +env2 := $(call ENV,$(env1)) +$(call assert_eq,$(env2),$(call ENV_SET,$(env2),b,val_b2)) +$(call assert_eq,$(env2),$(call ENV_SET,$(env2),c,val_c)) +$(call assert_eq,$(env1),$(call ENV_FIND,$(env2),a)) +$(call assert_eq,$(env2),$(call ENV_FIND,$(env2),b)) +$(call assert_eq,$(env2),$(call ENV_FIND,$(env2),c)) +$(call assert_eq,val_a,$(call ENV_GET,$(env2),a)) +$(call assert_eq,val_b2,$(call ENV_GET,$(env2),b)) +$(call assert_eq,val_c,$(call ENV_GET,$(env2),c)) + + +.PHONY: all +all: + @echo "All tests completed" diff --git a/make/types.mk b/make/types.mk new file mode 100644 index 0000000..234ca51 --- /dev/null +++ b/make/types.mk @@ -0,0 +1,484 @@ +# +# mal (Make Lisp) Object Types and Functions +# + +ifndef __mal_types_included +__mal_types_included := true + +_TOP_DIR := $(dir $(lastword $(MAKEFILE_LIST))) +include $(_TOP_DIR)util.mk + +# magic is \u2344 \u204a +__obj_magic = ⍄⁊ +# \u2256 +__equal = ≛ +__obj_hash_code = 0 + +__new_obj_hash_code = $(eval __obj_hash_code := $(call gmsl_plus,1,$(__obj_hash_code)))$(__obj_hash_code) + +__new_obj = $(__obj_magic)_$(1)_$(call __new_obj_hash_code) + +__new_obj_like = $(foreach type,$(word 2,$(subst _, ,$(1))),$(__obj_magic)_$(type)_$(__new_obj_hash_code)) + +__get_obj_values = $(strip \ + $(if $(filter $(__obj_magic)_hmap_%,$(1)),\ + $(sort $(foreach v,$(filter %_value,$(filter $(1)_%,$(.VARIABLES))),$(if $(call _undefined?,$(v)),,$(v)))),\ + $($(1)_value))) + +__ERROR := + +# +# General functions +# + +# Return the type of the object (or "make" if it's not a object +_obj_type = $(strip \ + $(if $(filter $(__obj_magic)_symb_%,$(1)),symbol,\ + $(if $(filter $(__obj_magic)_list_%,$(1)),list,\ + $(if $(filter $(__obj_magic)_numb_%,$(1)),number,\ + $(if $(filter $(__obj_magic)_func_%,$(1)),function,\ + $(if $(filter $(__obj_magic)_strn_%,$(1)),string,\ + $(if $(filter $(__obj_magic)__nil_%,$(1)),nil,\ + $(if $(filter $(__obj_magic)_true_%,$(1)),true,\ + $(if $(filter $(__obj_magic)_fals_%,$(1)),false,\ + $(if $(filter $(__obj_magic)_vect_%,$(1)),vector,\ + $(if $(filter $(__obj_magic)_atom_%,$(1)),atom,\ + $(if $(filter $(__obj_magic)_hmap_%,$(1)),hash_map,\ + $(if $(filter $(__obj_magic)_undf_%,$(1)),undefined,\ + make))))))))))))) +obj_type = $(call string,$(call _obj_type,$(1))) + +# return a printable form of the argument, the second parameter is +# 'print_readably' which backslashes quotes in string values +_pr_str = $(if $(1),$(foreach ot,$(call _obj_type,$(1)),$(if $(call _EQ,make,$(ot)),$(call _error,_pr_str failed on $(1)),$(call $(ot)_pr_str,$(1),$(2)))),) + +# Like _pr_str but takes multiple values in first argument, the second +# parameter is 'print_readably' which backslashes quotes in string +# values, the third parameter is the delimeter to use between each +# _pr_str'd value +_pr_str_mult = $(call _pr_str,$(word 1,$(1)),$(2))$(if $(word 2,$(1)),$(3)$(call _pr_str_mult,$(wordlist 2,$(words $(1)),$(1)),$(2),$(3)),) + +pr_str = $(call string,$(call _pr_str_mult,$(1),yes, )) +str = $(call string,$(call _pr_str_mult,$(1),,)) +prn = $(info $(call _pr_str_mult,$(1),yes, )) +println = $(info $(subst \n,$(NEWLINE),$(call _pr_str_mult,$(1),, ))) + +_clone_obj = $(strip \ + $(foreach new_hcode,$(call __new_obj_hash_code),\ + $(foreach new_obj,$(foreach type,$(word 2,$(subst _, ,$(1))),$(__obj_magic)_$(type)_$(new_hcode)),\ + $(if $(filter $(__obj_magic)_hmap_%,$(1)),\ + $(foreach v,$(call __get_obj_values,$(1)),\ + $(eval $(v:$(1)_%=$(new_obj)_%) := $($(v))))\ + $(eval $(new_obj)_size := $($(1)_size)),\ + $(if $(filter $(__obj_magic)_func_%,$(1)),\ + $(eval $(new_obj)_value = $(value $(1)_value)),\ + $(eval $(new_obj)_value := $(strip $($(1)_value)))))\ + $(new_obj)))) + +with_meta = $(strip \ + $(foreach new_obj,$(call _clone_obj,$(word 1,$(1))),\ + $(eval $(new_obj)_meta := $(strip $(word 2,$(1))))\ + $(new_obj))) + +meta = $(strip $($(1)_meta)) + + +# +# Special atomic values +# +__undefined = $(__obj_magic)_undf_0 +__nil = $(__obj_magic)__nil_0 +__true = $(__obj_magic)_true_0 +__false = $(__obj_magic)_fals_0 + +_undefined? = $(or $(call _EQ,undefined,$(origin $(1))),$(filter $(__undefined),$($(1)))) +undefined? = $(if $(call _undefined?,$(1)),$(__true),$(__false)) + +_nil? = $(if $(filter $(__obj_magic)__nil_%,$(1)),$(__true),) +nil? = $(if $(call _nil?,$(1)),$(__true),$(__false)) +nil_pr_str = nil + +_true? = $(if $(filter $(__obj_magic)_true_%,$(1)),$(__true),) +true? = $(if $(call _true?,$(1)),$(__true),$(__false)) +true_pr_str = true + +_false? = $(if $(filter $(__obj_magic)_fals_%,$(1)),$(__true),) +false? = $(if $(call _false?,$(1)),$(__true),$(__false)) +false_pr_str = false + + +# +# Numbers +# + +_pnumber = $(foreach hcode,$(call __new_obj_hash_code),$(__obj_magic)_numb_$(hcode)$(eval $(__obj_magic)_numb_$(hcode)_value := $(1))) +number = $(call _pnumber,$(call int_encode,$(1))) + +_number? = $(if $(filter $(__obj_magic)_numb_%,$(1)),$(__true),) +number? = $(if $(call _number?,$(1)),$(__true),$(__false)) + +number_pr_str = $(call int_decode,$($(1)_value)) + +number_plus = $(call _pnumber,$(call int_plus,$($(word 1,$(1))_value),$($(word 2,$(1))_value))) +number_subtract = $(call _pnumber,$(call int_subtract,$($(word 1,$(1))_value),$($(word 2,$(1))_value))) +number_multiply = $(call _pnumber,$(call int_multiply,$($(word 1,$(1))_value),$($(word 2,$(1))_value))) +number_divide = $(call _pnumber,$(call int_divide,$($(word 1,$(1))_value),$($(word 2,$(1))_value))) + +number_gt = $(if $(call int_gt,$($(word 1,$(1))_value),$($(word 2,$(1))_value)),$(__true),$(__false)) +number_gte = $(if $(call int_gte,$($(word 1,$(1))_value),$($(word 2,$(1))_value)),$(__true),$(__false)) +number_lt = $(if $(call int_lt,$($(word 1,$(1))_value),$($(word 2,$(1))_value)),$(__true),$(__false)) +number_lte = $(if $(call int_lte,$($(word 1,$(1))_value),$($(word 2,$(1))_value)),$(__true),$(__false)) + + +# +# Symbols +# +symbol = $(foreach hcode,$(call __new_obj_hash_code),$(__obj_magic)_symb_$(hcode)$(eval $(__obj_magic)_symb_$(hcode)_value := $(1))) + +_symbol? = $(if $(filter $(__obj_magic)_symb_%,$(1)),$(__true),) +symbol? = $(if $(call _symbol?,$(1)),$(__true),$(__false)) + +symbol_pr_str = $($(1)_value) + +# +# Strings +# +_string = $(foreach hcode,$(call __new_obj_hash_code),$(__obj_magic)_strn_$(hcode)$(eval $(__obj_magic)_strn_$(hcode)_value := $(1))) +string = $(foreach hcode,$(call __new_obj_hash_code),$(__obj_magic)_strn_$(hcode)$(eval $(__obj_magic)_strn_$(hcode)_value := $(call str_encode,$(1)))) + +_string? = $(if $(filter $(__obj_magic)_strn_%,$(1)),$(__true),) +string? = $(if $(call _string?,$(1)),$(__true),$(__false)) + +string_pr_str = $(if $(2),"$(subst $(DQUOTE),$(ESC_DQUOTE),$(subst $(SLASH),$(SLASH)$(SLASH),$(call str_decode,$($(1)_value))))",$(call str_decode,$($(1)_value))) + +subs = $(strip \ + $(foreach start,$(call gmsl_plus,1,$(call int_decode,$($(word 2,$(1))_value))),\ + $(foreach end,$(if $(3),$(call int_decode,$($(3)_value)),$(words $($(word 1,$(1))_value))),\ + $(call _string,$(wordlist $(start),$(end),$($(word 1,$(1))_value)))))) + +# +# Function objects +# + +# Return a function object. The first parameter is the +# function/macro 'source'. Note that any $ must be escaped as $$ to be +# preserved and become positional arguments for when the +# function/macro is later invoked. +function = $(foreach hcode,$(call __new_obj_hash_code),$(__obj_magic)_func_$(hcode)$(eval $(__obj_magic)_func_$(hcode)_value = $(1))) + +_function? = $(if $(filter $(__obj_magic)_func_%,$(1)),$(__true),) +function? = $(if $(call _function?,$(1)),$(__true),$(__false)) + +function_pr_str = <$(if $(word 6,$(value $(1)_value)),$(wordlist 1,5,$(value $(1)_value))...,$(value $(1)_value))> + +# Takes a function name and a list object of arguments and invokes +# the function with space separated arguments +_apply = $(call $(1),$($(2)_value)) + +# Takes a function object and a list object of arguments and invokes +# the function with space separated arguments +apply = $(call $(1)_value,$($(2)_value)) + +# Takes a space separated arguments and invokes the first argument +# (function object) using the remaining arguments. +sapply = $(call $(word 1,$(1))_value,$($(word 2,$(1))_value)) + +# +# hash maps (associative arrays) +# + +# create a new anonymous empty hash map +_hash_map = $(foreach hcode,$(call __new_obj_hash_code),$(__obj_magic)_hmap_$(hcode)$(eval $(__obj_magic)_hmap_$(hcode)_size := 0)) +hash_map = $(word 1,$(foreach new_hmap,$(call _hash_map),$(new_hmap) $(if $(1),$(call _assoc_seq!,$(new_hmap),$(1))))) + +_hash_map? = $(if $(filter $(__obj_magic)_hmap_%,$(1)),$(__true),) +hash_map? = $(if $(call _hash_map?,$(1)),$(__true),$(__false)) + +hash_map_pr_str = {$(foreach v,$(call __get_obj_values,$(1)),"$(foreach hcode,$(word 3,$(subst _, ,$(1))),$(patsubst $(1)_%,%,$(v:%_value=%)))" $(call _pr_str,$($(v)),$(2)))} + +# Set multiple key/values in a map +_assoc_seq! = $(call _assoc!,$(1),$(call str_decode,$($(word 1,$(2))_value)),$(word 2,$(2)))$(if $(word 3,$(2)),$(call _assoc_seq!,$(1),$(wordlist 3,$(words $(2)),$(2))),) + +# set a key/value in the hash map +_assoc! = $(foreach k,$(subst =,$(__equal),$(2)),$(if $(call _undefined?,$(1)_$(k)_value),$(eval $(1)_size := $(call gmsl_plus,$($(1)_size),1)),)$(eval $(1)_$(k)_value := $(3))$(1)) + +# set a key/value in a copy of the hash map +# TODO: multiple arguments +assoc = $(foreach hm,$(call _clone_obj,$(word 1,$(1))),$(call _assoc!,$(hm),$(call str_decode,$($(word 2,$(1))_value)),$(word 3,$(1)))) + +# unset a key in the hash map +_dissoc! = $(foreach k,$(subst =,$(__equal),$(2)),$(if $(call _undefined?,$(1)_$(k)_value),,$(eval $(1)_$(k)_value := $(__undefined))$(eval $(1)_size := $(call gmsl_subtract,$($(1)_size),1))))$(1) + +# unset a key in a copy of the hash map +# TODO: this could be made more efficient by not copying the key in +# the first place +# TODO: multiple arguments +dissoc = $(foreach hm,$(call _clone_obj,$(word 1,$(1))),$(call _dissoc!,$(hm),$(call str_decode,$($(word 2,$(1))_value)))) + +keys = $(foreach new_list,$(call _list),$(new_list)$(eval $(new_list)_value := $(foreach v,$(call __get_obj_values,$(1)),$(call string,$(word 4,$(subst _, ,$(v))))))) + +vals = $(foreach new_list,$(call _list),$(new_list)$(eval $(new_list)_value := $(foreach v,$(call __get_obj_values,$(1)),$($(v))))) + + + +# Hash map and vector functions + +# retrieve the value of a plain string key from the hash map, or +# retrive a vector by plain index +_get = $(strip \ + $(if $(call _hash_map?,$(1)),\ + $(foreach k,$(subst =,$(__equal),$(2)),$(if $(call _undefined?,$(1)_$(k)_value),,$($(1)_$(k)_value))),\ + $(if $(call _vector?,$(1)),\ + $(word $(call gmsl_plus,1,$(2)),$($(1)_value)),\ + ,))) + +# retrieve the value of a string key object from the hash map, or +# retrive a vector by number object index +get = $(strip \ + $(if $(call _hash_map?,$(word 1,$(1))),\ + $(call _get,$(word 1,$(1)),$(call str_decode,$($(word 2,$(1))_value))),\ + $(call _get,$(word 1,$(1)),$(call number_pr_str,$(word 2,$(1)))))) + +_contains? = $(strip \ + $(if $(call _hash_map?,$(1)),\ + $(foreach k,$(subst =,$(__equal),$(2)),$(if $(call _undefined?,$(1)_$(k)_value),,$(__true))),\ + $(if $(call _vector?,$(1)),\ + $(if $(word $(call gmsl_plus,1,$(2)),$($(1)_value)),$(__true),),\ + ,))) +contains? = $(if $(call _contains?,$(word 1,$(1)),$(call str_decode,$($(word 2,$(1))_value))),$(__true),$(__false)) + + +# +# Errors/Exceptions +# +_error = $(eval __ERROR := $(call string,$(1))) +throw = $(eval __ERROR := $(1)) + + +# +# vectors +# + +_vector = $(foreach hcode,$(call __new_obj_hash_code),$(__obj_magic)_vect_$(hcode)) +vector = $(word 1,$(foreach new_vect,$(call _vector),$(new_vect) $(eval $(new_vect)_value := $1))) + +_vector? = $(if $(filter $(__obj_magic)_vect_%,$(1)),$(__true),) +vector? = $(if $(call _vector?,$(1)),$(__true),$(__false)) + +vector_pr_str = [$(foreach v,$(call __get_obj_values,$(1)),$(call _pr_str,$(v),$(2)))] + +# +# list (same as vectors for now) +# + +_list = $(foreach hcode,$(call __new_obj_hash_code),$(__obj_magic)_list_$(hcode)) +list = $(word 1,$(foreach new_list,$(call _list),$(new_list) $(eval $(new_list)_value := $1))) + +_list? = $(if $(filter $(__obj_magic)_list_%,$(1)),$(__true),) +list? = $(if $(call _list?,$(1)),$(__true),$(__false)) + +list_pr_str = ($(foreach v,$(call __get_obj_values,$(1)),$(call _pr_str,$(v),$(2)))) + +cons = $(word 1,$(foreach new_list,$(call _list),$(new_list) $(eval $(new_list)_value := $(strip $(word 1,$(1)) $(call __get_obj_values,$(word 2,$(1))))))) + + +# +# atoms +# +atom = $(strip \ + $(foreach hcode,$(call __new_obj_hash_code),\ + $(foreach new_atom,$(__obj_magic)_atom_$(hcode),\ + $(new_atom)\ + $(eval $(new_atom)_value := $(1))))) + +_atom? = $(if $(filter $(__obj_magic)_atom_%,$(1)),$(__true),) +atom? = $(if $(call _atom?,$(1)),$(__true),$(__false)) + +atom_pr_str = (atom $(call _pr_str,$($(1)_value),$(2))) + +deref = $($(1)_value) + +reset! = $(eval $(word 1,$(1))_value := $(word 2,$(1)))$(word 2,$(1)) + +swap! = $(foreach resp,$(call $(word 2,$(1))_value,$($(word 1,$(1))_value) $(wordlist 3,$(words $(1)),$(1))),\ + $(eval $(word 1,$(1))_value := $(resp))\ + $(resp)) + + +# +# sequence operations +# + +_sequential? = $(if $(filter $(__obj_magic)_list_% $(__obj_magic)_vect_%,$(1)),$(__true),) +sequential? = $(if $(call _sequential?,$(1)),$(__true),$(__false)) + +raw_flat = $(foreach val,$(call __get_obj_values,$(1)),$($(val))) + +_nth = $(word $(call gmsl_plus,1,$(2)),$($(1)_value)) + +nth = $(word $(call gmsl_plus,1,$(call int_decode,$($(word 2,$(1))_value))),$($(word 1,$(1))_value)) + +empty? = $(if $(call _EQ,0,$(if $(call _hash_map?,$(1)),$($(1)_size),$(words $($(1)_value)))),$(__true),$(__false)) + +concat = $(word 1,$(foreach new_list,$(call _list),$(new_list) $(eval $(new_list)_value := $(strip $(foreach lst,$1,$(call __get_obj_values,$(lst))))))) + +conj = $(word 1,$(foreach new_list,$(call __new_obj_like,$(word 1,$(1))),$(new_list) $(eval $(new_list)_value := $(strip $($(word 1,$(1))_value) $(wordlist 2,$(words $(1)),$(1)))))) + +# conj that mutates a sequence in-place to append the call arguments +_conj! = $(eval $(1)_value := $(strip $($(1)_value) $2 $3 $4 $5 $6 $7 $8 $9 $(10) $(11) $(12) $(13) $(14) $(15) $(16) $(17) $(18) $(19) $(20)))$(1) + +_count = $(strip \ + $(if $(call _hash_map?,$(1)),\ + $($(1)_size),\ + $(words $($(1)_value)))) +count = $(call number,$(call _count,$(1))) + +sfirst = $(word 1,$($(1)_value)) + +slast = $(word $(words $($(1)_value)),$($(1)_value)) + +# Creates a new vector/list of the everything after but the first +# element +srest = $(word 1,$(foreach new_list,$(call _list),$(new_list) $(eval $(new_list)_value := $(wordlist 2,$(words $($(1)_value)),$($(1)_value))))) + +# maps a make function over a list object, using mutating _conj! +_smap = $(word 1,\ + $(foreach new_list,$(call _list),\ + $(new_list)\ + $(foreach v,$(call __get_obj_values,$(2)),\ + $(call _conj!,$(new_list),$(call $(1),$(v),$(3),$(4)))))) + +# Same as _smap but returns a vector +_smap_vec = $(word 1,\ + $(foreach new_vector,$(call vector),\ + $(new_vector)\ + $(foreach v,$(call __get_obj_values,$(2)),\ + $(call _conj!,$(new_vector),$(call $(1),$(v),$(3),$(4)))))) + +# Map a function object over a list object +smap = $(strip\ + $(foreach func,$(word 1,$(1)),\ + $(foreach lst,$(word 2,$(1)),\ + $(foreach type,$(word 2,$(subst _, ,$(lst))),\ + $(foreach new_hcode,$(call __new_obj_hash_code),\ + $(foreach sz,$(words $(call __get_obj_values,$(lst))),\ + $(eval $(__obj_magic)_$(type)_$(new_hcode)_value := $(strip \ + $(foreach val,$(call __get_obj_values,$(lst)),\ + $(call $(func)_value,$(val))))))\ + $(__obj_magic)_$(type)_$(new_hcode)))))) + + + +_equal? = $(strip \ + $(foreach ot1,$(call _obj_type,$(1)),$(foreach ot2,$(call _obj_type,$(2)),\ + $(if $(or $(call _EQ,$(ot1),$(ot2)),\ + $(and $(call _sequential?,$(1)),$(call _sequential?,$(2)))),\ + $(if $(or $(call _string?,$(1)),$(call _symbol?,$(1)),$(call _number?,$(1))),\ + $(call _EQ,$($(1)_value),$($(2)_value)),\ + $(if $(or $(call _vector?,$(1)),$(call _list?,$(1)),$(call _hash_map?,$(1))),\ + $(if $(and $(call _EQ,$(call _count,$(1)),$(call _count,$(2))),\ + $(call _EQ,$(call _count,$(1)),$(words $(call gmsl_pairmap,_equal?,$(call __get_obj_values,$(1)),$(call __get_obj_values,$(2)))))),$(__true),),\ + $(call _EQ,$(1),$(2)))))))) + +equal? = $(if $(call _equal?,$(word 1,$(1)),$(word 2,$(1))),$(__true),$(__false)) + +# +# ENV +# + +# An ENV environment is a hash-map with an __outer__ reference to an +# outer environment +define BIND_ARGS +$(strip \ + $(word 1,$(1) \ + $(foreach fparam,$(call _nth,$(2),0),\ + $(if $(call _EQ,&,$($(fparam)_value)), + $(call ENV_SET,$(1),$($(call _nth,$(2),1)_value),$(strip \ + $(foreach new_list,$(call _list), + $(word 1,$(new_list) \ + $(foreach val,$(3),$(call _conj!,$(new_list),$(val))))))),\ + $(foreach val,$(word 1,$(3)),\ + $(call ENV_SET,$(1),$($(fparam)_value),$(val))\ + $(foreach left,$(call srest,$(2)),\ + $(if $(call _EQ,0,$(call _count,$(left))),\ + ,\ + $(call BIND_ARGS,$(1),$(left),$(wordlist 2,$(words $(3)),$(3)))))))))) +endef + +# Create a new ENV and optional bind values in it +# $(1): outer environment (set as a key named __outer__) +# $(2): list/vector object of bind forms +# $(3): space separated list of expressions to bind +ENV = $(strip $(foreach new_env,$(call _assoc!,$(call _hash_map),__outer__,$(if $(1),$(1),$(__nil))),$(if $(2),$(call BIND_ARGS,$(new_env),$(2),$(3)),$(new_env)))) +ENV_FIND = $(strip \ + $(if $(call _contains?,$(1),$(subst =,$(__equal),$(2))),\ + $(1),\ + $(if $(call _EQ,$(__nil),$(call _get,$(1),__outer__)),\ + ,\ + $(call ENV_FIND,$(call _get,$(1),__outer__),$(2))))) + +ENV_GET = $(foreach env,|$(call ENV_FIND,$(1),$(2))|,$(if $(call _EQ,||,$(env)),$(call _error,'$(2)' not found),$(call _get,$(strip $(subst |,,$(env))),$(subst =,$(__equal),$(2))))) + +ENV_SET = $(if $(call _assoc!,$(1),$(subst =,$(__equal),$(2)),$(3)),$(1),) + +# +# Visualize Objects in memory +# + +__var_name = $(word 2,$(subst _, ,$(1)))_$(word 3,$(subst _, ,$(1))) +__var_idx := 0 +__var_print = $(foreach v,$(1),\ + $(foreach var,$(call __var_name,$(v)),\ + $(if $(or $(call _list?,$(v)),$(call _vector?,$(v))),\ + $(info $(2)$(var):)\ + $(eval __var_idx := $(call gmsl_plus,1,$(__var_idx)))\ + $(foreach lidx,__lidx_$(__var_idx),\ + $(eval $(lidx) := 0)\ + $(foreach val,$($(v)_value),\ + $(call __var_print,$(val),$(2)$(SPACE)$(SPACE)$($(lidx)): )\ + $(eval $(lidx) := $(call gmsl_plus,1,$($(lidx)))))),\ + $(if $(call _hash_map?,$(v)),\ + $(info $(2)$(var):)\ + $(foreach vkey,$(filter $(v)_%,$(.VARIABLES)),\ + $(foreach key,$(word 4,$(subst _, ,$(vkey))),\ + $(info $(2)$(SPACE)$(SPACE)$(subst $(__equal),=,$(key)): )\ + $(call __var_print,$($(vkey)),$(2)$(SPACE)$(SPACE)$(SPACE)$(SPACE)))),\ + $(if $(call _symbol?,$(v)),\ + $(info $(2)$(var): $($(v)_value)),\ + $(if $(call _number?,$(v)),\ + $(info $(2)$(var): $(call int_decode,$($(v)_value))),\ + $(if $(call _nil?,$(v)),\ + $(info $(2)nil),\ + $(if $(call _function?,$(v)),\ + $(if $(word 6,$(value $(v)_value)),\ + $(info $(2)$(var): $(wordlist 1,5,$(value $(v)_value))...),\ + $(info $(2)$(var): $(value $(v)_value))),\ + $(info $(2)$(var): ...))))))))) + + +visualize_memory = $(foreach var,$(sort $(foreach vv,$(filter $(__obj_magic)_%,$(.VARIABLES)),$(call __var_name,$(vv)))),$(call __var_print,$(__obj_magic)_$(var))) + +# +# Namespace of type functions +# +types_ns = pr-str pr_str str str prn prn println println \ + with-meta with_meta meta meta \ + type obj_type = equal? \ + nil? nil? true? true? false? false? \ + number? number? \ + > number_gt >= number_gte < number_lt <= number_lte \ + + number_plus - number_subtract * number_multiply / number_divide \ + symbol? symbol? function? function? \ + string? string? subs subs \ + hash-map hash_map map? hash_map? assoc assoc dissoc dissoc \ + get get contains? contains? keys keys vals vals \ + throw throw \ + list list list? list? \ + vector vector vector? vector? \ + atom atom atom? atom? deref deref reset! reset! swap! swap! \ + sequential? sequential? \ + cons cons nth nth empty? empty? count count concat concat \ + conj conj first sfirst last slast rest srest \ + apply sapply map smap \ + +endif diff --git a/make/util.mk b/make/util.mk new file mode 100644 index 0000000..43923fc --- /dev/null +++ b/make/util.mk @@ -0,0 +1,72 @@ +# +# mal (Make Lisp) utility functions/definitions +# + +ifndef __mal_util_included +__mal_util_included := true + +_TOP_DIR := $(dir $(lastword $(MAKEFILE_LIST))) +include $(_TOP_DIR)gmsl.mk + +SEMI := ; +COMMA := , +LCURLY := { +RCURLY := } +LPAREN := ( +RPAREN := ) +LBRACKET := [ +RBRACKET := ] +DQUOTE := "# " +SLASH := $(strip \ ) +ESC_DQUOTE := $(SLASH)$(DQUOTE) +SQUOTE := '# ' +QQUOTE := `# ` +SPACE := +SPACE += +NUMBERS := 0 1 2 3 4 5 6 7 8 9 +UNQUOTE := ~ +SPLICE_UNQUOTE := ~@ +define NEWLINE + + +endef +CARET := ^ +ATSIGN := @ + +# \u00ab +_LP := « +# \u00bb +_RP := » +# \u00ed +_LC := í +# \u00ec +_RC := ì +## \u00a7 +_SP := § +## \u00ae +_SUQ := ® +## \u015e +_DOL := Ş +## \u00b6 +_NL := ¶ +## \u00a8 +###_EDQ := ¨ + + +# +# Utility functions +# + +_EQ = $(if $(subst x$1,,x$2)$(subst x$2,,x$1),,true) + +_NOT = $(if $1,,true) + +# READ: read and parse input +str_encode = $(strip $(eval __temp := $$(subst $$$$,$(_DOL) ,$$(subst $(SPLICE_UNQUOTE),$(_SUQ) ,$$(subst $$(LPAREN),$$(_LP) ,$$(subst $$(RPAREN),$$(_RP) ,$$(subst $$(LCURLY),$$(_LC) ,$$(subst $$(RCURLY),$$(_RC) ,$$(subst $$(NEWLINE),$$(_NL) ,$$(subst $$(SPACE),$(_SP) ,$$1)))))))))$(foreach a,$(__gmsl_characters),$(eval __temp := $$(subst $$a,$$a$$(SPACE),$(__temp))))$(__temp)) + +str_decode = $(subst $(_SP),$(SPACE),$(subst $(_NL),$(NEWLINE),$(subst $(_LC),$(LCURLY),$(subst $(_RC),$(RCURLY),$(subst $(_LP),$(LPAREN),$(subst $(_RP),$(RPAREN),$(subst $(_SUQ),$(SPLICE_UNQUOTE),$(subst $(_DOL),$$,$(strip $(call gmsl_merge,,$(1))))))))))) + +# Read a whole file substituting newlines with $(_NL) +_read_file = $(subst $(_NL),$(NEWLINE),$(shell out=""; while read -r l; do out="$${out}$${l}$(_NL)"; done < $(1); echo "$$out")) + +endif diff --git a/mal.html b/mal.html new file mode 100644 index 0000000..de3a51d --- /dev/null +++ b/mal.html @@ -0,0 +1,52 @@ +<!doctype html> +<html> +<head> + <meta charset="utf-8"> + <meta http-equiv="X-UA-Compatible" content="chrome=1"> + <title>Building a Lisp</title> + <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no"> + <link href='http://fonts.googleapis.com/css?family=Source+Code+Pro' rel='stylesheet' type='text/css'> + <link rel="stylesheet" href="http://code.jquery.com/ui/1.9.2/themes/base/jquery-ui.css"> + <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"></script> + <script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.9.2/jquery-ui.min.js"></script> + <script src="http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.4.2/underscore-min.js"></script> + <!--<script>Josh = {Debug: true };</script>--> + <script src="js/josh.js/js/killring.js"></script> + <script src="js/josh.js/js/history.js"></script> + <script src="js/josh.js/js/readline.js"></script> + <script src="js/josh.js/js/shell.js"></script> + <script src="js/josh_readline.js"></script> + <style type="text/css"> + #shell-panel { + height: 400px; + width: 100%; + background-color: #002f05; + color: #00fe00; + padding: 20px 20px 20px 20px; + font-family: 'Source Code Pro'; + overflow: scroll; + overflow-x: hidden; + overflow-y: scroll; + border: 1px dashed #E6EBE0; + } + + #shell-cli .prompt { + font-weight: bold; + }</style> +</head> +<body> + <div class="wrapper"> + + <section> + <h1>Building a Lisp</h1> + + <div id="shell-panel"> + <div>Lisp REPL</div> + <div id="shell-view"></div> + </div> + </section> + </div> + + <script src="js/mal_web.js"></script> +</body> +</html> diff --git a/mal/Makefile b/mal/Makefile new file mode 100644 index 0000000..f90f574 --- /dev/null +++ b/mal/Makefile @@ -0,0 +1,17 @@ + +TESTS = + + +SOURCES = types.mal env.mal stepA_more.mal + +#.PHONY: stats tests $(TESTS) +.PHONY: stats + +stats: $(SOURCES) + @wc $^ + +#tests: $(TESTS) +# +#$(TESTS): +# @echo "Running $@"; \ +# python $@ || exit 1; \ diff --git a/mal/env.mal b/mal/env.mal new file mode 100644 index 0000000..40937c5 --- /dev/null +++ b/mal/env.mal @@ -0,0 +1,40 @@ +;; env + +(def! bind-env (fn* [env b e] + (if (empty? b) + env + + (if (= "&" (str (first b))) + (assoc env (str (nth b 1)) e) + + (bind-env (assoc env (str (first b)) (first e)) + (rest b) (rest e)))))) + +(def! new-env (fn* [& args] + (if (<= (count args) 1) + (atom {"--outer--" (first args)}) + (atom (bind-env {"--outer--" (first args)} + (nth args 1) (nth args 2)))))) + +(def! env-find (fn* [env k] + (let* [ks (str k) + data @env] + (if (contains? data ks) + env + (if (get data "--outer--") + (env-find (get data "--outer--") ks) + nil))))) + +(def! env-get (fn* [env k] + (let* [ks (str k) + e (env-find env ks)] + (if e + (get @e ks) + (throw (str "'" ks "' not found")))))) + +(def! env-set (fn* [env k v] + (do + (swap! env assoc (str k) v) + v))) + +;;(prn "loaded env.mal") diff --git a/mal/presentation.mal b/mal/presentation.mal new file mode 100755 index 0000000..3e88d38 --- /dev/null +++ b/mal/presentation.mal @@ -0,0 +1,125 @@ +;; Mal Presentation + +(def! clear + (fn* () + (str "[2J[;H"))) + +(def! bold + (fn* (s) + (str "[1m" s "[0m"))) + +(def! blue + (fn* (s) + (str "[1;34m" s "[0m"))) + +(def! title + (fn* (s) + (bold (blue (str s "\n"))))) + +(def! title2 + (fn* (s) + (bold (blue s)))) + + +(def! conj-slides + (list + (list + (title2 " __ __ _ _") + (title2 "| \/ | / \ | |") + (title2 "| |\/| | / _ \ | | ") + (title2 "| | | |/ ___ \| |___ ") + (title2 "|_| |_/_/ \_\_____|")) + (list + (title "gherkin") + "- a lisp1 written in bash4") + (list + (title "mal - an interpreter for a subset of Clojure")) + (list + (title "mal - an interpreter for a subset of Clojure") + "- written in GNU make") + (list + (title "mal - an interpreter for a subset of Clojure") + "- written in GNU make" + "- and Bash 4") + (list + (title "mal - an interpreter for a subset of Clojure") + "- written in GNU make" + "- and Bash 4" + "- and Javascript") + (list + (title "mal - an interpreter for a subset of Clojure") + "- written in GNU make" + "- and Bash 4" + "- and Javascript" + "- and Python") + (list + (title "mal - an interpreter for a subset of Clojure") + "- written in GNU make" + "- and Bash 4" + "- and Javascript" + "- and Python" + "- and Clojure") + (list + (title "mal - an interpreter for a subset of Clojure") + "- written in GNU make" + "- and Bash 4" + "- and Javascript" + "- and Python" + "- and Clojure" + "- and C and Java and PHP") + (list + (title "things it has") + "- scalars: integers, strings, symbols, nil, true, false" + "- immutable collections: lists, vectors, hash-maps" + "- metadata, atoms" + "- def!, fn*, let*" + " - varargs: (fn* (x y & more) ...)" + "- tail call optimization" + " - except GNU make implementation (no iteration)" + "- macros (quote, unquote, quasiquote, splice-quote)" + "- almost 300 unit tests") + (list + (title "things it does not have") + "- performance" + "- namespaces" + "- keywords" + "- GC (in bash, make, C implmentations)") + (list + (title "why?") + "- because!") + (list + (title "why?") + "- because!" + "- gherkin was an inspiration to higher levels of crazy" + "- evolved into learning tool" + "- each implementation broken into small 10 steps" + "- way to learn about Lisp and also the target language") + (list + (title "thanks to:") + "- Peter Norvig: inspiration: lispy" + " - http://norvig.com/lispy.html" + "- Alan Dipert: gherkin, original gherkin slides" + " - https://github.com/alandipert/gherkin") + (list + (title "mal - Make a Lisp") + "https://github.com/kanaka/mal") + (list + (title "demo")))) + +(def! present + (fn* (slides) + (if (> (count slides) 0) + (do + ;;(py!* "import os; r = os.system('clear')") + ;;(sh* "clear") + ;;(make* "$(shell clear)") + (println (clear)) + + ;;(prn (first slides)) + (apply println (map (fn* (line) (str "\n " line)) (first slides))) + (println "\n\n\n") + (readline "") + (present (rest slides)))))) + +(present conj-slides) + diff --git a/mal/step1_read_print.mal b/mal/step1_read_print.mal new file mode 100644 index 0000000..aba1f82 --- /dev/null +++ b/mal/step1_read_print.mal @@ -0,0 +1,26 @@ +;; read +(def! READ (fn* [strng] + (read-string strng))) + +;; eval +(def! EVAL (fn* [ast env] + ast)) + +;; print +(def! PRINT (fn* [exp] (pr-str exp))) + +;; repl +(def! rep (fn* [strng] + (PRINT (EVAL (READ strng), {})))) + +(def! -main (fn* [] + (let* [line (readline "mal-user> ")] + (if line + (do + (if (not (= "" line)) + (try* + (println (rep line)) + (catch* exc + (println "Uncaught exception:" exc)))) + (-main)))))) +(-main) diff --git a/mal/step2_eval.mal b/mal/step2_eval.mal new file mode 100644 index 0000000..65a5c78 --- /dev/null +++ b/mal/step2_eval.mal @@ -0,0 +1,59 @@ +;; read +(def! READ (fn* [strng] + (read-string strng))) + + +;; eval +(def! eval-ast (fn* [ast env] (do + ;;(do (prn "eval-ast" ast "/" (keys env)) ) + (cond + (symbol? ast) (or (get env (str ast)) + (throw (str ast " not found"))) + + (list? ast) (map (fn* [exp] (EVAL exp env)) ast) + + (vector? ast) (apply vector (map (fn* [exp] (EVAL exp env)) ast)) + + (map? ast) (apply hash-map + (apply concat + (map (fn* [k] [k (EVAL (get ast k) env)]) + (keys ast)))) + + "else" ast)))) + + +(def! EVAL (fn* [ast env] (do + ;;(do (prn "EVAL" ast "/" (keys @env)) ) + (if (not (list? ast)) + (eval-ast ast env) + + ;; apply list + (let* [el (eval-ast ast env) + f (first el) + args (rest el)] + (apply f args)))))) + + +;; print +(def! PRINT (fn* [exp] (pr-str exp))) + +;; repl +(def! repl-env {"+" + + "-" - + "*" * + "/" /}) +(def! rep (fn* [strng] + (PRINT (EVAL (READ strng), repl-env)))) + +(def! -main (fn* [] + (let* [line (readline "mal-user> ")] + (if line + (do + (if (not (= "" line)) + (try* + (let* [res (rep line)] + (println res)) + (catch* exc + (println "Uncaught exception:" exc)))) + (-main)))))) +(-main) diff --git a/mal/step3_env.mal b/mal/step3_env.mal new file mode 100644 index 0000000..13c1d7f --- /dev/null +++ b/mal/step3_env.mal @@ -0,0 +1,80 @@ +(load-file "../mal/env.mal") + +;; read +(def! READ (fn* [strng] + (read-string strng))) + + +;; eval +(def! eval-ast (fn* [ast env] (do + ;;(do (prn "eval-ast" ast "/" (keys env)) ) + (cond + (symbol? ast) (env-get env ast) + + (list? ast) (map (fn* [exp] (EVAL exp env)) ast) + + (vector? ast) (apply vector (map (fn* [exp] (EVAL exp env)) ast)) + + (map? ast) (apply hash-map + (apply concat + (map (fn* [k] [k (EVAL (get ast k) env)]) + (keys ast)))) + + "else" ast)))) + +(def! LET (fn* [env args] + (if (> (count args) 0) + (do + (env-set env (nth args 0) (EVAL (nth args 1) env)) + (LET env (rest (rest args))))))) + +(def! EVAL (fn* [ast env] (do + ;;(do (prn "EVAL" ast "/" (keys @env)) ) + (if (not (list? ast)) + (eval-ast ast env) + + ;; apply list + (let* [a0 (first ast)] + (cond + (= 'def! a0) + (env-set env (nth ast 1) (EVAL (nth ast 2) env)) + + (= 'let* a0) + (let* [let-env (new-env env)] + (do + (LET let-env (nth ast 1)) + (EVAL (nth ast 2) let-env))) + + "else" + (let* [el (eval-ast ast env) + f (first el) + args (rest el)] + (apply f args)))))))) + + +;; print +(def! PRINT (fn* [exp] (pr-str exp))) + +;; repl +(def! repl-env (new-env)) +(def! rep (fn* [strng] + (PRINT (EVAL (READ strng), repl-env)))) + +(def! _ref (fn* [k v] (env-set repl-env k v))) +(_ref "+" +) +(_ref "-" -) +(_ref "*" *) +(_ref "/" /) + +(def! -main (fn* [] + (let* [line (readline "mal-user> ")] + (if line + (do + (if (not (= "" line)) + (try* + (let* [res (rep line)] + (println res)) + (catch* exc + (println "Uncaught exception:" exc)))) + (-main)))))) +(-main) diff --git a/mal/step4_if_fn_do.mal b/mal/step4_if_fn_do.mal new file mode 100644 index 0000000..bdefd59 --- /dev/null +++ b/mal/step4_if_fn_do.mal @@ -0,0 +1,99 @@ +(load-file "../mal/types.mal") +(load-file "../mal/env.mal") + +;; read +(def! READ (fn* [strng] + (read-string strng))) + + +;; eval +(def! eval-ast (fn* [ast env] (do + ;;(do (prn "eval-ast" ast "/" (keys env)) ) + (cond + (symbol? ast) (env-get env ast) + + (list? ast) (map (fn* [exp] (EVAL exp env)) ast) + + (vector? ast) (apply vector (map (fn* [exp] (EVAL exp env)) ast)) + + (map? ast) (apply hash-map + (apply concat + (map (fn* [k] [k (EVAL (get ast k) env)]) + (keys ast)))) + + "else" ast)))) + +(def! LET (fn* [env args] + (if (> (count args) 0) + (do + (env-set env (nth args 0) (EVAL (nth args 1) env)) + (LET env (rest (rest args))))))) + +(def! EVAL (fn* [ast env] (do + ;;(do (prn "EVAL" ast "/" (keys @env)) ) + (if (not (list? ast)) + (eval-ast ast env) + + ;; apply list + (let* [a0 (first ast)] + (cond + (= 'def! a0) + (env-set env (nth ast 1) (EVAL (nth ast 2) env)) + + (= 'let* a0) + (let* [let-env (new-env env)] + (do + (LET let-env (nth ast 1)) + (EVAL (nth ast 2) let-env))) + + (= 'do a0) + (let* [el (eval-ast (rest ast) env)] + (nth el (- (count el) 1))) + + (= 'if a0) + (let* [cond (EVAL (nth ast 1) env)] + (if (or (= cond nil) (= cond false)) + (if (> (count ast) 3) + (EVAL (nth ast 3) env) + nil) + (EVAL (nth ast 2) env))) + + (= 'fn* a0) + (fn* [& args] + (EVAL (nth ast 2) (new-env env (nth ast 1) args))) + + "else" + (let* [el (eval-ast ast env) + f (first el) + args (rest el)] + (apply f args)))))))) + + +;; print +(def! PRINT (fn* [exp] (pr-str exp))) + +;; repl +(def! repl-env (new-env)) +(def! rep (fn* [strng] + (PRINT (EVAL (READ strng), repl-env)))) + +(def! _ref (fn* [k v] (env-set repl-env k v))) + +;; Import types related functions +(map (fn* [data] (_ref (nth data 0) (nth data 1))) types_ns) + +;; Defined using the language itself +(rep "(def! not (fn* [a] (if a false true)))") + +(def! -main (fn* [] + (let* [line (readline "mal-user> ")] + (if line + (do + (if (not (= "" line)) + (try* + (let* [res (rep line)] + (println res)) + (catch* exc + (println "Uncaught exception:" exc)))) + (-main)))))) +(-main) diff --git a/mal/step6_file.mal b/mal/step6_file.mal new file mode 100644 index 0000000..34acd67 --- /dev/null +++ b/mal/step6_file.mal @@ -0,0 +1,105 @@ +(load-file "../mal/types.mal") +(load-file "../mal/env.mal") + +;; read +(def! READ (fn* [strng] + (read-string strng))) + + +;; eval +(def! eval-ast (fn* [ast env] (do + ;;(do (prn "eval-ast" ast "/" (keys env)) ) + (cond + (symbol? ast) (env-get env ast) + + (list? ast) (map (fn* [exp] (EVAL exp env)) ast) + + (vector? ast) (apply vector (map (fn* [exp] (EVAL exp env)) ast)) + + (map? ast) (apply hash-map + (apply concat + (map (fn* [k] [k (EVAL (get ast k) env)]) + (keys ast)))) + + "else" ast)))) + +(def! LET (fn* [env args] + (if (> (count args) 0) + (do + (env-set env (nth args 0) (EVAL (nth args 1) env)) + (LET env (rest (rest args))))))) + +(def! EVAL (fn* [ast env] (do + ;;(do (prn "EVAL" ast "/" (keys @env)) ) + (if (not (list? ast)) + (eval-ast ast env) + + ;; apply list + (let* [a0 (first ast)] + (cond + (= 'def! a0) + (env-set env (nth ast 1) (EVAL (nth ast 2) env)) + + (= 'let* a0) + (let* [let-env (new-env env)] + (do + (LET let-env (nth ast 1)) + (EVAL (nth ast 2) let-env))) + + (= 'do a0) + (let* [el (eval-ast (rest ast) env)] + (nth el (- (count el) 1))) + + (= 'if a0) + (let* [cond (EVAL (nth ast 1) env)] + (if (or (= cond nil) (= cond false)) + (if (> (count ast) 3) + (EVAL (nth ast 3) env) + nil) + (EVAL (nth ast 2) env))) + + (= 'fn* a0) + (fn* [& args] + (EVAL (nth ast 2) (new-env env (nth ast 1) args))) + + "else" + (let* [el (eval-ast ast env) + f (first el) + args (rest el)] + (apply f args)))))))) + + +;; print +(def! PRINT (fn* [exp] (pr-str exp))) + +;; repl +(def! repl-env (new-env)) +(def! rep (fn* [strng] + (PRINT (EVAL (READ strng), repl-env)))) + +(def! _ref (fn* [k v] (env-set repl-env k v))) + +;; Import types related functions +(map (fn* [data] (_ref (nth data 0) (nth data 1))) types_ns) + +;; Defined using the language itself +(_ref 'read-string read-string) +(_ref 'eval (fn* [ast] (EVAL ast repl-env))) +(_ref 'slurp slurp) +(_ref 'slurp-do slurp-do) + +(rep "(def! not (fn* [a] (if a false true)))") +(rep "(def! load-file (fn* [f] (eval (read-string (slurp-do f)))))") + +(def! -main (fn* [] + (let* [line (readline "mal-user> ")] + (if line + (do + (if (not (= "" line)) + (try* + (let* [res (rep line)] + (println res)) + (catch* exc + (println "Uncaught exception:" exc)))) + (-main)))))) +(-main) diff --git a/mal/step7_quote.mal b/mal/step7_quote.mal new file mode 100644 index 0000000..b6e130d --- /dev/null +++ b/mal/step7_quote.mal @@ -0,0 +1,133 @@ +(load-file "../mal/types.mal") +(load-file "../mal/env.mal") + +;; read +(def! READ (fn* [strng] + (read-string strng))) + + +;; eval +(def! is-pair (fn* [x] + (if (sequential? x) + (if (> (count x) 0) + true)))) + +(def! QUASIQUOTE (fn* [ast] + (cond + (not (is-pair ast)) + (list 'quote ast) + + (= 'unquote (first ast)) + (nth ast 1) + + (if (is-pair (first ast)) + (if (= 'splice-unquote (first (first ast))) + true)) + (list 'concat (nth (first ast) 1) (QUASIQUOTE (rest ast))) + + "else" + (list 'cons (QUASIQUOTE (first ast)) (QUASIQUOTE (rest ast)))))) + +(def! eval-ast (fn* [ast env] (do + ;;(do (prn "eval-ast" ast "/" (keys env)) ) + (cond + (symbol? ast) (env-get env ast) + + (list? ast) (map (fn* [exp] (EVAL exp env)) ast) + + (vector? ast) (apply vector (map (fn* [exp] (EVAL exp env)) ast)) + + (map? ast) (apply hash-map + (apply concat + (map (fn* [k] [k (EVAL (get ast k) env)]) + (keys ast)))) + + "else" ast)))) + +(def! LET (fn* [env args] + (if (> (count args) 0) + (do + (env-set env (nth args 0) (EVAL (nth args 1) env)) + (LET env (rest (rest args))))))) + +(def! EVAL (fn* [ast env] (do + ;;(do (prn "EVAL" ast "/" (keys @env)) ) + (if (not (list? ast)) + (eval-ast ast env) + + ;; apply list + (let* [a0 (first ast)] + (cond + (= 'def! a0) + (env-set env (nth ast 1) (EVAL (nth ast 2) env)) + + (= 'let* a0) + (let* [let-env (new-env env)] + (do + (LET let-env (nth ast 1)) + (EVAL (nth ast 2) let-env))) + + (= 'quote a0) + (nth ast 1) + + (= 'quasiquote a0) + (let* [a1 (nth ast 1)] + (EVAL (QUASIQUOTE a1) env)) + + (= 'do a0) + (let* [el (eval-ast (rest ast) env)] + (nth el (- (count el) 1))) + + (= 'if a0) + (let* [cond (EVAL (nth ast 1) env)] + (if (or (= cond nil) (= cond false)) + (if (> (count ast) 3) + (EVAL (nth ast 3) env) + nil) + (EVAL (nth ast 2) env))) + + (= 'fn* a0) + (fn* [& args] + (EVAL (nth ast 2) (new-env env (nth ast 1) args))) + + "else" + (let* [el (eval-ast ast env) + f (first el) + args (rest el)] + (apply f args)))))))) + + +;; print +(def! PRINT (fn* [exp] (pr-str exp))) + +;; repl +(def! repl-env (new-env)) +(def! rep (fn* [strng] + (PRINT (EVAL (READ strng), repl-env)))) + +(def! _ref (fn* [k v] (env-set repl-env k v))) + +;; Import types related functions +(map (fn* [data] (_ref (nth data 0) (nth data 1))) types_ns) + +;; Defined using the language itself +(_ref 'read-string read-string) +(_ref 'eval (fn* [ast] (EVAL ast repl-env))) +(_ref 'slurp slurp) +(_ref 'slurp-do slurp-do) + +(rep "(def! not (fn* [a] (if a false true)))") +(rep "(def! load-file (fn* [f] (eval (read-string (slurp-do f)))))") + +(def! -main (fn* [] + (let* [line (readline "mal-user> ")] + (if line + (do + (if (not (= "" line)) + (try* + (let* [res (rep line)] + (println res)) + (catch* exc + (println "Uncaught exception:" exc)))) + (-main)))))) +(-main) diff --git a/mal/step8_macros.mal b/mal/step8_macros.mal new file mode 100644 index 0000000..cb8909e --- /dev/null +++ b/mal/step8_macros.mal @@ -0,0 +1,165 @@ +(load-file "../mal/types.mal") +(load-file "../mal/env.mal") + +;; read +(def! READ (fn* [strng] + (read-string strng))) + + +;; eval +(def! is-pair (fn* [x] + (if (sequential? x) + (if (> (count x) 0) + true)))) + +(def! QUASIQUOTE (fn* [ast] + (cond + (not (is-pair ast)) + (list 'quote ast) + + (= 'unquote (first ast)) + (nth ast 1) + + (if (is-pair (first ast)) + (if (= 'splice-unquote (first (first ast))) + true)) + (list 'concat (nth (first ast) 1) (QUASIQUOTE (rest ast))) + + "else" + (list 'cons (QUASIQUOTE (first ast)) (QUASIQUOTE (rest ast)))))) + +(def! is-macro-call (fn* [ast env] + (if (list? ast) + (let* [a0 (first ast)] + (if (symbol? a0) + (if (env-find env a0) + (let* [m (meta (env-get env a0))] + (if m + (if (get m "ismacro") + true))))))))) + +(def! MACROEXPAND (fn* [ast env] + (if (is-macro-call ast env) + (let* [mac (env-get env (first ast))] + (MACROEXPAND (apply mac (rest ast)) env)) + ast))) + +(def! eval-ast (fn* [ast env] (do + ;;(do (prn "eval-ast" ast "/" (keys env)) ) + (cond + (symbol? ast) (env-get env ast) + + (list? ast) (map (fn* [exp] (EVAL exp env)) ast) + + (vector? ast) (apply vector (map (fn* [exp] (EVAL exp env)) ast)) + + (map? ast) (apply hash-map + (apply concat + (map (fn* [k] [k (EVAL (get ast k) env)]) + (keys ast)))) + + "else" ast)))) + +(def! LET (fn* [env args] + (if (> (count args) 0) + (do + (env-set env (nth args 0) (EVAL (nth args 1) env)) + (LET env (rest (rest args))))))) + +(def! EVAL (fn* [ast env] (do + ;;(do (prn "EVAL" ast "/" (keys @env)) ) + (if (not (list? ast)) + (eval-ast ast env) + + ;; apply list + (let* [ast (MACROEXPAND ast env)] + (if (not (list? ast)) + ast + + (let* [a0 (first ast)] + (cond + (= 'def! a0) + (env-set env (nth ast 1) (EVAL (nth ast 2) env)) + + (= 'let* a0) + (let* [let-env (new-env env)] + (do + (LET let-env (nth ast 1)) + (EVAL (nth ast 2) let-env))) + + (= 'quote a0) + (nth ast 1) + + (= 'quasiquote a0) + (let* [a1 (nth ast 1)] + (EVAL (QUASIQUOTE a1) env)) + + (= 'defmacro! a0) + (let* [a1 (nth ast 1) + a2 (nth ast 2) + f (EVAL a2 env) + m (or (meta f) {}) + mac (with-meta f (assoc m "ismacro" true))] + (env-set env a1 mac)) + + (= 'macroexpand a0) + (let* [a1 (nth ast 1)] + (MACROEXPAND a1 env)) + + (= 'do a0) + (let* [el (eval-ast (rest ast) env)] + (nth el (- (count el) 1))) + + (= 'if a0) + (let* [cond (EVAL (nth ast 1) env)] + (if (or (= cond nil) (= cond false)) + (if (> (count ast) 3) + (EVAL (nth ast 3) env) + nil) + (EVAL (nth ast 2) env))) + + (= 'fn* a0) + (fn* [& args] + (EVAL (nth ast 2) (new-env env (nth ast 1) args))) + + "else" + (let* [el (eval-ast ast env) + f (first el) + args (rest el)] + (apply f args)))))))))) + + +;; print +(def! PRINT (fn* [exp] (pr-str exp))) + +;; repl +(def! repl-env (new-env)) +(def! rep (fn* [strng] + (PRINT (EVAL (READ strng) repl-env)))) + +(def! _ref (fn* [k v] (env-set repl-env k v))) + +;; Import types related functions +(map (fn* [data] (_ref (nth data 0) (nth data 1))) types_ns) + +;; Defined using the language itself +(_ref 'read-string read-string) +(_ref 'eval (fn* [ast] (EVAL ast repl-env))) +(_ref 'slurp slurp) +(_ref 'slurp-do slurp-do) + +(rep "(def! not (fn* [a] (if a false true)))") +(rep "(def! load-file (fn* [f] (eval (read-string (slurp-do f)))))") + +(def! -main (fn* [] + (let* [line (readline "mal-user> ")] + (if line + (do + (if (not (= "" line)) + (try* + (let* [res (rep line)] + (println res)) + (catch* exc + (println "Uncaught exception:" exc)))) + (-main)))))) +(-main) diff --git a/mal/stepA_more.mal b/mal/stepA_more.mal new file mode 100644 index 0000000..5426d59 --- /dev/null +++ b/mal/stepA_more.mal @@ -0,0 +1,179 @@ +(load-file "../mal/types.mal") +(load-file "../mal/env.mal") + +;; read +(def! READ (fn* [strng] + (read-string strng))) + + +;; eval +(def! is-pair (fn* [x] + (if (sequential? x) + (if (> (count x) 0) + true)))) + +(def! QUASIQUOTE (fn* [ast] + (cond + (not (is-pair ast)) + (list 'quote ast) + + (= 'unquote (first ast)) + (nth ast 1) + + (if (is-pair (first ast)) + (if (= 'splice-unquote (first (first ast))) + true)) + (list 'concat (nth (first ast) 1) (QUASIQUOTE (rest ast))) + + "else" + (list 'cons (QUASIQUOTE (first ast)) (QUASIQUOTE (rest ast)))))) + +(def! is-macro-call (fn* [ast env] + (if (list? ast) + (let* [a0 (first ast)] + (if (symbol? a0) + (if (env-find env a0) + (let* [m (meta (env-get env a0))] + (if m + (if (get m "ismacro") + true))))))))) + +(def! MACROEXPAND (fn* [ast env] + (if (is-macro-call ast env) + (let* [mac (env-get env (first ast))] + (MACROEXPAND (apply mac (rest ast)) env)) + ast))) + +(def! eval-ast (fn* [ast env] (do + ;;(do (prn "eval-ast" ast "/" (keys env)) ) + (cond + (symbol? ast) (env-get env ast) + + (list? ast) (map (fn* [exp] (EVAL exp env)) ast) + + (vector? ast) (apply vector (map (fn* [exp] (EVAL exp env)) ast)) + + (map? ast) (apply hash-map + (apply concat + (map (fn* [k] [k (EVAL (get ast k) env)]) + (keys ast)))) + + "else" ast)))) + +(def! LET (fn* [env args] + (if (> (count args) 0) + (do + (env-set env (nth args 0) (EVAL (nth args 1) env)) + (LET env (rest (rest args))))))) + +(def! EVAL (fn* [ast env] (do + ;;(do (prn "EVAL" ast "/" (keys @env)) ) + (if (not (list? ast)) + (eval-ast ast env) + + ;; apply list + (let* [ast (MACROEXPAND ast env)] + (if (not (list? ast)) + ast + + (let* [a0 (first ast)] + (cond + (= 'def! a0) + (env-set env (nth ast 1) (EVAL (nth ast 2) env)) + + (= 'let* a0) + (let* [let-env (new-env env)] + (do + (LET let-env (nth ast 1)) + (EVAL (nth ast 2) let-env))) + + (= 'quote a0) + (nth ast 1) + + (= 'quasiquote a0) + (let* [a1 (nth ast 1)] + (EVAL (QUASIQUOTE a1) env)) + + (= 'defmacro! a0) + (let* [a1 (nth ast 1) + a2 (nth ast 2) + f (EVAL a2 env) + m (or (meta f) {}) + mac (with-meta f (assoc m "ismacro" true))] + (env-set env a1 mac)) + + (= 'macroexpand a0) + (let* [a1 (nth ast 1)] + (MACROEXPAND a1 env)) + + (= 'try* a0) + (if (= 'catch* (nth (nth ast 2) 0)) + (try* + (EVAL (nth ast 1) env) + (catch* exc + (EVAL (nth (nth ast 2) 2) + (new-env env + [(nth (nth ast 2)1)] + [exc])))) + (EVAL (nth ast 1) env)) + + (= 'do a0) + (let* [el (eval-ast (rest ast) env)] + (nth el (- (count el) 1))) + + (= 'if a0) + (let* [cond (EVAL (nth ast 1) env)] + (if (or (= cond nil) (= cond false)) + (if (> (count ast) 3) + (EVAL (nth ast 3) env) + nil) + (EVAL (nth ast 2) env))) + + (= 'fn* a0) + (fn* [& args] + (EVAL (nth ast 2) (new-env env (nth ast 1) args))) + + "else" + (let* [el (eval-ast ast env) + f (first el) + args (rest el)] + (apply f args)))))))))) + + +;; print +(def! PRINT (fn* [exp] (pr-str exp))) + +;; repl +(def! repl-env (new-env)) +(def! rep (fn* [strng] + (PRINT (EVAL (READ strng) repl-env)))) + +(def! _ref (fn* [k v] (env-set repl-env k v))) + +;; Import types related functions +(map (fn* [data] (_ref (nth data 0) (nth data 1))) types_ns) + +;; Defined using the language itself +(_ref 'readline readline) +(_ref 'read-string read-string) +(_ref 'eval (fn* [ast] (EVAL ast repl-env))) +(_ref 'slurp slurp) +(_ref 'slurp-do slurp-do) + +(rep "(def! not (fn* [a] (if a false true)))") +(rep "(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))") +(rep "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))") +(rep "(def! load-file (fn* [f] (eval (read-string (slurp-do f)))))") + +(def! -main (fn* [] + (let* [line (readline "mal-user> ")] + (if line + (do + (if (not (= "" line)) + (try* + (let* [res (rep line)] + (println res)) + (catch* exc + (println "Uncaught exception:" exc)))) + (-main)))))) +(-main) diff --git a/mal/types.mal b/mal/types.mal new file mode 100644 index 0000000..6eaa388 --- /dev/null +++ b/mal/types.mal @@ -0,0 +1,16 @@ +(def! types_ns + [["pr-str" pr-str] ["str" str] ["prn" prn] ["println" println] + ["with-meta" with-meta] ["meta" meta] ["=" =] + ["nil?" nil?] ["true?" true?] ["false?" false?] ["symbol?" symbol?] + [">" >] [">=" >=] ["<" <] ["<=" <=] ["+" +] ["-" -] ["*" *] ["/" /] + ["hash-map" hash-map] ["map?" map?] + ["assoc" assoc] ["dissoc" dissoc] ["get" get] + ["contains?" contains?] ["keys" keys] ["vals" vals] + ["throw" throw] + ["list" list] ["list?" list?] ["vector" vector] ["vector?" vector?] + ["atom" atom] ["atom?" atom?] ["deref" deref] + ["reset!" reset!] ["swap!" swap!] + ["sequential?" sequential?] ["cons" cons] ["nth" nth] + ["empty?" empty?] ["count" count] ["concat" concat] + ["conj" conj] ["first" first] ["rest" rest] + ["apply" apply] ["map" map]]) diff --git a/php/reader.php b/php/reader.php new file mode 100644 index 0000000..0524b31 --- /dev/null +++ b/php/reader.php @@ -0,0 +1,115 @@ +<?php + +require_once 'types.php'; + +class Reader { + protected $tokens = array(); + protected $position = 0; + public function __construct($tokens) { + $this->tokens = $tokens; + $this->position = 0; + } + public function next() { + return $this->tokens[$this->position++]; + } + public function peek() { + return $this->tokens[$this->position]; + } +} + +class BlankException extends Exception { +} + +function _real_token($s) { + return $s !== '' && $s[0] !== ';'; +} + +function tokenize($str) { + $pat = "/[\s,]*(~@|[\[\]{}()'`~^@]|\"(?:\\\\.|[^\\\\\"])*\"|;.*|[^\s\[\]{}('\"`,;)]*)/"; + preg_match_all($pat, $str, $matches); + return array_values(array_filter($matches[1], '_real_token')); +} + +function read_atom($reader) { + $token = $reader->next(); + if (preg_match("/^-?[0-9]+$/", $token)) { + return intval($token, 10); + } elseif ($token[0] === "\"") { + $str = substr($token, 1, -1); + $str = preg_replace('/\\\\"/', '"', $str); + return $str; + } elseif ($token === "nil") { + return NULL; + } elseif ($token === "true") { + return true; + } elseif ($token === "false") { + return false; + } else { + return new_symbol($token); + } +} + +function read_list($reader, $constr='new_list', $start='(', $end=')') { + $ast = $constr(); + $token = $reader->next(); + if ($token !== $start) { + throw new Exception("expected '" . $start . "'"); + } + while (($token = $reader->peek()) !== $end) { + if ($token === "") { + throw new Exception("expected '" . $end . "', got EOF"); + } + $ast[] = read_form($reader); + } + $reader->next(); + return $ast; +} + +function read_hash_map($reader) { + $lst = read_list($reader, 'new_list', '{', '}'); + return call_user_func_array('new_hash_map', $lst->getArrayCopy()); +} + +function read_form($reader) { + $token = $reader->peek(); + switch ($token) { + case '\'': $reader->next(); + return new_list(new_symbol('quote'), + read_form($reader)); + case '`': $reader->next(); + return new_list(new_symbol('quasiquote'), + read_form($reader)); + case '~': $reader->next(); + return new_list(new_symbol('unquote'), + read_form($reader)); + case '~@': $reader->next(); + return new_list(new_symbol('splice-unquote'), + read_form($reader)); + case '^': $reader->next(); + $meta = read_form($reader); + return new_list(new_symbol('with-meta'), + read_form($reader), + $meta); + + case '@': $reader->next(); + return new_list(new_symbol('deref'), + read_form($reader)); + + case ')': throw new Exception("unexpected ')'"); + case '(': return read_list($reader); + case ']': throw new Exception("unexpected ']'"); + case '[': return read_list($reader, 'new_vector', '[', ']'); + case '}': throw new Exception("unexpected '}'"); + case '{': return read_hash_map($reader); + + default: return read_atom($reader); + } +} + +function read_str($str) { + $tokens = tokenize($str); + if (count($tokens) === 0) { throw new BlankException(); } + return read_form(new Reader($tokens)); +} + +?> diff --git a/php/readline.php b/php/readline.php new file mode 100644 index 0000000..28d720d --- /dev/null +++ b/php/readline.php @@ -0,0 +1,34 @@ +<?php + +$HISTORY_FILE = "/home/joelm/.mal-history"; + +function mal_readline($prompt) { + global $HISTORY_FILE; + static $history_loaded = false; + + // Load the history file + if (! $history_loaded) { + $history_loaded = true; + if ($file = fopen($HISTORY_FILE, "r")) { + while (!feof($file)) { + $line = fgets($file); + if ($line) { readline_add_history($line); } + } + fclose($file); + } + } + + $line = readline($prompt); + if ($line === false) { return NULL; } + readline_add_history($line); + + // Append to the history file + if ($file = fopen($HISTORY_FILE, "a")) { + fputs($file, $line . "\n"); + fclose($file); + } + + return $line; +} + +?> diff --git a/php/step0_repl.php b/php/step0_repl.php new file mode 100644 index 0000000..64b086b --- /dev/null +++ b/php/step0_repl.php @@ -0,0 +1,33 @@ +<?php + +require_once 'readline.php'; + +// read +function READ($str) { + return $str; +} + +// eval +function MAL_EVAL($ast, $env) { + return eval($ast); +} + +// print +function MAL_PRINT($exp) { + return var_export($exp, true) . "\n"; +} + +// repl +function rep($str) { + return MAL_PRINT(MAL_EVAL(READ($str), array())); +} + +do { + $line = mal_readline("user> "); + if ($line === NULL) { break; } + if (!empty($line)) { + print(rep($line)); + } +} while (true); + +?> diff --git a/php/step1_read_print.php b/php/step1_read_print.php new file mode 100644 index 0000000..01334e0 --- /dev/null +++ b/php/step1_read_print.php @@ -0,0 +1,42 @@ +<?php + +require_once 'readline.php'; +require_once 'types.php'; +require_once 'reader.php'; + +// read +function READ($str) { + return read_str($str); +} + +// eval +function MAL_EVAL($ast, $env) { + return $ast; +} + +// print +function MAL_PRINT($exp) { + return _pr_str($exp, True) . "\n"; +} + +// repl +function rep($str) { + return MAL_PRINT(MAL_EVAL(READ($str), array())); +} + +do { + try { + $line = mal_readline("user> "); + if ($line === NULL) { break; } + if ($line !== "") { + print(rep($line)); + } + } catch (BlankException $e) { + continue; + } catch (Exception $e) { + echo "Error: " . $e->getMessage() . "\n"; + echo $e->getTraceAsString() . "\n"; + } +} while (true); + +?> diff --git a/php/step2_eval.php b/php/step2_eval.php new file mode 100644 index 0000000..c9c3562 --- /dev/null +++ b/php/step2_eval.php @@ -0,0 +1,77 @@ +<?php + +require_once 'readline.php'; +require_once 'types.php'; +require_once 'reader.php'; + +// read +function READ($str) { + return read_str($str); +} + +// eval +function eval_ast($ast, $env) { + if (symbol_Q($ast)) { + return $env[$ast->value]; + } elseif (list_Q($ast) || vector_Q($ast)) { + if (list_Q($ast)) { + $el = new_list(); + } else { + $el = new_vector(); + } + foreach ($ast as $a) { $el[] = MAL_EVAL($a, $env); } + return $el; + } elseif (hash_map_Q($ast)) { + $new_hm = new_hash_map(); + foreach (array_keys($ast->getArrayCopy()) as $key) { + $new_hm[$key] = MAL_EVAL($ast[$key], $env); + } + return $new_hm; + } else { + return $ast; + } +} + +function MAL_EVAL($ast, $env) { + if (!list_Q($ast)) { + return eval_ast($ast, $env); + } + + // apply list + $el = eval_ast($ast, $env); + $f = $el[0]; + return call_user_func_array($f, array_slice($el->getArrayCopy(), 1)); +} + +// print +function MAL_PRINT($exp) { + return _pr_str($exp, True) . "\n"; +} + +// repl +$repl_env = array(); +function rep($str) { + global $repl_env; + return MAL_PRINT(MAL_EVAL(READ($str), $repl_env)); +} +$repl_env['+'] = function ($a, $b) { return intval($a + $b,10); }; +$repl_env['-'] = function ($a, $b) { return intval($a - $b,10); }; +$repl_env['*'] = function ($a, $b) { return intval($a * $b,10); }; +$repl_env['/'] = function ($a, $b) { return intval($a / $b,10); }; + +do { + try { + $line = mal_readline("user> "); + if ($line === NULL) { break; } + if ($line !== "") { + print(rep($line)); + } + } catch (BlankException $e) { + continue; + } catch (Exception $e) { + echo "Error: " . $e->getMessage() . "\n"; + echo $e->getTraceAsString() . "\n"; + } +} while (true); + +?> diff --git a/php/step3_env.php b/php/step3_env.php new file mode 100644 index 0000000..15d7c5c --- /dev/null +++ b/php/step3_env.php @@ -0,0 +1,94 @@ +<?php + +require_once 'readline.php'; +require_once 'types.php'; +require_once 'reader.php'; + +// read +function READ($str) { + return read_str($str); +} + +// eval +function eval_ast($ast, $env) { + if (symbol_Q($ast)) { + return $env->get($ast->value); + } elseif (list_Q($ast) || vector_Q($ast)) { + if (list_Q($ast)) { + $el = new_list(); + } else { + $el = new_vector(); + } + foreach ($ast as $a) { $el[] = MAL_EVAL($a, $env); } + return $el; + } elseif (hash_map_Q($ast)) { + $new_hm = new_hash_map(); + foreach (array_keys($ast->getArrayCopy()) as $key) { + $new_hm[$key] = MAL_EVAL($ast[$key], $env); + } + return $new_hm; + } else { + return $ast; + } +} + +function MAL_EVAL($ast, $env) { + if (!list_Q($ast)) { + return eval_ast($ast, $env); + } + + // apply list + $a0 = $ast[0]; + $a0v = (symbol_Q($a0) ? $a0->value : $a0); + switch ($a0v) { + case "def!": + $res = MAL_EVAL($ast[2], $env); + return $env->set($ast[1]->value, $res); + case "let*": + $a1 = $ast[1]; + $let_env = new Env($env); + for ($i=0; $i < count($a1); $i+=2) { + $let_env->set($a1[$i]->value, MAL_EVAL($a1[$i+1], $let_env)); + } + return MAL_EVAL($ast[2], $let_env); + default: + $el = eval_ast($ast, $env); + $f = $el[0]; + return call_user_func_array($f, array_slice($el->getArrayCopy(), 1)); + } +} + +// print +function MAL_PRINT($exp) { + return _pr_str($exp, True) . "\n"; +} + +// repl +$repl_env = new Env(NULL); +function rep($str) { + global $repl_env; + return MAL_PRINT(MAL_EVAL(READ($str), $repl_env)); +} +function _ref($k, $v) { global $repl_env; $repl_env->set($k, $v); } + +_ref('+', function ($a, $b) { return intval($a + $b,10); }); +_ref('-', function ($a, $b) { return intval($a - $b,10); }); +_ref('*', function ($a, $b) { return intval($a * $b,10); }); +_ref('/', function ($a, $b) { return intval($a / $b,10); }); + +do { + try { + $line = mal_readline("user> "); + if ($line === NULL) { break; } + if ($line !== "") { + print(rep($line)); + } + } catch (BlankException $e) { + continue; + } catch (Exception $e) { + echo "Error: " . $e->getMessage() . "\n"; + echo $e->getTraceAsString() . "\n"; + } +} while (true); + +?> diff --git a/php/step4_if_fn_do.php b/php/step4_if_fn_do.php new file mode 100644 index 0000000..3b9593d --- /dev/null +++ b/php/step4_if_fn_do.php @@ -0,0 +1,112 @@ +<?php + +require_once 'readline.php'; +require_once 'types.php'; +require_once 'reader.php'; + +// read +function READ($str) { + return read_str($str); +} + +// eval +function eval_ast($ast, $env) { + if (symbol_Q($ast)) { + return $env->get($ast->value); + } elseif (list_Q($ast) || vector_Q($ast)) { + if (list_Q($ast)) { + $el = new_list(); + } else { + $el = new_vector(); + } + foreach ($ast as $a) { $el[] = MAL_EVAL($a, $env); } + return $el; + } elseif (hash_map_Q($ast)) { + $new_hm = new_hash_map(); + foreach (array_keys($ast->getArrayCopy()) as $key) { + $new_hm[$key] = MAL_EVAL($ast[$key], $env); + } + return $new_hm; + } else { + return $ast; + } +} + +function MAL_EVAL($ast, $env) { + #echo "MAL_EVAL: " . _pr_str($ast) . "\n"; + if (!list_Q($ast)) { + return eval_ast($ast, $env); + } + + // apply list + $a0 = $ast[0]; + $a0v = (symbol_Q($a0) ? $a0->value : $a0); + switch ($a0v) { + case "def!": + $res = MAL_EVAL($ast[2], $env); + return $env->set($ast[1]->value, $res); + case "let*": + $a1 = $ast[1]; + $let_env = new Env($env); + for ($i=0; $i < count($a1); $i+=2) { + $let_env->set($a1[$i]->value, MAL_EVAL($a1[$i+1], $let_env)); + } + return MAL_EVAL($ast[2], $let_env); + case "do": + #$el = eval_ast(array_slice($ast->getArrayCopy(), 1), $env); + $el = eval_ast($ast->slice(1), $env); + return $el[count($el)-1]; + case "if": + $cond = MAL_EVAL($ast[1], $env); + if ($cond === NULL || $cond === false) { + if (count($ast) === 4) { return MAL_EVAL($ast[3], $env); } + else { return NULL; } + } else { + return MAL_EVAL($ast[2], $env); + } + case "fn*": + return function() use ($env, $ast) { + $fn_env = new Env($env, $ast[1], func_get_args()); + return MAL_EVAL($ast[2], $fn_env); + }; + default: + $el = eval_ast($ast, $env); + $f = $el[0]; + return call_user_func_array($f, array_slice($el->getArrayCopy(), 1)); + } +} + +// print +function MAL_PRINT($exp) { + return _pr_str($exp, True) . "\n"; +} + +// repl +$repl_env = new Env(NULL); +function rep($str) { + global $repl_env; + return MAL_PRINT(MAL_EVAL(READ($str), $repl_env)); +} +function _ref($k, $v) { global $repl_env; $repl_env->set($k, $v); } +// Import types functions +foreach ($types_ns as $k=>$v) { _ref($k, $v); } + +// Defined using the language itself +rep("(def! not (fn* (a) (if a false true)))"); + +do { + try { + $line = mal_readline("user> "); + if ($line === NULL) { break; } + if ($line !== "") { + print(rep($line)); + } + } catch (BlankException $e) { + continue; + } catch (Exception $e) { + echo "Error: " . $e->getMessage() . "\n"; + echo $e->getTraceAsString() . "\n"; + } +} while (true); + +?> diff --git a/php/step5_tco.php b/php/step5_tco.php new file mode 100644 index 0000000..54d7699 --- /dev/null +++ b/php/step5_tco.php @@ -0,0 +1,124 @@ +<?php + +require_once 'readline.php'; +require_once 'types.php'; +require_once 'reader.php'; + +// read +function READ($str) { + return read_str($str); +} + +// eval +function eval_ast($ast, $env) { + if (symbol_Q($ast)) { + return $env->get($ast->value); + } elseif (list_Q($ast) || vector_Q($ast)) { + if (list_Q($ast)) { + $el = new_list(); + } else { + $el = new_vector(); + } + foreach ($ast as $a) { $el[] = MAL_EVAL($a, $env); } + return $el; + } elseif (hash_map_Q($ast)) { + $new_hm = new_hash_map(); + foreach (array_keys($ast->getArrayCopy()) as $key) { + $new_hm[$key] = MAL_EVAL($ast[$key], $env); + } + return $new_hm; + } else { + return $ast; + } +} + +function MAL_EVAL($ast, $env) { + while (true) { + #echo "MAL_EVAL: " . _pr_str($ast) . "\n"; + if (!list_Q($ast)) { + return eval_ast($ast, $env); + } + + // apply list + $a0 = $ast[0]; + $a0v = (symbol_Q($a0) ? $a0->value : $a0); + switch ($a0v) { + case "def!": + $res = MAL_EVAL($ast[2], $env); + return $env->set($ast[1]->value, $res); + case "let*": + $a1 = $ast[1]; + $let_env = new Env($env); + for ($i=0; $i < count($a1); $i+=2) { + $let_env->set($a1[$i]->value, MAL_EVAL($a1[$i+1], $let_env)); + } + return MAL_EVAL($ast[2], $let_env); + case "do": + eval_ast($ast->slice(1, -1), $env); + $ast = $ast[count($ast)-1]; + break; + case "if": + $cond = MAL_EVAL($ast[1], $env); + if ($cond === NULL || $cond === false) { + if (count($ast) === 4) { $ast = $ast[3]; } + else { $ast = NULL; } + } else { + $ast = $ast[2]; + } + break; + case "fn*": + return new_function('MAL_EVAL', 'native', + new_hash_map('exp', $ast[2], + 'env', $env, + 'params', $ast[1])); + default: + $el = eval_ast($ast, $env); + $f = $el[0]; + $args = array_slice($el->getArrayCopy(), 1); + if ($f->type === 'native') { + $ast = $f->meta['exp']; + $env = new Env($f->meta['env'], $f->meta['params'], $args); + } else { + return $f->apply($args); + } + } + } +} + +// print +function MAL_PRINT($exp) { + return _pr_str($exp, True) . "\n"; +} + +// repl +$repl_env = new Env(NULL); +function rep($str) { + global $repl_env; + return MAL_PRINT(MAL_EVAL(READ($str), $repl_env)); +} +function _ref($k, $v) { + global $repl_env; + $repl_env->set($k, new_function($v)); +} +// Import types functions +foreach ($types_ns as $k=>$v) { _ref($k, $v); } + +// Defined using the language itself +rep("(def! not (fn* (a) (if a false true)))"); + +do { + try { + $line = mal_readline("user> "); + if ($line === NULL) { break; } + if ($line !== "") { + print(rep($line)); + } + } catch (BlankException $e) { + continue; + } catch (Exception $e) { + echo "Error: " . $e->getMessage() . "\n"; + echo $e->getTraceAsString() . "\n"; + } +} while (true); + +?> diff --git a/php/step6_file.php b/php/step6_file.php new file mode 100644 index 0000000..8e923e1 --- /dev/null +++ b/php/step6_file.php @@ -0,0 +1,142 @@ +<?php + +require_once 'readline.php'; +require_once 'types.php'; +require_once 'reader.php'; + +// read +function READ($str) { + return read_str($str); +} + +// eval +function eval_ast($ast, $env) { + if (symbol_Q($ast)) { + return $env->get($ast->value); + } elseif (list_Q($ast) || vector_Q($ast)) { + if (list_Q($ast)) { + $el = new_list(); + } else { + $el = new_vector(); + } + foreach ($ast as $a) { $el[] = MAL_EVAL($a, $env); } + return $el; + } elseif (hash_map_Q($ast)) { + $new_hm = new_hash_map(); + foreach (array_keys($ast->getArrayCopy()) as $key) { + $new_hm[$key] = MAL_EVAL($ast[$key], $env); + } + return $new_hm; + } else { + return $ast; + } +} + +function MAL_EVAL($ast, $env) { + while (true) { + #echo "MAL_EVAL: " . _pr_str($ast) . "\n"; + if (!list_Q($ast)) { + return eval_ast($ast, $env); + } + + // apply list + $a0 = $ast[0]; + $a0v = (symbol_Q($a0) ? $a0->value : $a0); + switch ($a0v) { + case "def!": + $res = MAL_EVAL($ast[2], $env); + return $env->set($ast[1]->value, $res); + case "let*": + $a1 = $ast[1]; + $let_env = new Env($env); + for ($i=0; $i < count($a1); $i+=2) { + $let_env->set($a1[$i]->value, MAL_EVAL($a1[$i+1], $let_env)); + } + return MAL_EVAL($ast[2], $let_env); + case "do": + eval_ast($ast->slice(1, -1), $env); + $ast = $ast[count($ast)-1]; + break; + case "if": + $cond = MAL_EVAL($ast[1], $env); + if ($cond === NULL || $cond === false) { + if (count($ast) === 4) { $ast = $ast[3]; } + else { $ast = NULL; } + } else { + $ast = $ast[2]; + } + break; + case "fn*": + return new_function('MAL_EVAL', 'native', + new_hash_map('exp', $ast[2], + 'env', $env, + 'params', $ast[1])); + default: + $el = eval_ast($ast, $env); + $f = $el[0]; + $args = array_slice($el->getArrayCopy(), 1); + if ($f->type === 'native') { + $ast = $f->meta['exp']; + $env = new Env($f->meta['env'], $f->meta['params'], $args); + } else { + return $f->apply($args); + } + } + } +} + +// print +function MAL_PRINT($exp) { + return _pr_str($exp, True) . "\n"; +} + +// repl +$repl_env = new Env(NULL); +function rep($str) { + global $repl_env; + return MAL_PRINT(MAL_EVAL(READ($str), $repl_env)); +} +function _ref($k, $v) { + global $repl_env; + $repl_env->set($k, new_function($v)); +} +// Import types functions +foreach ($types_ns as $k=>$v) { _ref($k, $v); } + +_ref('read-string', 'read_str'); +_ref('eval', function($ast) { + global $repl_env; return MAL_EVAL($ast, $repl_env); +}); +_ref('slurp', function($f) { + return file_get_contents($f); +}); +_ref('slurp-do', function($f) { + return "(do " . file_get_contents($f) . ")"; +}); + +// Defined using the language itself +rep("(def! not (fn* (a) (if a false true)))"); +rep("(def! load-file (fn* (f) (eval (read-string (slurp-do f)))))"); + +if (count($argv) > 1) { + for ($i=1; $i < count($argv); $i++) { + rep('(load-file "' . $argv[$i] . '")'); + } +} else { + do { + try { + $line = mal_readline("user> "); + if ($line === NULL) { break; } + if ($line !== "") { + print(rep($line)); + } + } catch (BlankException $e) { + continue; + } catch (Exception $e) { + echo "Error: " . $e->getMessage() . "\n"; + echo $e->getTraceAsString() . "\n"; + } + } while (true); +} + +?> diff --git a/php/step7_quote.php b/php/step7_quote.php new file mode 100644 index 0000000..2ccd130 --- /dev/null +++ b/php/step7_quote.php @@ -0,0 +1,165 @@ +<?php + +require_once 'readline.php'; +require_once 'types.php'; +require_once 'reader.php'; + +// read +function READ($str) { + return read_str($str); +} + +// eval +function is_pair($x) { + return sequential_Q($x) and count($x) > 0; +} + +function quasiquote($ast) { + if (!is_pair($ast)) { + return new_list(new_symbol("quote"), $ast); + } elseif (symbol_Q($ast[0]) && $ast[0]->value === 'unquote') { + return $ast[1]; + } elseif (is_pair($ast[0]) && symbol_Q($ast[0][0]) && + $ast[0][0]->value === 'splice-unquote') { + return new_list(new_symbol("concat"), $ast[0][1], + quasiquote($ast->slice(1))); + } else { + return new_list(new_symbol("cons"), quasiquote($ast[0]), + quasiquote($ast->slice(1))); + } +} + +function eval_ast($ast, $env) { + if (symbol_Q($ast)) { + return $env->get($ast->value); + } elseif (list_Q($ast) || vector_Q($ast)) { + if (list_Q($ast)) { + $el = new_list(); + } else { + $el = new_vector(); + } + foreach ($ast as $a) { $el[] = MAL_EVAL($a, $env); } + return $el; + } elseif (hash_map_Q($ast)) { + $new_hm = new_hash_map(); + foreach (array_keys($ast->getArrayCopy()) as $key) { + $new_hm[$key] = MAL_EVAL($ast[$key], $env); + } + return $new_hm; + } else { + return $ast; + } +} + +function MAL_EVAL($ast, $env) { + while (true) { + #echo "MAL_EVAL: " . _pr_str($ast) . "\n"; + if (!list_Q($ast)) { + return eval_ast($ast, $env); + } + + // apply list + $a0 = $ast[0]; + $a0v = (symbol_Q($a0) ? $a0->value : $a0); + switch ($a0v) { + case "def!": + $res = MAL_EVAL($ast[2], $env); + return $env->set($ast[1]->value, $res); + case "let*": + $a1 = $ast[1]; + $let_env = new Env($env); + for ($i=0; $i < count($a1); $i+=2) { + $let_env->set($a1[$i]->value, MAL_EVAL($a1[$i+1], $let_env)); + } + return MAL_EVAL($ast[2], $let_env); + case "quote": + return $ast[1]; + case "quasiquote": + return MAL_EVAL(quasiquote($ast[1]), $env); + case "do": + eval_ast($ast->slice(1, -1), $env); + $ast = $ast[count($ast)-1]; + break; + case "if": + $cond = MAL_EVAL($ast[1], $env); + if ($cond === NULL || $cond === false) { + if (count($ast) === 4) { $ast = $ast[3]; } + else { $ast = NULL; } + } else { + $ast = $ast[2]; + } + break; + case "fn*": + return new_function('MAL_EVAL', 'native', + new_hash_map('exp', $ast[2], + 'env', $env, + 'params', $ast[1])); + default: + $el = eval_ast($ast, $env); + $f = $el[0]; + $args = array_slice($el->getArrayCopy(), 1); + if ($f->type === 'native') { + $ast = $f->meta['exp']; + $env = new Env($f->meta['env'], $f->meta['params'], $args); + } else { + return $f->apply($args); + } + } + } +} + +// print +function MAL_PRINT($exp) { + return _pr_str($exp, True) . "\n"; +} + +// repl +$repl_env = new Env(NULL); +function rep($str) { + global $repl_env; + return MAL_PRINT(MAL_EVAL(READ($str), $repl_env)); +} +function _ref($k, $v) { + global $repl_env; + $repl_env->set($k, new_function($v)); +} +// Import types functions +foreach ($types_ns as $k=>$v) { _ref($k, $v); } + +_ref('read-string', 'read_str'); +_ref('eval', function($ast) { + global $repl_env; return MAL_EVAL($ast, $repl_env); +}); +_ref('slurp', function($f) { + return file_get_contents($f); +}); +_ref('slurp-do', function($f) { + return "(do " . file_get_contents($f) . ")"; +}); + +// Defined using the language itself +rep("(def! not (fn* (a) (if a false true)))"); +rep("(def! load-file (fn* (f) (eval (read-string (slurp-do f)))))"); + +if (count($argv) > 1) { + for ($i=1; $i < count($argv); $i++) { + rep('(load-file "' . $argv[$i] . '")'); + } +} else { + do { + try { + $line = mal_readline("user> "); + if ($line === NULL) { break; } + if ($line !== "") { + print(rep($line)); + } + } catch (BlankException $e) { + continue; + } catch (Exception $e) { + echo "Error: " . $e->getMessage() . "\n"; + echo $e->getTraceAsString() . "\n"; + } + } while (true); +} + +?> diff --git a/php/step8_macros.php b/php/step8_macros.php new file mode 100644 index 0000000..20e0f6a --- /dev/null +++ b/php/step8_macros.php @@ -0,0 +1,190 @@ +<?php + +require_once 'readline.php'; +require_once 'types.php'; +require_once 'reader.php'; + +// read +function READ($str) { + return read_str($str); +} + +// eval +function is_pair($x) { + return sequential_Q($x) and count($x) > 0; +} + +function quasiquote($ast) { + if (!is_pair($ast)) { + return new_list(new_symbol("quote"), $ast); + } elseif (symbol_Q($ast[0]) && $ast[0]->value === 'unquote') { + return $ast[1]; + } elseif (is_pair($ast[0]) && symbol_Q($ast[0][0]) && + $ast[0][0]->value === 'splice-unquote') { + return new_list(new_symbol("concat"), $ast[0][1], + quasiquote($ast->slice(1))); + } else { + return new_list(new_symbol("cons"), quasiquote($ast[0]), + quasiquote($ast->slice(1))); + } +} + +function is_macro_call($ast, $env) { + return is_pair($ast) && + symbol_Q($ast[0]) && + $env->find($ast[0]->value) && + $env->get($ast[0]->value)->ismacro; +} + +function macroexpand($ast, $env) { + while (is_macro_call($ast, $env)) { + $mac = $env->get($ast[0]->value); + $args = array_slice($ast->getArrayCopy(),1); + $ast = $mac->apply($args); + } + return $ast; +} + +function eval_ast($ast, $env) { + if (symbol_Q($ast)) { + return $env->get($ast->value); + } elseif (list_Q($ast) || vector_Q($ast)) { + if (list_Q($ast)) { + $el = new_list(); + } else { + $el = new_vector(); + } + foreach ($ast as $a) { $el[] = MAL_EVAL($a, $env); } + return $el; + } elseif (hash_map_Q($ast)) { + $new_hm = new_hash_map(); + foreach (array_keys($ast->getArrayCopy()) as $key) { + $new_hm[$key] = MAL_EVAL($ast[$key], $env); + } + return $new_hm; + } else { + return $ast; + } +} + +function MAL_EVAL($ast, $env) { + while (true) { + #echo "MAL_EVAL: " . _pr_str($ast) . "\n"; + if (!list_Q($ast)) { + return eval_ast($ast, $env); + } + + // apply list + $ast = macroexpand($ast, $env); + if (!list_Q($ast)) { return $ast; } + + $a0 = $ast[0]; + $a0v = (symbol_Q($a0) ? $a0->value : $a0); + switch ($a0v) { + case "def!": + $res = MAL_EVAL($ast[2], $env); + return $env->set($ast[1]->value, $res); + case "let*": + $a1 = $ast[1]; + $let_env = new Env($env); + for ($i=0; $i < count($a1); $i+=2) { + $let_env->set($a1[$i]->value, MAL_EVAL($a1[$i+1], $let_env)); + } + return MAL_EVAL($ast[2], $let_env); + case "quote": + return $ast[1]; + case "quasiquote": + return MAL_EVAL(quasiquote($ast[1]), $env); + case "defmacro!": + $func = MAL_EVAL($ast[2], $env); + $func->ismacro = true; + return $env->set($ast[1]->value, $func); + case "macroexpand": + return macroexpand($ast[1], $env); + case "do": + eval_ast($ast->slice(1, -1), $env); + $ast = $ast[count($ast)-1]; + break; + case "if": + $cond = MAL_EVAL($ast[1], $env); + if ($cond === NULL || $cond === false) { + if (count($ast) === 4) { $ast = $ast[3]; } + else { $ast = NULL; } + } else { + $ast = $ast[2]; + } + break; + case "fn*": + return new_function('MAL_EVAL', 'native', + new_hash_map('exp', $ast[2], + 'env', $env, + 'params', $ast[1])); + default: + $el = eval_ast($ast, $env); + $f = $el[0]; + $args = array_slice($el->getArrayCopy(), 1); + if ($f->type === 'native') { + $ast = $f->meta['exp']; + $env = new Env($f->meta['env'], $f->meta['params'], $args); + } else { + return $f->apply($args); + } + } + } +} + +// print +function MAL_PRINT($exp) { + return _pr_str($exp, True) . "\n"; +} + +// repl +$repl_env = new Env(NULL); +function rep($str) { + global $repl_env; + return MAL_PRINT(MAL_EVAL(READ($str), $repl_env)); +} +function _ref($k, $v) { + global $repl_env; + $repl_env->set($k, new_function($v)); +} +// Import types functions +foreach ($types_ns as $k=>$v) { _ref($k, $v); } + +_ref('read-string', 'read_str'); +_ref('eval', function($ast) { + global $repl_env; return MAL_EVAL($ast, $repl_env); +}); +_ref('slurp', function($f) { + return file_get_contents($f); +}); +_ref('slurp-do', function($f) { + return "(do " . file_get_contents($f) . ")"; +}); + +// Defined using the language itself +rep("(def! not (fn* (a) (if a false true)))"); +rep("(def! load-file (fn* (f) (eval (read-string (slurp-do f)))))"); + +if (count($argv) > 1) { + for ($i=1; $i < count($argv); $i++) { + rep('(load-file "' . $argv[$i] . '")'); + } +} else { + do { + try { + $line = mal_readline("user> "); + if ($line === NULL) { break; } + if ($line !== "") { + print(rep($line)); + } + } catch (BlankException $e) { + continue; + } catch (Exception $e) { + echo "Error: " . $e->getMessage() . "\n"; + echo $e->getTraceAsString() . "\n"; + } + } while (true); +} + +?> diff --git a/php/step9_interop.php b/php/step9_interop.php new file mode 100644 index 0000000..fd7c1d7 --- /dev/null +++ b/php/step9_interop.php @@ -0,0 +1,192 @@ +<?php + +require_once 'readline.php'; +require_once 'types.php'; +require_once 'reader.php'; + +// read +function READ($str) { + return read_str($str); +} + +// eval +function is_pair($x) { + return sequential_Q($x) and count($x) > 0; +} + +function quasiquote($ast) { + if (!is_pair($ast)) { + return new_list(new_symbol("quote"), $ast); + } elseif (symbol_Q($ast[0]) && $ast[0]->value === 'unquote') { + return $ast[1]; + } elseif (is_pair($ast[0]) && symbol_Q($ast[0][0]) && + $ast[0][0]->value === 'splice-unquote') { + return new_list(new_symbol("concat"), $ast[0][1], + quasiquote($ast->slice(1))); + } else { + return new_list(new_symbol("cons"), quasiquote($ast[0]), + quasiquote($ast->slice(1))); + } +} + +function is_macro_call($ast, $env) { + return is_pair($ast) && + symbol_Q($ast[0]) && + $env->find($ast[0]->value) && + $env->get($ast[0]->value)->ismacro; +} + +function macroexpand($ast, $env) { + while (is_macro_call($ast, $env)) { + $mac = $env->get($ast[0]->value); + $args = array_slice($ast->getArrayCopy(),1); + $ast = $mac->apply($args); + } + return $ast; +} + +function eval_ast($ast, $env) { + if (symbol_Q($ast)) { + return $env->get($ast->value); + } elseif (list_Q($ast) || vector_Q($ast)) { + if (list_Q($ast)) { + $el = new_list(); + } else { + $el = new_vector(); + } + foreach ($ast as $a) { $el[] = MAL_EVAL($a, $env); } + return $el; + } elseif (hash_map_Q($ast)) { + $new_hm = new_hash_map(); + foreach (array_keys($ast->getArrayCopy()) as $key) { + $new_hm[$key] = MAL_EVAL($ast[$key], $env); + } + return $new_hm; + } else { + return $ast; + } +} + +function MAL_EVAL($ast, $env) { + while (true) { + #echo "MAL_EVAL: " . _pr_str($ast) . "\n"; + if (!list_Q($ast)) { + return eval_ast($ast, $env); + } + + // apply list + $ast = macroexpand($ast, $env); + if (!list_Q($ast)) { return $ast; } + + $a0 = $ast[0]; + $a0v = (symbol_Q($a0) ? $a0->value : $a0); + switch ($a0v) { + case "def!": + $res = MAL_EVAL($ast[2], $env); + return $env->set($ast[1]->value, $res); + case "let*": + $a1 = $ast[1]; + $let_env = new Env($env); + for ($i=0; $i < count($a1); $i+=2) { + $let_env->set($a1[$i]->value, MAL_EVAL($a1[$i+1], $let_env)); + } + return MAL_EVAL($ast[2], $let_env); + case "quote": + return $ast[1]; + case "quasiquote": + return MAL_EVAL(quasiquote($ast[1]), $env); + case "defmacro!": + $func = MAL_EVAL($ast[2], $env); + $func->ismacro = true; + return $env->set($ast[1]->value, $func); + case "macroexpand": + return macroexpand($ast[1], $env); + case "php*": + return eval($ast[1]); + case "do": + eval_ast($ast->slice(1, -1), $env); + $ast = $ast[count($ast)-1]; + break; + case "if": + $cond = MAL_EVAL($ast[1], $env); + if ($cond === NULL || $cond === false) { + if (count($ast) === 4) { $ast = $ast[3]; } + else { $ast = NULL; } + } else { + $ast = $ast[2]; + } + break; + case "fn*": + return new_function('MAL_EVAL', 'native', + new_hash_map('exp', $ast[2], + 'env', $env, + 'params', $ast[1])); + default: + $el = eval_ast($ast, $env); + $f = $el[0]; + $args = array_slice($el->getArrayCopy(), 1); + if ($f->type === 'native') { + $ast = $f->meta['exp']; + $env = new Env($f->meta['env'], $f->meta['params'], $args); + } else { + return $f->apply($args); + } + } + } +} + +// print +function MAL_PRINT($exp) { + return _pr_str($exp, True) . "\n"; +} + +// repl +$repl_env = new Env(NULL); +function rep($str) { + global $repl_env; + return MAL_PRINT(MAL_EVAL(READ($str), $repl_env)); +} +function _ref($k, $v) { + global $repl_env; + $repl_env->set($k, new_function($v)); +} +// Import types functions +foreach ($types_ns as $k=>$v) { _ref($k, $v); } + +_ref('read-string', 'read_str'); +_ref('eval', function($ast) { + global $repl_env; return MAL_EVAL($ast, $repl_env); +}); +_ref('slurp', function($f) { + return file_get_contents($f); +}); +_ref('slurp-do', function($f) { + return "(do " . file_get_contents($f) . ")"; +}); + +// Defined using the language itself +rep("(def! not (fn* (a) (if a false true)))"); +rep("(def! load-file (fn* (f) (eval (read-string (slurp-do f)))))"); + +if (count($argv) > 1) { + for ($i=1; $i < count($argv); $i++) { + rep('(load-file "' . $argv[$i] . '")'); + } +} else { + do { + try { + $line = mal_readline("user> "); + if ($line === NULL) { break; } + if ($line !== "") { + print(rep($line)); + } + } catch (BlankException $e) { + continue; + } catch (Exception $e) { + echo "Error: " . $e->getMessage() . "\n"; + echo $e->getTraceAsString() . "\n"; + } + } while (true); +} + +?> diff --git a/php/stepA_more.php b/php/stepA_more.php new file mode 100644 index 0000000..cac80ed --- /dev/null +++ b/php/stepA_more.php @@ -0,0 +1,213 @@ +<?php + +require_once 'readline.php'; +require_once 'types.php'; +require_once 'reader.php'; + +// read +function READ($str) { + return read_str($str); +} + +// eval +function is_pair($x) { + return sequential_Q($x) and count($x) > 0; +} + +function quasiquote($ast) { + if (!is_pair($ast)) { + return new_list(new_symbol("quote"), $ast); + } elseif (symbol_Q($ast[0]) && $ast[0]->value === 'unquote') { + return $ast[1]; + } elseif (is_pair($ast[0]) && symbol_Q($ast[0][0]) && + $ast[0][0]->value === 'splice-unquote') { + return new_list(new_symbol("concat"), $ast[0][1], + quasiquote($ast->slice(1))); + } else { + return new_list(new_symbol("cons"), quasiquote($ast[0]), + quasiquote($ast->slice(1))); + } +} + +function is_macro_call($ast, $env) { + return is_pair($ast) && + symbol_Q($ast[0]) && + $env->find($ast[0]->value) && + $env->get($ast[0]->value)->ismacro; +} + +function macroexpand($ast, $env) { + while (is_macro_call($ast, $env)) { + $mac = $env->get($ast[0]->value); + $args = array_slice($ast->getArrayCopy(),1); + $ast = $mac->apply($args); + } + return $ast; +} + +function eval_ast($ast, $env) { + if (symbol_Q($ast)) { + return $env->get($ast->value); + } elseif (list_Q($ast) || vector_Q($ast)) { + if (list_Q($ast)) { + $el = new_list(); + } else { + $el = new_vector(); + } + foreach ($ast as $a) { $el[] = MAL_EVAL($a, $env); } + return $el; + } elseif (hash_map_Q($ast)) { + $new_hm = new_hash_map(); + foreach (array_keys($ast->getArrayCopy()) as $key) { + $new_hm[$key] = MAL_EVAL($ast[$key], $env); + } + return $new_hm; + } else { + return $ast; + } +} + +function MAL_EVAL($ast, $env) { + while (true) { + #echo "MAL_EVAL: " . _pr_str($ast) . "\n"; + if (!list_Q($ast)) { + return eval_ast($ast, $env); + } + + // apply list + $ast = macroexpand($ast, $env); + if (!list_Q($ast)) { return $ast; } + + $a0 = $ast[0]; + $a0v = (symbol_Q($a0) ? $a0->value : $a0); + switch ($a0v) { + case "def!": + $res = MAL_EVAL($ast[2], $env); + return $env->set($ast[1]->value, $res); + case "let*": + $a1 = $ast[1]; + $let_env = new Env($env); + for ($i=0; $i < count($a1); $i+=2) { + $let_env->set($a1[$i]->value, MAL_EVAL($a1[$i+1], $let_env)); + } + return MAL_EVAL($ast[2], $let_env); + case "quote": + return $ast[1]; + case "quasiquote": + return MAL_EVAL(quasiquote($ast[1]), $env); + case "defmacro!": + $func = MAL_EVAL($ast[2], $env); + $func->ismacro = true; + return $env->set($ast[1]->value, $func); + case "macroexpand": + return macroexpand($ast[1], $env); + case "php*": + return eval($ast[1]); + case "try*": + $a1 = $ast[1]; + $a2 = $ast[2]; + if ($a2[0]->value === "catch*") { + try { + return MAL_EVAL($a1, $env); + } catch (Error $e) { + $catch_env = new Env($env, array($a2[1]), + array($e->obj)); + return MAL_EVAL($a2[2], $catch_env); + } catch (Exception $e) { + $catch_env = new Env($env, array($a2[1]), + array($e->getMessage())); + return MAL_EVAL($a2[2], $catch_env); + } + } else { + return MAL_EVAL($a1, $env); + } + case "do": + eval_ast($ast->slice(1, -1), $env); + $ast = $ast[count($ast)-1]; + break; + case "if": + $cond = MAL_EVAL($ast[1], $env); + if ($cond === NULL || $cond === false) { + if (count($ast) === 4) { $ast = $ast[3]; } + else { $ast = NULL; } + } else { + $ast = $ast[2]; + } + break; + case "fn*": + return new_function('MAL_EVAL', 'native', + new_hash_map('exp', $ast[2], + 'env', $env, + 'params', $ast[1])); + default: + $el = eval_ast($ast, $env); + $f = $el[0]; + $args = array_slice($el->getArrayCopy(), 1); + if ($f->type === 'native') { + $ast = $f->meta['exp']; + $env = new Env($f->meta['env'], $f->meta['params'], $args); + } else { + return $f->apply($args); + } + } + } +} + +// print +function MAL_PRINT($exp) { + return _pr_str($exp, True) . "\n"; +} + +// repl +$repl_env = new Env(NULL); +function rep($str) { + global $repl_env; + return MAL_PRINT(MAL_EVAL(READ($str), $repl_env)); +} +function _ref($k, $v) { + global $repl_env; + $repl_env->set($k, new_function($v)); +} +// Import types functions +foreach ($types_ns as $k=>$v) { _ref($k, $v); } + +_ref('readline', 'mal_readline'); +_ref('read-string', 'read_str'); +_ref('eval', function($ast) { + global $repl_env; return MAL_EVAL($ast, $repl_env); +}); +_ref('slurp', function($f) { + return file_get_contents($f); +}); +_ref('slurp-do', function($f) { + return "(do " . file_get_contents($f) . ")"; +}); + +// Defined using the language itself +rep("(def! not (fn* (a) (if a false true)))"); +rep("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))"); +rep("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))"); +rep("(def! load-file (fn* (f) (eval (read-string (slurp-do f)))))"); + +if (count($argv) > 1) { + for ($i=1; $i < count($argv); $i++) { + rep('(load-file "' . $argv[$i] . '")'); + } +} else { + do { + try { + $line = mal_readline("user> "); + if ($line === NULL) { break; } + if ($line !== "") { + print(rep($line)); + } + } catch (BlankException $e) { + continue; + } catch (Exception $e) { + echo "Error: " . $e->getMessage() . "\n"; + echo $e->getTraceAsString() . "\n"; + } + } while (true); +} + +?> diff --git a/php/types.php b/php/types.php new file mode 100644 index 0000000..0c4ab33 --- /dev/null +++ b/php/types.php @@ -0,0 +1,488 @@ +<?php + +function _pr_str($obj, $print_readably=True) { + if (list_Q($obj)) { + $ret = array(); + foreach ($obj as $e) { + array_push($ret, _pr_str($e, $print_readably)); + } + return "(" . implode(" ", $ret) . ")"; + } elseif (vector_Q($obj)) { + $ret = array(); + foreach ($obj as $e) { + array_push($ret, _pr_str($e, $print_readably)); + } + return "[" . implode(" ", $ret) . "]"; + } elseif (hash_map_Q($obj)) { + $ret = array(); + foreach (array_keys($obj->getArrayCopy()) as $k) { + $ret[] = _pr_str($k, $print_readably); + $ret[] = _pr_str($obj[$k], $print_readably); + } + return "{" . implode(" ", $ret) . "}"; + } elseif (is_string($obj)) { + if ($print_readably) { + $obj = preg_replace('/"/', '\\"', preg_replace('/\\\\/', '\\\\\\\\', $obj)); + return '"' . $obj . '"'; + } else { + return $obj; + } + } elseif (is_integer($obj)) { + return $obj; + } elseif ($obj === NULL) { + return "nil"; + } elseif ($obj === true) { + return "true"; + } elseif ($obj === false) { + return "false"; + } elseif (symbol_Q($obj)) { + return $obj->value; + } elseif (atom_Q($obj)) { + return "(atom " . _pr_str($obj->value, $print_readably) . ")"; + } elseif (function_Q($obj)) { + return "(fn* [...] ...)"; + } elseif (is_callable($obj)) { // only step4 and below + return "#<function ...>"; + } else { + throw new Exception("_pr_str unknown type: " . gettype($obj)); + } +} + +function pr_str() { + $ps = array_map(function ($obj) { return _pr_str($obj, True); }, + func_get_args()); + return implode(" ", $ps); +} + +function str() { + $ps = array_map(function ($obj) { return _pr_str($obj, False); }, + func_get_args()); + return implode("", $ps); +} + +function prn() { + $ps = array_map(function ($obj) { return _pr_str($obj, True); }, + func_get_args()); + print implode(" ", $ps) . "\n"; + return null; +} + +function println() { + $ps = array_map(function ($obj) { return _pr_str($obj, False); }, + func_get_args()); + print implode(" ", $ps) . "\n"; + return null; +} + +function with_meta($obj, $m) { + $new_obj = clone $obj; + $new_obj->meta = $m; + return $new_obj; +} + +function meta($obj) { + return $obj->meta; +} + +function equal_Q($a, $b) { + $ota = gettype($a) === "object" ? get_class($a) : gettype($a); + $otb = gettype($b) === "object" ? get_class($b) : gettype($b); + if (!($ota === $otb or (sequential_Q($a) and sequential_Q($b)))) { + return false; + } elseif (symbol_Q($a)) { + #print "ota: $ota, otb: $otb\n"; + return $a->value === $b->value; + } elseif (list_Q($a) or vector_Q($a)) { + if ($a->count() !== $b->count()) { return false; } + for ($i=0; $i<$a->count(); $i++) { + if (!equal_Q($a[$i], $b[$i])) { return false; } + } + return true; + } else { + return $a === $b; + } +} + +// nil, true, false, string +function nil_Q($obj) { return $obj === NULL; } +function true_Q($obj) { return $obj === true; } +function false_Q($obj) { return $obj === false; } +function string_Q($obj) { return is_string($obj); } + + +// symbols +class SymbolClass { + public $value = NULL; + public $meta = NULL; + public function __construct($value) { + $this->value = $value; + } +} + +function new_symbol($name) { return new SymbolClass($name); } + +function symbol_Q($obj) { return ($obj instanceof SymbolClass); } + + +// Functions +class FunctionClass { + public $func = NULL; + public $type = 'native'; // 'native' or 'platform' + public $meta = NULL; + public $ismacro = False; + public function __construct($func, $type, $meta=NULL, $ismacro=False) { + $this->func = $func; + $this->type = $type; + $this->meta = $meta; + $this->ismacro = $ismacro; + } + public function __invoke() { + $args = func_get_args(); + if ($this->type === 'native') { + $fn_env = new Env($this->meta['env'], + $this->meta['params'], $args); + $evalf = $this->func; + return $evalf($this->meta['exp'], $fn_env); + } else { + return call_user_func_array($this->func, $args); + } + } + public function apply($args) { + return call_user_func_array(array(&$this, '__invoke'),$args); + } +} + +function new_function($func, $type='platform', $meta=NULL, $ismacro=False) { + return new FunctionClass($func, $type, $meta, $ismacro); +} + +function function_Q($obj) { return $obj instanceof FunctionClass; } + +// Parent class of list, vector, hash-map +// http://www.php.net/manual/en/class.arrayobject.php +class SeqClass extends ArrayObject { + public function slice($start, $length=NULL) { + $sc = new $this(); + if ($start >= count($this)) { + $arr = array(); + } else { + $arr = array_slice($this->getArrayCopy(), $start, $length); + } + $sc->exchangeArray($arr); + return $sc; + } +} + + +// Hash Maps +class HashMapClass extends ArrayObject { + public $meta = NULL; +} + +function new_hash_map() { + $args = func_get_args(); + if (count($args) % 2 === 1) { + throw new Exception("Odd number of hash map arguments"); + } + $hm = new HashMapClass(); + array_unshift($args, $hm); + return call_user_func_array('assoc_BANG', $args); +} + +function hash_map_Q($obj) { return $obj instanceof HashMapClass; } + +function assoc_BANG($hm) { + $args = func_get_args(); + if (count($args) % 2 !== 1) { + throw new Exception("Odd number of assoc arguments"); + } + for ($i=1; $i<count($args); $i+=2) { + $ktoken = $args[$i]; + $vtoken = $args[$i+1]; + // TODO: support more than string keys + if (gettype($ktoken) !== "string") { + throw new Exception("expected hash-map key string, got: " . gettype($ktoken)); + } + $hm[$ktoken] = $vtoken; + } + return $hm; +} + +function assoc($src_hm) { + $args = func_get_args(); + $hm = clone $src_hm; + $args[0] = $hm; + return call_user_func_array('assoc_BANG', $args); +} + +function dissoc_BANG($hm) { + $args = func_get_args(); + for ($i=1; $i<count($args); $i++) { + $ktoken = $args[$i]; + unset($hm[$ktoken]); + } + return $hm; +} + +function dissoc($src_hm) { + $args = func_get_args(); + $hm = clone $src_hm; + $args[0] = $hm; + return call_user_func_array('dissoc_BANG', $args); +} + +function get($hm, $k) { + if ($hm && $hm->offsetExists($k)) { + return $hm[$k]; + } else { + return NULL; + } +} + +function contains_Q($hm, $k) { return array_key_exists($k, $hm); } + +function keys($hm) { + return call_user_func_array('new_list', array_keys($hm->getArrayCopy())); +} +function vals($hm) { + return call_user_func_array('new_list', array_values($hm->getArrayCopy())); +} + + +// errors/exceptions +class Error extends Exception { + public $obj = null; + public function __construct($obj) { + parent::__construct("Mal Error", 0, null); + $this->obj = $obj; + } +} + +function mal_throw($obj) { throw new Error($obj); } + + +// lists +class ListClass extends SeqClass { + public $meta = NULL; +} + +function new_list() { + $v = new ListClass(); + $v->exchangeArray(func_get_args()); + return $v; +} + +function list_Q($obj) { return $obj instanceof ListClass; } + +// vectors +class VectorClass extends SeqClass { + public $meta = NULL; +} + +function new_vector() { + $v = new VectorClass(); + $v->exchangeArray(func_get_args()); + return $v; +} + +function vector_Q($obj) { return $obj instanceof VectorClass; } + + +// Atoms + +class Atom { + public $value = NULL; + public $meta = NULL; + public function __construct($value) { + $this->value = $value; + } +} +function new_atom($val) { return new Atom($val); } +function atom_Q($atm) { return $atm instanceof Atom; } +function deref($atm) { return $atm->value; } +function reset_BANG($atm, $val) { return $atm->value = $val; } +function swap_BANG($atm, $f) { + $args = array_slice(func_get_args(),2); + array_unshift($args, $atm->value); + $atm->value = call_user_func_array($f, $args); + return $atm->value; +} + + +// Sequence operations +function sequential_Q($seq) { return list_Q($seq) or vector_Q($seq); } + +function scount($seq) { return ($seq === NULL ? 0 : $seq->count()); } + +function empty_Q($seq) { return $seq->count() === 0; } + +function cons($a, $b) { + $tmp = $b->getArrayCopy(); + array_unshift($tmp, $a); + $l = new ListClass(); + $l->exchangeArray($tmp); + return $l; +} + +function concat() { + $args = func_get_args(); + $tmp = array(); + foreach ($args as $arg) { + $tmp = array_merge($tmp, $arg->getArrayCopy()); + } + $l = new ListClass(); + $l->exchangeArray($tmp); + return $l; +} + +function conj($src) { + $args = array_slice(func_get_args(), 1); + $tmp = $src->getArrayCopy(); + foreach ($args as $arg) { + $tmp[] = $arg; + } + if (list_Q($src)) { + $s = new ListClass(); + } else { + $s = new VectorClass(); + } + $s->exchangeArray($tmp); + return $s; +} + +function first($seq) { + if (count($seq) === 0) { + return NULL; + } else { + return $seq[0]; + } +} + +function rest($seq) { + $l = new ListClass(); + $l->exchangeArray(array_slice($seq->getArrayCopy(), 1)); + return $l; +} + +function nth($seq, $idx) { + return $seq[$idx]; +} + +function apply($f, $args) { + return $f->apply($args->getArrayCopy()); +} + +function map($f, $seq) { + $l = new ListClass(); + $l->exchangeArray(array_map($f, $seq->getArrayCopy())); + return $l; +} + + +// Environment +class Env { + public $data = array(); + public $outer = NULL; + public function __construct($outer, $binds=NULL, $exprs=NULL) { + $this->outer = $outer; + if ($binds) { + if (sequential_Q($exprs)) { + $exprs = $exprs->getArrayCopy(); + } + for ($i=0; $i<count($binds); $i++) { + if ($binds[$i]->value === "&") { + if ($exprs !== NULL && $i < count($exprs)) { + $lst = call_user_func_array('new_list', array_slice($exprs, $i)); + } else { + $lst = new_list(); + } + $this->data[$binds[$i+1]->value] = $lst; + break; + } else { + if ($exprs !== NULL && $i < count($exprs)) { + $this->data[$binds[$i]->value] = $exprs[$i]; + } else { + $this->data[$binds[$i]->value] = NULL; + } + } + } + } + } + public function find($key) { + if (array_key_exists($key, $this->data)) { + return $this; + } elseif ($this->outer) { + return $this->outer->find($key); + } else { + return NULL; + } + } + public function set($key, $value) { + $this->data[$key] = $value; + return $value; + } + public function get($key) { + $env = $this->find($key); + if (!$env) { + throw new Exception("'" . $key . "' not found"); + } else { + return $env->data[$key]; + } + } +} + +// types_ns is namespace of type functions +$types_ns = array( + 'pr-str'=> function () { return call_user_func_array('pr_str', func_get_args()); }, + 'str'=> function () { return call_user_func_array('str', func_get_args()); }, + 'prn'=> function () { return call_user_func_array('prn', func_get_args()); }, + 'println'=>function () { return call_user_func_array('println', func_get_args()); }, + 'with-meta'=> function ($a, $b) { return with_meta($a, $b); }, + 'meta'=> function ($a) { return meta($a); }, + '='=> function ($a, $b) { return equal_Q($a, $b); }, + 'nil?'=> function ($a) { return nil_Q($a); }, + 'true?'=> function ($a) { return true_Q($a); }, + 'false?'=> function ($a) { return false_Q($a); }, + '+'=> function ($a, $b) { return intval($a + $b,10); }, + '-'=> function ($a, $b) { return intval($a - $b,10); }, + '*'=> function ($a, $b) { return intval($a * $b,10); }, + '/'=> function ($a, $b) { return intval($a / $b,10); }, + '<'=> function ($a, $b) { return $a < $b; }, + '<='=> function ($a, $b) { return $a <= $b; }, + '>'=> function ($a, $b) { return $a > $b; }, + '>='=> function ($a, $b) { return $a >= $b; }, + 'symbol?'=> function ($a) { return symbol_Q($a); }, + 'string?'=> function ($a) { return string_Q($a); }, + 'hash-map' => function () { return call_user_func_array('new_hash_map', func_get_args()); }, + 'map?'=> function ($a) { return hash_map_Q($a); }, + 'assoc' => function () { return call_user_func_array('assoc', func_get_args()); }, + 'dissoc' => function () { return call_user_func_array('dissoc', func_get_args()); }, + 'get' => function ($a, $b) { return get($a, $b); }, + 'contains?' => function ($a, $b) { return contains_Q($a, $b); }, + 'keys' => function ($a) { return keys($a); }, + 'vals' => function ($a) { return vals($a); }, + 'throw'=> function ($a) { return mal_throw($a); }, + 'list'=> function () { return call_user_func_array('new_list', func_get_args()); }, + 'list?'=> function ($a) { return list_Q($a); }, + 'vector'=> function () { return call_user_func_array('new_vector', func_get_args()); }, + 'vector?'=> function ($a) { return vector_Q($a); }, + 'atom'=> function ($a) { return new_atom($a); }, + 'atom?'=> function ($a) { return atom_Q($a); }, + 'deref'=> function ($a) { return deref($a); }, + 'reset!'=> function ($a, $b) { return reset_BANG($a, $b); }, + 'swap!'=> function () { return call_user_func_array('swap_BANG', func_get_args()); }, + 'sequential?'=> function ($a) { return sequential_Q($a); }, + 'count'=> function ($a) { return scount($a); }, + 'empty?'=> function ($a) { return empty_Q($a); }, + 'cons'=> function ($a, $b) { return cons($a, $b); }, + 'concat'=> function () { return call_user_func_array('concat', func_get_args()); }, + 'conj'=> function () { return call_user_func_array('conj', func_get_args()); }, + 'first'=> function ($a) { return first($a); }, + 'rest'=> function ($a) { return rest($a); }, + 'nth'=> function ($a, $b) { return nth($a, $b); }, + 'apply'=> function ($a, $b) { return apply($a, $b); }, + 'map'=> function ($a, $b) { return map($a, $b); } +); + + +?> diff --git a/python/Makefile b/python/Makefile new file mode 100644 index 0000000..1c8e467 --- /dev/null +++ b/python/Makefile @@ -0,0 +1,27 @@ + +TESTS = + + +SOURCES = mal_types.py mal_readline.py reader.py stepA_more.py + +#all: mal.sh +# +#mal.sh: $(SOURCES) +# cat $+ > $@ +# echo "#!/bin/bash" > $@ +# cat $+ | grep -v "^source " >> $@ +# chmod +x $@ +# +#clean: +# rm -f mal.sh + +.PHONY: stats tests $(TESTS) + +stats: $(SOURCES) + @wc $^ + +tests: $(TESTS) + +$(TESTS): + @echo "Running $@"; \ + python $@ || exit 1; \ diff --git a/python/mal_readline.py b/python/mal_readline.py new file mode 100644 index 0000000..e8cf957 --- /dev/null +++ b/python/mal_readline.py @@ -0,0 +1,24 @@ +import os, readline as pyreadline + +history_loaded = False +histfile = os.path.expanduser("~/.mal-history") + +def readline(prompt="user> "): + if not history_loaded: + try: + with open(histfile, "r") as hf: + for line in hf.readlines(): + pyreadline.add_history(line.rstrip("\r\n")) + pass + except IOError: + print("Could not open %s" % histfile) + pass + + try: + line = raw_input(prompt) + pyreadline.add_history(line) + with open(histfile, "a") as hf: + hf.write(line + "\n") + return line + except EOFError: + return None diff --git a/python/mal_types.py b/python/mal_types.py new file mode 100644 index 0000000..fa0a11e --- /dev/null +++ b/python/mal_types.py @@ -0,0 +1,268 @@ +import copy +from itertools import chain + +# General functions + +def _pr_str(obj, print_readably=True): + _r = print_readably + if list_Q(obj): + return "(" + " ".join(map(lambda e: _pr_str(e,_r), obj)) + ")" + elif vector_Q(obj): + return "[" + " ".join(map(lambda e: _pr_str(e,_r), obj)) + "]" + elif hash_map_Q(obj): + ret = [] + for k in obj.keys(): + ret.extend((_pr_str(k), _pr_str(obj[k],_r))) + return "{" + " ".join(ret) + "}" + elif string_Q(obj): + if print_readably: + return '"' + obj.encode('unicode_escape').replace('"', '\\"') + '"' + else: + return obj + elif nil_Q(obj): + return "nil" + elif true_Q(obj): + return "true" + elif false_Q(obj): + return "false" + elif atom_Q(obj): + return "(atom " + _pr_str(obj.val,_r) + ")" + else: + return obj.__str__() + +def pr_str(*args): + return " ".join(map(lambda exp: _pr_str(exp, True), args)) + +def do_str(*args): + return "".join(map(lambda exp: _pr_str(exp, False), args)) + +def prn(*args): + print " ".join(map(lambda exp: _pr_str(exp, True), args)) + return None + +def println(*args): + line = " ".join(map(lambda exp: _pr_str(exp, False), args)) + print line.replace('\\n', '\n') + return None + +def with_meta(obj, meta): + new_obj = copy.copy(obj) + new_obj.__meta__ = meta + return new_obj + +def meta(obj): + if hasattr(obj, "__meta__"): return obj.__meta__ + else: return None + +def equal_Q(a, b): + ota, otb = type(a), type(b) + if not (ota == otb or (sequential_Q(a) and sequential_Q(b))): + return False; + if symbol_Q(a): + return a == b + elif list_Q(a) or vector_Q(a): + if len(a) != len(b): return False + for i in range(len(a)): + if not equal_Q(a[i], b[i]): return False + return True + elif hash_map_Q(a): + akeys = a.keys() + akeys.sort() + bkeys = b.keys() + bkeys.sort() + if len(akeys) != len(bkeys): return False + for i in range(len(akeys)): + if akeys[i] != bkeys[i]: return False + if not equal_Q(a[akeys[i]], b[bkeys[i]]): return False + return True + else: + return a == b + +# nil, true, false +def nil_Q(exp): return exp is None +def true_Q(exp): return exp is True +def false_Q(exp): return exp is False +def string_Q(exp): return type(exp) in [str, unicode] + +# numbers +int_plus = lambda a,b: a+b +int_minus = lambda a,b: a-b +int_multiply = lambda a,b: a*b +int_divide = lambda a,b: a/b +int_lt = lambda a,b: a<b +int_lte = lambda a,b: a<=b +int_gt = lambda a,b: a>b +int_gte = lambda a,b: a>=b + +# symbols +class Symbol(str): pass +def new_symbol(str): return Symbol(str) +def symbol_Q(exp): return type(exp) == Symbol + + +# functions +def new_function(func, exp, env, params): + def f(*args): + return func(exp, Env(env, params, args)) + f.__meta__ = {"exp": exp, "env": env, "params": params} + return f +def function_Q(f): return type(f) == type(function_Q) + +# hash maps +class Hash_Map(dict): pass +def new_hash_map(*key_vals): + hm = Hash_Map() + for i in range(0,len(key_vals),2): hm[key_vals[i]] = key_vals[i+1] + return hm +def hash_map_Q(exp): return type(exp) == Hash_Map + +def assoc(src_hm, *key_vals): + hm = copy.copy(src_hm) + for i in range(0,len(key_vals),2): hm[key_vals[i]] = key_vals[i+1] + return hm + +def dissoc(src_hm, *keys): + hm = copy.copy(src_hm) + for key in keys: del hm[key] + return hm + +def get(hm, key): + if key in hm: + return hm[key] + else: + return None + +def contains_Q(hm, key): return key in hm + +def keys(hm): return new_list(*hm.keys()) + +def vals(hm): return new_list(*hm.values()) + + +# errors/exceptions +def throw(exc): raise Exception(exc) + + +# lists +class List(list): + def __add__(self, rhs): return List(list.__add__(self, rhs)) + def __getitem__(self, i): + if type(i) == slice: return List(list.__getitem__(self, i)) + elif i >= len(self): return None + else: return list.__getitem__(self, i) + def __getslice__(self, *a): return List(list.__getslice__(self, *a)) +def new_list(*vals): return List(vals) +def list_Q(exp): return type(exp) == List + + +# vectors +class Vector(list): + def __add__(self, rhs): return Vector(list.__add__(self, rhs)) + def __getitem__(self, i): + if type(i) == slice: return Vector(list.__getitem__(self, i)) + elif i >= len(self): return None + else: return list.__getitem__(self, i) + def __getslice__(self, *a): return Vector(list.__getslice__(self, *a)) +def new_vector(*vals): return Vector(vals) +def vector_Q(exp): return type(exp) == Vector + + +# atoms +class Atom(object): + def __init__(self, val): + self.val = val +def new_atom(val): return Atom(val) +def atom_Q(exp): return type(exp) == Atom +def deref(atm): return atm.val +def reset_BANG(atm,val): + atm.val = val + return atm.val +def swap_BANG(atm,f,*args): + atm.val = f(atm.val,*args) + return atm.val + + + +# Sequence operations +def sequential_Q(seq): return list_Q(seq) or vector_Q(seq) + +def coll_Q(coll): return sequential_Q(coll) or hash_map_Q(coll) + +def cons(x, seq): return List([x]) + List(seq) + +def nth(lst, idx): return lst[idx] + +def count(lst): return len(lst) + +def empty_Q(lst): return len(lst) == 0 + +def concat(*lsts): return List(chain(*lsts)) + +# retains metadata +def conj(lst, *args): + new_lst = List(lst + list(args)) + if hasattr(lst, "__meta__"): + new_lst.__meta__ = lst.__meta__ + return new_lst + +def first(lst): return lst[0] + +def rest(lst): return List(lst[1:]) + +def apply(f, *args): + return f(*(list(args[0:-1])+args[-1])) + +def mapf(f, lst): + return List(map(f, lst)) + + +# Environment + +class Env(): + def __init__(self, outer=None, binds=None, exprs=None): + self.data = {} + self.outer = outer or None + + if binds: + for i in range(len(binds)): + if binds[i] == "&": + self.data[binds[i+1]] = exprs[i:] + break + else: + self.data[binds[i]] = exprs[i] + + def find(self, key): + if key in self.data: return self + elif self.outer: return self.outer.find(key) + else: return None + + def set(self, key, value): + self.data[key] = value + return value + + def get(self, key): + env = self.find(key) + if not env: raise Exception("'" + key + "' not found") + return env.data[key] + +types_ns = { + 'pr-str': pr_str, 'str': do_str, 'prn': prn, 'println': println, + 'with-meta': with_meta, 'meta': meta, + '=': equal_Q, + 'nil?': nil_Q, 'true?': true_Q, 'false?': false_Q, + 'symbol?': symbol_Q, + '<': int_lt, '<=': int_lte, '>': int_gt, '>=': int_gte, + '+': int_plus, '-': int_minus, '*': int_multiply, '/': int_divide, + 'hash-map': new_hash_map, 'map?': hash_map_Q, + 'assoc': assoc, 'dissoc': dissoc, 'get': get, + 'contains?': contains_Q, 'keys': keys, 'vals': vals, + 'throw': throw, + 'list': new_list, 'list?': list_Q, + 'vector': new_vector, 'vector?': vector_Q, + 'atom': new_atom, 'atom?': atom_Q, 'deref': deref, + 'reset!': reset_BANG, 'swap!': swap_BANG, + 'sequential?': sequential_Q, + 'cons': cons, 'nth': nth, 'count': count, 'empty?': empty_Q, + 'concat': concat, "conj": conj, "first": first, "rest": rest, + 'apply': apply, 'map': mapf} + diff --git a/python/reader.py b/python/reader.py new file mode 100644 index 0000000..ddd6a32 --- /dev/null +++ b/python/reader.py @@ -0,0 +1,104 @@ +import re +from mal_types import (new_symbol, Symbol, new_hash_map, List, new_list, Vector) + +class Blank(Exception): pass + +class Reader(): + def __init__(self, tokens, position=0): + self.tokens = tokens + self.position = position + + def next(self): + self.position += 1 + return self.tokens[self.position-1] + + def peek(self): + if len(self.tokens) > self.position: + return self.tokens[self.position] + else: + return None + +def tokenize(str): + tre = re.compile(r"""[\s,]*(~@|[\[\]{}()'`~^@]|"(?:[\\].|[^\\"])*"|;.*|[^\s\[\]{}()'"`@,;]+)"""); + return [t for t in re.findall(tre, str) if t[0] != ';'] + +def read_atom(reader): + int_re = re.compile(r"-?[0-9]+$") + float_re = re.compile(r"-?[0-9][0-9.]*$") + token = reader.next() + if re.match(int_re, token): return int(token) + elif re.match(float_re, token): return int(token) + elif token[0] == '"': return token[1:-1].replace('\\"', '"') + elif token == "nil": return None + elif token == "true": return True + elif token == "false": return False + else: return Symbol(token) + +def read_sequence(reader, typ=list, start='(', end=')'): + ast = typ() + token = reader.next() + if token != start: raise Exception("expected '" + start + "'") + + token = reader.peek() + while token != end: + if not token: raise Exception("expected '" + end + "', got EOF") + ast.append(read_form(reader)) + token = reader.peek() + reader.next() + return ast + +def read_hash_map(reader): + lst = read_sequence(reader, list, '{', '}') + return new_hash_map(*lst) + +def read_list(reader): + return read_sequence(reader, List, '(', ')') + +def read_vector(reader): + return read_sequence(reader, Vector, '[', ']') + +def read_form(reader): + token = reader.peek() + # reader macros/transforms + if token[0] == ';': + reader.next() + return None + elif token == '\'': + reader.next() + return new_list(Symbol('quote'), read_form(reader)) + elif token == '`': + reader.next() + return new_list(Symbol('quasiquote'), read_form(reader)) + elif token == '~': + reader.next() + return new_list(Symbol('unquote'), read_form(reader)) + elif token == '~@': + reader.next() + return new_list(Symbol('splice-unquote'), read_form(reader)) + elif token == '^': + reader.next() + meta = read_form(reader) + return new_list(Symbol('with-meta'), read_form(reader), meta) + elif token == '@': + reader.next() + return new_list(Symbol('deref'), read_form(reader)) + + # list + elif token == ')': raise Exception("unexpected ')'") + elif token == '(': return read_list(reader) + + # vector + elif token == ']': raise Exception("unexpected ']'"); + elif token == '[': return read_vector(reader); + + # hash-map + elif token == '}': raise Exception("unexpected '}'"); + elif token == '{': return read_hash_map(reader); + + # atom + else: return read_atom(reader); + +def read_str(str): + tokens = tokenize(str) + if len(tokens) == 0: raise Blank + return read_form(Reader(tokens)) diff --git a/python/step0_repl.py b/python/step0_repl.py new file mode 100644 index 0000000..8d42c33 --- /dev/null +++ b/python/step0_repl.py @@ -0,0 +1,32 @@ +import sys, traceback +import mal_readline + +# read +def READ(str): + return str + +# eval +def EVAL(ast, env): + # try it as an expression then a statement + try: + return eval(ast) + except SyntaxError: + exec compile(ast, '', 'single') in globals() + return None + +# print +def PRINT(exp): + return exp + +# repl +def REP(str): + return PRINT(EVAL(READ(str), {})) + +while True: + try: + line = mal_readline.readline("user> ") + if line == None: break + if line == "": continue + print(REP(line)) + except Exception as e: + print "".join(traceback.format_exception(sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2])) diff --git a/python/step1_read_print.py b/python/step1_read_print.py new file mode 100644 index 0000000..165dfa3 --- /dev/null +++ b/python/step1_read_print.py @@ -0,0 +1,32 @@ +import sys, traceback +import mal_readline +from mal_types import (pr_str, sequential_Q, symbol_Q, coll_Q, list_Q, + vector_Q, hash_map_Q, new_symbol, new_function, + new_list, new_vector, new_hash_map, Env, types_ns) +from reader import (read_str, Blank) + +# read +def READ(str): + return read_str(str) + +# eval +def EVAL(ast, env): + #print("EVAL %s" % ast) + return ast + +def PRINT(exp): + return pr_str(exp) + +# repl +def REP(str): + return PRINT(EVAL(READ(str), {})) + +while True: + try: + line = mal_readline.readline("user> ") + if line == None: break + if line == "": continue + print(REP(line)) + except Blank: continue + except Exception as e: + print "".join(traceback.format_exception(*sys.exc_info())) diff --git a/python/step2_eval.py b/python/step2_eval.py new file mode 100644 index 0000000..bb5d6f8 --- /dev/null +++ b/python/step2_eval.py @@ -0,0 +1,60 @@ +import sys, traceback +import mal_readline +from mal_types import (pr_str, sequential_Q, symbol_Q, coll_Q, list_Q, + vector_Q, hash_map_Q, new_symbol, new_function, + new_list, new_vector, new_hash_map, Env, types_ns) +from reader import (read_str, Blank) + +# read +def READ(str): + return read_str(str) + +# eval +def eval_ast(ast, env): + if symbol_Q(ast): + return env[ast] + elif list_Q(ast): + return new_list(*map(lambda a: EVAL(a, env), ast)) + elif vector_Q(ast): + return new_vector(*map(lambda a: EVAL(a, env), ast)) + elif hash_map_Q(ast): + keyvals = [] + for k in ast.keys(): + keyvals.append(EVAL(k, env)) + keyvals.append(EVAL(ast[k], env)) + return new_hash_map(*keyvals) + else: + return ast # primitive value, return unchanged + +def EVAL(ast, env): + #print("EVAL %s" % ast) + if not list_Q(ast): + return eval_ast(ast, env) + + # apply list + el = eval_ast(ast, env) + f = el[0] + return f(*el[1:]) + +def PRINT(exp): + return pr_str(exp) + +# repl +repl_env = {} +def REP(str): + return PRINT(EVAL(READ(str), repl_env)) + +repl_env['+'] = lambda a,b: a+b +repl_env['-'] = lambda a,b: a-b +repl_env['*'] = lambda a,b: a*b +repl_env['/'] = lambda a,b: a/b + +while True: + try: + line = mal_readline.readline("user> ") + if line == None: break + if line == "": continue + print(REP(line)) + except Blank: continue + except Exception as e: + print "".join(traceback.format_exception(*sys.exc_info())) diff --git a/python/step3_env.py b/python/step3_env.py new file mode 100644 index 0000000..f95a978 --- /dev/null +++ b/python/step3_env.py @@ -0,0 +1,76 @@ +import sys, traceback +import mal_readline +from mal_types import (pr_str, sequential_Q, symbol_Q, coll_Q, list_Q, + vector_Q, hash_map_Q, new_symbol, new_function, + new_list, new_vector, new_hash_map, Env, types_ns) +from reader import (read_str, Blank) + +# read +def READ(str): + return read_str(str) + +# eval +def eval_ast(ast, env): + if symbol_Q(ast): + return env.get(ast) + elif list_Q(ast): + return new_list(*map(lambda a: EVAL(a, env), ast)) + elif vector_Q(ast): + return new_vector(*map(lambda a: EVAL(a, env), ast)) + elif hash_map_Q(ast): + keyvals = [] + for k in ast.keys(): + keyvals.append(EVAL(k, env)) + keyvals.append(EVAL(ast[k], env)) + return new_hash_map(*keyvals) + else: + return ast # primitive value, return unchanged + +def EVAL(ast, env): + #print("EVAL %s" % ast) + if not list_Q(ast): + return eval_ast(ast, env) + + # apply list + if len(ast) == 0: return ast + a0 = ast[0] + + if "def!" == a0: + a1, a2 = ast[1], ast[2] + res = EVAL(a2, env) + return env.set(a1, res) + elif "let*" == a0: + a1, a2 = ast[1], ast[2] + let_env = Env(env) + for i in range(0, len(a1), 2): + let_env.set(a1[i], EVAL(a1[i+1], let_env)) + return EVAL(a2, let_env) + else: + el = eval_ast(ast, env) + f = el[0] + return f(*el[1:]) + +# print +def PRINT(exp): + return pr_str(exp) + +# repl +repl_env = Env() +def REP(str): + return PRINT(EVAL(READ(str), repl_env)) +def _ref(k,v): repl_env.set(k, v) + +_ref('+', lambda a,b: a+b) +_ref('-', lambda a,b: a-b) +_ref('*', lambda a,b: a*b) +_ref('/', lambda a,b: a/b) + +while True: + try: + line = mal_readline.readline("user> ") + if line == None: break + if line == "": continue + print(REP(line)) + except Blank: continue + except Exception as e: + print "".join(traceback.format_exception(*sys.exc_info())) diff --git a/python/step4_if_fn_do.py b/python/step4_if_fn_do.py new file mode 100644 index 0000000..4b54d8f --- /dev/null +++ b/python/step4_if_fn_do.py @@ -0,0 +1,91 @@ +import sys, traceback +import mal_readline +from mal_types import (pr_str, sequential_Q, symbol_Q, coll_Q, list_Q, + vector_Q, hash_map_Q, new_symbol, new_function, + new_list, new_vector, new_hash_map, Env, types_ns) +from reader import (read_str, Blank) + +# read +def READ(str): + return read_str(str) + +# eval +def eval_ast(ast, env): + if symbol_Q(ast): + return env.get(ast) + elif list_Q(ast): + return new_list(*map(lambda a: EVAL(a, env), ast)) + elif vector_Q(ast): + return new_vector(*map(lambda a: EVAL(a, env), ast)) + elif hash_map_Q(ast): + keyvals = [] + for k in ast.keys(): + keyvals.append(EVAL(k, env)) + keyvals.append(EVAL(ast[k], env)) + return new_hash_map(*keyvals) + else: + return ast # primitive value, return unchanged + +def EVAL(ast, env): + #print("EVAL %s" % ast) + if not list_Q(ast): + return eval_ast(ast, env) + + # apply list + if len(ast) == 0: return ast + a0 = ast[0] + + if "def!" == a0: + a1, a2 = ast[1], ast[2] + res = EVAL(a2, env) + return env.set(a1, res) + elif "let*" == a0: + a1, a2 = ast[1], ast[2] + let_env = Env(env) + for i in range(0, len(a1), 2): + let_env.set(a1[i], EVAL(a1[i+1], let_env)) + return EVAL(a2, let_env) + elif "do" == a0: + el = eval_ast(ast[1:], env) + return el[-1] + elif "if" == a0: + a1, a2 = ast[1], ast[2] + cond = EVAL(a1, env) + if cond is None or cond is False: + if len(ast) > 3: return EVAL(ast[3], env) + else: return None + else: + return EVAL(a2, env) + elif "fn*" == a0: + a1, a2 = ast[1], ast[2] + return new_function(EVAL, a2, env, a1) + else: + el = eval_ast(ast, env) + f = el[0] + return f(*el[1:]) + +# print +def PRINT(exp): + return pr_str(exp) + +# repl +repl_env = Env() +def REP(str): + return PRINT(EVAL(READ(str), repl_env)) +def _ref(k,v): repl_env.set(k, v) + +# Import types functions +for name, val in types_ns.items(): _ref(name, val) + +# Defined using the language itself +REP("(def! not (fn* (a) (if a false true)))") + +while True: + try: + line = mal_readline.readline("user> ") + if line == None: break + if line == "": continue + print(REP(line)) + except Blank: continue + except Exception as e: + print "".join(traceback.format_exception(*sys.exc_info())) diff --git a/python/step5_tco.py b/python/step5_tco.py new file mode 100644 index 0000000..ffde863 --- /dev/null +++ b/python/step5_tco.py @@ -0,0 +1,99 @@ +import sys, traceback +import mal_readline +from mal_types import (pr_str, sequential_Q, symbol_Q, coll_Q, list_Q, + vector_Q, hash_map_Q, new_symbol, new_function, + new_list, new_vector, new_hash_map, Env, types_ns) +from reader import (read_str, Blank) + +# read +def READ(str): + return read_str(str) + +# eval +def eval_ast(ast, env): + if symbol_Q(ast): + return env.get(ast) + elif list_Q(ast): + return new_list(*map(lambda a: EVAL(a, env), ast)) + elif vector_Q(ast): + return new_vector(*map(lambda a: EVAL(a, env), ast)) + elif hash_map_Q(ast): + keyvals = [] + for k in ast.keys(): + keyvals.append(EVAL(k, env)) + keyvals.append(EVAL(ast[k], env)) + return new_hash_map(*keyvals) + else: + return ast # primitive value, return unchanged + +def EVAL(ast, env): + while True: + #print("EVAL %s" % ast) + if not list_Q(ast): + return eval_ast(ast, env) + + # apply list + if len(ast) == 0: return ast + a0 = ast[0] + + if "def!" == a0: + a1, a2 = ast[1], ast[2] + res = EVAL(a2, env) + return env.set(a1, res) + elif "let*" == a0: + a1, a2 = ast[1], ast[2] + let_env = Env(env) + for i in range(0, len(a1), 2): + let_env.set(a1[i], EVAL(a1[i+1], let_env)) + return EVAL(a2, let_env) + elif "do" == a0: + eval_ast(ast[1:-1], env) + ast = ast[-1] + # Continue loop (TCO) + elif "if" == a0: + a1, a2 = ast[1], ast[2] + cond = EVAL(a1, env) + if cond is None or cond is False: + if len(ast) > 3: ast = ast[3] + else: ast = None + else: + ast = a2 + # Continue loop (TCO) + elif "fn*" == a0: + a1, a2 = ast[1], ast[2] + return new_function(EVAL, a2, env, a1) + else: + el = eval_ast(ast, env) + f = el[0] + if hasattr(f, '__meta__') and f.__meta__.has_key("exp"): + m = f.__meta__ + ast = m['exp'] + env = Env(m['env'], m['params'], el[1:]) + else: + return f(*el[1:]) + +# print +def PRINT(exp): + return pr_str(exp) + +# repl +repl_env = Env() +def REP(str): + return PRINT(EVAL(READ(str), repl_env)) +def _ref(k,v): repl_env.set(k, v) + +# Import types functions +for name, val in types_ns.items(): _ref(name, val) + +# Defined using the language itself +REP("(def! not (fn* (a) (if a false true)))") + +while True: + try: + line = mal_readline.readline("user> ") + if line == None: break + if line == "": continue + print(REP(line)) + except Blank: continue + except Exception as e: + print "".join(traceback.format_exception(*sys.exc_info())) diff --git a/python/step6_file.py b/python/step6_file.py new file mode 100644 index 0000000..b53863a --- /dev/null +++ b/python/step6_file.py @@ -0,0 +1,108 @@ +import sys, traceback +import mal_readline +from mal_types import (pr_str, sequential_Q, symbol_Q, coll_Q, list_Q, + vector_Q, hash_map_Q, new_symbol, new_function, + new_list, new_vector, new_hash_map, Env, types_ns) +from reader import (read_str, Blank) + +# read +def READ(str): + return read_str(str) + +# eval +def eval_ast(ast, env): + if symbol_Q(ast): + return env.get(ast) + elif list_Q(ast): + return new_list(*map(lambda a: EVAL(a, env), ast)) + elif vector_Q(ast): + return new_vector(*map(lambda a: EVAL(a, env), ast)) + elif hash_map_Q(ast): + keyvals = [] + for k in ast.keys(): + keyvals.append(EVAL(k, env)) + keyvals.append(EVAL(ast[k], env)) + return new_hash_map(*keyvals) + else: + return ast # primitive value, return unchanged + +def EVAL(ast, env): + while True: + #print("EVAL %s" % ast) + if not list_Q(ast): + return eval_ast(ast, env) + + # apply list + if len(ast) == 0: return ast + a0 = ast[0] + + if "def!" == a0: + a1, a2 = ast[1], ast[2] + res = EVAL(a2, env) + return env.set(a1, res) + elif "let*" == a0: + a1, a2 = ast[1], ast[2] + let_env = Env(env) + for i in range(0, len(a1), 2): + let_env.set(a1[i], EVAL(a1[i+1], let_env)) + return EVAL(a2, let_env) + elif "do" == a0: + eval_ast(ast[1:-1], env) + ast = ast[-1] + # Continue loop (TCO) + elif "if" == a0: + a1, a2 = ast[1], ast[2] + cond = EVAL(a1, env) + if cond is None or cond is False: + if len(ast) > 3: ast = ast[3] + else: ast = None + else: + ast = a2 + # Continue loop (TCO) + elif "fn*" == a0: + a1, a2 = ast[1], ast[2] + return new_function(EVAL, a2, env, a1) + else: + el = eval_ast(ast, env) + f = el[0] + if hasattr(f, '__meta__') and f.__meta__.has_key("exp"): + m = f.__meta__ + ast = m['exp'] + env = Env(m['env'], m['params'], el[1:]) + else: + return f(*el[1:]) + +# print +def PRINT(exp): + return pr_str(exp) + +# repl +repl_env = Env() +def REP(str): + return PRINT(EVAL(READ(str), repl_env)) +def _ref(k,v): repl_env.set(k, v) + +# Import types functions +for name, val in types_ns.items(): _ref(name, val) + +_ref('read-string', read_str) +_ref('eval', lambda ast: EVAL(ast, repl_env)) +_ref('slurp', lambda file: open(file).read()) +_ref('slurp-do', lambda file: "(do" + open(file).read() + ")") + +# Defined using the language itself +REP("(def! not (fn* (a) (if a false true)))") +REP("(def! load-file (fn* (f) (eval (read-string (slurp-do f)))))") + +if len(sys.argv) >= 2: + REP('(load-file "' + sys.argv[1] + '")') +else: + while True: + try: + line = mal_readline.readline("user> ") + if line == None: break + if line == "": continue + print(REP(line)) + except Blank: continue + except Exception as e: + print "".join(traceback.format_exception(*sys.exc_info())) diff --git a/python/step7_quote.py b/python/step7_quote.py new file mode 100644 index 0000000..3054bb0 --- /dev/null +++ b/python/step7_quote.py @@ -0,0 +1,125 @@ +import sys, traceback +import mal_readline +from mal_types import (pr_str, sequential_Q, symbol_Q, coll_Q, list_Q, + vector_Q, hash_map_Q, new_symbol, new_function, + new_list, new_vector, new_hash_map, Env, types_ns) +from reader import (read_str, Blank) + +# read +def READ(str): + return read_str(str) + +# eval +def is_pair(x): + return sequential_Q(x) and len(x) > 0 + +def quasiquote(ast): + if not is_pair(ast): + return new_list(new_symbol("quote"), ast) + elif ast[0] == 'unquote': + return ast[1] + elif is_pair(ast[0]) and ast[0][0] == 'splice-unquote': + return new_list(new_symbol("concat"), ast[0][1], quasiquote(ast[1:])) + else: + return new_list(new_symbol("cons"), quasiquote(ast[0]), quasiquote(ast[1:])) + +def eval_ast(ast, env): + if symbol_Q(ast): + return env.get(ast) + elif list_Q(ast): + return new_list(*map(lambda a: EVAL(a, env), ast)) + elif vector_Q(ast): + return new_vector(*map(lambda a: EVAL(a, env), ast)) + elif hash_map_Q(ast): + keyvals = [] + for k in ast.keys(): + keyvals.append(EVAL(k, env)) + keyvals.append(EVAL(ast[k], env)) + return new_hash_map(*keyvals) + else: + return ast # primitive value, return unchanged + +def EVAL(ast, env): + while True: + #print("EVAL %s" % ast) + if not list_Q(ast): + return eval_ast(ast, env) + + # apply list + if len(ast) == 0: return ast + a0 = ast[0] + + if "def!" == a0: + a1, a2 = ast[1], ast[2] + res = EVAL(a2, env) + return env.set(a1, res) + elif "let*" == a0: + a1, a2 = ast[1], ast[2] + let_env = Env(env) + for i in range(0, len(a1), 2): + let_env.set(a1[i], EVAL(a1[i+1], let_env)) + return EVAL(a2, let_env) + elif "quote" == a0: + return ast[1] + elif "quasiquote" == a0: + return EVAL(quasiquote(ast[1]), env) + elif "do" == a0: + eval_ast(ast[1:-1], env) + ast = ast[-1] + # Continue loop (TCO) + elif "if" == a0: + a1, a2 = ast[1], ast[2] + cond = EVAL(a1, env) + if cond is None or cond is False: + if len(ast) > 3: ast = ast[3] + else: ast = None + else: + ast = a2 + # Continue loop (TCO) + elif "fn*" == a0: + a1, a2 = ast[1], ast[2] + return new_function(EVAL, a2, env, a1) + else: + el = eval_ast(ast, env) + f = el[0] + if hasattr(f, '__meta__') and f.__meta__.has_key("exp"): + m = f.__meta__ + ast = m['exp'] + env = Env(m['env'], m['params'], el[1:]) + else: + return f(*el[1:]) + +# print +def PRINT(exp): + return pr_str(exp) + +# repl +repl_env = Env() +def REP(str): + return PRINT(EVAL(READ(str), repl_env)) +def _ref(k,v): repl_env.set(k, v) + +# Import types functions +for name, val in types_ns.items(): _ref(name, val) + +_ref('read-string', read_str) +_ref('eval', lambda ast: EVAL(ast, repl_env)) +_ref('slurp', lambda file: open(file).read()) +_ref('slurp-do', lambda file: "(do" + open(file).read() + ")") + +# Defined using the language itself +REP("(def! not (fn* (a) (if a false true)))") +REP("(def! load-file (fn* (f) (eval (read-string (slurp-do f)))))") + +if len(sys.argv) >= 2: + REP('(load-file "' + sys.argv[1] + '")') +else: + while True: + try: + line = mal_readline.readline("user> ") + if line == None: break + if line == "": continue + print(REP(line)) + except Blank: continue + except Exception as e: + print "".join(traceback.format_exception(*sys.exc_info())) diff --git a/python/step8_macros.py b/python/step8_macros.py new file mode 100644 index 0000000..616e7d3 --- /dev/null +++ b/python/step8_macros.py @@ -0,0 +1,145 @@ +import sys, traceback +import mal_readline +from mal_types import (pr_str, sequential_Q, symbol_Q, coll_Q, list_Q, + vector_Q, hash_map_Q, new_symbol, new_function, + new_list, new_vector, new_hash_map, Env, types_ns) +from reader import (read_str, Blank) + +# read +def READ(str): + return read_str(str) + +# eval +def is_pair(x): + return sequential_Q(x) and len(x) > 0 + +def quasiquote(ast): + if not is_pair(ast): + return new_list(new_symbol("quote"), ast) + elif ast[0] == 'unquote': + return ast[1] + elif is_pair(ast[0]) and ast[0][0] == 'splice-unquote': + return new_list(new_symbol("concat"), ast[0][1], quasiquote(ast[1:])) + else: + return new_list(new_symbol("cons"), quasiquote(ast[0]), quasiquote(ast[1:])) + +def is_macro_call(ast, env): + return (list_Q(ast) and + symbol_Q(ast[0]) and + env.find(ast[0]) and + hasattr(env.get(ast[0]), '_ismacro_')) + +def macroexpand(ast, env): + while is_macro_call(ast, env): + mac = env.get(ast[0]) + ast = macroexpand(mac(*ast[1:]), env) + return ast + +def eval_ast(ast, env): + if symbol_Q(ast): + return env.get(ast) + elif list_Q(ast): + return new_list(*map(lambda a: EVAL(a, env), ast)) + elif vector_Q(ast): + return new_vector(*map(lambda a: EVAL(a, env), ast)) + elif hash_map_Q(ast): + keyvals = [] + for k in ast.keys(): + keyvals.append(EVAL(k, env)) + keyvals.append(EVAL(ast[k], env)) + return new_hash_map(*keyvals) + else: + return ast # primitive value, return unchanged + +def EVAL(ast, env): + while True: + #print("EVAL %s" % ast) + if not list_Q(ast): + return eval_ast(ast, env) + + # apply list + ast = macroexpand(ast, env) + if not list_Q(ast): return ast + if len(ast) == 0: return ast + + a0 = ast[0] + if "def!" == a0: + a1, a2 = ast[1], ast[2] + res = EVAL(a2, env) + return env.set(a1, res) + elif "let*" == a0: + a1, a2 = ast[1], ast[2] + let_env = Env(env) + for i in range(0, len(a1), 2): + let_env.set(a1[i], EVAL(a1[i+1], let_env)) + return EVAL(a2, let_env) + elif "quote" == a0: + return ast[1] + elif "quasiquote" == a0: + return EVAL(quasiquote(ast[1]), env) + elif 'defmacro!' == a0: + func = EVAL(ast[2], env) + func._ismacro_ = True + return env.set(ast[1], func) + elif 'macroexpand' == a0: + return macroexpand(ast[1], env) + elif "do" == a0: + eval_ast(ast[1:-1], env) + ast = ast[-1] + # Continue loop (TCO) + elif "if" == a0: + a1, a2 = ast[1], ast[2] + cond = EVAL(a1, env) + if cond is None or cond is False: + if len(ast) > 3: ast = ast[3] + else: ast = None + else: + ast = a2 + # Continue loop (TCO) + elif "fn*" == a0: + a1, a2 = ast[1], ast[2] + return new_function(EVAL, a2, env, a1) + else: + el = eval_ast(ast, env) + f = el[0] + if hasattr(f, '__meta__') and f.__meta__.has_key("exp"): + m = f.__meta__ + ast = m['exp'] + env = Env(m['env'], m['params'], el[1:]) + else: + return f(*el[1:]) + +# print +def PRINT(exp): + return pr_str(exp) + +# repl +repl_env = Env() +def REP(str): + return PRINT(EVAL(READ(str), repl_env)) +def _ref(k,v): repl_env.set(k, v) + +# Import types functions +for name, val in types_ns.items(): _ref(name, val) + +_ref('read-string', read_str) +_ref('eval', lambda ast: EVAL(ast, repl_env)) +_ref('slurp', lambda file: open(file).read()) +_ref('slurp-do', lambda file: "(do" + open(file).read() + ")") + +# Defined using the language itself +REP("(def! not (fn* (a) (if a false true)))") +REP("(def! load-file (fn* (f) (eval (read-string (slurp-do f)))))") + +if len(sys.argv) >= 2: + REP('(load-file "' + sys.argv[1] + '")') +else: + while True: + try: + line = mal_readline.readline("user> ") + if line == None: break + if line == "": continue + print(REP(line)) + except Blank: continue + except Exception as e: + print "".join(traceback.format_exception(*sys.exc_info())) diff --git a/python/step9_interop.py b/python/step9_interop.py new file mode 100644 index 0000000..3a20960 --- /dev/null +++ b/python/step9_interop.py @@ -0,0 +1,154 @@ +import sys, traceback +import mal_readline +from mal_types import (pr_str, sequential_Q, symbol_Q, coll_Q, list_Q, + vector_Q, hash_map_Q, new_symbol, new_function, + new_list, new_vector, new_hash_map, Env, types_ns) +from reader import (read_str, Blank) + +# read +def READ(str): + return read_str(str) + +# eval +def is_pair(x): + return sequential_Q(x) and len(x) > 0 + +def quasiquote(ast): + if not is_pair(ast): + return new_list(new_symbol("quote"), ast) + elif ast[0] == 'unquote': + return ast[1] + elif is_pair(ast[0]) and ast[0][0] == 'splice-unquote': + return new_list(new_symbol("concat"), ast[0][1], quasiquote(ast[1:])) + else: + return new_list(new_symbol("cons"), quasiquote(ast[0]), quasiquote(ast[1:])) + +def is_macro_call(ast, env): + return (list_Q(ast) and + symbol_Q(ast[0]) and + env.find(ast[0]) and + hasattr(env.get(ast[0]), '_ismacro_')) + +def macroexpand(ast, env): + while is_macro_call(ast, env): + mac = env.get(ast[0]) + ast = macroexpand(mac(*ast[1:]), env) + return ast + +def eval_ast(ast, env): + if symbol_Q(ast): + return env.get(ast) + elif list_Q(ast): + return new_list(*map(lambda a: EVAL(a, env), ast)) + elif vector_Q(ast): + return new_vector(*map(lambda a: EVAL(a, env), ast)) + elif hash_map_Q(ast): + keyvals = [] + for k in ast.keys(): + keyvals.append(EVAL(k, env)) + keyvals.append(EVAL(ast[k], env)) + return new_hash_map(*keyvals) + else: + return ast # primitive value, return unchanged + +def EVAL(ast, env): + while True: + #print("EVAL %s" % ast) + if not list_Q(ast): + return eval_ast(ast, env) + + # apply list + ast = macroexpand(ast, env) + if not list_Q(ast): return ast + if len(ast) == 0: return ast + + a0 = ast[0] + if "def!" == a0: + a1, a2 = ast[1], ast[2] + res = EVAL(a2, env) + return env.set(a1, res) + elif "let*" == a0: + a1, a2 = ast[1], ast[2] + let_env = Env(env) + for i in range(0, len(a1), 2): + let_env.set(a1[i], EVAL(a1[i+1], let_env)) + return EVAL(a2, let_env) + elif "quote" == a0: + return ast[1] + elif "quasiquote" == a0: + return EVAL(quasiquote(ast[1]), env) + elif 'defmacro!' == a0: + func = EVAL(ast[2], env) + func._ismacro_ = True + return env.set(ast[1], func) + elif 'macroexpand' == a0: + return macroexpand(ast[1], env) + elif "py!*" == a0: + exec compile(ast[1], '', 'single') in globals() + return None + elif "py*" == a0: + return eval(ast[1]) + elif "." == a0: + el = eval_ast(ast[2:], env) + f = eval(ast[1]) + return f(*el) + elif "do" == a0: + eval_ast(ast[1:-1], env) + ast = ast[-1] + # Continue loop (TCO) + elif "if" == a0: + a1, a2 = ast[1], ast[2] + cond = EVAL(a1, env) + if cond is None or cond is False: + if len(ast) > 3: ast = ast[3] + else: ast = None + else: + ast = a2 + # Continue loop (TCO) + elif "fn*" == a0: + a1, a2 = ast[1], ast[2] + return new_function(EVAL, a2, env, a1) + else: + el = eval_ast(ast, env) + f = el[0] + if hasattr(f, '__meta__') and f.__meta__.has_key("exp"): + m = f.__meta__ + ast = m['exp'] + env = Env(m['env'], m['params'], el[1:]) + else: + return f(*el[1:]) + +# print +def PRINT(exp): + return pr_str(exp) + +# repl +repl_env = Env() +def REP(str): + return PRINT(EVAL(READ(str), repl_env)) +def _ref(k,v): repl_env.set(k, v) + +# Import types functions +for name, val in types_ns.items(): _ref(name, val) + +_ref('read-string', read_str) +_ref('eval', lambda ast: EVAL(ast, repl_env)) +_ref('slurp', lambda file: open(file).read()) +_ref('slurp-do', lambda file: "(do" + open(file).read() + ")") + +# Defined using the language itself +REP("(def! not (fn* (a) (if a false true)))") +REP("(def! load-file (fn* (f) (eval (read-string (slurp-do f)))))") + +if len(sys.argv) >= 2: + REP('(load-file "' + sys.argv[1] + '")') +else: + while True: + try: + line = mal_readline.readline("user> ") + if line == None: break + if line == "": continue + print(REP(line)) + except Blank: continue + except Exception as e: + print "".join(traceback.format_exception(*sys.exc_info())) diff --git a/python/stepA_more.py b/python/stepA_more.py new file mode 100644 index 0000000..c0c5004 --- /dev/null +++ b/python/stepA_more.py @@ -0,0 +1,168 @@ +import sys, traceback +import mal_readline +from mal_types import (pr_str, sequential_Q, symbol_Q, coll_Q, list_Q, + vector_Q, hash_map_Q, new_symbol, new_function, + new_list, new_vector, new_hash_map, Env, types_ns) +from reader import (read_str, Blank) + +# read +def READ(str): + return read_str(str) + +# eval +def is_pair(x): + return sequential_Q(x) and len(x) > 0 + +def quasiquote(ast): + if not is_pair(ast): + return new_list(new_symbol("quote"), ast) + elif ast[0] == 'unquote': + return ast[1] + elif is_pair(ast[0]) and ast[0][0] == 'splice-unquote': + return new_list(new_symbol("concat"), ast[0][1], quasiquote(ast[1:])) + else: + return new_list(new_symbol("cons"), quasiquote(ast[0]), quasiquote(ast[1:])) + +def is_macro_call(ast, env): + return (list_Q(ast) and + symbol_Q(ast[0]) and + env.find(ast[0]) and + hasattr(env.get(ast[0]), '_ismacro_')) + +def macroexpand(ast, env): + while is_macro_call(ast, env): + mac = env.get(ast[0]) + ast = macroexpand(mac(*ast[1:]), env) + return ast + +def eval_ast(ast, env): + if symbol_Q(ast): + return env.get(ast) + elif list_Q(ast): + return new_list(*map(lambda a: EVAL(a, env), ast)) + elif vector_Q(ast): + return new_vector(*map(lambda a: EVAL(a, env), ast)) + elif hash_map_Q(ast): + keyvals = [] + for k in ast.keys(): + keyvals.append(EVAL(k, env)) + keyvals.append(EVAL(ast[k], env)) + return new_hash_map(*keyvals) + else: + return ast # primitive value, return unchanged + +def EVAL(ast, env): + while True: + #print("EVAL %s" % ast) + if not list_Q(ast): + return eval_ast(ast, env) + + # apply list + ast = macroexpand(ast, env) + if not list_Q(ast): return ast + if len(ast) == 0: return ast + + a0 = ast[0] + if "def!" == a0: + a1, a2 = ast[1], ast[2] + res = EVAL(a2, env) + return env.set(a1, res) + elif "let*" == a0: + a1, a2 = ast[1], ast[2] + let_env = Env(env) + for i in range(0, len(a1), 2): + let_env.set(a1[i], EVAL(a1[i+1], let_env)) + return EVAL(a2, let_env) + elif "quote" == a0: + return ast[1] + elif "quasiquote" == a0: + return EVAL(quasiquote(ast[1]), env) + elif 'defmacro!' == a0: + func = EVAL(ast[2], env) + func._ismacro_ = True + return env.set(ast[1], func) + elif 'macroexpand' == a0: + return macroexpand(ast[1], env) + elif "py!*" == a0: + exec compile(ast[1], '', 'single') in globals() + return None + elif "py*" == a0: + return eval(ast[1]) + elif "." == a0: + el = eval_ast(ast[2:], env) + f = eval(ast[1]) + return f(*el) + elif "try*" == a0: + a1, a2 = ast[1], ast[2] + if a2[0] == "catch*": + try: + return EVAL(a1, env); + except Exception as exc: + exc = exc.message + catch_env = Env(env, [a2[1]], [exc]) + return EVAL(a2[2], catch_env) + else: + return EVAL(a1, env); + elif "do" == a0: + eval_ast(ast[1:-1], env) + ast = ast[-1] + # Continue loop (TCO) + elif "if" == a0: + a1, a2 = ast[1], ast[2] + cond = EVAL(a1, env) + if cond is None or cond is False: + if len(ast) > 3: ast = ast[3] + else: ast = None + else: + ast = a2 + # Continue loop (TCO) + elif "fn*" == a0: + a1, a2 = ast[1], ast[2] + return new_function(EVAL, a2, env, a1) + else: + el = eval_ast(ast, env) + f = el[0] + if hasattr(f, '__meta__') and f.__meta__.has_key("exp"): + m = f.__meta__ + ast = m['exp'] + env = Env(m['env'], m['params'], el[1:]) + else: + return f(*el[1:]) + +# print +def PRINT(exp): + return pr_str(exp) + +# repl +repl_env = Env() +def REP(str): + return PRINT(EVAL(READ(str), repl_env)) +def _ref(k,v): repl_env.set(k, v) + +# Import types functions +for name, val in types_ns.items(): _ref(name, val) + +_ref('readline', lambda prompt: mal_readline.readline(prompt)) +_ref('read-string', read_str) +_ref('eval', lambda ast: EVAL(ast, repl_env)) +_ref('slurp', lambda file: open(file).read()) +_ref('slurp-do', lambda file: "(do" + open(file).read() + ")") + +# Defined using the language itself +REP("(def! not (fn* (a) (if a false true)))") +REP("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))") +REP("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))") +REP("(def! load-file (fn* (f) (eval (read-string (slurp-do f)))))") + +if len(sys.argv) >= 2: + REP('(load-file "' + sys.argv[1] + '")') +else: + while True: + try: + line = mal_readline.readline("user> ") + if line == None: break + if line == "": continue + print(REP(line)) + except Blank: continue + except Exception as e: + print "".join(traceback.format_exception(*sys.exc_info())) diff --git a/runtest.py b/runtest.py new file mode 100755 index 0000000..736768a --- /dev/null +++ b/runtest.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python + +import os, sys, re +import argparse + +# http://pexpect.sourceforge.net/pexpect.html +from pexpect import spawn, EOF, TIMEOUT + +# TODO: do we need to support '\n' too +sep = "\r\n" +rundir = None + +parser = argparse.ArgumentParser( + description="Run a test file against a Mal implementation") +parser.add_argument('--rundir', + help="change to the directory before running tests") +parser.add_argument('--start-timeout', default=10, type=int, + help="default timeout for initial prompt") +parser.add_argument('--test-timeout', default=20, type=int, + help="default timeout for each individual test action") + +parser.add_argument('test_file', type=argparse.FileType('r'), + help="a test file formatted as with mal test data") +parser.add_argument('mal_cmd', nargs="*", + help="Mal implementation command line. Use '--' to " + "specify a Mal command line with dashed options.") + +args = parser.parse_args(sys.argv[1:]) +test_data = args.test_file.read().split('\n') + +if args.rundir: os.chdir(args.rundir) + +p = spawn(args.mal_cmd[0], args.mal_cmd[1:]) + +test_idx = 0 +def read_test(data): + global test_idx + form, output, ret = None, "", None + while data: + test_idx += 1 + line = data.pop(0) + if re.match(r"^\s*$", line): # blank line + continue + elif line[0:3] == ";;;": # ignore comment + continue + elif line[0:2] == ";;": # output comment + print line[3:] + continue + elif line[0:2] == ";": # unexpected comment + print "Test data error at line %d:\n%s" % (test_idx, line) + return None, None, None, test_idx + form = line # the line is a form to send + + # Now find the output and return value + while data: + line = data[0] + if line[0:3] == ";=>": + ret = line[3:].replace('\\r', '\r').replace('\\n', '\n') + test_idx += 1 + data.pop(0) + break + elif line[0:2] == "; ": + output = output + line[2:] + sep + test_idx += 1 + data.pop(0) + else: + ret = "*" + break + if ret: break + + return form, output, ret, test_idx + + +# Wait for the initial prompt +idx = p.expect(['user> ', 'mal-user> ', EOF, TIMEOUT], + timeout=args.start_timeout) +if idx not in [0,1]: + print "Never got 'user> ' prompt" + print " Got : %s" % repr(p.before) + sys.exit(1) + +fail_cnt = 0 + +while test_data: + form, out, ret, line_num = read_test(test_data) + if form == None: + break + sys.stdout.write("TEST: %s -> [%s,%s]" % (form, repr(out), repr(ret))) + sys.stdout.flush() + expected = "%s%s%s%s" % (form, sep, out, ret) + + p.sendline(form) + try: + idx = p.expect(['\r\nuser> ', '\nuser> ', + '\r\nmal-user> ', '\nmal-user> '], + timeout=args.test_timeout) + #print "%s,%s,%s" % (idx, repr(p.before), repr(p.after)) + if ret == "*" or p.before == expected: + print " -> SUCCESS" + else: + print " -> FAIL (line %d):" % line_num + print " Expected : %s" % repr(expected) + print " Got : %s" % repr(p.before) + fail_cnt += 1 + except EOF: + print "Got EOF" + sys.exit(1) + except TIMEOUT: + print "Got TIMEOUT, received: %s" % repr(p.before) + sys.exit(1) + +if fail_cnt > 0: + print "FAILURES: %d" % fail_cnt + sys.exit(2) +sys.exit(0) diff --git a/tests/inc.mal b/tests/inc.mal new file mode 100644 index 0000000..39ebc55 --- /dev/null +++ b/tests/inc.mal @@ -0,0 +1,4 @@ +(def! inc1 (fn* (a) (+ 1 a))) +(def! inc2 (fn* (a) (+ 2 a))) +(def! inc3 (fn* (a) + (+ 3 a))) diff --git a/tests/incB.mal b/tests/incB.mal new file mode 100644 index 0000000..ed28734 --- /dev/null +++ b/tests/incB.mal @@ -0,0 +1,14 @@ +;; A comment in a file +(def! inc4 (fn* (a) (+ 4 a))) +(def! inc5 (fn* (a) ;; a comment after code + (+ 5 a))) + +;; Test map split across lines +(def! mymap {"a" + 1}) + +;; Test commas as whitespace +(def! myvec [1 2, 3]) + +(prn "incB.mal finished") +"incB.mal return string" diff --git a/tests/step1_read_print.mal b/tests/step1_read_print.mal new file mode 100644 index 0000000..f0d7a9a --- /dev/null +++ b/tests/step1_read_print.mal @@ -0,0 +1,112 @@ +;; Testing read of comments + ;; whole line comment (not an exception) +1 ; comment after expression +;=>1 +1; comment after expression +;=>1 + + +;; Testing read of nil/true/false +nil +;=>nil +true +;=>true +false +;=>false + + +;; Testing read of numbers +1 +;=>1 +7 +;=>7 + 7 +;=>7 + + +;; Testing read of symbols ++ +;=>+ +abc +;=>abc + abc +;=>abc +abc5 +;=>abc5 +abc-def +;=>abc-def + + +;; Testing read of strings +"abc" +;=>"abc" + "abc" +;=>"abc" +"abc (with parens)" +;=>"abc (with parens)" +"abc\"def" +;=>"abc\"def" +;;;"abc\ndef" +;;;;=>"abc\ndef" + + +;; Testing read of lists +(+ 1 2) +;=>(+ 1 2) +((3 4)) +;=>((3 4)) +(+ 1 (+ 2 3)) +;=>(+ 1 (+ 2 3)) + ( + 1 (+ 2 3 ) ) +;=>(+ 1 (+ 2 3)) + + +;; Testing read of vectors +[+ 1 2] +;=>[+ 1 2] +[[3 4]] +;=>[[3 4]] +[+ 1 [+ 2 3]] +;=>[+ 1 [+ 2 3]] + [ + 1 [+ 2 3 ] ] +;=>[+ 1 [+ 2 3]] + + +;; Testing read of hash maps +{"abc" 1} +;=>{"abc" 1} +{"a" {"b" 2}} +;=>{"a" {"b" 2}} +{"a" {"b" {"c" 3}}} +;=>{"a" {"b" {"c" 3}}} +{ "a" {"b" { "cde" 3 } }} +;=>{"a" {"b" {"cde" 3}}} + + +;; Testing read of quoting +'1 +;=>(quote 1) +'(1 2 3) +;=>(quote (1 2 3)) +`1 +;=>(quasiquote 1) +`(1 2 3) +;=>(quasiquote (1 2 3)) +~1 +;=>(unquote 1) +~(1 2 3) +;=>(unquote (1 2 3)) +~@(1 2 3) +;=>(splice-unquote (1 2 3)) + + +;; Testing read of ^/metadata +^{"a" 1} [1 2 3] +;=>(with-meta [1 2 3] {"a" 1}) + + +;; Testing read of @/deref +@a +;=>(deref a) + + diff --git a/tests/step2_eval.mal b/tests/step2_eval.mal new file mode 100644 index 0000000..33c7b17 --- /dev/null +++ b/tests/step2_eval.mal @@ -0,0 +1,19 @@ +;; Testing evaluation of arithmetic operations +(+ 1 2) +;=>3 + +(+ 5 (* 2 3)) +;=>11 + +(- (+ 5 (* 2 3)) 3) +;=>8 + +(/ (- (+ 5 (* 2 3)) 3) 4) +;=>2 + +;; Testing evaluation within collection literals +[1 2 (+ 1 2)] +;=>[1 2 3] + +{"a" (+ 7 8)} +;=>{"a" 15} diff --git a/tests/step3_env.mal b/tests/step3_env.mal new file mode 100644 index 0000000..448a446 --- /dev/null +++ b/tests/step3_env.mal @@ -0,0 +1,38 @@ +;; Testing REPL_ENV +(+ 1 2) +;=>3 +(/ (- (+ 5 (* 2 3)) 3) 4) +;=>2 + + +;; Testing def! +(def! x 3) +;=>3 +x +;=>3 +(def! x 4) +;=>4 +x +;=>4 +(def! y (+ 1 7)) +;=>8 +y +;=>8 + + +;; Testing let* +(let* (z 9) z) +;=>9 +(let* (x 9) x) +;=>9 +x +;=>4 +(let* (z (+ 2 3)) (+ 1 z)) +;=>6 +(let* (p (+ 2 3) q (+ 2 p)) (+ p q)) +;=>12 + + +;; Testing vector evaluation +(let* (a 5 b 6) [3 4 a [b 7] 8]) +;=>[3 4 5 [6 7] 8] diff --git a/tests/step4_if_fn_do.mal b/tests/step4_if_fn_do.mal new file mode 100644 index 0000000..a4ce46f --- /dev/null +++ b/tests/step4_if_fn_do.mal @@ -0,0 +1,345 @@ +;; ----------------------------------------------------- + +;; Testing string quoting + +"" +;=>"" + +"abc" +;=>"abc" + +"abc def" +;=>"abc def" + +"\"" +;=>"\"" + + +;; Testing pr-str + +(pr-str) +;=>"" + +(pr-str "") +;=>"\"\"" + +(pr-str "abc") +;=>"\"abc\"" + +(pr-str "abc def" "ghi jkl") +;=>"\"abc def\" \"ghi jkl\"" + +(pr-str "\"") +;=>"\"\\\"\"" + +(pr-str (list 1 2 "abc" "\"") "def") +;=>"(1 2 \"abc\" \"\\\"\") \"def\"" + + +;; Testing str + +(str) +;=>"" + +(str "") +;=>"" + +(str "abc") +;=>"abc" + +(str "\"") +;=>"\"" + +(str 1 "abc" 3) +;=>"1abc3" + +(str "abc def" "ghi jkl") +;=>"abc defghi jkl" + +;;; TODO: get this working properly +;;;(str (list 1 2 "abc" "\"") "def") +;;;;=>"(1 2 \"abc\" \"\\\"\")def" + + +;; Testing prn +(prn) +; +;=>nil + +(prn "") +; "" +;=>nil + +(prn "abc") +; "abc" +;=>nil + +(prn "abc def" "ghi jkl") +; "abc def" "ghi jkl" + +(prn "\"") +; "\"" +;=>nil + +(prn (list 1 2 "abc" "\"") "def") +; (1 2 "abc" "\"") "def" +;=>nil + + +;; Testing println +(println) +; +;=>nil + +(println "") +; +;=>nil + +(println "abc") +; abc +;=>nil + +(println "abc def" "ghi jkl") +; abc def ghi jkl + +(println "\"") +; " +;=>nil + +(println (list 1 2 "abc" "\"") "def") +; (1 2 abc ") def +;=>nil + +;; ----------------------------------------------------- + + +;; Testing list functions +(list) +;=>() +(list? (list)) +;=>true +(empty? (list)) +;=>true +(empty? (list 1)) +;=>false +(list 1 2 3) +;=>(1 2 3) +(count (list 1 2 3)) +;=>3 +(if (> (count (list 1 2 3)) 3) "yes" "no") +;=>"no" +(if (>= (count (list 1 2 3)) 3) "yes" "no") +;=>"yes" + + +;; Testing if form +(if true 7 8) +;=>7 +(if false 7 8) +;=>8 +(if true (+ 1 7) (+ 1 8)) +;=>8 +(if false (+ 1 7) (+ 1 8)) +;=>9 +(if nil 7 8) +;=>8 +(if 0 7 8) +;=>7 +(if "" 7 8) +;=>7 +(if (list) 7 8) +;=>7 +(if (list 1 2 3) 7 8) +;=>7 +(if [] 7 8) +;=>7 + + +;; Testing 1-way if form +(if false (+ 1 7)) +;=>nil +(if nil 8 7) +;=>7 +(if true (+ 1 7)) +;=>8 + + +;; Testing basic conditionals +(= 2 1) +;=>false +(= 1 1) +;=>true +(= 1 2) +;=>false +(= 1 (+ 1 1)) +;=>false +(= 2 (+ 1 1)) +;=>true + +(> 2 1) +;=>true +(> 1 1) +;=>false +(> 1 2) +;=>false + +(>= 2 1) +;=>true +(>= 1 1) +;=>true +(>= 1 2) +;=>false + +(< 2 1) +;=>false +(< 1 1) +;=>false +(< 1 2) +;=>true + +(<= 2 1) +;=>false +(<= 1 1) +;=>true +(<= 1 2) +;=>true + + +;; Testing equality +(= 1 1) +;=>true +(= 0 0) +;=>true +(= 1 0) +;=>false +(= "" "") +;=>true +(= "abc" "") +;=>false +(= "" "abc") +;=>false +(= "abc" "def") +;=>false + +(= (list) (list)) +;=>true +(= (list 1 2) (list 1 2)) +;=>true +(= (list 1) (list)) +;=>false +(= (list) (list 1)) +;=>false +(= 0 (list)) +;=>false +(= (list) 0) +;=>false +(= (list) "") +;=>false +(= "" (list)) +;=>false + +(= [] (list)) +;=>true +(= (list 1 2) [1 2]) +;=>true +(= (list 1) []) +;=>false +(= [] [1]) +;=>false +(= 0 []) +;=>false +(= [] 0) +;=>false +(= [] "") +;=>false +(= "" []) +;=>false + + +;; Testing builtin and user defined functions +(+ 1 2) +;=>3 +( (fn* (a b) (+ b a)) 3 4) +;=>7 +( (fn* () 4) ) +;=>4 + + +;; Testing closures +( ( (fn* (a) (fn* (b) (+ a b))) 5) 7) +;=>12 + +(def! gen-plus5 (fn* () (fn* (b) (+ 5 b)))) +(def! plus5 (gen-plus5)) +(plus5 7) +;=>12 + +(def! gen-plusX (fn* (x) (fn* (b) (+ x b)))) +(def! plus7 (gen-plusX 7)) +(plus7 8) +;=>15 + + +;; Testing variable length arguments + +( (fn* (& more) (count more)) 1 2 3) +;=>3 +( (fn* (& more) (count more)) 1) +;=>1 +( (fn* (& more) (count more)) ) +;=>0 +( (fn* (a & more) (count more)) 1 2 3) +;=>2 +( (fn* (a & more) (count more)) 1) +;=>0 + + +;; Testing language defined not function +(not false) +;=>true +(not true) +;=>false +(not "a") +;=>false +(not 0) +;=>false + + +;; Testing do form +(do (prn "prn output1")) +; "prn output1" +;=>nil +(do (prn "prn output2") 7) +; "prn output2" +;=>7 +(do (prn "prn output1") (prn "prn output2") (+ 1 2)) +; "prn output1" +; "prn output2" +;=>3 + +(do (def! a 6) 7 (+ a 8)) +;=>14 +a +;=>6 + + +;; Testing recursive sumdown function +(def! sumdown (fn* (N) (if (> N 0) (+ N (sumdown (- N 1))) 0))) +(sumdown 1) +;=>1 +(sumdown 2) +;=>3 +(sumdown 6) +;=>21 + + +;; Testing recursive fibonacci function +(def! fib (fn* (N) (if (= N 0) 1 (if (= N 1) 1 (+ (fib (- N 1)) (fib (- N 2))))))) +(fib 1) +;=>1 +(fib 2) +;=>2 +(fib 4) +;=>5 +(fib 10) +;=>89 diff --git a/tests/step6_file.mal b/tests/step6_file.mal new file mode 100644 index 0000000..c6df3eb --- /dev/null +++ b/tests/step6_file.mal @@ -0,0 +1,17 @@ +;; Testing load-file + +(load-file "../tests/inc.mal") +(inc1 7) +;=>8 +(inc2 7) +;=>9 +(inc3 9) +;=>12 + +(load-file "../tests/incB.mal") +; "incB.mal finished" +;=>"incB.mal return string" +(inc4 7) +;=>11 +(inc5 7) +;=>12 diff --git a/tests/step7_quote.mal b/tests/step7_quote.mal new file mode 100644 index 0000000..c41c7e2 --- /dev/null +++ b/tests/step7_quote.mal @@ -0,0 +1,69 @@ +;; Testing regular quote +(quote 7) +;=>7 +'7 +;=>7 +(quote (1 2 3)) +;=>(1 2 3) +'(1 2 3) +;=>(1 2 3) +(quote (1 2 (3 4))) +;=>(1 2 (3 4)) +'(1 2 (3 4)) +;=>(1 2 (3 4)) + + +;; Testing simple quasiquote +(quasiquote 7) +;=>7 +`7 +;=>7 +(quasiquote (1 2 3)) +;=>(1 2 3) +`(1 2 3) +;=>(1 2 3) +(quasiquote (1 2 (3 4))) +;=>(1 2 (3 4)) +`(1 2 (3 4)) +;=>(1 2 (3 4)) + + +;; Testing unquote +`~7 +;=>7 +(def! a 8) +;=>8 +`a +;=>a +`~a +;=>8 +`(1 a 3) +;=>(1 a 3) +`(1 ~a 3) +;=>(1 8 3) +(def! b '(1 "b" "d")) +;=>(1 "b" "d") +`(1 b 3) +;=>(1 b 3) +`(1 ~b 3) +;=>(1 (1 "b" "d") 3) + + +;; Testing splice-unquote +(def! c '(1 "b" "d")) +;=>(1 "b" "d") +`(1 c 3) +;=>(1 c 3) +`(1 ~@c 3) +;=>(1 1 "b" "d" 3) + + +;; Testing symbol equality +(= 'abc 'abc) +;=>true +(= 'abc 'abcd) +;=>false +(= 'abc "abc") +;=>false +(= "abc" 'abc) +;=>false diff --git a/tests/step8_macros.mal b/tests/step8_macros.mal new file mode 100644 index 0000000..351e0ca --- /dev/null +++ b/tests/step8_macros.mal @@ -0,0 +1,94 @@ +;; Testing trivial macros +(defmacro! one (fn* () 1)) +(one) +;=>1 +(defmacro! two (fn* () 2)) +(two) +;=>2 + +;; Testing unless macros +(defmacro! unless (fn* (pred a b) `(if ~pred ~b ~a))) +(unless false 7 8) +;=>7 +(unless true 7 8) +;=>8 +(defmacro! unless2 (fn* (pred a b) `(if (not ~pred) ~a ~b))) +(unless2 false 7 8) +;=>7 +(unless2 true 7 8) +;=>8 + +;; Testing macroexpand +(macroexpand (unless2 2 3 4)) +;=>(if (not 2) 3 4) + +;; +;; Loading core.mal +(load-file "../core.mal") + +;; Testing and macro +(and) +;=>true +(and 1) +;=>1 +(and 1 2) +;=>2 +(and 1 2 3) +;=>3 +(and 1 2 3 4) +;=>4 +(and 1 2 3 4 false) +;=>false +(and 1 2 3 4 false 5) +;=>false + +;; Testing or macro +(or) +;=>nil +(or 1) +;=>1 +(or 1 2 3 4) +;=>1 +(or false 2) +;=>2 +(or false nil 3) +;=>3 +(or false nil false false nil 4) +;=>4 +(or false nil 3 false nil 4) +;=>3 + +;; Testing -> macro + +(-> 7) +;=>7 +(-> (list 7 8 9) first) +;=>7 +(-> (list 7 8 9) (first)) +;=>7 +(-> (list 7 8 9) first (+ 7)) +;=>14 +(-> (list 7 8 9) rest (rest) first (+ 7)) +;=>16 + +;; Testing cond macro + +(cond) +;=>nil +(cond true 7) +;=>7 +(cond true 7 true 8) +;=>7 +(cond false 7 true 8) +;=>8 +(cond false 7 false 8 "else" 9) +;=>9 +(cond false 7 (= 2 2) 8 "else" 9) +;=>8 +(cond false 7 false 8 false 9) +;=>nil + +;Testing all EVAL of non-default locations +(let* [x (or nil "yes")] x) +;=>"yes" + diff --git a/tests/stepA_more.mal b/tests/stepA_more.mal new file mode 100644 index 0000000..bae226d --- /dev/null +++ b/tests/stepA_more.mal @@ -0,0 +1,294 @@ +;; +;; Testing try*/catch* + +(try* (abc 1 2) (catch* exc (prn exc)))) +; "'abc' not found" +;=>nil + +;;;TODO: fix so long lines don't trigger ANSI escape codes +;;;(try* (throw {"data" "foo"}) (catch* exc (do (prn "exc is:" exc) 7))) +;;;; "exc is:" {"data" "foo"} +;;;;=>7 + +(try* (throw "my exception") (catch* exc (do (prn "exc:" exc) 7))) +; "exc:" "my exception" +;=>7 + + +;; +;; Testing builtin functions + +(symbol? 'abc) +;=>true +(symbol? "abc") +;=>false + +(nil? nil) +;=>true +(nil? true) +;=>false + +(true? true) +;=>true +(true? false) +;=>false +(true? true?) +;=>false + +(false? false) +;=>true +(false? true) +;=>false + +(sequential? (list 1 2 3)) +;=>true +(sequential? [15]) +;=>true +(sequential? sequential?) +;=>false +(sequential? nil) +;=>false +(sequential? "abc") +;=>false + + +;; Testing apply function +(apply + (list 2 3)) +;=>5 +(apply + 4 (list 5)) +;=>9 +(apply prn (list 1 2 "3" (list))) +; 1 2 "3" () +;=>nil + + +;; Testing map function +(def! nums (list 1 2 3)) +(def! double (fn* (a) (* 2 a))) +(double 3) +;=>6 +(map double nums) +;=>(2 4 6) + + +;; Testing concat function +(concat) +;=>() +(concat (list 1 2)) +;=>(1 2) +(concat (list 1 2) (list 3 4)) +;=>(1 2 3 4) +(concat (list 1 2) (list 3 4) (list 5 6)) +;=>(1 2 3 4 5 6) +(concat [1 2] (list 3 4) [5 6]) +;=>(1 2 3 4 5 6) +(concat (concat)) +;=>() + +;; Testing cons function +(cons 1 (list)) +;=>(1) +(cons 1 (list 2)) +;=>(1 2) +(cons 1 (list 2 3)) +;=>(1 2 3) +(cons (list 1) (list 2 3)) +;=>((1) 2 3) +(cons [1] [2 3]) +;=>([1] 2 3) +(cons 1 [2 3]) +;=>(1 2 3) + +;; Testing conj function +(conj (list) 1) +;=>(1) +(conj (list 1) 2) +;=>(1 2) +(conj (list 2 3) 4) +;=>(2 3 4) +(conj (list 2 3) 4 5 6) +;=>(2 3 4 5 6) +(conj (list 1) (list 2 3)) +;=>(1 (2 3)) +(conj [1 2] [3 4] ) +;=>(1 2 [3 4]) + +;; Testing first/rest functions +(first '()) +;=>nil +(first '(6)) +;=>6 +(first '(7 8 9)) +;=>7 +(first []) +;=>nil +(first [10]) +;=>10 +(first [10 11 12]) +;=>10 + +(rest '()) +;=>() +(rest '(6)) +;=>() +(rest '(7 8 9)) +;=>(8 9) +(rest []) +;=>() +(rest [10]) +;=>() +(rest [10 11 12]) +;=>(11 12) + + + +;; +;; Testing hash-maps +(hash-map "a" 1) +;=>{"a" 1} + +{"a" 1} +;=>{"a" 1} + +(assoc {} "a" 1) +;=>{"a" 1} + +(def! hm1 (hash-map)) +;=>{} + +(map? hm1) +;=>true +(map? 1) +;=>false +(map? []) +;=>false + +(get hm1 "a") +;=>nil + +(contains? hm1 "a") +;=>false + +(def! hm2 (assoc hm1 "a" 1)) +;=>{"a" 1} + +(get hm1 "a") +;=>nil + +(contains? hm1 "a") +;=>false + +(get hm2 "a") +;=>1 + +(contains? hm2 "a") +;=>true + +(keys hm2) +;=>("a") + +(vals hm2) +;=>(1) + +(def! hm3 (assoc hm2 "b" 2)) +(count (keys hm3)) +;=>2 +(count (vals hm3)) +;=>2 + +(dissoc hm3 "a") +;=>{"b" 2} + +(dissoc hm3 "a" "b") +;=>{} + +(count (keys hm3)) +;=>2 + + +;; +;; Testing metadata +(meta [1 2 3]) +;=>nil + +(with-meta [1 2 3] {"a" 1}) +;=>[1 2 3] + +(meta (with-meta [1 2 3] {"a" 1})) +;=>{"a" 1} + +(def! lst (with-meta [4 5 6] {"b" 2})) +;=>[4 5 6] + +(def! f-wm (with-meta (fn* [a] (+ 1 a)) {"abc" 1})) +(meta f-wm) +;=>{"abc" 1} + + +;; +;; Testing atoms + +(def! inc3 (fn* (a) (+ 3 a))) + +(def! a (atom 2)) +;=>(atom 2) + +;;;(type a) +;;;;=>"atom" + +(deref a) +;=>2 + +@a +;=>2 + +(reset! a 3) +;=>3 + +@a +;=>3 + +(swap! a inc3) +;=>6 + +@a +;=>6 + +(swap! a (fn* (a) a)) +;=>6 + +(swap! a (fn* (a) (* 2 a))) +;=>12 + + +;; +;; Testing read-str and eval +(read-string "[1 2 (3 4) nil]") +;=>[1 2 (3 4) nil] + +(eval (read-string "(+ 4 5)")) +;=>9 + +;; +;; Testing readline +(readline "mal-user> ") +"hello" +;=>"\"hello\"" + +;; +;; Testing macros cond and or +(cond 1 2 3 4) +;=>2 +(cond false 2 3 4) +;=>4 +(cond false 2 false 4) +;=>nil + +(or) +;=>nil +(or 1) +;=>1 +(or 1 2) +;=>1 +(or nil 2) +;=>2 |
