~tim/malcc

32559fa40c19c6b3889fa67a4185966ceffda607 — Tim Morgan 10 months ago 40a4321 master
Moving to GitHub
85 files changed, 2 insertions(+), 18267 deletions(-)

D .github/workflows/build.yml
D .gitignore
D .gitmodules
D Dockerfile
D LICENSE
D Makefile
M README.md
D core.c
D core.h
D env.c
D env.h
D examples/fib.mal
D examples/filter.mal
D examples/hello.mal
D hashmap.c
D hashmap.h
D mal/LICENSE
D mal/Makefile
D mal/README.md
D mal/core.mal
D mal/mal/Dockerfile
D mal/mal/Makefile
D mal/mal/core.mal
D mal/mal/env.mal
D mal/mal/run
D mal/mal/step0_repl.mal
D mal/mal/step1_read_print.mal
D mal/mal/step2_eval.mal
D mal/mal/step3_env.mal
D mal/mal/step4_if_fn_do.mal
D mal/mal/step6_file.mal
D mal/mal/step7_quote.mal
D mal/mal/step8_macros.mal
D mal/mal/step9_try.mal
D mal/mal/stepA_mal.mal
D mal/perf.mal
D mal/run_argv_test.sh
D mal/runtest.py
D mal/tests/docker-build.sh
D mal/tests/docker-run.sh
D mal/tests/docker/Dockerfile
D mal/tests/inc.mal
D mal/tests/incA.mal
D mal/tests/incB.mal
D mal/tests/incC.mal
D mal/tests/perf1.mal
D mal/tests/perf2.mal
D mal/tests/perf3.mal
D mal/tests/print_argv.mal
D mal/tests/step0_repl.mal
D mal/tests/step1_read_print.mal
D mal/tests/step2_eval.mal
D mal/tests/step3_env.mal
D mal/tests/step4_if_fn_do.mal
D mal/tests/step5_tco.mal
D mal/tests/step6_file.mal
D mal/tests/step7_quote.mal
D mal/tests/step8_macros.mal
D mal/tests/step9_try.mal
D mal/tests/stepA_mal.mal
D mal/tests/test.txt
D malcc.c
D printer.c
D printer.h
D reader.c
D reader.h
D self_hosted_run
D step0_repl.c
D step1_read_print.c
D step2_eval.c
D step3_env.c
D step4_if_fn_do.c
D step5_tco.c
D step6_file.c
D step7_quote.c
D step8_macros.c
D step9_try.c
D stepA_mal.c
D tests/regex.mal
D tests/utf-8.mal
D tinycc
D types.c
D types.h
D util.c
D util.h
D .github/workflows/build.yml => .github/workflows/build.yml +0 -19
@@ 1,19 0,0 @@
name: Build

on: push

jobs:
  build:
    runs-on: ubuntu-18.04
    steps:
    - uses: actions/checkout@v1
    - name: fetch submodules
      run: git submodule update --init
    - name: install dependencies
      run: sudo apt update && sudo apt install -y -q build-essential libedit-dev libgc-dev libpcre3-dev python
    - name: set environment
      run: export LC_ALL="C.UTF-8"
    - name: build
      run: make all
    - name: test
      run: make test

D .gitignore => .gitignore +0 -17
@@ 1,17 0,0 @@
step0_repl
step1_read_print
step2_eval
step3_env
step4_if_fn_do
step5_tco
step6_file
step7_quote
step8_macros
step9_try
stepA_mal
malcc
mal-in-mal
mal-in-mal.c
mal-in-mal.dSYM
*.o
history.txt

D .gitmodules => .gitmodules +0 -3
@@ 1,3 0,0 @@
[submodule "tinycc"]
	path = tinycc
	url = https://repo.or.cz/tinycc.git

D Dockerfile => Dockerfile +0 -25
@@ 1,25 0,0 @@
FROM ubuntu:18.04

RUN apt-get update
RUN apt-get install -y -q \
  build-essential \
  gdb \
  libedit-dev \
  libgc-dev \
  libpcre3-dev \
  python \
  valgrind \
  wget

RUN cd /tmp && \
    wget http://eradman.com/entrproject/code/entr-4.1.tar.gz && \
    tar xzf entr-4.1.tar.gz && \
    cd eradman-entr-* && \
    ./configure && \
    make install

ENV LC_ALL C.UTF-8

WORKDIR /malcc

CMD ["bash"]

D LICENSE => LICENSE +0 -21
@@ 1,21 0,0 @@
MIT License

Copyright (c) 2019 Tim Morgan

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

D Makefile => Makefile +0 -145
@@ 1,145 0,0 @@
OS:=$(shell uname)
CC=gcc
CFLAGS=-Itinycc -Wall -Wextra -Werror -g
LDLIBS=-ledit -ltermcap -lgc -lpcre -ldl

ALL_STEPS=step0_repl step1_read_print step2_eval step3_env step4_if_fn_do step5_tco step6_file step7_quote step8_macros step9_try stepA_mal malcc

.PHONY: all clean test test-current cloc docker-build

all: $(ALL_STEPS)

step0_repl: step0_repl.o
step1_read_print: step1_read_print.o hashmap.o printer.o reader.o types.o util.o
step2_eval: step2_eval.o hashmap.o printer.o reader.o types.o util.o tinycc/libtcc.a
step3_env: step3_env.o env.o hashmap.o printer.o reader.o types.o util.o tinycc/libtcc.a
step4_if_fn_do: step4_if_fn_do.o core.o env.o hashmap.o printer.o reader.o types.o util.o tinycc/libtcc.a
step5_tco: step5_tco.o core.o env.o hashmap.o printer.o reader.o types.o util.o tinycc/libtcc.a
step6_file: step6_file.o core.o env.o hashmap.o printer.o reader.o types.o util.o tinycc/libtcc.a
step7_quote: step7_quote.o core.o env.o hashmap.o printer.o reader.o types.o util.o tinycc/libtcc.a
step8_macros: step8_macros.o core.o env.o hashmap.o printer.o reader.o types.o util.o tinycc/libtcc.a
step9_try: step9_try.o core.o env.o hashmap.o printer.o reader.o types.o util.o tinycc/libtcc.a
stepA_mal: stepA_mal.o core.o env.o hashmap.o printer.o reader.o types.o util.o tinycc/libtcc.a
malcc: malcc.o core.o env.o hashmap.o printer.o reader.o types.o util.o tinycc/libtcc.a

tinycc/libtcc.a:
	cd tinycc && ./configure && make

clean:
	rm -f $(ALL_STEPS) *.o
	cd tinycc && make clean

mal-in-mal: all
	cd mal/mal && ../../malcc --compile stepA_mal.mal ../../mal-in-mal

test: test0 test1 test2 test3 test4 test5 test6 test7 test8 test9 testA test-malcc test-self-hosted test-supplemental test-mal-in-mal

RUN_TEST_CMD=mal/runtest.py --rundir mal/tests --hard --deferrable --optional --start-timeout 1 --test-timeout 1

test0: all
	$(RUN_TEST_CMD) step0_repl.mal ../../step0_repl

test1: all
	$(RUN_TEST_CMD) step1_read_print.mal ../../step1_read_print

test2: all
	$(RUN_TEST_CMD) step2_eval.mal ../../step2_eval

test3: all
	$(RUN_TEST_CMD) step3_env.mal ../../step3_env

test4: all
	$(RUN_TEST_CMD) step4_if_fn_do.mal ../../step4_if_fn_do

test5: all
	$(RUN_TEST_CMD) step5_tco.mal ../../step5_tco

test6: all
	$(RUN_TEST_CMD) step6_file.mal ../../step6_file
	mal/run_argv_test.sh ./step6_file

test7: all
	$(RUN_TEST_CMD) step7_quote.mal ../../step7_quote

test8: all
	$(RUN_TEST_CMD) step8_macros.mal ../../step8_macros

test9: all
	$(RUN_TEST_CMD) step9_try.mal ../../step9_try

testA: all
	$(RUN_TEST_CMD) step2_eval.mal ../../stepA_mal
	$(RUN_TEST_CMD) step3_env.mal ../../stepA_mal
	$(RUN_TEST_CMD) step4_if_fn_do.mal ../../stepA_mal
	$(RUN_TEST_CMD) step5_tco.mal ../../stepA_mal
	$(RUN_TEST_CMD) step6_file.mal ../../stepA_mal
	$(RUN_TEST_CMD) step7_quote.mal ../../stepA_mal
	$(RUN_TEST_CMD) step8_macros.mal ../../stepA_mal
	$(RUN_TEST_CMD) step9_try.mal ../../stepA_mal
	$(RUN_TEST_CMD) stepA_mal.mal ../../stepA_mal

test-malcc: all
	$(RUN_TEST_CMD) step2_eval.mal ../../malcc
	$(RUN_TEST_CMD) step3_env.mal ../../malcc
	$(RUN_TEST_CMD) step4_if_fn_do.mal ../../malcc
	$(RUN_TEST_CMD) step5_tco.mal ../../malcc
	$(RUN_TEST_CMD) step6_file.mal ../../malcc
	$(RUN_TEST_CMD) step7_quote.mal ../../malcc
	$(RUN_TEST_CMD) step8_macros.mal ../../malcc
	$(RUN_TEST_CMD) step9_try.mal ../../malcc
	$(RUN_TEST_CMD) stepA_mal.mal ../../malcc

test-self-hosted: all
	$(RUN_TEST_CMD) --test-timeout 30 step2_eval.mal ../../self_hosted_run
	$(RUN_TEST_CMD) --test-timeout 30 step3_env.mal ../../self_hosted_run
	$(RUN_TEST_CMD) --test-timeout 30 step4_if_fn_do.mal ../../self_hosted_run
	$(RUN_TEST_CMD) --test-timeout 30 step5_tco.mal ../../self_hosted_run
	$(RUN_TEST_CMD) --test-timeout 30 step6_file.mal ../../self_hosted_run
	$(RUN_TEST_CMD) --test-timeout 30 step7_quote.mal ../../self_hosted_run
	$(RUN_TEST_CMD) --test-timeout 30 step8_macros.mal ../../self_hosted_run
	$(RUN_TEST_CMD) --test-timeout 30 step9_try.mal ../../self_hosted_run
	$(RUN_TEST_CMD) --test-timeout 30 stepA_mal.mal ../../self_hosted_run

test-supplemental: all
	$(RUN_TEST_CMD) --test-timeout 30 ../../tests/utf-8.mal ../../malcc
	$(RUN_TEST_CMD) --test-timeout 30 ../../tests/regex.mal ../../malcc

test-mal-in-mal: mal-in-mal
	$(RUN_TEST_CMD) --test-timeout 30 step2_eval.mal ../../mal-in-mal
	$(RUN_TEST_CMD) --test-timeout 30 step3_env.mal ../../mal-in-mal
	$(RUN_TEST_CMD) --test-timeout 30 step4_if_fn_do.mal ../../mal-in-mal
	$(RUN_TEST_CMD) --test-timeout 30 step5_tco.mal ../../mal-in-mal
	$(RUN_TEST_CMD) --test-timeout 30 step6_file.mal ../../mal-in-mal
	$(RUN_TEST_CMD) --test-timeout 30 step7_quote.mal ../../mal-in-mal
	$(RUN_TEST_CMD) --test-timeout 30 step8_macros.mal ../../mal-in-mal
	$(RUN_TEST_CMD) --test-timeout 30 step9_try.mal ../../mal-in-mal
	$(RUN_TEST_CMD) --test-timeout 30 stepA_mal.mal ../../mal-in-mal

perf: all
	cd mal/tests && ../../malcc perf1.mal && ../../malcc perf2.mal && ../../malcc perf3.mal

cloc:
	cloc --exclude-dir='tinycc,mal' --not-match-f='hashmap.*|step.*' .

docker-build:
	docker build . -t malcc

RUN_DOCKER_CMD=docker run --security-opt seccomp=unconfined -t -i --rm -v $(PWD):/malcc malcc

docker-bash: docker-build
	$(RUN_DOCKER_CMD) bash

docker-test: docker-build
	$(RUN_DOCKER_CMD) make test

docker-test-supplemental: docker-build
	$(RUN_DOCKER_CMD) make test-supplemental

docker-watch: docker-build
	$(RUN_DOCKER_CMD) bash -c "ls *.c *.h Makefile | entr -c -s 'make test'"

update-mal-directory:
	rm -rf /tmp/mal mal
	mkdir mal
	git clone https://github.com/kanaka/mal.git /tmp/mal
	cp -r /tmp/mal/LICENSE /tmp/mal/Makefile /tmp/mal/README.md /tmp/mal/core.mal /tmp/mal/mal /tmp/mal/perf.mal /tmp/mal/run_argv_test.sh /tmp/mal/runtest.py /tmp/mal/tests mal/

M README.md => README.md +2 -141
@@ 1,142 1,3 @@
# malcc
# Moved

Mal (Make A Lisp) Compiler in C

![](https://github.com/seven1m/malcc/workflows/Build/badge.svg)

## Overview

[Mal](https://github.com/kanaka/mal) is Clojure inspired Lisp interpreter
created by Joel Martin.

**malcc** is an incremental compiler implementation for the Mal language.
It uses the [Tiny C Compiler](https://bellard.org/tcc/) as the compiler backend
and has full support for the Mal language, including macros, tail-call elimination,
and even run-time eval.

malcc can also be used as an ahead-of-time compiler for Mal, producing a single
binary file for distribution (though using it this way sacrifices run-time eval
functionality since the compiler is not shipped in the resulting binary).

## Building and Running

malcc has been tested on Ubuntu 18.04 and macOS 10.14 Mojave.

**Prerequisites on Mac:**

```bash
sudo xcode-select --install
brew install pcre libgc
```

On Macos 10.14.4, there is a problem building TinyCC. This fixes that:

```bash
cd /usr/local/lib
sudo ln -s ../../lib/libSystem.B.dylib libgcc_s.10.4.dylib
```

**Prerequisites on Ubuntu/Debian:**

```bash
apt-get install libpcre3-dev libedit-dev libgc-dev
```

**Building malcc:**

```bash
git submodule update --init
make all
```

**Running the REPL:**

```bash
→ ./malcc
Mal [malcc]
user> (+ 1 2)
3
user> ^D
```

**Running a mal file:**

```bash
→ ./malcc examples/fib.mal
55
12586269025
```

**Ahead-of-Time compiling a mal file:**

```bash
→ ./malcc --compile examples/fib.mal fib
→ ./fib
55
12586269025
```

## Speed

malcc is fast! Running the microbenchmarks on my Macbook Pro yields an
order-of-magnitude speedup for long-running code vs the C++ implementation:

**C++:**

```bash
→ ../cpp/stepA_mal perf1.mal
"Elapsed time: 1 msecs"
→ ../cpp/stepA_mal perf2.mal
"Elapsed time: 2 msecs"
→ ../cpp/stepA_mal perf3.mal
iters over 10 seconds: 12415
```

**malcc:**

```bash
→ ../../stepA_mal perf1.mal
"Elapsed time: 0 msecs"
→ ../../stepA_mal perf2.mal
"Elapsed time: 3 msecs"
→ ../../stepA_mal perf3.mal
iters over 10 seconds: 226216
```

Note: I'm not sure if this is a fair comparison, but I could not coax the C
interpreter implementation of mal to run the perf3 test, so I figured the C++
implementation was the next-best thing.

## Approach

I followed the Mal guide to implement malcc, using the same steps that any
other implementation would follow. Naturally, since malcc is a compiler
rather than a straight interpreter, there are some differences from other
implementations:

1. Additional functions not specified in the Mal guide are employed to
   generate the C code that is passed to the TinyCC compiler. These functions
   all start with `gen_` and should be fairly self-explanatory.
2. `load-file` is implemented as a special form rather than a simple function.
   This is because macros defined in a file must be found and compiled during
   code generation--they cannot be discovered at run-time.
3. I chose to publish malcc in a separate repository and have structured it to
   suit my taste. A copy of the mal implementation of mal and the mal tests
   were copied into the `mal` directory to provide test coverage.

## Contributing

Contributors are welcome! ❤️

File an issue on [GitHub](https://github.com/seven1m/malcc/issues) if you find
a bug or you want to propose a new feature!

## License

This project's sourcecode is copyrighted by Tim Morgan and licensed under the
MIT license, included in the `LICENSE` file in this repository.

The subdirectory `mal` contains a copy of the Mal language repository and is
copyrighted by Joel Martin and released under the Mozilla Public License 2.0
(MPL 2.0). The text of the MPL 2.0 license is included in the `mal/LICENSE`
file.
I have moved this back to GitHub here: https://github.com/seven1m/malcc

D core.c => core.c +0 -808
@@ 1,808 0,0 @@
#include <assert.h>
#include <gc.h>
#include <pcre.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/time.h>

#include "env.h"
#include "core.h"
#include "reader.h"
#include "printer.h"
#include "types.h"
#include "util.h"

struct hashmap* core_ns() {
  struct hashmap *ns = GC_MALLOC(sizeof(struct hashmap));
  hashmap_init(ns, hashmap_hash_string, hashmap_compare_string, 100);
  hashmap_set_key_alloc_funcs(ns, hashmap_alloc_key_string, NULL);
  hashmap_put(ns, "+", core_add);
  hashmap_put(ns, "-", core_sub);
  hashmap_put(ns, "*", core_mul);
  hashmap_put(ns, "/", core_div);
  hashmap_put(ns, "count", core_count);
  hashmap_put(ns, "prn", core_prn);
  hashmap_put(ns, "list", core_list);
  hashmap_put(ns, "list?", core_is_list);
  hashmap_put(ns, "empty?", core_is_empty);
  hashmap_put(ns, "=", core_is_equal);
  hashmap_put(ns, ">", core_is_gt);
  hashmap_put(ns, ">=", core_is_gte);
  hashmap_put(ns, "<", core_is_lt);
  hashmap_put(ns, "<=", core_is_lte);
  hashmap_put(ns, "pr-str", core_pr_str);
  hashmap_put(ns, "str", core_str);
  hashmap_put(ns, "println", core_println);
  hashmap_put(ns, "read-string", core_read_string);
  hashmap_put(ns, "slurp", core_slurp);
  hashmap_put(ns, "atom", core_atom);
  hashmap_put(ns, "atom?", core_is_atom);
  hashmap_put(ns, "deref", core_deref);
  hashmap_put(ns, "reset!", core_reset);
  hashmap_put(ns, "swap!", core_swap);
  hashmap_put(ns, "cons", core_cons);
  hashmap_put(ns, "concat", core_concat);
  hashmap_put(ns, "nth", core_nth);
  hashmap_put(ns, "first", core_first);
  hashmap_put(ns, "rest", core_rest);
  hashmap_put(ns, "throw", core_throw);
  hashmap_put(ns, "apply", core_apply);
  hashmap_put(ns, "map", core_map);
  hashmap_put(ns, "nil?", core_is_nil);
  hashmap_put(ns, "true?", core_is_true);
  hashmap_put(ns, "false?", core_is_false);
  hashmap_put(ns, "symbol?", core_is_symbol);
  hashmap_put(ns, "keyword?", core_is_keyword);
  hashmap_put(ns, "vector?", core_is_vector);
  hashmap_put(ns, "map?", core_is_map);
  hashmap_put(ns, "sequential?", core_is_sequential);
  hashmap_put(ns, "symbol", core_symbol);
  hashmap_put(ns, "keyword", core_keyword);
  hashmap_put(ns, "vector", core_vector);
  hashmap_put(ns, "hash-map", core_hash_map);
  hashmap_put(ns, "assoc", core_assoc);
  hashmap_put(ns, "dissoc", core_dissoc);
  hashmap_put(ns, "get", core_get);
  hashmap_put(ns, "contains?", core_contains);
  hashmap_put(ns, "keys", core_keys);
  hashmap_put(ns, "vals", core_vals);
  hashmap_put(ns, "readline", core_readline);
  hashmap_put(ns, "meta", core_meta);
  hashmap_put(ns, "with-meta", core_with_meta);
  hashmap_put(ns, "seq", core_seq);
  hashmap_put(ns, "conj", core_conj);
  hashmap_put(ns, "time-ms", core_time_ms);
  hashmap_put(ns, "string?", core_is_string);
  hashmap_put(ns, "number?", core_is_number);
  hashmap_put(ns, "fn?", core_is_fn);
  hashmap_put(ns, "macro?", core_is_macro);
  hashmap_put(ns, "regex?", core_is_regex);
  hashmap_put(ns, "regex-match", core_regex_match);
  return ns;
}

MalType* core_add(MalEnv *env, size_t argc, MalType **args) {
  UNUSED(env);
  if (argc == 0) {
    return mal_number(0);
  } else {
    long long result = args[0]->number;
    for (size_t i=1; i<argc; i++) {
      result += args[i]->number;
    }
    return mal_number(result);
  }
}

MalType* core_sub(MalEnv *env, size_t argc, MalType **args) {
  UNUSED(env);
  assert(argc > 0);
  long long result = args[0]->number;
  for (size_t i=1; i<argc; i++) {
    result -= args[i]->number;
  }
  return mal_number(result);
}

MalType* core_mul(MalEnv *env, size_t argc, MalType **args) {
  UNUSED(env);
  if (argc == 0) {
    return mal_number(1);
  } else {
    long long result = args[0]->number;
    for (size_t i=1; i<argc; i++) {
      result *= args[i]->number;
    }
    return mal_number(result);
  }
}

MalType* core_div(MalEnv *env, size_t argc, MalType **args) {
  UNUSED(env);
  assert(argc > 0);
  long long result = args[0]->number;
  for (size_t i=1; i<argc; i++) {
    result /= args[i]->number;
  }
  return mal_number(result);
}

MalType* core_count(MalEnv *env, size_t argc, MalType **args) {
  UNUSED(env);
  mal_assert(argc == 1, "Expected 1 argument to count");
  MalType *arg = args[0];
  switch (arg->type) {
    case MAL_EMPTY_TYPE:
    case MAL_NIL_TYPE:
      return mal_number(0);
    case MAL_CONS_TYPE:
      return mal_number(mal_list_len(arg));
    case MAL_VECTOR_TYPE:
      return mal_number(mal_vector_len(arg));
    default:
      printf("Object type to count not supported\n");
      return mal_nil();
  }
}

MalType* core_prn(MalEnv *env, size_t argc, MalType **args) {
  UNUSED(env);
  MalType *out = mal_string("");
  for (size_t i=0; i<argc; i++) {
    mal_string_append(out, pr_str(args[i], 1));
    if (i < argc-1) mal_string_append_char(out, ' ');
  }
  printf("%s\n", out->str);
  return mal_nil();
}

MalType* core_list(MalEnv *env, size_t argc, MalType **args) {
  UNUSED(env);
  MalType *vec = mal_vector();
  for (size_t i=0; i<argc; i++) {
    mal_vector_push(vec, args[i]);
  }
  return mal_vector_to_list(vec);
}

MalType* core_is_list(MalEnv *env, size_t argc, MalType **args) {
  UNUSED(env);
  mal_assert(argc == 1, "Expected 1 argument to list?");
  MalType *arg = args[0];
  return is_empty(arg) || is_cons(arg) ? mal_true() : mal_false();
}

MalType* core_is_empty(MalEnv *env, size_t argc, MalType **args) {
  UNUSED(env);
  mal_assert(argc == 1, "Expected 1 argument to empty?");
  MalType *arg = args[0];
  if (is_empty(arg)) return mal_true();
  if (is_vector(arg) && arg->vec_len == 0) return mal_true();
  return mal_false();
}

MalType* core_is_equal(MalEnv *env, size_t argc, MalType **args) {
  UNUSED(env);
  mal_assert(argc == 2, "Expected 2 argument to =");
  return is_equal(args[0], args[1]) ? mal_true() : mal_false();
}

MalType* core_is_gt(MalEnv *env, size_t argc, MalType **args) {
  UNUSED(env);
  mal_assert(argc == 2, "Expected 2 argument to >");
  MalType *arg1 = args[0];
  MalType *arg2 = args[1];
  mal_assert(is_number(arg1) && is_number(arg2), "Both arguments to > must be numbers");
  return arg1->number > arg2->number ? mal_true() : mal_false();
}

MalType* core_is_gte(MalEnv *env, size_t argc, MalType **args) {
  UNUSED(env);
  mal_assert(argc == 2, "Expected 2 argument to >=");
  MalType *arg1 = args[0];
  MalType *arg2 = args[1];
  mal_assert(is_number(arg1) && is_number(arg2), "Both arguments to >= must be numbers");
  return arg1->number == arg2->number || arg1->number > arg2->number ? mal_true() : mal_false();
}

MalType* core_is_lt(MalEnv *env, size_t argc, MalType **args) {
  UNUSED(env);
  mal_assert(argc == 2, "Expected 2 argument to <");
  MalType *arg1 = args[0];
  MalType *arg2 = args[1];
  mal_assert(is_number(arg1) && is_number(arg2), "Both arguments to < must be numbers");
  return arg1->number < arg2->number ? mal_true() : mal_false();
}

MalType* core_is_lte(MalEnv *env, size_t argc, MalType **args) {
  UNUSED(env);
  mal_assert(argc == 2, "Expected 2 argument to <=");
  MalType *arg1 = args[0];
  MalType *arg2 = args[1];
  mal_assert(is_number(arg1) && is_number(arg2), "Both arguments to <= must be numbers");
  return arg1->number == arg2->number || arg1->number < arg2->number ? mal_true() : mal_false();
}

MalType* core_pr_str(MalEnv *env, size_t argc, MalType **args) {
  UNUSED(env);
  MalType *out = mal_string("");
  for (size_t i=0; i<argc; i++) {
    mal_string_append(out, pr_str(args[i], 1));
    if (i < argc-1) mal_string_append_char(out, ' ');
  }
  return out;
}

MalType* core_str(MalEnv *env, size_t argc, MalType **args) {
  UNUSED(env);
  MalType *out = mal_string("");
  for (size_t i=0; i<argc; i++) {
    mal_string_append(out, pr_str(args[i], 0));
  }
  return out;
}

MalType* core_println(MalEnv *env, size_t argc, MalType **args) {
  UNUSED(env);
  MalType *out = mal_string("");
  for (size_t i=0; i<argc; i++) {
    mal_string_append(out, pr_str(args[i], 0));
    if (i < argc-1) mal_string_append_char(out, ' ');
  }
  printf("%s\n", out->str);
  return mal_nil();
}

MalType* core_read_string(MalEnv *env, size_t argc, MalType **args) {
  UNUSED(env);
  mal_assert(argc == 1, "Expected 1 argument to read-string");
  MalType *code = args[0];
  mal_assert(is_string(code), "read-string expects a string argument");
  return read_str(code->str);
}

MalType* core_slurp(MalEnv *env, size_t argc, MalType **args) {
  UNUSED(env);
  mal_assert(argc == 1, "Expected 1 argument to slurp");
  MalType *filename = args[0];
  mal_assert(is_string(filename), "slurp expects a string argument");
  return read_file(filename->str);
}

MalType* core_atom(MalEnv *env, size_t argc, MalType **args) {
  UNUSED(env);
  mal_assert(argc == 1, "Expected 1 argument to atom");
  return mal_atom(args[0]);
}

MalType* core_is_atom(MalEnv *env, size_t argc, MalType **args) {
  UNUSED(env);
  mal_assert(argc == 1, "Expected 1 argument to atom?");
  return is_atom(args[0]) ? mal_true() : mal_false();
}

MalType* core_deref(MalEnv *env, size_t argc, MalType **args) {
  UNUSED(env);
  mal_assert(argc == 1, "Expected 1 argument to deref");
  MalType *val = args[0];
  mal_assert(is_atom(val), "deref expects an atom argument");
  return val->atom_val;
}

MalType* core_reset(MalEnv *env, size_t argc, MalType **args) {
  UNUSED(env);
  mal_assert(argc == 2, "Expected 2 arguments to reset!");
  MalType *atom = args[0];
  mal_assert(is_atom(atom), "reset! expects an atom argument");
  MalType *inner_val = args[1];
  atom->atom_val = inner_val;
  return inner_val;
}

MalType* core_swap(MalEnv *env, size_t argc, MalType **args) {
  UNUSED(env);
  mal_assert(argc >= 2, "Expected at least 2 arguments to swap!");
  MalType *atom = args[0];
  mal_assert(is_atom(atom), "swap! expects an atom argument");
  MalType *lambda = args[1];
  mal_assert(is_lambda(lambda), "swap! expects a lambda argument");
  MalType **swap_args = GC_MALLOC(argc * sizeof(MalType*));
  swap_args[0] = atom->atom_val;
  for(size_t i=2; i<argc; i++) {
    swap_args[i - 1] = args[i];
  }
  atom->atom_val = trampoline(mal_continuation(lambda->fn, lambda->env, argc - 1, swap_args));
  return atom->atom_val;
}

MalType* core_cons(MalEnv *env, size_t argc, MalType **args) {
  UNUSED(env);
  mal_assert(argc == 2, "Expected 2 arguments to cons");
  MalType *new_item = args[0];
  if (is_vector(args[1])) {
    MalType *vec = args[1];
    MalType *new_vec = mal_vector();
    mal_vector_push(new_vec, new_item);
    for (size_t i=0; i<mal_vector_len(vec); i++) {
      mal_vector_push(new_vec, vec->vec[i]);
    }
    return mal_vector_to_list(new_vec);
  } else {
    return mal_cons(new_item, args[1]);
  }
}

MalType* core_concat(MalEnv *env, size_t argc, MalType **args) {
  UNUSED(env);
  MalType *final = mal_vector(), *item;
  struct list_or_vector_iter *iter;
  for (size_t i=0; i<argc; i++) {
    for (iter = list_or_vector_iter(args[i]); iter; iter = list_or_vector_iter_next(iter)) {
      item = list_or_vector_iter_get_obj(iter);
      mal_vector_push(final, item);
    }
  }
  return mal_vector_to_list(final);
}

MalType* core_nth(MalEnv *env, size_t argc, MalType **args) {
  UNUSED(env);
  mal_assert(argc == 2, "Expected 2 arguments to nth");
  MalType *list_or_vector = args[0];
  mal_assert(is_list_like(list_or_vector), "nth expects a list or a vector argument");
  MalType *mal_index = args[1];
  mal_assert(is_number(mal_index), "nth expects a number as the index argument");
  size_t index = (size_t)mal_index->number;
  if (is_vector(list_or_vector)) {
    size_t size = mal_vector_len(list_or_vector);
    if (index >= size) {
      return mal_error(mal_string("nth index out of range"));
    }
    return mal_vector_ref(list_or_vector, index);
  } else {
    size_t size = mal_list_len(list_or_vector);
    if (index >= size) {
      return mal_error(mal_string("nth index out of range"));
    }
    return mal_list_ref(list_or_vector, index);
  }
}

MalType* core_first(MalEnv *env, size_t argc, MalType **args) {
  UNUSED(env);
  mal_assert(argc == 1, "Expected 1 argument to first");
  MalType *list = args[0];
  mal_assert(is_list_like(list) || is_nil(list), "first expects a list or a vector argument");
  if (is_empty(list) || is_nil(list)) {
    return mal_nil();
  } else if (is_cons(list)) {
    return list->car;
  } else if (list->vec_len > 0) {
    return list->vec[0];
  } else {
    return mal_nil();
  }
}

MalType* core_rest(MalEnv *env, size_t argc, MalType **args) {
  UNUSED(env);
  mal_assert(argc == 1, "Expected 1 argument to rest");
  MalType *list = args[0];
  mal_assert(is_list_like(list) || is_nil(list), "rest expects a list or a vector argument");
  if (is_empty(list) || is_nil(list)) {
    return mal_empty();
  } else if (is_cons(list)) {
    return list->cdr;
  } else if (list->vec_len > 0) {
    return mal_cdr2(list);
  } else {
    return mal_empty();
  }
}

MalType* core_throw(MalEnv *env, size_t argc, MalType **args) {
  UNUSED(env);
  mal_assert(argc == 1, "Expected 1 argument to throw");
  MalType *val = args[0];
  return mal_error(val);
}

MalType* core_apply(MalEnv *env, size_t argc, MalType **args) {
  UNUSED(env);
  mal_assert(argc >= 2, "Expected at least 2 arguments to apply");
  MalType *lambda = args[0];
  mal_assert(is_lambda(lambda), "Expected first argument of apply to be a lambda");
  MalType *args_vec = mal_vector();
  struct list_or_vector_iter *iter;
  MalType *arg;
  for (size_t i=1; i<argc; i++) {
    if (is_list_like(args[i])) {
      for (iter = list_or_vector_iter(args[i]); iter; iter = list_or_vector_iter_next(iter)) {
        arg = list_or_vector_iter_get_obj(iter);
        mal_vector_push(args_vec, arg);
      }
    } else {
      mal_vector_push(args_vec, args[i]);
    }
  }
  return trampoline(mal_continuation(lambda->fn, lambda->env, args_vec->vec_len, args_vec->vec));
}

MalType* core_map(MalEnv *env, size_t argc, MalType **args) {
  UNUSED(env);
  mal_assert(argc == 2, "Expected 2 arguments to map");
  MalType *lambda = args[0];
  mal_assert(is_lambda(lambda), "Expected first argument of map to be a lambda");
  MalType *list = args[1];
  mal_assert(is_list_like(list), "Expected second argument of map to be a list or vector");
  MalType *result_vec = mal_vector();
  struct list_or_vector_iter *iter;
  MalType *val;
  for (iter = list_or_vector_iter(list); iter; iter = list_or_vector_iter_next(iter)) {
    val = list_or_vector_iter_get_obj(iter);
    val = trampoline(mal_continuation_1(lambda->fn, lambda->env, val));
    bubble_if_error(val);
    mal_vector_push(result_vec, val);
  }
  return mal_vector_to_list(result_vec);
}

MalType* core_is_nil(MalEnv *env, size_t argc, MalType **args) {
  UNUSED(env);
  mal_assert(argc == 1, "Expected 1 argument to nil?");
  MalType *val = args[0];
  return is_nil(val) ? mal_true() : mal_false();
}

MalType* core_is_true(MalEnv *env, size_t argc, MalType **args) {
  UNUSED(env);
  mal_assert(argc == 1, "Expected 1 argument to true?");
  MalType *val = args[0];
  return is_true(val) ? mal_true() : mal_false();
}

MalType* core_is_false(MalEnv *env, size_t argc, MalType **args) {
  UNUSED(env);
  mal_assert(argc == 1, "Expected 1 argument to false?");
  MalType *val = args[0];
  return is_false(val) ? mal_true() : mal_false();
}

MalType* core_is_symbol(MalEnv *env, size_t argc, MalType **args) {
  UNUSED(env);
  mal_assert(argc == 1, "Expected 1 argument to symbol?");
  MalType *val = args[0];
  return is_symbol(val) ? mal_true() : mal_false();
}

MalType* core_is_keyword(MalEnv *env, size_t argc, MalType **args) {
  UNUSED(env);
  mal_assert(argc == 1, "Expected 1 argument to keyword?");
  MalType *val = args[0];
  return is_keyword(val) ? mal_true() : mal_false();
}

MalType* core_is_vector(MalEnv *env, size_t argc, MalType **args) {
  UNUSED(env);
  mal_assert(argc == 1, "Expected 1 argument to vector?");
  MalType *val = args[0];
  return is_vector(val) ? mal_true() : mal_false();
}

MalType* core_is_map(MalEnv *env, size_t argc, MalType **args) {
  UNUSED(env);
  mal_assert(argc == 1, "Expected 1 argument to map?");
  MalType *val = args[0];
  return is_hashmap(val) ? mal_true() : mal_false();
}

MalType* core_is_sequential(MalEnv *env, size_t argc, MalType **args) {
  UNUSED(env);
  mal_assert(argc == 1, "Expected 1 argument to sequential?");
  MalType *val = args[0];
  return is_list_like(val) ? mal_true() : mal_false();
}

MalType* core_symbol(MalEnv *env, size_t argc, MalType **args) {
  UNUSED(env);
  mal_assert(argc == 1, "Expected 1 argument to symbol function");
  MalType *val = args[0];
  mal_assert(is_string(val), "symbol function expects a string argument");
  return mal_symbol(val->str);
}

MalType* core_keyword(MalEnv *env, size_t argc, MalType **args) {
  UNUSED(env);
  mal_assert(argc == 1, "Expected 1 argument to keyword function");
  MalType *val = args[0];
  if (is_keyword(val)) {
    return val;
  }
  mal_assert(is_string(val), "keyword function expects a string argument");
  return mal_keyword(val->str);
}

MalType* core_vector(MalEnv *env, size_t argc, MalType **args) {
  UNUSED(env);
  MalType *vec = mal_vector();
  for (size_t i=0; i<argc; i++) {
    mal_vector_push(vec, args[i]);
  }
  return vec;
}

MalType* core_hash_map(MalEnv *env, size_t argc, MalType **args) {
  UNUSED(env);
  mal_assert(argc % 2 == 0, "Expected even number of arguments to hash-map");
  MalType *map = mal_hashmap();
  for (size_t i=0; i<argc; i+=2) {
    mal_hashmap_put(map, args[i], args[i+1]);
  }
  return map;
}

MalType* core_assoc(MalEnv *env, size_t argc, MalType **args) {
  UNUSED(env);
  mal_assert(argc % 2 == 1, "Expected odd number of arguments to assoc");
  MalType *map = args[0];
  mal_assert(is_hashmap(map), "Expected first argument to assoc to be a hash-map");
  MalType *new_map = mal_hashmap();
  struct hashmap_iter *iter;
  MalType *key, *val;
  for (iter = hashmap_iter(&map->hashmap); iter; iter = hashmap_iter_next(&map->hashmap, iter)) {
    key = read_str((char*)hashmap_iter_get_key(iter));
    val = (MalType*)hashmap_iter_get_data(iter);
    mal_hashmap_put(new_map, key, val);
  }
  for (size_t i=1; i<argc; i+=2) {
    mal_hashmap_put(new_map, args[i], args[i+1]);
  }
  return new_map;
}

MalType* core_dissoc(MalEnv *env, size_t argc, MalType **args) {
  UNUSED(env);
  mal_assert(argc >= 2, "Expected at least 2 arguments to disassoc");
  MalType *map = args[0];
  mal_assert(is_hashmap(map), "Expected first argument to disassoc to be a hash-map");
  MalType *new_map = mal_hashmap();
  struct hashmap_iter *iter;
  MalType *key, *val;
  int skip;
  for (iter = hashmap_iter(&map->hashmap); iter; iter = hashmap_iter_next(&map->hashmap, iter)) {
    skip = 0;
    key = read_str((char*)hashmap_iter_get_key(iter));
    for (size_t i=1; i<argc; i++) {
      if (is_equal(args[i], key)) skip = 1;
    }
    if (skip) continue;
    val = (MalType*)hashmap_iter_get_data(iter);
    mal_hashmap_put(new_map, key, val);
  }
  return new_map;
}

MalType* core_get(MalEnv *env, size_t argc, MalType **args) {
  UNUSED(env);
  mal_assert(argc == 2, "Expected 2 arguments to get");
  MalType *map = args[0];
  if (is_nil(map)) {
    return mal_nil();
  }
  mal_assert(is_hashmap(map), "Expected first argument to get function to be a hash-map");
  MalType *key = args[1];
  MalType *val = mal_hashmap_get(map, key);
  if (val) {
    return val;
  } else {
    return mal_nil();
  }
}

MalType* core_contains(MalEnv *env, size_t argc, MalType **args) {
  UNUSED(env);
  mal_assert(argc == 2, "Expected 2 arguments to contains?");
  MalType *map = args[0];
  mal_assert(is_hashmap(map), "Expected first argument to contains function to be a hash-map");
  MalType *key = args[1];
  return mal_hashmap_get(map, key) ? mal_true() : mal_false();
}

MalType* core_keys(MalEnv *env, size_t argc, MalType **args) {
  UNUSED(env);
  mal_assert(argc == 1, "Expected 1 argument to keys");
  MalType *map = args[0];
  mal_assert(is_hashmap(map), "Expected first argument to keys function to be a hash-map");
  MalType *keys_vec = mal_hashmap_keys_to_vector(map);
  return mal_vector_to_list(keys_vec);
}

MalType* core_vals(MalEnv *env, size_t argc, MalType **args) {
  UNUSED(env);
  mal_assert(argc == 1, "Expected 1 argument to vals");
  MalType *map = args[0];
  mal_assert(is_hashmap(map), "Expected first argument to vals function to be a hash-map");
  MalType *vals_vec = mal_vector();
  struct hashmap_iter *iter;
  MalType *val;
  for (iter = hashmap_iter(&map->hashmap); iter; iter = hashmap_iter_next(&map->hashmap, iter)) {
    val = (MalType*)hashmap_iter_get_data(iter);
    mal_vector_push(vals_vec, val);
  }
  return mal_vector_to_list(vals_vec);
}

MalType* core_readline(MalEnv *env, size_t argc, MalType **args) {
  UNUSED(env);
  mal_assert(argc == 1, "Expected 1 argument to readline");
  MalType *prompt = args[0];
  mal_assert(is_string(prompt), "Expected first argument to readline to be a string");
  char buffer[1000];
  printf("%s", prompt->str);
  if (fgets(buffer, 1000, stdin) == NULL) {
    return mal_nil();
  } else {
    size_t len = strlen(buffer);
    if (buffer[len-1] == '\n') {
      buffer[len-1] = 0; // strip the newline
    }
    return mal_string(buffer);
  }
}

MalType* core_meta(MalEnv *env, size_t argc, MalType **args) {
  UNUSED(env);
  mal_assert(argc == 1, "Expected 1 argument to meta");
  MalType *val = args[0];
  if (val->meta) {
    return val->meta;
  } else {
    return mal_nil();
  }
}

MalType* core_with_meta(MalEnv *env, size_t argc, MalType **args) {
  UNUSED(env);
  mal_assert(argc == 2, "Expected 2 arguments to with-meta");
  MalType *val = args[0];
  MalType *new_val = mal_alloc();
  memcpy(new_val, val, sizeof(MalType));
  new_val->meta = args[1];
  return new_val;
}

MalType* core_seq(MalEnv *env, size_t argc, MalType **args) {
  UNUSED(env);
  mal_assert(argc == 1, "Expected 1 arguments to seq");
  MalType *val = args[0];
  switch (val->type) {
    case MAL_CONS_TYPE:
      return val;
    case MAL_STRING_TYPE:
      if (val->str_len == 0) {
        return mal_nil();
      } else {
        return mal_string_to_list(val);
      }
    case MAL_VECTOR_TYPE:
      if (val->vec_len == 0) {
        return mal_nil();
      } else {
        return mal_vector_to_list(val);
      }
    default:
      return mal_nil();
	}
}

MalType* core_conj(MalEnv *env, size_t argc, MalType **args) {
  UNUSED(env);
  mal_assert(argc >= 2, "Expected at least 2 arguments to conj");
  MalType *collection = args[0];
  mal_assert(
    is_empty(collection) || is_cons(collection) || is_vector(collection),
    "Expected first argument to conj to be a list or vector"
  );
  if (is_empty(collection) || is_cons(collection)) {
    MalType *node = collection;
    for (size_t i=1; i<argc; i++) {
      node = mal_cons(args[i], node);
    }
    return node;
  } else {
    MalType *vec = mal_vector();
    for (size_t i=0; i<collection->vec_len; i++) {
      mal_vector_push(vec, collection->vec[i]);
    }
    for (size_t i=1; i<argc; i++) {
      mal_vector_push(vec, args[i]);
    }
    return vec;
  }
}

MalType* core_time_ms(MalEnv *env, size_t argc, MalType **args) {
  UNUSED(env);
  UNUSED(args);
  mal_assert(argc == 0, "Expected 0 arguments to time-ms");
  struct timeval tval;
  gettimeofday(&tval, NULL);
  long long microseconds = (tval.tv_sec * 1000000) + tval.tv_usec;
  return mal_number(microseconds / 1000);
}

MalType* core_is_string(MalEnv *env, size_t argc, MalType **args) {
  UNUSED(env);
  mal_assert(argc == 1, "Expected 1 argument to string?");
  MalType *val = args[0];
  return is_string(val) ? mal_true() : mal_false();
}

MalType* core_is_number(MalEnv *env, size_t argc, MalType **args) {
  UNUSED(env);
  mal_assert(argc == 1, "Expected 1 argument to number?");
  MalType *val = args[0];
  return is_number(val) ? mal_true() : mal_false();
}

MalType* core_is_fn(MalEnv *env, size_t argc, MalType **args) {
  UNUSED(env);
  mal_assert(argc == 1, "Expected 1 argument to fn?");
  MalType *val = args[0];
  return is_lambda(val) && !val->is_macro ? mal_true() : mal_false();
}

MalType* core_is_macro(MalEnv *env, size_t argc, MalType **args) {
  UNUSED(env);
  mal_assert(argc == 1, "Expected 1 argument to macro?");
  MalType *val = args[0];
  return is_macro(val) ? mal_true() : mal_false();
}

MalType* core_is_regex(MalEnv *env, size_t argc, MalType **args) {
  UNUSED(env);
  mal_assert(argc == 1, "Expected 1 argument to regex?");
  MalType *val = args[0];
  return is_regex(val) ? mal_true() : mal_false();
}

MalType* core_regex_match(MalEnv *env, size_t argc, MalType **args) {
  UNUSED(env);
  mal_assert(argc == 2, "Expected 2 argument to regex-match");
  MalType *regex = args[0];
  mal_assert(is_regex(regex), "Expected first argument to regex-match to be a regex");
  MalType *str = args[1];
  mal_assert(is_string(str), "Expected second argument to regex-match to be a string");

  const char *pcreErrorStr;
  int pcreErrorOffset;
  pcre *reCompiled = pcre_compile(regex->regex, 0, &pcreErrorStr, &pcreErrorOffset, NULL);
  if(reCompiled == NULL) {
    return mal_error(mal_string("Could not compile regex."));
  }

  pcre_extra *pcreExtra = pcre_study(reCompiled, PCRE_EXTENDED, &pcreErrorStr);
  if(pcreErrorStr != NULL) {
    return mal_error(mal_string("Could not study regex."));
  }

  int subStrVec[30];
  int pcreExecRet = pcre_exec(reCompiled, pcreExtra, str->str, str->str_len, 0, 0, subStrVec, 30);

  pcre_free(reCompiled);

  return pcreExecRet < 0 ? mal_nil() : mal_number(subStrVec[0]);
}

void add_core_ns_to_env(MalEnv *env) {
  struct hashmap *ns = core_ns();
  struct hashmap_iter *core_iter;
  char *name;
  MalType* (*fn)(MalEnv*, size_t, MalType**);
  for (core_iter = hashmap_iter(ns); core_iter; core_iter = hashmap_iter_next(ns, core_iter)) {
    name = (char*)hashmap_iter_get_key(core_iter);
    fn = hashmap_iter_get_data(core_iter);
    env_set(env, name, mal_builtin_function(fn, name, env));
  }
}

D core.h => core.h +0 -74
@@ 1,74 0,0 @@
#ifndef __MAL_CORE__
#define __MAL_CORE__

#include <stddef.h>

#include "env.h"
#include "types.h"

struct hashmap* core_ns();
MalType* core_add(MalEnv *env, size_t argc, MalType **args);
MalType* core_sub(MalEnv *env, size_t argc, MalType **args);
MalType* core_mul(MalEnv *env, size_t argc, MalType **args);
MalType* core_div(MalEnv *env, size_t argc, MalType **args);
MalType* core_count(MalEnv *env, size_t argc, MalType **args);
MalType* core_prn(MalEnv *env, size_t argc, MalType **args);
MalType* core_list(MalEnv *env, size_t argc, MalType **args);
MalType* core_is_list(MalEnv *env, size_t argc, MalType **args);
MalType* core_is_empty(MalEnv *env, size_t argc, MalType **args);
MalType* core_is_equal(MalEnv *env, size_t argc, MalType **args);
MalType* core_is_gt(MalEnv *env, size_t argc, MalType **args);
MalType* core_is_gte(MalEnv *env, size_t argc, MalType **args);
MalType* core_is_lt(MalEnv *env, size_t argc, MalType **args);
MalType* core_is_lte(MalEnv *env, size_t argc, MalType **args);
MalType* core_pr_str(MalEnv *env, size_t argc, MalType **args);
MalType* core_str(MalEnv *env, size_t argc, MalType **args);
MalType* core_println(MalEnv *env, size_t argc, MalType **args);
MalType* core_read_string(MalEnv *env, size_t argc, MalType **args);
MalType* core_slurp(MalEnv *env, size_t argc, MalType **args);
MalType* core_atom(MalEnv *env, size_t argc, MalType **args);
MalType* core_is_atom(MalEnv *env, size_t argc, MalType **args);
MalType* core_deref(MalEnv *env, size_t argc, MalType **args);
MalType* core_reset(MalEnv *env, size_t argc, MalType **args);
MalType* core_swap(MalEnv *env, size_t argc, MalType **args);
MalType* core_cons(MalEnv *env, size_t argc, MalType **args);
MalType* core_concat(MalEnv *env, size_t argc, MalType **args);
MalType* core_nth(MalEnv *env, size_t argc, MalType **args);
MalType* core_first(MalEnv *env, size_t argc, MalType **args);
MalType* core_rest(MalEnv *env, size_t argc, MalType **args);
MalType* core_throw(MalEnv *env, size_t argc, MalType **args);
MalType* core_apply(MalEnv *env, size_t argc, MalType **args);
MalType* core_map(MalEnv *env, size_t argc, MalType **args);
MalType* core_is_nil(MalEnv *env, size_t argc, MalType **args);
MalType* core_is_true(MalEnv *env, size_t argc, MalType **args);
MalType* core_is_false(MalEnv *env, size_t argc, MalType **args);
MalType* core_is_symbol(MalEnv *env, size_t argc, MalType **args);
MalType* core_is_keyword(MalEnv *env, size_t argc, MalType **args);
MalType* core_is_vector(MalEnv *env, size_t argc, MalType **args);
MalType* core_is_map(MalEnv *env, size_t argc, MalType **args);
MalType* core_is_sequential(MalEnv *env, size_t argc, MalType **args);
MalType* core_symbol(MalEnv *env, size_t argc, MalType **args);
MalType* core_keyword(MalEnv *env, size_t argc, MalType **args);
MalType* core_vector(MalEnv *env, size_t argc, MalType **args);
MalType* core_hash_map(MalEnv *env, size_t argc, MalType **args);
MalType* core_assoc(MalEnv *env, size_t argc, MalType **args);
MalType* core_dissoc(MalEnv *env, size_t argc, MalType **args);
MalType* core_get(MalEnv *env, size_t argc, MalType **args);
MalType* core_contains(MalEnv *env, size_t argc, MalType **args);
MalType* core_keys(MalEnv *env, size_t argc, MalType **args);
MalType* core_vals(MalEnv *env, size_t argc, MalType **args);
MalType* core_readline(MalEnv *env, size_t argc, MalType **args);
MalType* core_meta(MalEnv *env, size_t argc, MalType **args);
MalType* core_with_meta(MalEnv *env, size_t argc, MalType **args);
MalType* core_seq(MalEnv *env, size_t argc, MalType **args);
MalType* core_conj(MalEnv *env, size_t argc, MalType **args);
MalType* core_time_ms(MalEnv *env, size_t argc, MalType **args);
MalType* core_is_string(MalEnv *env, size_t argc, MalType **args);
MalType* core_is_number(MalEnv *env, size_t argc, MalType **args);
MalType* core_is_fn(MalEnv *env, size_t argc, MalType **args);
MalType* core_is_macro(MalEnv *env, size_t argc, MalType **args);
MalType* core_is_regex(MalEnv *env, size_t argc, MalType **args);
MalType* core_regex_match(MalEnv *env, size_t argc, MalType **args);
void add_core_ns_to_env(MalEnv *env);

#endif

D env.c => env.c +0 -57
@@ 1,57 0,0 @@
#include <gc.h>
#include <stdio.h>
#include <stdlib.h>

#include "env.h"
#include "hashmap.h"
#include "printer.h"
#include "types.h"
#include "util.h"

MalEnv* build_top_env() {
  MalEnv *top_env = build_env(NULL);
  top_env->num = 0;
  return top_env;
}

MalEnv* build_env(MalEnv *outer) {
  MalEnv *env = GC_MALLOC(sizeof(MalEnv));
  env->outer = outer;
  hashmap_init(&env->data, hashmap_hash_string, hashmap_compare_string, 100);
  hashmap_set_key_alloc_funcs(&env->data, hashmap_alloc_key_string, NULL);
  return env;
}

MalType* env_get(MalEnv *env, char *key) {
  env = env_find(env, key);
  if (!env) {
    return mal_error(mal_sprintf("'%s' not found", key));
  }
  MalType *val = hashmap_get(&env->data, key);
  if (val) {
    return val;
  } else {
    return mal_error(mal_sprintf("'%s' not found", key));
  }
}

MalType* env_set(MalEnv *env, char *key, MalType *val) {
  if (is_blank_line(val)) return val;
  hashmap_remove(&env->data, key);
  hashmap_put(&env->data, key, val);
  return val;
}

void env_delete(MalEnv *env, char *key) {
  hashmap_remove(&env->data, key);
}

MalEnv* env_find(MalEnv *env, char *key) {
  if (hashmap_get(&env->data, key)) {
    return env;
  } else if (env->outer) {
    return env_find(env->outer, key);
  } else {
    return NULL;
  }
}

D env.h => env.h +0 -20
@@ 1,20 0,0 @@
#ifndef __MAL_ENV__
#define __MAL_ENV__

#include <stdarg.h>
#include <stddef.h>

#include "hashmap.h"
#include "types.h"

#define env_get_bubble_error(env, key) ({ MalType *v = env_get((env), (key)); if (is_error(v)) { return v; }; v; })

MalEnv* build_top_env();
MalEnv* build_env(MalEnv *outer);
MalType* env_get(MalEnv *env, char *key);
MalType* env_set(MalEnv *env, char *key, MalType *val);
void env_delete(MalEnv *env, char *key);
MalEnv* env_find(MalEnv *env, char *key);
void inspect_env(MalEnv *env);

#endif

D examples/fib.mal => examples/fib.mal +0 -19
@@ 1,19 0,0 @@
(def! fib
  (fn* (n)
    (if (< n 2)
        n
        (+
          (fib (- n 1))
          (fib (- n 2))))))

(def! fib2
  (fn* (n)
    (let* (f (fn* (n1 n2 c)
                  (if (= c n)
                    n2
                    (f n2 (+ n1 n2) (+ c 1)))))
      (f 0 1 1))))

(println (fib 10))
(println (fib2 50))


D examples/filter.mal => examples/filter.mal +0 -12
@@ 1,12 0,0 @@
(def! filter
      (fn* (l f)
           (let* [filter* (fn* (l1 l2 f)
                               (if (empty? l1)
                                 (seq l2)
                                 (if (apply f (first l1))
                                   (filter* (rest l1) (concat l2 (list (first l1))) f)
                                   (filter* (rest l1) l2 f))))]
             (filter* l [] f))))

(prn (filter '(1 2 3 4 5 6 7 8 0 9) (fn* (i) (< i 5))))
(prn (filter [0 1 2 3 4 5 6 7 8 9] (fn* (i) (< i 5))))

D examples/hello.mal => examples/hello.mal +0 -1
@@ 1,1 0,0 @@
(println "hello world")

D hashmap.c => hashmap.c +0 -724
@@ 1,724 0,0 @@
/*
 * Copyright (c) 2016-2018 David Leeds <davidesleeds@gmail.com>
 *
 * Hashmap is free software; you can redistribute it and/or modify
 * it under the terms of the MIT license. See LICENSE for details.
 *
 * Updated 2018-02-16 by Tim Morgan to use GC_MALLOC and friends.
 */

#include <gc.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>

#include "hashmap.h"

#ifndef HASHMAP_NOASSERT
#include <assert.h>
#define HASHMAP_ASSERT(expr)            assert(expr)
#else
#define HASHMAP_ASSERT(expr)
#endif

/* Table sizes must be powers of 2 */
#define HASHMAP_SIZE_MIN                (1 << 5)    /* 32 */
#define HASHMAP_SIZE_DEFAULT            (1 << 8)    /* 256 */
#define HASHMAP_SIZE_MOD(map, val)      ((val) & ((map)->table_size - 1))

/* Limit for probing is 1/2 of table_size */
#define HASHMAP_PROBE_LEN(map)          ((map)->table_size >> 1)
/* Return the next linear probe index */
#define HASHMAP_PROBE_NEXT(map, index)  HASHMAP_SIZE_MOD(map, (index) + 1)

/* Check if index b is less than or equal to index a */
#define HASHMAP_INDEX_LE(map, a, b)     \
    ((a) == (b) || (((b) - (a)) & ((map)->table_size >> 1)) != 0)


struct hashmap_entry {
    void *key;
    void *data;
#ifdef HASHMAP_METRICS
    size_t num_collisions;
#endif
};


/*
 * Enforce a maximum 0.75 load factor.
 */
static inline size_t hashmap_table_min_size_calc(size_t num_entries)
{
    return num_entries + (num_entries / 3);
}

/*
 * Calculate the optimal table size, given the specified max number
 * of elements.
 */
static size_t hashmap_table_size_calc(size_t num_entries)
{
    size_t table_size;
    size_t min_size;

    table_size = hashmap_table_min_size_calc(num_entries);

    /* Table size is always a power of 2 */
    min_size = HASHMAP_SIZE_MIN;
    while (min_size < table_size) {
        min_size <<= 1;
    }
    return min_size;
}

/*
 * Get a valid hash table index from a key.
 */
static inline size_t hashmap_calc_index(const struct hashmap *map,
    const void *key)
{
    return HASHMAP_SIZE_MOD(map, map->hash(key));
}

/*
 * Return the next populated entry, starting with the specified one.
 * Returns NULL if there are no more valid entries.
 */
static struct hashmap_entry *hashmap_entry_get_populated(
    const struct hashmap *map, struct hashmap_entry *entry)
{
    for (; entry < &map->table[map->table_size]; ++entry) {
        if (entry->key) {
            return entry;
        }
    }
    return NULL;
}

/*
 * Find the hashmap entry with the specified key, or an empty slot.
 * Returns NULL if the entire table has been searched without finding a match.
 */
static struct hashmap_entry *hashmap_entry_find(const struct hashmap *map,
    const void *key, bool find_empty)
{
    size_t i;
    size_t index;
    size_t probe_len = HASHMAP_PROBE_LEN(map);
    struct hashmap_entry *entry;

    index = hashmap_calc_index(map, key);

    /* Linear probing */
    for (i = 0; i < probe_len; ++i) {
        entry = &map->table[index];
        if (!entry->key) {
            if (find_empty) {
#ifdef HASHMAP_METRICS
                entry->num_collisions = i;
#endif
                return entry;
            }
            return NULL;
        }
        if (map->key_compare(key, entry->key) == 0) {
            return entry;
        }
        index = HASHMAP_PROBE_NEXT(map, index);
    }
    return NULL;
}

/*
 * Removes the specified entry and processes the proceeding entries to reduce
 * the load factor and keep the chain continuous.  This is a required
 * step for hash maps using linear probing.
 */
static void hashmap_entry_remove(struct hashmap *map,
    struct hashmap_entry *removed_entry)
{
    size_t i;
#ifdef HASHMAP_METRICS
    size_t removed_i = 0;
#endif
    size_t index;
    size_t entry_index;
    size_t removed_index = (removed_entry - map->table);
    struct hashmap_entry *entry;

    /* Free the key */
    if (map->key_free) {
        map->key_free(removed_entry->key);
    }
    --map->num_entries;

    /* Fill the free slot in the chain */
    index = HASHMAP_PROBE_NEXT(map, removed_index);
    for (i = 1; i < map->table_size; ++i) {
        entry = &map->table[index];
        if (!entry->key) {
            /* Reached end of chain */
            break;
        }
        entry_index = hashmap_calc_index(map, entry->key);
        /* Shift in entries with an index <= to the removed slot */
        if (HASHMAP_INDEX_LE(map, removed_index, entry_index)) {
#ifdef HASHMAP_METRICS
            entry->num_collisions -= (i - removed_i);
            removed_i = i;
#endif
            memcpy(removed_entry, entry, sizeof(*removed_entry));
            removed_index = index;
            removed_entry = entry;
        }
        index = HASHMAP_PROBE_NEXT(map, index);
    }
    /* Clear the last removed entry */
    memset(removed_entry, 0, sizeof(*removed_entry));
}

/*
 * Reallocates the hash table to the new size and rehashes all entries.
 * new_size MUST be a power of 2.
 * Returns 0 on success and -errno on allocation or hash function failure.
 */
static int hashmap_rehash(struct hashmap *map, size_t new_size)
{
    size_t old_size;
    struct hashmap_entry *old_table;
    struct hashmap_entry *new_table;
    struct hashmap_entry *entry;
    struct hashmap_entry *new_entry;

    HASHMAP_ASSERT(new_size >= HASHMAP_SIZE_MIN);
    HASHMAP_ASSERT((new_size & (new_size - 1)) == 0);

    new_table = (struct hashmap_entry *)GC_MALLOC(new_size * sizeof(struct hashmap_entry));
    if (!new_table) {
        return -ENOMEM;
    }
    /* Backup old elements in case of rehash failure */
    old_size = map->table_size;
    old_table = map->table;
    map->table_size = new_size;
    map->table = new_table;
    /* Rehash */
    for (entry = old_table; entry < &old_table[old_size]; ++entry) {
        if (!entry->data) {
            /* Only copy entries with data */
            continue;
        }
        new_entry = hashmap_entry_find(map, entry->key, true);
        if (!new_entry) {
            /*
             * The load factor is too high with the new table
             * size, or a poor hash function was used.
             */
            goto revert;
        }
        /* Shallow copy (intentionally omits num_collisions) */
        new_entry->key = entry->key;
        new_entry->data = entry->data;
    }
    GC_FREE(old_table);
    return 0;
revert:
    map->table_size = old_size;
    map->table = old_table;
    GC_FREE(new_table);
    return -EINVAL;
}

/*
 * Iterate through all entries and free all keys.
 */
static void hashmap_free_keys(struct hashmap *map)
{
    struct hashmap_iter *iter;

    if (!map->key_free) {
        return;
    }
    for (iter = hashmap_iter(map); iter;
        iter = hashmap_iter_next(map, iter)) {
        map->key_free((void *)hashmap_iter_get_key(iter));
    }
}

/*
 * Initialize an empty hashmap.
 *
 * hash_func should return an even distribution of numbers between 0
 * and SIZE_MAX varying on the key provided.  If set to NULL, the default
 * case-sensitive string hash function is used: hashmap_hash_string
 *
 * key_compare_func should return 0 if the keys match, and non-zero otherwise.
 * If set to NULL, the default case-sensitive string comparator function is
 * used: hashmap_compare_string
 *
 * initial_size is optional, and may be set to the max number of entries
 * expected to be put in the hash table.  This is used as a hint to
 * pre-allocate the hash table to the minimum size needed to avoid
 * gratuitous rehashes.  If initial_size is 0, a default size will be used.
 *
 * Returns 0 on success and -errno on failure.
 */
int hashmap_init(struct hashmap *map, size_t (*hash_func)(const void *),
    int (*key_compare_func)(const void *, const void *),
    size_t initial_size)
{
    HASHMAP_ASSERT(map != NULL);

    if (!initial_size) {
        initial_size = HASHMAP_SIZE_DEFAULT;
    } else {
        /* Convert init size to valid table size */
        initial_size = hashmap_table_size_calc(initial_size);
    }
    map->table_size_init = initial_size;
    map->table_size = initial_size;
    map->num_entries = 0;
    map->table = (struct hashmap_entry *)GC_MALLOC(initial_size * sizeof(struct hashmap_entry));
    if (!map->table) {
        return -ENOMEM;
    }
    map->hash = hash_func ?
            hash_func : hashmap_hash_string;
    map->key_compare = key_compare_func ?
            key_compare_func : hashmap_compare_string;
    map->key_alloc = NULL;
    map->key_free = NULL;
    return 0;
}

/*
 * Free the hashmap and all associated memory.
 */
void hashmap_destroy(struct hashmap *map)
{
    if (!map) {
        return;
    }
    hashmap_free_keys(map);
    GC_FREE(map->table);
    memset(map, 0, sizeof(*map));
}

/*
 * Enable internal memory management of hash keys.
 */
void hashmap_set_key_alloc_funcs(struct hashmap *map,
    void *(*key_alloc_func)(const void *),
    void (*key_free_func)(void *))
{
    HASHMAP_ASSERT(map != NULL);

    map->key_alloc = key_alloc_func;
    map->key_free = key_free_func;
}

/*
 * Add an entry to the hashmap.  If an entry with a matching key already
 * exists and has a data pointer associated with it, the existing data
 * pointer is returned, instead of assigning the new value.  Compare
 * the return value with the data passed in to determine if a new entry was
 * created.  Returns NULL if memory allocation failed.
 */
void *hashmap_put(struct hashmap *map, const void *key, void *data)
{
    struct hashmap_entry *entry;

    HASHMAP_ASSERT(map != NULL);
    HASHMAP_ASSERT(key != NULL);

    /* Rehash with 2x capacity if load factor is approaching 0.75 */
    if (map->table_size <= hashmap_table_min_size_calc(map->num_entries)) {
        hashmap_rehash(map, map->table_size << 1);
    }
    entry = hashmap_entry_find(map, key, true);
    if (!entry) {
        /*
         * Cannot find an empty slot.  Either out of memory, or using
         * a poor hash function.  Attempt to rehash once to reduce
         * chain length.
         */
        if (hashmap_rehash(map, map->table_size << 1) < 0) {
            return NULL;
        }
        entry = hashmap_entry_find(map, key, true);
        if (!entry) {
            return NULL;
        }
    }
    if (!entry->key) {
        /* Allocate copy of key to simplify memory management */
        if (map->key_alloc) {
            entry->key = map->key_alloc(key);
            if (!entry->key) {
                return NULL;
            }
        } else {
            entry->key = (void *)key;
        }
        ++map->num_entries;
    } else if (entry->data) {
        /* Do not overwrite existing data */
        return entry->data;
    }
    entry->data = data;
    return data;
}

/*
 * Return the data pointer, or NULL if no entry exists.
 */
void *hashmap_get(const struct hashmap *map, const void *key)
{
    struct hashmap_entry *entry;

    HASHMAP_ASSERT(map != NULL);
    HASHMAP_ASSERT(key != NULL);

    entry = hashmap_entry_find(map, key, false);
    if (!entry) {
        return NULL;
    }
    return entry->data;
}

/*
 * Remove an entry with the specified key from the map.
 * Returns the data pointer, or NULL, if no entry was found.
 */
void *hashmap_remove(struct hashmap *map, const void *key)
{
    struct hashmap_entry *entry;
    void *data;

    HASHMAP_ASSERT(map != NULL);
    HASHMAP_ASSERT(key != NULL);

    entry = hashmap_entry_find(map, key, false);
    if (!entry) {
        return NULL;
    }
    data = entry->data;
    /* Clear the entry and make the chain contiguous */
    hashmap_entry_remove(map, entry);
    return data;
}

/*
 * Remove all entries.
 */
void hashmap_clear(struct hashmap *map)
{
    HASHMAP_ASSERT(map != NULL);

    hashmap_free_keys(map);
    map->num_entries = 0;
    memset(map->table, 0, sizeof(struct hashmap_entry) * map->table_size);
}

/*
 * Remove all entries and reset the hash table to its initial size.
 */
void hashmap_reset(struct hashmap *map)
{
    struct hashmap_entry *new_table;

    HASHMAP_ASSERT(map != NULL);

    hashmap_clear(map);
    if (map->table_size == map->table_size_init) {
        return;
    }
    new_table = (struct hashmap_entry *)GC_REALLOC(map->table,
        sizeof(struct hashmap_entry) * map->table_size_init);
    if (!new_table) {
        return;
    }
    map->table = new_table;
    map->table_size = map->table_size_init;
}

/*
 * Return the number of entries in the hash map.
 */
size_t hashmap_size(const struct hashmap *map)
{
    HASHMAP_ASSERT(map != NULL);

    return map->num_entries;
}

/*
 * Get a new hashmap iterator.  The iterator is an opaque
 * pointer that may be used with hashmap_iter_*() functions.
 * Hashmap iterators are INVALID after a put or remove operation is performed.
 * hashmap_iter_remove() allows safe removal during iteration.
 */
struct hashmap_iter *hashmap_iter(const struct hashmap *map)
{
    HASHMAP_ASSERT(map != NULL);

    if (!map->num_entries) {
        return NULL;
    }
    return (struct hashmap_iter *)hashmap_entry_get_populated(map,
        map->table);
}

/*
 * Return an iterator to the next hashmap entry.  Returns NULL if there are
 * no more entries.
 */
struct hashmap_iter *hashmap_iter_next(const struct hashmap *map,
    const struct hashmap_iter *iter)
{
    struct hashmap_entry *entry = (struct hashmap_entry *)iter;

    HASHMAP_ASSERT(map != NULL);

    if (!iter) {
        return NULL;
    }
    return (struct hashmap_iter *)hashmap_entry_get_populated(map,
        entry + 1);
}

/*
 * Remove the hashmap entry pointed to by this iterator and return an
 * iterator to the next entry.  Returns NULL if there are no more entries.
 */
struct hashmap_iter *hashmap_iter_remove(struct hashmap *map,
    const struct hashmap_iter *iter)
{
    struct hashmap_entry *entry = (struct hashmap_entry *)iter;

    HASHMAP_ASSERT(map != NULL);

    if (!iter) {
        return NULL;
    }
    if (!entry->key) {
        /* Iterator is invalid, so just return the next valid entry */
        return hashmap_iter_next(map, iter);
    }
    hashmap_entry_remove(map, entry);
    return (struct hashmap_iter *)hashmap_entry_get_populated(map, entry);
}

/*
 * Return the key of the entry pointed to by the iterator.
 */
const void *hashmap_iter_get_key(const struct hashmap_iter *iter)
{
    if (!iter) {
        return NULL;
    }
    return (const void *)((struct hashmap_entry *)iter)->key;
}

/*
 * Return the data of the entry pointed to by the iterator.
 */
void *hashmap_iter_get_data(const struct hashmap_iter *iter)
{
    if (!iter) {
        return NULL;
    }
    return ((struct hashmap_entry *)iter)->data;
}

/*
 * Set the data pointer of the entry pointed to by the iterator.
 */
void hashmap_iter_set_data(const struct hashmap_iter *iter, void *data)
{
    if (!iter) {
        return;
    }
    ((struct hashmap_entry *)iter)->data = data;
}

/*
 * Invoke func for each entry in the hashmap.  Unlike the hashmap_iter_*()
 * interface, this function supports calls to hashmap_remove() during iteration.
 * However, it is an error to put or remove an entry other than the current one,
 * and doing so will immediately halt iteration and return an error.
 * Iteration is stopped if func returns non-zero.  Returns func's return
 * value if it is < 0, otherwise, 0.
 */
int hashmap_foreach(const struct hashmap *map,
    int (*func)(const void *, void *, void *), void *arg)
{
    struct hashmap_entry *entry;
    size_t num_entries;
    const void *key;
    int rc;

    HASHMAP_ASSERT(map != NULL);
    HASHMAP_ASSERT(func != NULL);

    entry = map->table;
    for (entry = map->table; entry < &map->table[map->table_size];
        ++entry) {
        if (!entry->key) {
            continue;
        }
        num_entries = map->num_entries;
        key = entry->key;
        rc = func(entry->key, entry->data, arg);
        if (rc < 0) {
            return rc;
        }
        if (rc > 0) {
            return 0;
        }
        /* Run this entry again if func() deleted it */
        if (entry->key != key) {
            --entry;
        } else if (num_entries != map->num_entries) {
            /* Stop immediately if func put/removed another entry */
            return -1;
        }
    }
    return 0;
}

/*
 * Default hash function for string keys.
 * This is an implementation of the well-documented Jenkins one-at-a-time
 * hash function.
 */
size_t hashmap_hash_string(const void *key)
{
    const char *key_str = (const char *)key;
    size_t hash = 0;

    for (; *key_str; ++key_str) {
        hash += *key_str;
        hash += (hash << 10);
        hash ^= (hash >> 6);
    }
    hash += (hash << 3);
    hash ^= (hash >> 11);
    hash += (hash << 15);
    return hash;
}

/*
 * Default key comparator function for string keys.
 */
int hashmap_compare_string(const void *a, const void *b)
{
    return strcmp((const char *)a, (const char *)b);
}

/*
 * Default key allocation function for string keys.  Use free() for the
 * key_free_func.
 */
void *hashmap_alloc_key_string(const void *key)
{
    return (void *)strdup((const char *)key);
}

/*
 * Case insensitive hash function for string keys.
 */
size_t hashmap_hash_string_i(const void *key)
{
    const char *key_str = (const char *)key;
    size_t hash = 0;

    for (; *key_str; ++key_str) {
        hash += tolower(*key_str);
        hash += (hash << 10);
        hash ^= (hash >> 6);
    }
    hash += (hash << 3);
    hash ^= (hash >> 11);
    hash += (hash << 15);
    return hash;
}

/*
 * Case insensitive key comparator function for string keys.
 */
int hashmap_compare_string_i(const void *a, const void *b)
{
    return strcasecmp((const char *)a, (const char *)b);
}


#ifdef HASHMAP_METRICS
/*
 * Return the load factor.
 */
double hashmap_load_factor(const struct hashmap *map)
{
    HASHMAP_ASSERT(map != NULL);

    if (!map->table_size) {
        return 0;
    }
    return (double)map->num_entries / map->table_size;
}

/*
 * Return the average number of collisions per entry.
 */
double hashmap_collisions_mean(const struct hashmap *map)
{
    struct hashmap_entry *entry;
    size_t total_collisions = 0;

    HASHMAP_ASSERT(map != NULL);

    if (!map->num_entries) {
        return 0;
    }
    for (entry = map->table; entry < &map->table[map->table_size];
        ++entry) {
        if (!entry->key) {
            continue;
        }
        total_collisions += entry->num_collisions;
    }
    return (double)total_collisions / map->num_entries;
}

/*
 * Return the variance between entry collisions.  The higher the variance,
 * the more likely the hash function is poor and is resulting in clustering.
 */
double hashmap_collisions_variance(const struct hashmap *map)
{
    struct hashmap_entry *entry;
    double mean_collisions;
    double variance;
    double total_variance = 0;

    HASHMAP_ASSERT(map != NULL);

    if (!map->num_entries) {
        return 0;
    }
    mean_collisions = hashmap_collisions_mean(map);
    for (entry = map->table; entry < &map->table[map->table_size];
        ++entry) {
        if (!entry->key) {
            continue;
        }
        variance = (double)entry->num_collisions - mean_collisions;
        total_variance += variance * variance;
    }
    return total_variance / map->num_entries;
}
#endif

D hashmap.h => hashmap.h +0 -281
@@ 1,281 0,0 @@
/*
 * Copyright (c) 2016-2018 David Leeds <davidesleeds@gmail.com>
 *
 * Hashmap is free software; you can redistribute it and/or modify
 * it under the terms of the MIT license. See LICENSE for details.
 */

#ifndef __HASHMAP_H__
#define __HASHMAP_H__

#include <stddef.h>

/*
 * Define HASHMAP_METRICS to compile in performance analysis
 * functions for use in assessing hash function performance.
 */
/* #define HASHMAP_METRICS */

/*
 * Define HASHMAP_NOASSERT to compile out all assertions used internally.
 */
/* #define HASHMAP_NOASSERT */

/*
 * Macros to declare type-specific versions of hashmap_*() functions to
 * allow compile-time type checking and avoid the need for type casting.
 */
#define HASHMAP_FUNCS_DECLARE(name, key_type, data_type)                \
    data_type *name##_hashmap_put(struct hashmap *map,                  \
            const key_type *key, data_type *data);                      \
    data_type *name##_hashmap_get(const struct hashmap *map,            \
            const key_type *key);                                       \
    data_type *name##_hashmap_remove(struct hashmap *map,               \
            const key_type *key);                                       \
    const key_type *name##_hashmap_iter_get_key(                        \
            const struct hashmap_iter *iter);                           \
    data_type *name##_hashmap_iter_get_data(                            \
            const struct hashmap_iter *iter);                           \
    void name##_hashmap_iter_set_data(const struct hashmap_iter *iter,  \
            data_type *data);                                           \
    int name##_hashmap_foreach(const struct hashmap *map,               \
            int (*func)(const key_type *, data_type *, void *), void *arg);

#define HASHMAP_FUNCS_CREATE(name, key_type, data_type)                 \
    data_type *name##_hashmap_put(struct hashmap *map,                  \
            const key_type *key, data_type *data)                       \
    {                                                                   \
        return (data_type *)hashmap_put(map, (const void *)key,         \
                (void *)data);                                          \
    }                                                                   \
    data_type *name##_hashmap_get(const struct hashmap *map,            \
            const key_type *key)                                        \
    {                                                                   \
        return (data_type *)hashmap_get(map, (const void *)key);        \
    }                                                                   \
    data_type *name##_hashmap_remove(struct hashmap *map,               \
            const key_type *key)                                        \
    {                                                                   \
        return (data_type *)hashmap_remove(map, (const void *)key);     \
    }                                                                   \
    const key_type *name##_hashmap_iter_get_key(                        \
            const struct hashmap_iter *iter)                            \
    {                                                                   \
        return (const key_type *)hashmap_iter_get_key(iter);            \
    }                                                                   \
    data_type *name##_hashmap_iter_get_data(                            \
            const struct hashmap_iter *iter)                            \
    {                                                                   \
        return (data_type *)hashmap_iter_get_data(iter);                \
    }                                                                   \
    void name##_hashmap_iter_set_data(const struct hashmap_iter *iter,  \
            data_type *data)                                            \
    {                                                                   \
        hashmap_iter_set_data(iter, (void *)data);                      \
    }                                                                   \
    struct __##name##_hashmap_foreach_state {                           \
        int (*func)(const key_type *, data_type *, void *);             \
        void *arg;                                                      \
    };                                                                  \
    static inline int __##name##_hashmap_foreach_callback(              \
            const void *key, void *data, void *arg)                     \
    {                                                                   \
        struct __##name##_hashmap_foreach_state *s =                    \
            (struct __##name##_hashmap_foreach_state *)arg;             \
        return s->func((const key_type *)key,                           \
                (data_type *)data, s->arg);                             \
    }                                                                   \
    int name##_hashmap_foreach(const struct hashmap *map,               \
            int (*func)(const key_type *, data_type *, void *),         \
            void *arg)                                                  \
    {                                                                   \
        struct __##name##_hashmap_foreach_state s = { func, arg };      \
        return hashmap_foreach(map,                                     \
            __##name##_hashmap_foreach_callback, &s);                   \
    }


struct hashmap_iter;
struct hashmap_entry;

/*
 * The hashmap state structure.
 */
struct hashmap {
    size_t table_size_init;
    size_t table_size;
    size_t num_entries;
    struct hashmap_entry *table;
    size_t (*hash)(const void *);
    int (*key_compare)(const void *, const void *);
    void *(*key_alloc)(const void *);
    void (*key_free)(void *);
};

/*
 * Initialize an empty hashmap.
 *
 * hash_func should return an even distribution of numbers between 0
 * and SIZE_MAX varying on the key provided.  If set to NULL, the default
 * case-sensitive string hash function is used: hashmap_hash_string
 *
 * key_compare_func should return 0 if the keys match, and non-zero otherwise.
 * If set to NULL, the default case-sensitive string comparator function is
 * used: hashmap_compare_string
 *
 * initial_size is optional, and may be set to the max number of entries
 * expected to be put in the hash table.  This is used as a hint to
 * pre-allocate the hash table to the minimum size needed to avoid
 * gratuitous rehashes.  If initial_size is 0, a default size will be used.
 *
 * Returns 0 on success and -errno on failure.
 */
int hashmap_init(struct hashmap *map, size_t (*hash_func)(const void *),
    int (*key_compare_func)(const void *, const void *),
    size_t initial_size);

/*
 * Free the hashmap and all associated memory.
 */
void hashmap_destroy(struct hashmap *map);

/*
 * Enable internal memory allocation and management of hash keys.
 */
void hashmap_set_key_alloc_funcs(struct hashmap *map,
    void *(*key_alloc_func)(const void *),
    void (*key_free_func)(void *));

/*
 * Add an entry to the hashmap.  If an entry with a matching key already
 * exists and has a data pointer associated with it, the existing data
 * pointer is returned, instead of assigning the new value.  Compare
 * the return value with the data passed in to determine if a new entry was
 * created.  Returns NULL if memory allocation failed.
 */
void *hashmap_put(struct hashmap *map, const void *key, void *data);

/*
 * Return the data pointer, or NULL if no entry exists.
 */
void *hashmap_get(const struct hashmap *map, const void *key);

/*
 * Remove an entry with the specified key from the map.
 * Returns the data pointer, or NULL, if no entry was found.
 */
void *hashmap_remove(struct hashmap *map, const void *key);

/*
 * Remove all entries.
 */
void hashmap_clear(struct hashmap *map);

/*
 * Remove all entries and reset the hash table to its initial size.
 */
void hashmap_reset(struct hashmap *map);

/*
 * Return the number of entries in the hash map.
 */
size_t hashmap_size(const struct hashmap *map);

/*
 * Get a new hashmap iterator.  The iterator is an opaque
 * pointer that may be used with hashmap_iter_*() functions.
 * Hashmap iterators are INVALID after a put or remove operation is performed.
 * hashmap_iter_remove() allows safe removal during iteration.
 */
struct hashmap_iter *hashmap_iter(const struct hashmap *map);

/*
 * Return an iterator to the next hashmap entry.  Returns NULL if there are
 * no more entries.
 */
struct hashmap_iter *hashmap_iter_next(const struct hashmap *map,
    const struct hashmap_iter *iter);

/*
 * Remove the hashmap entry pointed to by this iterator and returns an
 * iterator to the next entry.  Returns NULL if there are no more entries.
 */
struct hashmap_iter *hashmap_iter_remove(struct hashmap *map,
    const struct hashmap_iter *iter);

/*
 * Return the key of the entry pointed to by the iterator.
 */
const void *hashmap_iter_get_key(const struct hashmap_iter *iter);

/*
 * Return the data of the entry pointed to by the iterator.
 */
void *hashmap_iter_get_data(const struct hashmap_iter *iter);

/*
 * Set the data pointer of the entry pointed to by the iterator.
 */
void hashmap_iter_set_data(const struct hashmap_iter *iter, void *data);

/*
 * Invoke func for each entry in the hashmap.  Unlike the hashmap_iter_*()
 * interface, this function supports calls to hashmap_remove() during iteration.
 * However, it is an error to put or remove an entry other than the current one,
 * and doing so will immediately halt iteration and return an error.
 * Iteration is stopped if func returns non-zero.  Returns func's return
 * value if it is < 0, otherwise, 0.
 */
int hashmap_foreach(const struct hashmap *map,
    int (*func)(const void *, void *, void *), void *arg);

/*
 * Default hash function for string keys.
 * This is an implementation of the well-documented Jenkins one-at-a-time
 * hash function.
 */
size_t hashmap_hash_string(const void *key);

/*
 * Default key comparator function for string keys.
 */
int hashmap_compare_string(const void *a, const void *b);

/*
 * Default key allocation function for string keys.  Use free() for the
 * key_free_func.
 */
void *hashmap_alloc_key_string(const void *key);

/*
 * Case insensitive hash function for string keys.
 */
size_t hashmap_hash_string_i(const void *key);

/*
 * Case insensitive key comparator function for string keys.
 */
int hashmap_compare_string_i(const void *a, const void *b);


#ifdef HASHMAP_METRICS
/*
 * Return the load factor.
 */
double hashmap_load_factor(const struct hashmap *map);

/*
 * Return the average number of collisions per entry.
 */
double hashmap_collisions_mean(const struct hashmap *map);

/*
 * Return the variance between entry collisions.  The higher the variance,
 * the more likely the hash function is poor and is resulting in clustering.
 */
double hashmap_collisions_variance(const struct hashmap *map);
#endif


#endif /* __HASHMAP_H__ */


D mal/LICENSE => mal/LICENSE +0 -387
@@ 1,387 0,0 @@
Copyright (C) 2015 Joel Martin <github@martintribe.org>

Mal (make-a-lisp) is licensed under the MPL 2.0 (Mozilla Public
License 2.0). The text of the MPL 2.0 license is included below and
can be found at https://www.mozilla.org/MPL/2.0/

Many of the implementations run or compile using a line editing
library. In some cases, the implementations provide an option in the
code to switch between the GNU GPL licensed GNU readline library and
the BSD licensed editline (libedit) library.


Mozilla Public License Version 2.0
==================================

1. Definitions
--------------

1.1. "Contributor"
    means each individual or legal entity that creates, contributes to
    the creation of, or owns Covered Software.

1.2. "Contributor Version"
    means the combination of the Contributions of others (if any) used
    by a Contributor and that particular Contributor's Contribution.

1.3. "Contribution"
    means Covered Software of a particular Contributor.

1.4. "Covered Software"
    means Source Code Form to which the initial Contributor has attached
    the notice in Exhibit A, the Executable Form of such Source Code
    Form, and Modifications of such Source Code Form, in each case
    including portions thereof.

1.5. "Incompatible With Secondary Licenses"
    means

    (a) that the initial Contributor has attached the notice described
        in Exhibit B to the Covered Software; or

    (b) that the Covered Software was made available under the terms of
        version 1.1 or earlier of the License, but not also under the
        terms of a Secondary License.

1.6. "Executable Form"
    means any form of the work other than Source Code Form.

1.7. "Larger Work"
    means a work that combines Covered Software with other material, in
    a separate file or files, that is not Covered Software.

1.8. "License"
    means this document.

1.9. "Licensable"
    means having the right to grant, to the maximum extent possible,
    whether at the time of the initial grant or subsequently, any and
    all of the rights conveyed by this License.

1.10. "Modifications"
    means any of the following:

    (a) any file in Source Code Form that results from an addition to,
        deletion from, or modification of the contents of Covered
        Software; or

    (b) any new file in Source Code Form that contains any Covered
        Software.

1.11. "Patent Claims" of a Contributor
    means any patent claim(s), including without limitation, method,
    process, and apparatus claims, in any patent Licensable by such
    Contributor that would be infringed, but for the grant of the
    License, by the making, using, selling, offering for sale, having
    made, import, or transfer of either its Contributions or its
    Contributor Version.

1.12. "Secondary License"
    means either the GNU General Public License, Version 2.0, the GNU
    Lesser General Public License, Version 2.1, the GNU Affero General
    Public License, Version 3.0, or any later versions of those
    licenses.

1.13. "Source Code Form"
    means the form of the work preferred for making modifications.

1.14. "You" (or "Your")
    means an individual or a legal entity exercising rights under this
    License. For legal entities, "You" includes any entity that
    controls, is controlled by, or is under common control with You. For
    purposes of this definition, "control" means (a) the power, direct
    or indirect, to cause the direction or management of such entity,
    whether by contract or otherwise, or (b) ownership of more than
    fifty percent (50%) of the outstanding shares or beneficial
    ownership of such entity.

2. License Grants and Conditions
--------------------------------

2.1. Grants

Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:

(a) under intellectual property rights (other than patent or trademark)
    Licensable by such Contributor to use, reproduce, make available,
    modify, display, perform, distribute, and otherwise exploit its
    Contributions, either on an unmodified basis, with Modifications, or
    as part of a Larger Work; and

(b) under Patent Claims of such Contributor to make, use, sell, offer
    for sale, have made, import, and otherwise transfer either its
    Contributions or its Contributor Version.

2.2. Effective Date

The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.

2.3. Limitations on Grant Scope

The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:

(a) for any code that a Contributor has removed from Covered Software;
    or

(b) for infringements caused by: (i) Your and any other third party's
    modifications of Covered Software, or (ii) the combination of its
    Contributions with other software (except as part of its Contributor
    Version); or

(c) under Patent Claims infringed by Covered Software in the absence of
    its Contributions.

This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).

2.4. Subsequent Licenses

No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).

2.5. Representation

Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.

2.6. Fair Use

This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.

2.7. Conditions

Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.

3. Responsibilities
-------------------

3.1. Distribution of Source Form

All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.

3.2. Distribution of Executable Form

If You distribute Covered Software in Executable Form then:

(a) such Covered Software must also be made available in Source Code
    Form, as described in Section 3.1, and You must inform recipients of
    the Executable Form how they can obtain a copy of such Source Code
    Form by reasonable means in a timely manner, at a charge no more
    than the cost of distribution to the recipient; and

(b) You may distribute such Executable Form under the terms of this
    License, or sublicense it under different terms, provided that the
    license for the Executable Form does not attempt to limit or alter
    the recipients' rights in the Source Code Form under this License.

3.3. Distribution of a Larger Work

You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).

3.4. Notices

You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.

3.5. Application of Additional Terms

You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.

4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------

If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.

5. Termination
--------------

5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.

5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.

5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.

************************************************************************
*                                                                      *
*  6. Disclaimer of Warranty                                           *
*  -------------------------                                           *
*                                                                      *
*  Covered Software is provided under this License on an "as is"       *
*  basis, without warranty of any kind, either expressed, implied, or  *
*  statutory, including, without limitation, warranties that the       *
*  Covered Software is free of defects, merchantable, fit for a        *
*  particular purpose or non-infringing. The entire risk as to the     *
*  quality and performance of the Covered Software is with You.        *
*  Should any Covered Software prove defective in any respect, You     *
*  (not any Contributor) assume the cost of any necessary servicing,   *
*  repair, or correction. This disclaimer of warranty constitutes an   *
*  essential part of this License. No use of any Covered Software is   *
*  authorized under this License except under this disclaimer.         *
*                                                                      *
************************************************************************

************************************************************************
*                                                                      *
*  7. Limitation of Liability                                          *
*  --------------------------                                          *
*                                                                      *
*  Under no circumstances and under no legal theory, whether tort      *
*  (including negligence), contract, or otherwise, shall any           *
*  Contributor, or anyone who distributes Covered Software as          *
*  permitted above, be liable to You for any direct, indirect,         *
*  special, incidental, or consequential damages of any character      *
*  including, without limitation, damages for lost profits, loss of    *
*  goodwill, work stoppage, computer failure or malfunction, or any    *
*  and all other commercial damages or losses, even if such party      *
*  shall have been informed of the possibility of such damages. This   *
*  limitation of liability shall not apply to liability for death or   *
*  personal injury resulting from such party's negligence to the       *
*  extent applicable law prohibits such limitation. Some               *
*  jurisdictions do not allow the exclusion or limitation of           *
*  incidental or consequential damages, so this exclusion and          *
*  limitation may not apply to You.                                    *
*                                                                      *
************************************************************************

8. Litigation
-------------

Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.

9. Miscellaneous
----------------

This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.

10. Versions of the License
---------------------------

10.1. New Versions

Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.

10.2. Effect of New Versions

You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.

10.3. Modified Versions

If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).

10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses

If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.

Exhibit A - Source Code Form License Notice
-------------------------------------------

  This Source Code Form is subject to the terms of the Mozilla Public
  License, v. 2.0. If a copy of the MPL was not distributed with this
  file, You can obtain one at http://mozilla.org/MPL/2.0/.

If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.

You may add additional accurate notices of copyright ownership.

Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------

  This Source Code Form is "Incompatible With Secondary Licenses", as
  defined by the Mozilla Public License, v. 2.0.



D mal/Makefile => mal/Makefile +0 -504
@@ 1,504 0,0 @@
# Usage/help
all help:
	@echo
	@echo 'USAGE:'
	@echo
	@echo 'Rules/Targets:'
	@echo
	@echo 'make "IMPL"                       # build all steps of IMPL'
	@echo 'make "IMPL^STEP"                  # build STEP of IMPL'
	@echo
	@echo 'make "test"                       # test all implementations'
	@echo 'make "test^IMPL"                  # test all steps of IMPL'
	@echo 'make "test^STEP"                  # test STEP for all implementations'
	@echo 'make "test^IMPL^STEP"             # test STEP of IMPL'
	@echo
	@echo 'make "perf"                       # run microbenchmarks for all implementations'
	@echo 'make "perf^IMPL"                  # run microbenchmarks for IMPL'
	@echo
	@echo 'make "repl^IMPL"                  # run stepA of IMPL'
	@echo 'make "repl^IMPL^STEP"             # test STEP of IMPL'
	@echo
	@echo 'make "clean"                      # run 'make clean' for all implementations'
	@echo 'make "clean^IMPL"                 # run 'make clean' for IMPL'
	@echo
	@echo 'make "stats"                      # run 'make stats' for all implementations'
	@echo 'make "stats-lisp"                 # run 'make stats-lisp' for all implementations'
	@echo 'make "stats^IMPL"                 # run 'make stats' for IMPL'
	@echo 'make "stats-lisp^IMPL"            # run 'make stats-lisp' for IMPL'
	@echo
	@echo 'Options/Settings:'
	@echo
	@echo 'make MAL_IMPL=IMPL "test^mal..."  # use IMPL for self-host tests'
	@echo 'make REGRESS=1 "test..."          # test with previous step tests too'
	@echo 'make DOCKERIZE=1 ...              # to dockerize above rules/targets'
	@echo 'make TEST_OPTS="--opt ..."        # options to pass to runtest.py'
	@echo
	@echo 'Other:'
	@echo
	@echo 'make "docker-build^IMPL"          # build docker image for IMPL'
	@echo
	@echo 'make "docker-shell^IMPL"          # start bash shell in docker image for IMPL'
	@echo

#
# Command line settings
#

MAL_IMPL = js

# cbm or qbasic
basic_MODE = cbm
# clj or cljs (Clojure vs ClojureScript/lumo)
clojure_MODE = clj
# python, js, cpp, or neko
haxe_MODE = neko
# octave or matlab
matlab_MODE = octave
# python, python2 or python3
python_MODE = python
# scheme (chibi, kawa, gauche, chicken, sagittarius, cyclone, foment)
scheme_MODE = chibi
# js wace_libc wace_fooboot
wasm_MODE = wace_libc

# Path to loccount for counting LOC stats
LOCCOUNT = loccount

# Extra options to pass to runtest.py
TEST_OPTS =

# Test with previous test files not just the test files for the
# current step. Step 0 and 1 tests are special and not included in
# later steps.
REGRESS =

DEFERRABLE=1
OPTIONAL=1

# Run target/rule within docker image for the implementation
DOCKERIZE =


#
# Implementation specific settings
#

IMPLS = ada ada.2 awk bash basic c chuck clojure coffee common-lisp cpp crystal cs d dart \
	elisp elixir elm erlang es6 factor fantom forth fsharp go groovy gnu-smalltalk \
	guile haskell haxe hy io java js julia kotlin livescript logo lua make mal \
	matlab miniMAL nasm nim objc objpascal ocaml perl perl6 php picolisp plpgsql \
	plsql powershell ps python r racket rexx rpython ruby rust scala scheme skew \
	swift swift3 swift4 tcl ts vb vhdl vimscript wasm yorick

EXTENSION = .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_try
stepA = stepA_mal

argv_STEP = step6_file


regress_step0 = step0
regress_step1 = step1
regress_step2 = step2
regress_step3 = $(regress_step2) step3
regress_step4 = $(regress_step3) step4
regress_step5 = $(regress_step4) step5
regress_step6 = $(regress_step5) step6
regress_step7 = $(regress_step6) step7
regress_step8 = $(regress_step7) step8
regress_step9 = $(regress_step8) step9
regress_stepA = $(regress_step9) stepA

step5_EXCLUDES += bash        # never completes at 10,000
step5_EXCLUDES += basic       # too slow, and limited to ints of 2^16
step5_EXCLUDES += logo        # too slow for 10,000
step5_EXCLUDES += make        # no TCO capability (iteration or recursion)
step5_EXCLUDES += mal         # host impl dependent
step5_EXCLUDES += matlab      # never completes at 10,000
step5_EXCLUDES += plpgsql     # too slow for 10,000
step5_EXCLUDES += plsql       # too slow for 10,000
step5_EXCLUDES += powershell  # too slow for 10,000
step5_EXCLUDES += $(if $(filter cpp,$(haxe_MODE)),haxe,) # cpp finishes 10,000, segfaults at 100,000

dist_EXCLUDES += mal
# TODO: still need to implement dist
dist_EXCLUDES += guile io julia matlab swift


# Extra options to pass to runtest.py
logo_TEST_OPTS = --start-timeout 60 --test-timeout 120
mal_TEST_OPTS = --start-timeout 60 --test-timeout 120
miniMAL_TEST_OPTS = --start-timeout 60 --test-timeout 120
perl6_TEST_OPTS = --test-timeout=60
plpgsql_TEST_OPTS = --start-timeout 60 --test-timeout 180
plsql_TEST_OPTS = --start-timeout 120 --test-timeout 120
vimscript_TEST_OPTS = --test-timeout 30
ifeq ($(MAL_IMPL),vimscript)
mal_TEST_OPTS = --start-timeout 60 --test-timeout 180
else ifeq ($(MAL_IMPL),powershell)
mal_TEST_OPTS = --start-timeout 60 --test-timeout 180
endif


#
# Implementation specific utility functions
#

basic_STEP_TO_PROG_cbm    = basic/$($(1)).bas
basic_STEP_TO_PROG_qbasic = basic/$($(1))

clojure_STEP_TO_PROG_clj  = clojure/target/$($(1)).jar
clojure_STEP_TO_PROG_cljs = clojure/src/mal/$($(1)).cljc

haxe_STEP_TO_PROG_neko   = haxe/$($(1)).n
haxe_STEP_TO_PROG_python = haxe/$($(1)).py
haxe_STEP_TO_PROG_cpp    = haxe/cpp/$($(1))
haxe_STEP_TO_PROG_js     = haxe/$($(1)).js

scheme_STEP_TO_PROG_chibi       = scheme/$($(1)).scm
scheme_STEP_TO_PROG_kawa        = scheme/out/$($(1)).class
scheme_STEP_TO_PROG_gauche      = scheme/$($(1)).scm
scheme_STEP_TO_PROG_chicken     = scheme/$($(1))
scheme_STEP_TO_PROG_sagittarius = scheme/$($(1)).scm
scheme_STEP_TO_PROG_cyclone     = scheme/$($(1))
scheme_STEP_TO_PROG_foment      = scheme/$($(1)).scm

# Map of step (e.g. "step8") to executable file for that step
ada_STEP_TO_PROG =     ada/$($(1))
ada.2_STEP_TO_PROG =   ada.2/$($(1))
awk_STEP_TO_PROG =     awk/$($(1)).awk
bash_STEP_TO_PROG =    bash/$($(1)).sh
basic_STEP_TO_PROG =   $(basic_STEP_TO_PROG_$(basic_MODE))
c_STEP_TO_PROG =       c/$($(1))
chuck_STEP_TO_PROG =   chuck/$($(1)).ck
clojure_STEP_TO_PROG = $(clojure_STEP_TO_PROG_$(clojure_MODE))
coffee_STEP_TO_PROG =  coffee/$($(1)).coffee
common-lisp_STEP_TO_PROG =  common-lisp/$($(1))
cpp_STEP_TO_PROG =     cpp/$($(1))
crystal_STEP_TO_PROG = crystal/$($(1))
cs_STEP_TO_PROG =      cs/$($(1)).exe
d_STEP_TO_PROG =       d/$($(1))
dart_STEP_TO_PROG =    dart/$($(1)).dart
elisp_STEP_TO_PROG =   elisp/$($(1)).el
elixir_STEP_TO_PROG =  elixir/lib/mix/tasks/$($(1)).ex
elm_STEP_TO_PROG =     elm/$($(1)).js
erlang_STEP_TO_PROG =  erlang/$($(1))
es6_STEP_TO_PROG =     es6/$($(1)).mjs
factor_STEP_TO_PROG =  factor/$($(1))/$($(1)).factor
fantom_STEP_TO_PROG =  fantom/lib/fan/$($(1)).pod
forth_STEP_TO_PROG =   forth/$($(1)).fs
fsharp_STEP_TO_PROG =  fsharp/$($(1)).exe
go_STEP_TO_PROG =      go/$($(1))
groovy_STEP_TO_PROG =  groovy/$($(1)).groovy
gnu-smalltalk_STEP_TO_PROG = gnu-smalltalk/$($(1)).st
guile_STEP_TO_PROG =   guile/$($(1)).scm
haskell_STEP_TO_PROG = haskell/$($(1))
haxe_STEP_TO_PROG =    $(haxe_STEP_TO_PROG_$(haxe_MODE))
hy_STEP_TO_PROG =      hy/$($(1)).hy
io_STEP_TO_PROG =      io/$($(1)).io
java_STEP_TO_PROG =    java/target/classes/mal/$($(1)).class
js_STEP_TO_PROG =      js/$($(1)).js
julia_STEP_TO_PROG =   julia/$($(1)).jl
kotlin_STEP_TO_PROG =  kotlin/$($(1)).jar
livescript_STEP_TO_PROG = livescript/$($(1)).js
logo_STEP_TO_PROG =    logo/$($(1)).lg
lua_STEP_TO_PROG =     lua/$($(1)).lua
make_STEP_TO_PROG =    make/$($(1)).mk
mal_STEP_TO_PROG =     mal/$($(1)).mal
matlab_STEP_TO_PROG =  matlab/$($(1)).m
miniMAL_STEP_TO_PROG = miniMAL/$($(1)).json
nasm_STEP_TO_PROG =    nasm/$($(1))
nim_STEP_TO_PROG =     nim/$($(1))
objc_STEP_TO_PROG =    objc/$($(1))
objpascal_STEP_TO_PROG = objpascal/$($(1))
ocaml_STEP_TO_PROG =   ocaml/$($(1))
perl_STEP_TO_PROG =    perl/$($(1)).pl
perl6_STEP_TO_PROG =   perl6/$($(1)).pl
php_STEP_TO_PROG =     php/$($(1)).php
picolisp_STEP_TO_PROG = picolisp/$($(1)).l
plpgsql_STEP_TO_PROG = plpgsql/$($(1)).sql
plsql_STEP_TO_PROG =   plsql/$($(1)).sql
powershell_STEP_TO_PROG =  powershell/$($(1)).ps1
ps_STEP_TO_PROG =      ps/$($(1)).ps
python_STEP_TO_PROG =  python/$($(1)).py
r_STEP_TO_PROG =       r/$($(1)).r
racket_STEP_TO_PROG =  racket/$($(1)).rkt
rexx_STEP_TO_PROG =    rexx/$($(1)).rexxpp
rpython_STEP_TO_PROG = rpython/$($(1))
ruby_STEP_TO_PROG =    ruby/$($(1)).rb
rust_STEP_TO_PROG =    rust/$($(1))
scala_STEP_TO_PROG =   scala/target/scala-2.11/classes/$($(1)).class
scheme_STEP_TO_PROG =  $(scheme_STEP_TO_PROG_$(scheme_MODE))
skew_STEP_TO_PROG =    skew/$($(1)).js
swift_STEP_TO_PROG =   swift/$($(1))
swift3_STEP_TO_PROG =  swift3/$($(1))
swift4_STEP_TO_PROG =  swift4/$($(1))
tcl_STEP_TO_PROG =     tcl/$($(1)).tcl
ts_STEP_TO_PROG =      ts/$($(1)).js
vb_STEP_TO_PROG =      vb/$($(1)).exe
vhdl_STEP_TO_PROG =    vhdl/$($(1))
vimscript_STEP_TO_PROG = vimscript/$($(1)).vim
wasm_STEP_TO_PROG =    wasm/$($(1)).wasm
yorick_STEP_TO_PROG =  yorick/$($(1)).i


#
# General settings and utility functions
#

# Needed some argument munging
COMMA = ,
noop =
SPACE = $(noop) $(noop)
export FACTOR_ROOTS := .

opt_DEFERRABLE      = $(if $(strip $(DEFERRABLE)),$(if $(filter t true T True TRUE 1 y yes Yes YES,$(DEFERRABLE)),--deferrable,--no-deferrable),--no-deferrable)
opt_OPTIONAL        = $(if $(strip $(OPTIONAL)),$(if $(filter t true T True TRUE 1 y yes Yes YES,$(OPTIONAL)),--optional,--no-optional),--no-optional)

# Return list of test files for a given step. If REGRESS is set then
# test files will include step 2 tests through tests for the step
# being tested.
STEP_TEST_FILES = $(strip $(wildcard \
		    $(foreach s,$(if $(strip $(REGRESS)),\
			$(filter-out $(if $(filter $(1),$(step5_EXCLUDES)),step5,),\
			  $(regress_$(2)))\
			,$(2)),\
		      $(1)/tests/$($(s))$(EXTENSION) tests/$($(s))$(EXTENSION))))

# DOCKERIZE utility functions
lc = $(subst A,a,$(subst B,b,$(subst C,c,$(subst D,d,$(subst E,e,$(subst F,f,$(subst G,g,$(subst H,h,$(subst I,i,$(subst J,j,$(subst K,k,$(subst L,l,$(subst M,m,$(subst N,n,$(subst O,o,$(subst P,p,$(subst Q,q,$(subst R,r,$(subst S,s,$(subst T,t,$(subst U,u,$(subst V,v,$(subst W,w,$(subst X,x,$(subst Y,y,$(subst Z,z,$1))))))))))))))))))))))))))
impl_to_image = kanaka/mal-test-$(call lc,$(1))

actual_impl = $(if $(filter mal,$(1)),$(MAL_IMPL),$(1))

# Takes impl
# Returns nothing if DOCKERIZE is not set, otherwise returns the
# docker prefix necessary to run make within the docker environment
# for this impl
get_build_command = $(strip $(if $(strip $(DOCKERIZE)),\
    docker run \
    -it --rm -u $(shell id -u) \
    -v $(dir $(abspath $(lastword $(MAKEFILE_LIST)))):/mal \
    -w /mal/$(1) \
    $(if $(strip $($(1)_MODE)),-e $(1)_MODE=$($(1)_MODE),) \
    $(if $(filter factor,$(1)),-e FACTOR_ROOTS=$(FACTOR_ROOTS),) \
    $(call impl_to_image,$(1)) \
    $(MAKE) $(if $(strip $($(1)_MODE)),$(1)_MODE=$($(1)_MODE),) \
    ,\
    $(MAKE) $(if $(strip $($(1)_MODE)),$(1)_MODE=$($(1)_MODE),)))

# Takes impl and step args. Optional env vars and dockerize args
# Returns a command prefix (docker command and environment variables)
# necessary to launch the given impl and step
get_run_prefix = $(strip $(if $(strip $(DOCKERIZE) $(4)),\
    docker run -e STEP=$($2) -e MAL_IMPL=$(MAL_IMPL) \
    -it --rm -u $(shell id -u) \
    -v $(dir $(abspath $(lastword $(MAKEFILE_LIST)))):/mal \
    -w /mal/$(call actual_impl,$(1)) \
    $(if $(strip $($(1)_MODE)),-e $(1)_MODE=$($(1)_MODE),) \
    $(if $(filter factor,$(1)),-e FACTOR_ROOTS=$(FACTOR_ROOTS),) \
    $(foreach env,$(3),-e $(env)) \
    $(call impl_to_image,$(call actual_impl,$(1))) \
    ,\
    env STEP=$($2) MAL_IMPL=$(MAL_IMPL) \
    $(if $(strip $($(1)_MODE)),$(1)_MODE=$($(1)_MODE),) \
    $(if $(filter factor,$(1)),FACTOR_ROOTS=$(FACTOR_ROOTS),) \
    $(3)))

# Takes impl and step
# Returns the runtest command prefix (with runtest options) for testing the given step
get_runtest_cmd = $(call get_run_prefix,$(1),$(2),$(if $(filter cs fsharp tcl vb,$(1)),RAW=1,)) \
		    ../runtest.py $(opt_DEFERRABLE) $(opt_OPTIONAL) $(call $(1)_TEST_OPTS) $(TEST_OPTS)

# Takes impl and step
# Returns the runtest command prefix (with runtest options) for testing the given step
get_argvtest_cmd = $(call get_run_prefix,$(1),$(2)) ../run_argv_test.sh

# Derived lists
STEPS = $(sort $(filter-out %_EXCLUDES,$(filter step%,$(.VARIABLES))))
DO_IMPLS = $(filter-out $(SKIP_IMPLS),$(IMPLS))
IMPL_TESTS = $(foreach impl,$(DO_IMPLS),test^$(impl))
STEP_TESTS = $(foreach step,$(STEPS),test^$(step))
ALL_TESTS = $(filter-out $(foreach e,$(step5_EXCLUDES),test^$(e)^step5),\
              $(strip $(sort \
                $(foreach impl,$(DO_IMPLS),\
                  $(foreach step,$(STEPS),test^$(impl)^$(step))))))

DOCKER_BUILD = $(foreach impl,$(DO_IMPLS),docker-build^$(impl))

DOCKER_SHELL = $(foreach impl,$(DO_IMPLS),docker-shell^$(impl))

IMPL_PERF = $(foreach impl,$(filter-out $(perf_EXCLUDES),$(DO_IMPLS)),perf^$(impl))

IMPL_STATS = $(foreach impl,$(DO_IMPLS),stats^$(impl))

IMPL_REPL = $(foreach impl,$(DO_IMPLS),repl^$(impl))
ALL_REPL = $(strip $(sort \
             $(foreach impl,$(DO_IMPLS),\
               $(foreach step,$(STEPS),repl^$(impl)^$(step)))))


#
# Build rules
#

# Enable secondary expansion for all rules
.SECONDEXPANSION:

# Build a program in an implementation directory
# Make sure we always try and build first because the dependencies are
# encoded in the implementation Makefile not here
.PHONY: $(foreach i,$(DO_IMPLS),$(foreach s,$(STEPS),$(call $(i)_STEP_TO_PROG,$(s))))
$(foreach i,$(DO_IMPLS),$(foreach s,$(STEPS),$(call $(i)_STEP_TO_PROG,$(s)))):
	$(foreach impl,$(word 1,$(subst /, ,$(@))),\
	  $(if $(DOCKERIZE), \
	    $(call get_build_command,$(impl)) $(patsubst $(impl)/%,%,$(@)), \
	    $(call get_build_command,$(impl)) -C $(impl) $(subst $(impl)/,,$(@))))

# Allow IMPL, and IMPL^STEP
$(DO_IMPLS): $$(foreach s,$$(STEPS),$$(call $$(@)_STEP_TO_PROG,$$(s)))

$(foreach i,$(DO_IMPLS),$(foreach s,$(STEPS),$(i)^$(s))): $$(call $$(word 1,$$(subst ^, ,$$(@)))_STEP_TO_PROG,$$(word 2,$$(subst ^, ,$$(@))))


#
# Test rules
#

$(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 get_runtest_cmd,$(impl),$(step)) ../$(test) -- ../$(impl)/run' && \
	      $(call get_runtest_cmd,$(impl),$(step)) ../$(test) -- ../$(impl)/run && \
	      $(if $(filter tests/$(argv_STEP)$(EXTENSION),$(test)),\
	        echo '----------------------------------------------' && \
	        echo 'Testing ARGV of $@; step file: $+' && \
	        echo 'Running: $(call get_argvtest_cmd,$(impl),$(step)) ../$(impl)/run ' && \
	        $(call get_argvtest_cmd,$(impl),$(step)) ../$(impl)/run  && ,\
		true && ))\
	    true))

# Allow test, tests, test^STEP, test^IMPL, and test^IMPL^STEP
test: $(ALL_TESTS)
tests: $(ALL_TESTS)

$(IMPL_TESTS): $$(filter $$@^%,$$(ALL_TESTS))

$(STEP_TESTS): $$(foreach step,$$(subst test^,,$$@),$$(filter %^$$(step),$$(ALL_TESTS)))


#
# Docker build rules
#

docker-build: $(DOCKER_BUILD)

$(DOCKER_BUILD):
	@echo "----------------------------------------------"; \
	$(foreach impl,$(word 2,$(subst ^, ,$(@))),\
	  echo "Running: docker build -t $(call impl_to_image,$(impl)) .:"; \
	  cd $(impl) && docker build -t $(call impl_to_image,$(impl)) .)

#
# Docker shell rules
#

$(DOCKER_SHELL):
	@echo "----------------------------------------------"; \
	$(foreach impl,$(word 2,$(subst ^, ,$(@))),\
	  echo "Running: $(call get_run_prefix,$(impl),stepA,,dockerize) bash"; \
	  $(call get_run_prefix,$(impl),stepA,,dockerize) bash)


#
# Performance test rules
#

perf: $(IMPL_PERF)

$(IMPL_PERF):
	@echo "----------------------------------------------"; \
	$(foreach impl,$(word 2,$(subst ^, ,$(@))),\
	  cd $(if $(filter mal,$(impl)),$(MAL_IMPL),$(impl)); \
	  echo "Performance test for $(impl):"; \
	  echo 'Running: $(call get_run_prefix,$(impl),stepA) ../$(impl)/run ../tests/perf1.mal'; \
	  $(call get_run_prefix,$(impl),stepA) ../$(impl)/run ../tests/perf1.mal; \
	  echo 'Running: $(call get_run_prefix,$(impl),stepA) ../$(impl)/run ../tests/perf2.mal'; \
	  $(call get_run_prefix,$(impl),stepA) ../$(impl)/run ../tests/perf2.mal; \
	  echo 'Running: $(call get_run_prefix,$(impl),stepA) ../$(impl)/run ../tests/perf3.mal'; \
	  $(call get_run_prefix,$(impl),stepA) ../$(impl)/run ../tests/perf3.mal)


#
# REPL invocation rules
#

$(ALL_REPL): $$(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)); \
	    echo 'REPL implementation $(impl), step file: $+'; \
	    echo 'Running: $(call get_run_prefix,$(impl),$(step)) ../$(impl)/run $(RUN_ARGS)'; \
	    $(call get_run_prefix,$(impl),$(step)) ../$(impl)/run $(RUN_ARGS);))

# Allow repl^IMPL^STEP and repl^IMPL (which starts REPL of stepA)
$(IMPL_REPL): $$@^stepA

#
# Stats test rules
#

# For a concise summary:
#   make stats | egrep -A1 "^Stats for|^all" | egrep -v "^all|^--"
stats: $(IMPL_STATS)

$(IMPL_STATS):
	@$(foreach impl,$(word 2,$(subst ^, ,$(@))),\
	  echo "Stats for $(impl):"; \
	  $(LOCCOUNT) -x "Makefile|node_modules" $(impl))

#
# Utility functions
#
print-%:
	@echo "$($(*))"

#
# Recursive rules (call make FOO in each subdirectory)
#

define recur_template
.PHONY: $(1)
$(1): $(2)
$(2):
	@echo "----------------------------------------------"; \
	$$(foreach impl,$$(word 2,$$(subst ^, ,$$(@))),\
	  $$(if $$(DOCKERIZE), \
	    echo "Running: $$(call get_build_command,$$(impl)) --no-print-directory $(1)"; \
	    $$(call get_build_command,$$(impl)) --no-print-directory $(1), \
	    echo "Running: $$(call get_build_command,$$(impl)) --no-print-directory -C $$(impl) $(1)"; \
	    $$(call get_build_command,$$(impl)) --no-print-directory -C $$(impl) $(1)))
endef

recur_impls_ = $(filter-out $(foreach impl,$($(1)_EXCLUDES),$(1)^$(impl)),$(foreach impl,$(IMPLS),$(1)^$(impl)))

# recursive clean
$(eval $(call recur_template,clean,$(call recur_impls_,clean)))

# recursive dist
$(eval $(call recur_template,dist,$(call recur_impls_,dist)))

D mal/README.md => mal/README.md +0 -1232
@@ 1,1232 0,0 @@
# mal - Make a Lisp

[![Build Status](https://travis-ci.org/kanaka/mal.svg?branch=master)](https://travis-ci.org/kanaka/mal)

## Description

**1. Mal is a Clojure inspired Lisp interpreter**

**2. Mal is implemented in 75 languages (76 implementations total)**

| Language | Creator |
| -------- | ------- |
| [Ada](#ada) | [Chris Moore](https://github.com/zmower) |
| [Ada #2](#ada2) | [Nicolas Boulenguez](https://github.com/asarhaddon) |
| [GNU Awk](#gnu-awk) | [Miutsuru Kariya](https://github.com/kariya-mitsuru) |
| [Bash 4](#bash-4) | [Joel Martin](https://github.com/kanaka)  |
| [BASIC](#basic-c64-and-qbasic) (C64 &amp; QBasic) | [Joel Martin](https://github.com/kanaka) |
| [C](#c) | [Joel Martin](https://github.com/kanaka)  |
| [C++](#c-1) | [Stephen Thirlwall](https://github.com/sdt) |
| [C#](#c-2) | [Joel Martin](https://github.com/kanaka)  |
| [ChucK](#chuck) | [Vasilij Schneidermann](https://github.com/wasamasa) |
| [Clojure](#clojure) (Clojure &amp; ClojureScript) | [Joel Martin](https://github.com/kanaka) |
| [CoffeeScript](#coffeescript) | [Joel Martin](https://github.com/kanaka)  |
| [Common Lisp](#common-lisp) | [Iqbal Ansari](https://github.com/iqbalansari) |
| [Crystal](#crystal) | [Linda_pp](https://github.com/rhysd) |
| [D](#d) | [Dov Murik](https://github.com/dubek) |
| [Dart](#dart) | [Harry Terkelsen](https://github.com/hterkelsen) |
| [Elixir](#elixir) | [Martin Ek](https://github.com/ekmartin) |
| [Elm](#elm) | [Jos van Bakel](https://github.com/c0deaddict) |
| [Emacs Lisp](#emacs-lisp) | [Vasilij Schneidermann](https://github.com/wasamasa) |
| [Erlang](#erlang) | [Nathan Fiedler](https://github.com/nlfiedler) |
| [ES6](#es6-ecmascript-2015) (ECMAScript 2015) | [Joel Martin](https://github.com/kanaka) |
| [F#](#f) | [Peter Stephens](https://github.com/pstephens) |
| [Factor](#factor) | [Jordan Lewis](https://github.com/jordanlewis) |
| [Fantom](#fantom) | [Dov Murik](https://github.com/dubek) |
| [Forth](#forth) | [Chris Houser](https://github.com/chouser) |
| [GNU Guile](#gnu-guile-21) | [Mu Lei](https://github.com/NalaGinrut) |
| [GNU Smalltalk](#gnu-smalltalk) | [Vasilij Schneidermann](https://github.com/wasamasa) |
| [Go](#go) | [Joel Martin](https://github.com/kanaka)  |
| [Groovy](#groovy) | [Joel Martin](https://github.com/kanaka)  |
| [Haskell](#haskell) | [Joel Martin](https://github.com/kanaka)  |
| [Haxe](#haxe-neko-python-c-and-javascript) (Neko, Python, C++, &amp; JS) | [Joel Martin](https://github.com/kanaka) |
| [Hy](#hy) | [Joel Martin](https://github.com/kanaka)  |
| [Io](#io) | [Dov Murik](https://github.com/dubek) |
| [Java](#java-17) | [Joel Martin](https://github.com/kanaka)  |
| [JavaScript](#javascriptnode) ([Demo](http://kanaka.github.io/mal)) | [Joel Martin](https://github.com/kanaka) |
| [Julia](#julia) | [Joel Martin](https://github.com/kanaka)  |
| [Kotlin](#kotlin) | [Javier Fernandez-Ivern](https://github.com/ivern) |
| [LiveScript](#livescript) | [Jos van Bakel](https://github.com/c0deaddict) |
| [Logo](#logo) | [Dov Murik](https://github.com/dubek) |
| [Lua](#lua) | [Joel Martin](https://github.com/kanaka)  |
| [GNU Make](#gnu-make-381) | [Joel Martin](https://github.com/kanaka)  |
| [mal itself](#mal) | [Joel Martin](https://github.com/kanaka)  |
| [MATLAB](#matlab-gnu-octave-and-matlab) (GNU Octave &amp; MATLAB) | [Joel Martin](https://github.com/kanaka) |
| [miniMAL](#minimal) ([Repo](https://github.com/kanaka/miniMAL), [Demo](https://kanaka.github.io/miniMAL/)) | [Joel Martin](https://github.com/kanaka) |
| [NASM](#nasm) | [Ben Dudson](https://github.com/bendudson) |
| [Nim](#nim-0170) | [Dennis Felsing](https://github.com/def-) |
| [Object Pascal](#object-pascal) | [Joel Martin](https://github.com/kanaka)  |
| [Objective C](#objective-c) | [Joel Martin](https://github.com/kanaka)  |
| [OCaml](#ocaml-4010) | [Chris Houser](https://github.com/chouser) |
| [Perl](#perl-58) | [Joel Martin](https://github.com/kanaka)  |
| [Perl 6](#perl-6) | [Hinrik Örn Sigurðsson](https://github.com/hinrik) |
| [PHP](#php-53) | [Joel Martin](https://github.com/kanaka)  |
| [Picolisp](#picolisp) | [Vasilij Schneidermann](https://github.com/wasamasa) |
| [PL/pgSQL](#plpgsql-postgres-sql-procedural-language) (Postgres) | [Joel Martin](https://github.com/kanaka) |
| [PL/SQL](#plsql-oracle-sql-procedural-language) (Oracle) | [Joel Martin](https://github.com/kanaka) |
| [PostScript](#postscript-level-23) | [Joel Martin](https://github.com/kanaka)  |
| [PowerShell](#powershell) | [Joel Martin](https://github.com/kanaka)  |
| [Python](#python-2x-and-3x) (2.X &amp; 3.X) | [Joel Martin](https://github.com/kanaka) |
| [RPython](#rpython) | [Joel Martin](https://github.com/kanaka)  |
| [R](#r) | [Joel Martin](https://github.com/kanaka)  |
| [Racket](#racket-53) | [Joel Martin](https://github.com/kanaka)  |
| [Rexx](#rexx) | [Dov Murik](https://github.com/dubek) |
| [Ruby](#ruby-19) | [Joel Martin](https://github.com/kanaka)  |
| [Rust](#rust-100-nightly) | [Joel Martin](https://github.com/kanaka)  |
| [Scala](#scala) | [Joel Martin](https://github.com/kanaka)  |
| [Scheme (R7RS)](#scheme-r7rs) | [Vasilij Schneidermann](https://github.com/wasamasa) |
| [Skew](#skew) | [Dov Murik](https://github.com/dubek) |
| [Swift 2](#swift) | [Keith Rollin](https://github.com/keith-rollin) |
| [Swift 3](#swift-3) | [Joel Martin](https://github.com/kanaka)  |
| [Swift 4](#swift-4) | [陆遥](https://github.com/LispLY)  |
| [Tcl](#tcl-86) | [Dov Murik](https://github.com/dubek) |
| [TypeScript](#typescript) | [Masahiro Wakame](https://github.com/vvakame) |
| [VHDL](#vhdl) | [Dov Murik](https://github.com/dubek) |
| [Vimscript](#vimscript) | [Dov Murik](https://github.com/dubek) |
| [Visual Basic.NET](#visual-basicnet) | [Joel Martin](https://github.com/kanaka)  |
| [WebAssembly](#webassembly-wasm) (wasm) | [Joel Martin](https://github.com/kanaka) |
| [Yorick](#yorick) | [Dov Murik](https://github.com/dubek) |


**3. Mal is a learning tool**

Each implementation of mal is separated into
11 incremental, self-contained (and testable) steps that demonstrate
core concepts of Lisp. The last step is capable of self-hosting
(running the mal implementation of mal). See the [make-a-lisp process
guide](process/guide.md). 

The make-a-lisp steps are:

* [step0_repl](process/guide.md#step0)
* [step1_read_print](process/guide.md#step1)
* [step2_eval](process/guide.md#step2)
* [step3_env](process/guide.md#step3)
* [step4_if_fn_do](process/guide.md#step4)
* [step5_tco](process/guide.md#step5)
* [step6_file](process/guide.md#step6)
* [step7_quote](process/guide.md#step7)
* [step8_macros](process/guide.md#step8)
* [step9_try](process/guide.md#step9)
* [stepA_mal](process/guide.md#stepA)

Each make-a-lisp step has an associated architectural diagram. That elements
that are new for that step are highlighted in red.
Here is the final diagram for [step A](process/guide.md#stepA):

![stepA_mal architecture](process/stepA_mal.png)

If you are interesting in creating a mal implementation (or just
interested in using mal for something), please drop by the #mal
channel on freenode. In addition to the [make-a-lisp process
guide](process/guide.md) there is also a [mal/make-a-lisp
FAQ](docs/FAQ.md) where I attempt to answer some common questions.


## Presentations

Mal was presented publicly for the first time in a lightning talk at
Clojure West 2014 (unfortunately there is no video). See
examples/clojurewest2014.mal for the presentation that was given at the
conference (yes, the presentation is a mal program).

At Midwest.io 2015, Joel Martin gave a presentation on Mal titled
"Achievement Unlocked: A Better Path to Language Learning".
[Video](https://www.youtube.com/watch?v=lgyOAiRtZGw),
[Slides](http://kanaka.github.io/midwest.io.mal/).

More recently Joel gave a presentation on "Make Your Own Lisp Interpreter
in 10 Incremental Steps" at LambdaConf 2016:
[Part 1](https://www.youtube.com/watch?v=jVhupfthTEk),
[Part 2](https://www.youtube.com/watch?v=X5OQBMGpaTU),
[Part 3](https://www.youtube.com/watch?v=6mARZzGgX4U),
[Part 4](https://www.youtube.com/watch?v=dCO1SYR5kDU),
[Slides](http://kanaka.github.io/lambdaconf/).

## Building/running implementations

The simplest way to run any given implementation is to use docker.
Every implementation has a docker image pre-built with language
dependencies installed. You can launch the REPL using a convenient
target in the top level Makefile (where IMPL is the implementation
directory name and stepX is the step to run):

```
make DOCKERIZE=1 "repl^IMPL^stepX"
    # OR stepA is the default step:
make DOCKERIZE=1 "repl^IMPL"
```


### Ada

The Ada implementation was developed with GNAT 4.9 on debian. It also
compiles unchanged on windows if you have windows versions of git,
GNAT and (optionally) make.  There are no external dependencies
(readline not implemented).

```
cd ada
make
./stepX_YYY
```

### Ada.2

The second Ada implementation was developed with GNAT 8 and links with
the GNU readline library.

```
cd ada
make
./stepX_YYY
```

### GNU awk

The GNU awk implementation of mal has been tested with GNU awk 4.1.1.

```
cd gawk
gawk -O -f stepX_YYY.awk
```

### Bash 4

```
cd bash
bash stepX_YYY.sh
```

### BASIC (C64 and QBasic)

The BASIC implementation uses a preprocessor that can generate BASIC
code that is compatible with both C64 BASIC (CBM v2) and QBasic. The
C64 mode has been tested with
[cbmbasic](https://github.com/kanaka/cbmbasic) (the patched version is
currently required to fix issues with line input) and the QBasic mode
has been tested with [qb64](http://www.qb64.net/).

Generate C64 code and run it using cbmbasic:

```
cd basic
make stepX_YYY.bas
STEP=stepX_YYY ./run
```

Generate QBasic code and load it into qb64:

```
cd basic
make MODE=qbasic stepX_YYY.bas
./qb64 stepX_YYY.bas
```

Thanks to [Steven Syrek](https://github.com/sjsyrek) for the original
inspiration for this implementation.


### C

The C implementation of mal requires the following libraries (lib and
header packages): glib, libffi6, libgc, and either the libedit or GNU readline
library.

```
cd c
make
./stepX_YYY
```

### C++

The C++ implementation of mal requires g++-4.9 or clang++-3.5 and
a readline compatible library to build. See the `cpp/README.md` for
more details:

```
cd cpp
make
    # OR
make CXX=clang++-3.5
./stepX_YYY
```


### C# ###

The C# implementation of mal has been tested on Linux using the Mono
C# compiler (mcs) and the Mono runtime (version 2.10.8.1). Both are
required to build and run the C# implementation.

```
cd cs
make
mono ./stepX_YYY.exe
```

### ChucK

The ChucK implementation has been tested with ChucK 1.3.5.2.

```
cd chuck
./run
```

### Clojure

For the most part the Clojure implementation requires Clojure 1.5,
however, to pass all tests, Clojure 1.8.0-RC4 is required.

```
cd clojure
lein with-profile +stepX trampoline run
```

### CoffeeScript

```
sudo npm install -g coffee-script
cd coffee
coffee ./stepX_YYY
```

### Common Lisp

The implementation has been tested with SBCL, CCL, CMUCL, GNU CLISP, ECL and
Allegro CL on Ubuntu 16.04 and Ubuntu 12.04, see
the [README](common-lisp/README.org) for more details. Provided you have the
dependencies mentioned installed, do the following to run the implementation

```
cd common-lisp
make
./run
```

### Crystal

The Crystal implementation of mal has been tested with Crystal 0.26.1.

```
cd crystal
crystal run ./stepX_YYY.cr
    # OR
make   # needed to run tests
./stepX_YYY
```

### D

The D implementation of mal was tested with GDC 4.8.  It requires the GNU
readline library.

```
cd d
make
./stepX_YYY
```

### Dart

The Dart implementation has been tested with Dart 1.20.

```
cd dart
dart ./stepX_YYY
```

### Emacs Lisp

The Emacs Lisp implementation of mal has been tested with Emacs 24.3
and 24.5.  While there is very basic readline editing (`<backspace>`
and `C-d` work, `C-c` cancels the process), it is recommended to use
`rlwrap`.

```
cd elisp
emacs -Q --batch --load stepX_YYY.el
# with full readline support
rlwrap emacs -Q --batch --load stepX_YYY.el
```

### Elixir

The Elixir implementation of mal has been tested with Elixir 1.0.5.

```
cd elixir
mix stepX_YYY
# Or with readline/line editing functionality:
iex -S mix stepX_YYY
```

### Elm

The Elm implementation of mal has been tested with Elm 0.18.0

```
cd elm
make stepX_YYY.js
STEP=stepX_YYY ./run
```

### Erlang

The Erlang implementation of mal requires [Erlang/OTP R17](http://www.erlang.org/download.html)
and [rebar](https://github.com/rebar/rebar) to build.

```
cd erlang
make
    # OR
MAL_STEP=stepX_YYY rebar compile escriptize # build individual step
./stepX_YYY
```

### ES6 (ECMAScript 2015)

The ES6 / ECMAScript 2015 implementation uses the
[babel](https://babeljs.io) compiler to generate ES5 compatible
JavaScript. The generated code has been tested with Node 0.12.4.

```
cd es6
make
node build/stepX_YYY.js
```


### F# ###

The F# implementation of mal has been tested on Linux using the Mono
F# compiler (fsharpc) and the Mono runtime (version 3.12.1). The mono C#
compiler (mcs) is also necessary to compile the readline dependency. All are
required to build and run the F# implementation.

```
cd fsharp
make
mono ./stepX_YYY.exe
```

### Factor

The Factor implementation of mal has been tested with Factor 0.97
([factorcode.org](http://factorcode.org)).

```
cd factor
FACTOR_ROOTS=. factor -run=stepX_YYY
```

### Fantom

The Fantom implementation of mal has been tested with Fantom 1.0.70.

```
cd fantom
make lib/fan/stepX_YYY.pod
STEP=stepX_YYY ./run
```

### Forth

```
cd forth
gforth stepX_YYY.fs
```

### GNU Guile 2.1+

```
cd guile
guile -L ./ stepX_YYY.scm
```

### GNU Smalltalk

The Smalltalk implementation of mal has been tested with GNU Smalltalk 3.2.91.

```
cd gnu-smalltalk
./run
```

### Go

The Go implementation of mal requires that go is installed on on the
path. The implementation has been tested with Go 1.3.1.

```
cd go
make
./stepX_YYY
```


### Groovy

The Groovy implementation of mal requires Groovy to run and has been
tested with Groovy 1.8.6.

```
cd groovy
make
groovy ./stepX_YYY.groovy
```

### Haskell

The Haskell implementation requires the ghc compiler version 7.10.1 or
later and also the Haskell parsec and readline (or editline) packages.

```
cd haskell
make
./stepX_YYY
```

### Haxe (Neko, Python, C++ and JavaScript)

The Haxe implementation of mal requires Haxe version 3.2 to compile.
Four different Haxe targets are supported: Neko, Python, C++, and
JavaScript.

```
cd haxe
# Neko
make all-neko
neko ./stepX_YYY.n
# Python
make all-python
python3 ./stepX_YYY.py
# C++
make all-cpp
./cpp/stepX_YYY
# JavaScript
make all-js
node ./stepX_YYY.js
```

### Hy

The Hy implementation of mal has been tested with Hy 0.13.0.

```
cd hy
./stepX_YYY.hy
```

### Io

The Io implementation of mal has been tested with Io version 20110905.

```
cd io
io ./stepX_YYY.io
```

### Java 1.7

The Java implementation of mal requires maven2 to build.

```
cd java
mvn compile
mvn -quiet exec:java -Dexec.mainClass=mal.stepX_YYY
    # OR
mvn -quiet exec:java -Dexec.mainClass=mal.stepX_YYY -Dexec.args="CMDLINE_ARGS"
```

### JavaScript/Node

```
cd js
npm update
node stepX_YYY.js
```

### Julia

The Julia implementation of mal requires Julia 0.4.

```
cd julia
julia stepX_YYY.jl
```

### Kotlin

The Kotlin implementation of mal has been tested with Kotlin 1.0.

```
cd kotlin
make
java -jar stepX_YYY.jar
```

### LiveScript

The LiveScript implementation of mal has been tested with LiveScript 1.5.

```
cd livescript
make
node_modules/.bin/lsc stepX_YYY.ls
```

### Logo

The Logo implementation of mal has been tested with UCBLogo 6.0.

```
cd logo
logo stepX_YYY.lg
```

### Lua

The Lua implementation of mal has been tested with Lua 5.2. The
implementation requires that luarocks and the lua-rex-pcre library
are installed.

```
cd lua
make  # to build and link linenoise.so
./stepX_YYY.lua
```

### Mal

Running the mal implementation of mal involves running stepA of one of
the other implementations and passing the mal step to run as a command
line argument.

```
cd IMPL
IMPL_STEPA_CMD ../mal/stepX_YYY.mal

```

### GNU Make 3.81

```
cd make
make -f stepX_YYY.mk
```

### NASM

The NASM implementation of mal is written for x86-64 Linux, and has been tested
with Linux 3.16.0-4-amd64 and NASM version 2.11.05.

```
cd nasm
make
./stepX_YYY
```

### Nim 0.17.0

The Nim implementation of mal has been tested with Nim 0.17.0.

```
cd nim
make
  # OR
nimble build
./stepX_YYY
```

### Object Pascal

The Object Pascal implementation of mal has been built and tested on
Linux using the Free Pascal compiler version 2.6.2 and 2.6.4.

```
cd objpascal
make
./stepX_YYY
```

### Objective C

The Objective C implementation of mal has been built and tested on
Linux using clang/LLVM 3.6. It has also been built and tested on OS
X using XCode 7.

```
cd objc
make
./stepX_YYY
```

### OCaml 4.01.0

```
cd ocaml
make
./stepX_YYY
```

### MATLAB (GNU Octave and MATLAB)

The MatLab implementation has been tested with GNU Octave 4.2.1.
It has also been tested with MATLAB version R2014a on Linux. Note that
MATLAB is a commercial product.

```
cd matlab
./stepX_YYY
octave -q --no-gui --no-history --eval "stepX_YYY();quit;"
matlab -nodisplay -nosplash -nodesktop -nojvm -r "stepX_YYY();quit;"
    # OR with command line arguments
octave -q --no-gui --no-history --eval "stepX_YYY('arg1','arg2');quit;"
matlab -nodisplay -nosplash -nodesktop -nojvm -r "stepX_YYY('arg1','arg2');quit;"
```

### miniMAL

[miniMAL](https://github.com/kanaka/miniMAL) is small Lisp interpreter
implemented in less than 1024 bytes of JavaScript. To run the miniMAL
implementation of mal you need to download/install the miniMAL
interpreter (which requires Node.js).
```
cd miniMAL
# Download miniMAL and dependencies
npm install
export PATH=`pwd`/node_modules/minimal-lisp/:$PATH
# Now run mal implementation in miniMAL
miniMAL ./stepX_YYY
```

### Perl 5.8

For readline line editing support, install Term::ReadLine::Perl or
Term::ReadLine::Gnu from CPAN.

```
cd perl
perl stepX_YYY.pl
```

### Perl 6

The Perl 6 implementation was tested on Rakudo Perl 6 2016.04.

```
cd perl6
perl6 stepX_YYY.pl
```

### PHP 5.3

The PHP implementation of mal requires the php command line interface
to run.

```
cd php
php stepX_YYY.php
```

### Picolisp

The Picolisp implementation requires libreadline and Picolisp 3.1.11
or later.

```
cd picolisp
./run
```

### PL/pgSQL (Postgres SQL Procedural Language)

The PL/pgSQL implementation of mal requires a running Postgres server
(the "kanaka/mal-test-plpgsql" docker image automatically starts
a Postgres server). The implementation connects to the Postgres server
and create a database named "mal" to store tables and stored
procedures. The wrapper script uses the psql command to connect to the
server and defaults to the user "postgres" but this can be overridden
with the PSQL_USER environment variable. A password can be specified
using the PGPASSWORD environment variable. The implementation has been
tested with Postgres 9.4.

```
cd plpgsql
./wrap.sh stepX_YYY.sql
    # OR
PSQL_USER=myuser PGPASSWORD=mypass ./wrap.sh stepX_YYY.sql
```

### PL/SQL (Oracle SQL Procedural Language)

The PL/pgSQL implementation of mal requires a running Oracle DB
server (the "kanaka/mal-test-plsql" docker image automatically
starts an Oracle Express server). The implementation connects to the
Oracle server to create types, tables and stored procedures. The
default SQL*Plus logon value (username/password@connect_identifier) is
"system/oracle" but this can be overridden with the ORACLE_LOGON
environment variable. The implementation has been tested with Oracle
Express Edition 11g Release 2. Note that any SQL*Plus connection
warnings (user password expiration, etc) will interfere with the
ability of the wrapper script to communicate with the DB.

```
cd plsql
./wrap.sh stepX_YYY.sql
    # OR
ORACLE_LOGON=myuser/mypass@ORCL ./wrap.sh stepX_YYY.sql
```

### Postscript Level 2/3

The Postscript implementation of mal requires ghostscript to run. It
has been tested with ghostscript 9.10.

```
cd ps
gs -q -dNODISPLAY -I./ stepX_YYY.ps
```

### PowerShell

The PowerShell implementation of mal requires the PowerShell script
language. It has been tested with PowerShell 6.0.0 Alpha 9 on Linux.

```
cd powershell
powershell ./stepX_YYY.ps1
```

### Python (2.X and 3.X)

```
cd python
python stepX_YYY.py
```

### RPython

You must have [rpython](https://rpython.readthedocs.org/) on your path
(included with [pypy](https://bitbucket.org/pypy/pypy/)).

```
cd rpython
make        # this takes a very long time
./stepX_YYY
```

### R

The R implementation of mal requires R (r-base-core) to run.

```
cd r
make libs  # to download and build rdyncall
Rscript stepX_YYY.r
```

### Racket (5.3)

The Racket implementation of mal requires the Racket
compiler/interpreter to run.

```
cd racket
./stepX_YYY.rkt
```

### Rexx

The Rexx implementation of mal has been tested with Regina Rexx 3.6.

```
cd rexx
make
rexx -a ./stepX_YYY.rexxpp
```

### Ruby (1.9+)

```
cd ruby
ruby stepX_YYY.rb
```

### Rust (1.0.0 nightly)

The rust implementation of mal requires the rust compiler and build
tool (cargo) to build.

```
cd rust
cargo run --release --bin stepX_YYY
```

### Scala ###

Install scala and sbt (http://www.scala-sbt.org/0.13/tutorial/Installing-sbt-on-Linux.html):

```
cd scala
sbt 'run-main stepX_YYY'
    # OR
sbt compile
scala -classpath target/scala*/classes stepX_YYY
```

### Scheme (R7RS) ###

The Scheme implementation of mal has been tested with Chibi-Scheme
0.7.3, Kawa 2.4, Gauche 0.9.5, CHICKEN 4.11.0, Sagittarius 0.8.3,
Cyclone 0.6.3 (Git version) and Foment 0.4 (Git version).  You should
be able to get it running on other conforming R7RS implementations
after figuring out how libraries are loaded and adjusting the
`Makefile` and `run` script accordingly.

```
cd scheme
make symlinks
# chibi
scheme_MODE=chibi ./run
# kawa
make kawa
scheme_MODE=kawa ./run
# gauche
scheme_MODE=gauche ./run
# chicken
make chicken
scheme_MODE=chicken ./run
# sagittarius
scheme_MODE=sagittarius ./run
# cyclone
make cyclone
scheme_MODE=cyclone ./run
# foment
scheme_MODE=foment ./run
```

### Skew ###

The Skew implementation of mal has been tested with Skew 0.7.42.

```
cd skew
make
node stepX_YYY.js
```


### Swift

The Swift implementation of mal requires the Swift 2.0 compiler (XCode
7.0) to build. Older versions will not work due to changes in the
language and standard library.

```
cd swift
make
./stepX_YYY
```

### Swift 3

The Swift 3 implementation of mal requires the Swift 3.0 compiler. It
has been tested with Swift 3 Preview 3.

```
cd swift3
make
./stepX_YYY
```

### Swift 4

The Swift 4 implementation of mal requires the Swift 4.0 compiler. It
has been tested with Swift 4.2.3 release.

```
cd swift4
make
./stepX_YYY
```

### Tcl 8.6

The Tcl implementation of mal requires Tcl 8.6 to run.  For readline line
editing support, install tclreadline.

```
cd tcl
tclsh ./stepX_YYY.tcl
```

### TypeScript

The TypeScript implementation of mal requires the TypeScript 2.2 compiler.
It has been tested with Node.js v6.

```
cd ts
make
node ./stepX_YYY.js
```

### VHDL

The VHDL implementation of mal has been tested with GHDL 0.29.

```
cd vhdl
make
./run_vhdl.sh ./stepX_YYY
```

### Vimscript

The Vimscript implementation of mal requires Vim 8.0 to run.

```
cd vimscript
./run_vimscript.sh ./stepX_YYY.vim
```

### Visual Basic.NET ###

The VB.NET implementation of mal has been tested on Linux using the Mono
VB compiler (vbnc) and the Mono runtime (version 2.10.8.1). Both are
required to build and run the VB.NET implementation.

```
cd vb
make
mono ./stepX_YYY.exe
```

### WebAssembly (wasm) ###

The WebAssembly implementation is written in
[Wam](https://github.com/kanaka/wam) (WebAssembly Macro language) and
runs under the [wac/wace](https://github.com/kanaka/wac) WebAssembly
runtime.

```
cd wasm
make
wace ./stepX_YYY.wasm
```

### Yorick

The Yorick implementation of mal was tested on Yorick 2.2.04.

```
cd yorick
yorick -batch ./stepX_YYY.i
```



## Running tests

The top level Makefile has a number of useful targets to assist with
implementation development and testing. The `help` target provides
a list of the targets and options:

```
make help
```

### Functional tests

The are over 600 generic functional tests (for all implementations)
in the `tests/` directory. Each step has a corresponding test file
containing tests specific to that step. The `runtest.py` test harness
launches a Mal step implementation and then feeds the tests one at
a time to the implementation and compares the output/return value to
the expected output/return value.

* To run all the tests across all implementations (be prepared to wait):

```
make test
```

* To run all tests against a single implementation:

```
make "test^IMPL"

# e.g.
make "test^clojure"
make "test^js"
```

* To run tests for a single step against all implementations:

```
make "test^stepX"

# e.g.
make "test^step2"
make "test^step7"
```

* To run tests for a specific step against a single implementation:

```
make "test^IMPL^stepX"

# e.g
make "test^ruby^step3"
make "test^ps^step4"
```

### Self-hosted functional tests

* To run the functional tests in self-hosted mode, you specify `mal`
  as the test implementation and use the `MAL_IMPL` make variable
  to change the underlying host language (default is JavaScript):
```
make MAL_IMPL=IMPL "test^mal^step2"

# e.g.
make "test^mal^step2"   # js is default
make MAL_IMPL=ruby "test^mal^step2"
make MAL_IMPL=python "test^mal^step2"
```

### Starting the REPL

* To start the REPL of an implementation in a specific step:

```
make "repl^IMPL^stepX"

# e.g
make "repl^ruby^step3"
make "repl^ps^step4"
```

* If you omit the step, then `stepA` is used:

```
make "repl^IMPL"

# e.g
make "repl^ruby"
make "repl^ps"
```

* To start the REPL of the self-hosted implementation, specify `mal` as the
  REPL implementation and use the `MAL_IMPL` make variable to change the
  underlying host language (default is JavaScript):
```
make MAL_IMPL=IMPL "repl^mal^stepX"

# e.g.
make "repl^mal^step2"   # js is default
make MAL_IMPL=ruby "repl^mal^step2"
make MAL_IMPL=python "repl^mal"
```

### Performance tests

Warning: These performance tests are neither statistically valid nor
comprehensive; runtime performance is a not a primary goal of mal. If
you draw any serious conclusions from these performance tests, then
please contact me about some amazing oceanfront property in Kansas
that I'm willing to sell you for cheap.

* To run performance tests against a single implementation:
```
make "perf^IMPL"

# e.g.
make "perf^js"
```

* To run performance tests against all implementations:
```
make "perf"
```

### Generating language statistics

* To report line and byte statistics for a single implementation:
```
make "stats^IMPL"

# e.g.
make "stats^js"
```

* To report line and bytes statistics for general Lisp code (env, core
  and stepA):
```
make "stats-lisp^IMPL"

# e.g.
make "stats-lisp^js"
```

## Dockerized testing

Every implementation directory contains a Dockerfile to create
a docker image containing all the dependencies for that
implementation. In addition, the top-level Makefile contains support
for running the tests target (and perf, stats, repl, etc) within
a docker container for that implementation by passing *"DOCKERIZE=1"*
on the make command line. For example:

```
make DOCKERIZE=1 "test^js^step3"
```

Existing implementations already have docker images built and pushed
to the docker registry. However, if
you wish to build or rebuild a docker image locally, the toplevel
Makefile provides a rule for building docker images:

```
make "docker-build^IMPL"
```


**Notes**:
* Docker images are named *"kanaka/mal-test-IMPL"*
* JVM-based language implementations (Groovy, Java, Clojure, Scala):
  you will probably need to run this command once manually
  first `make DOCKERIZE=1 "repl^IMPL"` before you can run tests because
  runtime dependencies need to be downloaded to avoid the tests timing
  out. These dependencies are downloaded to dot-files in the /mal
  directory so they will persist between runs.


## External Implementations

The following implementations are maintained as separate projects:

### HolyC

* [by Alexander Bagnalla](https://github.com/bagnalla/holyc_mal)

### Rust

* [by Tim Morgan](https://github.com/seven1m/mal-rust)
* [by vi](https://github.com/vi/mal-rust-vi) - using [Pest](https://pest.rs/) grammar, not using typical Mal infrastructure (cargo-ized steps and built-in converted tests).


## Other mal Projects

 * [malc](https://github.com/dubek/malc) - Mal (Make A Lisp) compiler. Compiles a Mal program to LLVM assembly language, then binary.
 * [malcc](https://git.sr.ht/~tim/malcc) (@seven1m) - malcc is an incremental compiler implementation for the Mal language. It uses the Tiny C Compiler as the compiler backend and has full support for the Mal language, including macros, tail-call elimination, and even run-time eval. ["I Built a Lisp Compiler"](https://mpov.timmorgan.org/i-built-a-lisp-compiler/) post about the process.
 * [frock](https://github.com/chr15m/frock) - Clojure-flavoured PHP. Uses mal/php to run programs.

## License

Mal (make-a-lisp) is licensed under the MPL 2.0 (Mozilla Public
License 2.0). See LICENSE.txt for more details.

D mal/core.mal => mal/core.mal +0 -87
@@ 1,87 0,0 @@
(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* (condvar (gensym))
          `(let* (~condvar ~(first xs))
            (if ~condvar (and ~@(rest xs)) ~condvar)))))))

(defmacro! or
  (fn* (& xs)
    (if (empty? xs)
      nil
      (if (= 1 (count xs))
        (first xs)
        (let* (condvar (gensym))
          `(let* (~condvar ~(first xs))
             (if ~condvar ~condvar (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))))))

nil

D mal/mal/Dockerfile => mal/mal/Dockerfile +0 -34
@@ 1,34 0,0 @@
FROM ubuntu:18.04
MAINTAINER Joel Martin <github@martintribe.org>

##########################################################
# General requirements for testing or common across many
# implementations
##########################################################

RUN apt-get -y update

# Required for running tests
RUN apt-get -y install make python

# Some typical implementation and test requirements
RUN apt-get -y install curl libreadline-dev libedit-dev

RUN mkdir -p /mal
WORKDIR /mal

##########################################################
# Specific implementation requirements
##########################################################

# For building node modules
RUN apt-get -y install g++

# Add nodesource apt repo config for 10.x stable
RUN apt-get -y install gnupg
RUN curl -sL https://deb.nodesource.com/setup_10.x | bash -

# Install nodejs
RUN apt-get -y install nodejs

ENV NPM_CONFIG_CACHE /mal/.npm

D mal/mal/Makefile => mal/mal/Makefile +0 -7
@@ 1,7 0,0 @@
all: mal.mal

mal.mal: stepA_mal.mal
	cp $< $@

clean:
	rm -f mal.mal

D mal/mal/core.mal => mal/mal/core.mal +0 -80
@@ 1,80 0,0 @@
(def! _fn? (fn* [x]
  (if (fn? x)
    (if (get (meta x) "ismacro")
      false
      true)
    false)))

(def! macro? (fn* [x]
  (if (fn? x)
    (if (get (meta x) "ismacro")
      true
      false)
    false)))

(def! core_ns
  [["=" =]
   ["throw" throw]
   ["nil?" nil?]
   ["true?" true?]
   ["false?" false?]
   ["number?" number?]
   ["string?" string?]
   ["symbol" symbol]
   ["symbol?" symbol?]
   ["keyword" keyword]
   ["keyword?" keyword?]
   ["fn?" _fn?]
   ["macro?" macro?]

   ["pr-str" pr-str]
   ["str" str]
   ["prn" prn]
   ["println" println]
   ["readline" readline]
   ["read-string" read-string]
   ["slurp" slurp]
   ["<" <]
   ["<=" <=]
   [">" >]
   [">=" >=]
   ["+" +]
   ["-" -]
   ["*" *]
   ["/" /]
   ["time-ms" time-ms]

   ["list" list]
   ["list?" list?]
   ["vector" vector]
   ["vector?" vector?]
   ["hash-map" hash-map]
   ["map?" map?]
   ["assoc" assoc]
   ["dissoc" dissoc]
   ["get" get]
   ["contains?" contains?]
   ["keys" keys]
   ["vals" vals]

   ["sequential?" sequential?]
   ["cons" cons]
   ["concat" concat]
   ["nth" nth]
   ["first" first]
   ["rest" rest]
   ["empty?" empty?]
   ["count" count]
   ["apply" apply]
   ["map" map]

   ["conj" conj]
   ["seq" seq]

   ["with-meta" with-meta]
   ["meta" meta]
   ["atom" atom]
   ["atom?" atom?]
   ["deref" deref]
   ["reset!" reset!]
   ["swap!" swap!]])

D mal/mal/env.mal => mal/mal/env.mal +0 -40
@@ 1,40 0,0 @@
;; 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")

D mal/mal/run => mal/mal/run +0 -5
@@ 1,5 0,0 @@
#!/bin/bash
cd $(dirname $0)
MAL_FILE=./../mal/${STEP:-stepA_mal}.mal
export STEP=stepA_mal # force MAL_IMPL to use stepA
exec ./../${MAL_IMPL:-js}/run ${MAL_FILE} "${@}"

D mal/mal/step0_repl.mal => mal/mal/step0_repl.mal +0 -30
@@ 1,30 0,0 @@
;; read
(def! READ (fn* [strng]
  strng))

;; eval
(def! EVAL (fn* [ast env]
  ast))

;; print
(def! PRINT (fn* [exp] exp))

;; repl
(def! rep (fn* [strng]
  (PRINT (EVAL (READ strng) {}))))

;; repl loop
(def! repl-loop (fn* []
  (let* [line (readline "mal-user> ")]
    (if line
      (do
        (if (not (= "" line))
          (try*
            (println (rep line))
            (catch* exc
              (println "Uncaught exception:" exc))))
        (repl-loop))))))

(def! -main (fn* [& args] 
  (repl-loop)))
(-main)

D mal/mal/step1_read_print.mal => mal/mal/step1_read_print.mal +0 -30
@@ 1,30 0,0 @@
;; 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) {}))))

;; repl loop
(def! repl-loop (fn* []
  (let* [line (readline "mal-user> ")]
    (if line
      (do
        (if (not (= "" line))
          (try*
            (println (rep line))
            (catch* exc
              (println "Uncaught exception:" exc))))
        (repl-loop))))))

(def! -main (fn* [& args] 
  (repl-loop)))
(-main)

D mal/mal/step2_eval.mal => mal/mal/step2_eval.mal +0 -64
@@ 1,64 0,0 @@
;; 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) (let* [res (get env (str ast))]
                    (if res res (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
    (if (empty? ast)
      ast
      (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))))

;; repl loop
(def! repl-loop (fn* []
  (let* [line (readline "mal-user> ")]
    (if line
      (do
        (if (not (= "" line))
          (try*
            (println (rep line))
            (catch* exc
              (println "Uncaught exception:" exc))))
        (repl-loop))))))

(def! -main (fn* [& args] 
  (repl-loop)))
(-main)

D mal/mal/step3_env.mal => mal/mal/step3_env.mal +0 -85
@@ 1,85 0,0 @@
(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
        (nil? a0)
        ast

        (= '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))))

(env-set repl-env "+" +)
(env-set repl-env "-" -)
(env-set repl-env "*" *)
(env-set repl-env "/" /)

;; repl loop
(def! repl-loop (fn* []
  (let* [line (readline "mal-user> ")]
    (if line
      (do
        (if (not (= "" line))
          (try*
            (println (rep line))
            (catch* exc
              (println "Uncaught exception:" exc))))
        (repl-loop))))))

(def! -main (fn* [& args] 
  (repl-loop)))
(-main)

D mal/mal/step4_if_fn_do.mal => mal/mal/step4_if_fn_do.mal +0 -103
@@ 1,103 0,0 @@
(load-file "../mal/env.mal")
(load-file "../mal/core.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
        (nil? a0)
        ast

        (= '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))))

;; core.mal: defined directly using mal
(map (fn* [data] (env-set repl-env (nth data 0) (nth data 1))) core_ns)

;; core.mal: defined using the new language itself 
(rep "(def! not (fn* [a] (if a false true)))")

;; repl loop
(def! repl-loop (fn* []
  (let* [line (readline "mal-user> ")]
    (if line
      (do
        (if (not (= "" line))
          (try*
            (println (rep line))
            (catch* exc
              (println "Uncaught exception:" exc))))
        (repl-loop))))))

(def! -main (fn* [& args] 
  (repl-loop)))
(-main)

D mal/mal/step6_file.mal => mal/mal/step6_file.mal +0 -108
@@ 1,108 0,0 @@
(load-file "../mal/env.mal")
(load-file "../mal/core.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
        (nil? a0)