c/mal
coffee/mal.coffee
crystal/mal
+d/mal
haskell/mal
js/mal.js
js/web/mal.js
# Settings
#
-IMPLS = awk bash c clojure coffee cpp crystal cs erlang elixir es6 factor forth fsharp go groovy \
+IMPLS = awk bash c d clojure coffee cpp crystal cs erlang elixir es6 factor forth fsharp go groovy \
guile haskell java julia js kotlin lua make mal ocaml matlab miniMAL nim \
perl php ps python r racket rpython ruby rust scala swift tcl vb vimscript
EXCLUDE_TESTS += test^c^step5 # segfault
EXCLUDE_TESTS += test^cpp^step5 # completes at 10,000
EXCLUDE_TESTS += test^cs^step5 # fatal stack overflow fault
+EXCLUDE_TESTS += test^d^step5 # completes at 10,000, fatal stack overflow at 1,000,000
EXCLUDE_TESTS += test^erlang^step5 # erlang is TCO, test passes
EXCLUDE_TESTS += test^elixir^step5 # elixir is TCO, test passes
EXCLUDE_TESTS += test^fsharp^step5 # completes at 10,000, fatal stack overflow at 100,000
awk_STEP_TO_PROG = awk/$($(1)).awk
bash_STEP_TO_PROG = bash/$($(1)).sh
c_STEP_TO_PROG = c/$($(1))
+d_STEP_TO_PROG = d/$($(1))
clojure_STEP_TO_PROG = clojure/src/$($(1)).clj
coffee_STEP_TO_PROG = coffee/$($(1)).coffee
cpp_STEP_TO_PROG = cpp/$($(1))
awk_RUNSTEP = awk -O -f ../$(2) $(3)
bash_RUNSTEP = bash ../$(2) $(3)
c_RUNSTEP = ../$(2) $(3)
+d_RUNSTEP = ../$(2) $(3)
clojure_RUNSTEP = lein with-profile +$(1) trampoline run $(3)
coffee_RUNSTEP = coffee ../$(2) $(3)
cpp_RUNSTEP = ../$(2) $(3)
Mal is a Clojure inspired Lisp interpreter.
-Mal is implemented in 43 different languages:
+Mal is implemented in 44 different languages:
* GNU awk
* Bash shell
* Clojure
* CoffeeScript
* Crystal
+* D
* Elixir
* Erlang
* ES6 (ECMAScript 6 / ECMAScript 2015)
./stepX_YYY
```
+### D
+
+*The D implementation was created by [Dov Murik](https://github.com/dubek)*
+
+The D implementation of mal was tested with GDC 4.8. It requires the GNU
+readline library.
+
+```
+cd d
+make
+./stepX_YYY
+```
+
### Elixir
*The Elixir implementation was created by [Martin Ek (ekmartin)](https://github.com/ekmartin)*
--- /dev/null
+FROM ubuntu:vivid
+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
+##########################################################
+
+RUN apt-get -y install gdc
+
+ENV HOME /mal
--- /dev/null
+CFLAGS += -g -O2 -Wall
+LDFLAGS += -lreadline
+
+#####################
+
+TESTS =
+
+SOURCES_BASE = readline.d types.d reader.d printer.d
+SOURCES_LISP = env.d mal_core.d stepA_mal.d
+SOURCES = $(SOURCES_BASE) $(SOURCES_LISP)
+
+#####################
+
+EARLY_SRCS = step0_repl.d step1_read_print.d step2_eval.d
+LATE_SRCS = step3_env.d step4_if_fn_do.d step5_tco.d step6_file.d \
+ step7_quote.d step8_macros.d step9_try.d stepA_mal.d
+SRCS = $(EARLY_SRCS) $(LATE_SRCS)
+OBJS = $(SRCS:%.d=%.o)
+BINS = $(OBJS:%.o=%)
+EARLY_OBJS = types.o readline.o reader.o printer.o env.o
+OTHER_OBJS = $(EARLY_OBJS) mal_core.o
+EARLY_STEPS_BINS = $(EARLY_SRCS:%.d=%)
+LATE_STEPS_BINS = $(LATE_SRCS:%.d=%)
+
+#####################
+
+all: $(BINS) mal
+
+mal: $(word $(words $(BINS)),$(BINS))
+ cp $< $@
+
+$(OBJS) $(OTHER_OBJS): %.o: %.d
+ gdc $(CFLAGS) -c $(@:%.o=%.d) -o $@
+
+$(EARLY_STEPS_BINS): $(EARLY_OBJS)
+$(LATE_STEPS_BINS): $(OTHER_OBJS)
+
+$(BINS): %: %.o
+ gdc $+ -o $@ $(LDFLAGS)
+
+clean:
+ rm -f $(OBJS) $(BINS) $(OTHER_OBJS) mal
+
+.PHONY: stats stats-lisp tests $(TESTS)
+
+stats: $(SOURCES)
+ @wc $^
+ @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]"
+stats-lisp: $(SOURCES_LISP)
+ @wc $^
+ @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]"
+
+tests: $(TESTS)
+
+$(TESTS):
+ @echo "Running $@"; \
+ ./$@ || exit 1; \
--- /dev/null
+import types;
+
+class Env {
+ Env outer;
+ MalType[MalSymbol] data;
+
+ this(Env outer_v, MalType[] binds = [], MalType[] exprs = [])
+ {
+ outer = outer_v;
+ foreach (int i, MalType b; binds)
+ {
+ auto arg_name = verify_cast!MalSymbol(b);
+ if (arg_name.name == "&")
+ {
+ auto rest_arg_name = verify_cast!MalSymbol(binds[i + 1]);
+ auto rest_exprs = new MalList(exprs[i..$]);
+ set(rest_arg_name, rest_exprs);
+ break;
+ }
+ else
+ {
+ set(arg_name, exprs[i]);
+ }
+ }
+ }
+
+ MalType set(MalSymbol key, MalType val)
+ {
+ data[key] = val;
+ return val;
+ }
+
+ Env find(MalSymbol key)
+ {
+ auto val = (key in data);
+ if (val !is null) {
+ return this;
+ } else if (outer is null) {
+ return null;
+ } else {
+ return outer.find(key);
+ }
+ }
+
+ MalType get(MalSymbol key)
+ {
+ auto found = find(key);
+ if (found is null) {
+ throw new Exception("'" ~ key.print(true) ~ "' not found");
+ }
+ return found.data[key];
+ }
+}
--- /dev/null
+import types : MalType;
+import env : Env;
+
+MalType EVAL(MalType ast, Env env);
--- /dev/null
+import core.time;
+import std.algorithm;
+import std.array;
+import std.datetime;
+import std.file;
+import std.stdio;
+import env;
+import main;
+import reader;
+import readline;
+import types;
+import printer;
+
+static MalType mal_equal(MalType[] a ...)
+{
+ verify_args_count(a, 2);
+ return bool_to_mal(a[0] == a[1]);
+}
+
+static MalType mal_throw(MalType[] a ...)
+{
+ verify_args_count(a, 1);
+ throw new MalException(a[0]);
+}
+
+static MalType mal_symbol(MalType[] a ...)
+{
+ verify_args_count(a, 1);
+ auto s = verify_cast!MalString(a[0]);
+ return new MalSymbol(s.val);
+}
+
+static MalType mal_keyword(MalType[] a ...)
+{
+ verify_args_count(a, 1);
+ auto s = verify_cast!MalString(a[0]);
+ return new MalString("\u029e" ~ s.val);
+}
+
+static MalType mal_keyword_q(MalType[] a ...)
+{
+ verify_args_count(a, 1);
+ auto s = cast(MalString) a[0];
+ if (s is null) return mal_false;
+ return bool_to_mal(s.is_keyword());
+}
+
+static MalType mal_pr_str(MalType[] a ...)
+{
+ auto items_strs = a.map!(e => pr_str(e, true));
+ return new MalString(array(items_strs).join(" "));
+}
+
+static MalType mal_str(MalType[] a ...)
+{
+ auto items_strs = a.map!(e => pr_str(e, false));
+ return new MalString(array(items_strs).join(""));
+}
+
+static MalType mal_prn(MalType[] a ...)
+{
+ auto items_strs = a.map!(e => pr_str(e, true));
+ writeln(array(items_strs).join(" "));
+ return mal_nil;
+}
+
+static MalType mal_println(MalType[] a ...)
+{
+ auto items_strs = a.map!(e => pr_str(e, false));
+ writeln(array(items_strs).join(" "));
+ return mal_nil;
+}
+
+static MalType mal_read_string(MalType[] a ...)
+{
+ verify_args_count(a, 1);
+ auto s = verify_cast!MalString(a[0]);
+ return read_str(s.val);
+}
+
+static MalType mal_readline(MalType[] a ...)
+{
+ verify_args_count(a, 1);
+ auto s = verify_cast!MalString(a[0]);
+ auto line = _readline(s.val);
+ return line is null ? mal_nil : new MalString(line);
+}
+
+static MalType mal_slurp(MalType[] a ...)
+{
+ verify_args_count(a, 1);
+ auto filename = verify_cast!MalString(a[0]).val;
+ auto content = cast(string) std.file.read(filename);
+ return new MalString(content);
+}
+
+alias TwoIntFunc = MalType function(long x, long y);
+
+MalType binary_int_op(TwoIntFunc f, MalType[] a ...)
+{
+ verify_args_count(a, 2);
+ MalInteger i0 = verify_cast!MalInteger(a[0]);
+ MalInteger i1 = verify_cast!MalInteger(a[1]);
+ return f(i0.val, i1.val);
+}
+
+static MalType mal_time_ms(MalType[] a ...)
+{
+ immutable epoch = SysTime(unixTimeToStdTime(0));
+ immutable hnsecs_since_epoch = Clock.currTime(UTC()) - epoch;
+ immutable ms = hnsecs_since_epoch.total!"msecs"();
+ return new MalInteger(ms);
+}
+
+static bool is_nil(MalType v)
+{
+ return cast(MalNil)(v) !is null;
+}
+
+static MalType mal_assoc(MalType[] a ...)
+{
+ verify_min_args_count(a, 1);
+ auto hm = verify_cast!MalHashmap(a[0]);
+ auto new_hm = new MalHashmap(hm.data.dup);
+ new_hm.put_kv_list(a[1..$]);
+ return new_hm;
+}
+
+static MalType mal_dissoc(MalType[] a ...)
+{
+ verify_min_args_count(a, 1);
+ auto hm = verify_cast!MalHashmap(a[0]);
+ auto new_hm = new MalHashmap(hm.data.dup);
+ foreach (k; a[1..$])
+ {
+ new_hm.remove(k);
+ }
+ return new_hm;
+}
+
+static MalType mal_get(MalType[] a ...)
+{
+ verify_args_count(a, 2);
+ if (is_nil(a[0])) return mal_nil;
+ auto hm = verify_cast!MalHashmap(a[0]);
+ return hm.get(a[1]);
+}
+
+static MalType mal_contains_q(MalType[] a ...)
+{
+ verify_args_count(a, 2);
+ if (is_nil(a[0])) return mal_false;
+ auto hm = verify_cast!MalHashmap(a[0]);
+ return bool_to_mal(hm.contains(a[1]));
+}
+
+static MalType mal_keys(MalType[] a ...)
+{
+ verify_args_count(a, 1);
+ auto hm = verify_cast!MalHashmap(a[0]);
+ auto keys = hm.data.keys.map!(s => cast(MalType)(new MalString(s)));
+ return new MalList(array(keys));
+}
+
+static MalType mal_vals(MalType[] a ...)
+{
+ verify_args_count(a, 1);
+ auto hm = verify_cast!MalHashmap(a[0]);
+ return new MalList(hm.data.values);
+}
+
+static MalType mal_cons(MalType[] a ...)
+{
+ verify_args_count(a, 2);
+ auto lst = verify_cast!MalSequential(a[1]);
+ return new MalList([a[0]] ~ lst.elements);
+}
+
+static MalType mal_concat(MalType[] a ...)
+{
+ MalType[] res;
+ foreach (e; a)
+ {
+ auto lst = verify_cast!MalSequential(e);
+ res ~= lst.elements;
+ }
+ return new MalList(res);
+}
+
+static MalType mal_nth(MalType[] a ...)
+{
+ verify_args_count(a, 2);
+ if (is_nil(a[0]))
+ {
+ throw new Exception("nth: index out of range");
+ }
+ auto seq = verify_cast!MalSequential(a[0]);
+ auto index = verify_cast!MalInteger(a[1]).val;
+ if (index >= seq.elements.length)
+ {
+ throw new Exception("nth: index out of range");
+ }
+ return seq.elements[index];
+}
+
+static MalType mal_first(MalType[] a ...)
+{
+ verify_args_count(a, 1);
+ if (is_nil(a[0])) return mal_nil;
+ auto seq = verify_cast!MalSequential(a[0]);
+ if (seq.elements.length == 0) return mal_nil;
+ return seq.elements[0];
+}
+
+static MalType mal_rest(MalType[] a ...)
+{
+ verify_args_count(a, 1);
+ if (is_nil(a[0])) return new MalList([]);
+ auto seq = verify_cast!MalSequential(a[0]);
+ if (seq.elements.length == 0) return new MalList([]);
+ return new MalList(seq.elements[1..$]);
+}
+
+
+static MalType mal_empty_q(MalType[] a ...)
+{
+ verify_args_count(a, 1);
+ if (is_nil(a[0]))
+ {
+ return mal_true;
+ }
+ auto s = verify_cast!MalSequential(a[0]);
+ return bool_to_mal(s.elements.length == 0);
+}
+
+static MalType mal_count(MalType[] a ...)
+{
+ verify_args_count(a, 1);
+ if (is_nil(a[0]))
+ {
+ return new MalInteger(0);
+ }
+ auto s = verify_cast!MalSequential(a[0]);
+ return new MalInteger(cast(int)(s.elements.length));
+}
+
+static MalType mal_apply(MalType[] a ...)
+{
+ verify_min_args_count(a, 2);
+ auto last_seq_elems = verify_cast!MalSequential(a[$-1]).elements;
+ auto funcargs = a.length == 2 ? last_seq_elems : (a[1..$-1] ~ last_seq_elems);
+
+ auto builtinfn = cast(MalBuiltinFunc) a[0];
+ if (builtinfn !is null)
+ {
+ return builtinfn.fn(funcargs);
+ }
+
+ auto malfunc = verify_cast!MalFunc(a[0]);
+ auto callenv = new Env(malfunc.def_env, malfunc.arg_names, funcargs);
+
+ return EVAL(malfunc.func_body, callenv);
+}
+
+static MalType mal_map(MalType[] a ...)
+{
+ verify_args_count(a, 2);
+ auto seq = verify_cast!MalSequential(a[1]);
+ auto mapped_items = seq.elements.map!(e => mal_apply(a[0], new MalList([e])));
+ return new MalList(array(mapped_items));
+}
+
+static MalType mal_conj(MalType[] a ...)
+{
+ verify_min_args_count(a, 1);
+ auto seq = verify_cast!MalSequential(a[0]);
+ return reduce!((s,e) => s.conj(e))(seq, a[1..$]);
+}
+
+static MalType mal_meta(MalType[] a ...)
+{
+ verify_args_count(a, 1);
+ auto metaobj = cast(MalMeta) a[0];
+ if (metaobj is null) return mal_nil;
+ return metaobj.meta();
+}
+
+static MalType mal_with_meta(MalType[] a ...)
+{
+ verify_args_count(a, 2);
+ auto metaobj = cast(MalMeta) a[0];
+ if (metaobj is null) return a[0];
+ return metaobj.with_meta(a[1]);
+}
+
+static MalType mal_reset_bang(MalType[] a ...)
+{
+ verify_args_count(a, 2);
+ verify_cast!MalAtom(a[0]).val = a[1];
+ return a[1];
+}
+
+static MalType mal_swap_bang(MalType[] a ...)
+{
+ verify_min_args_count(a, 2);
+ auto atom = verify_cast!MalAtom(a[0]);
+ auto args = [atom.val] ~ a[2..$];
+ auto newval = mal_apply([a[1], new MalList(args)]);
+ return mal_reset_bang([atom, newval]);
+}
+
+BuiltinStaticFuncType[string] core_ns;
+
+static this()
+{
+ core_ns = [
+ "=": &mal_equal,
+ "throw": &mal_throw,
+
+ "nil?": (a ...) => mal_type_q!MalNil(a),
+ "true?": (a ...) => mal_type_q!MalTrue(a),
+ "false?": (a ...) => mal_type_q!MalFalse(a),
+ "symbol": &mal_symbol,
+ "symbol?": (a ...) => mal_type_q!MalSymbol(a),
+ "keyword": &mal_keyword,
+ "keyword?": &mal_keyword_q,
+
+ "pr-str": &mal_pr_str,
+ "str": &mal_str,
+ "prn": &mal_prn,
+ "println": &mal_println,
+ "read-string": &mal_read_string,
+ "readline": &mal_readline,
+ "slurp": &mal_slurp,
+
+ "<": (a ...) => binary_int_op((x,y) => bool_to_mal(x < y), a),
+ "<=": (a ...) => binary_int_op((x,y) => bool_to_mal(x <= y), a),
+ ">": (a ...) => binary_int_op((x,y) => bool_to_mal(x > y), a),
+ ">=": (a ...) => binary_int_op((x,y) => bool_to_mal(x >= y), a),
+ "+": (a ...) => binary_int_op((x,y) => new MalInteger(x + y), a),
+ "-": (a ...) => binary_int_op((x,y) => new MalInteger(x - y), a),
+ "*": (a ...) => binary_int_op((x,y) => new MalInteger(x * y), a),
+ "/": (a ...) => binary_int_op((x,y) => new MalInteger(x / y), a),
+ "time-ms": &mal_time_ms,
+
+ "list": (a ...) => new MalList(a),
+ "list?": (a ...) => mal_type_q!MalList(a),
+ "vector": (a ...) => new MalVector(a),
+ "vector?": (a ...) => mal_type_q!MalVector(a),
+ "hash-map": (a ...) => new MalHashmap(a),
+ "map?": (a ...) => mal_type_q!MalHashmap(a),
+ "assoc": &mal_assoc,
+ "dissoc": &mal_dissoc,
+ "get": &mal_get,
+ "contains?": &mal_contains_q,
+ "keys": &mal_keys,
+ "vals": &mal_vals,
+
+ "sequential?": (a ...) => mal_type_q!MalSequential(a),
+ "cons": &mal_cons,
+ "concat": &mal_concat,
+ "nth": &mal_nth,
+ "first": &mal_first,
+ "rest": &mal_rest,
+ "empty?": &mal_empty_q,
+ "count": &mal_count,
+ "apply": &mal_apply,
+ "map": &mal_map,
+
+ "conj": &mal_conj,
+
+ "meta": &mal_meta,
+ "with-meta": &mal_with_meta,
+ "atom": (a ...) => new MalAtom(verify_args_count(a, 1)[0]),
+ "atom?": (a ...) => mal_type_q!MalAtom(a),
+ "deref": (a ...) => verify_cast!MalAtom(verify_args_count(a, 1)[0]).val,
+ "reset!": &mal_reset_bang,
+ "swap!": &mal_swap_bang
+ ];
+}
--- /dev/null
+import types;
+
+string pr_str(MalType obj, bool readable = true)
+{
+ return obj.print(readable);
+}
--- /dev/null
+import std.array;
+import std.regex;
+import std.stdio;
+import types;
+
+MalSymbol sym_quote;
+MalSymbol sym_quasiquote;
+MalSymbol sym_unquote;
+MalSymbol sym_splice_unquote;
+MalSymbol sym_deref;
+MalSymbol sym_with_meta;
+
+static this()
+{
+ sym_quote = new MalSymbol("quote");
+ sym_quasiquote = new MalSymbol("quasiquote");
+ sym_unquote = new MalSymbol("unquote");
+ sym_splice_unquote = new MalSymbol("splice-unquote");
+ sym_deref = new MalSymbol("deref");
+ sym_with_meta = new MalSymbol("with-meta");
+}
+
+class Reader
+{
+ int pos = 0;
+ const string[] tokens;
+
+ this(string[] the_tokens)
+ {
+ tokens = the_tokens.dup;
+ }
+
+ string peek()
+ {
+ if (pos >= tokens.length) return null;
+ return tokens[pos];
+ }
+
+ string next()
+ {
+ auto token = peek();
+ pos++;
+ return token;
+ }
+}
+
+auto tokenize_ctr = ctRegex!(r"[\s,]*(~@|[\[\]{}()'`~^@]|" `"` `(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"` r"`,;)]*)");
+
+string[] tokenize(string str)
+{
+ string[] tokens;
+ foreach(c; matchAll(str, tokenize_ctr))
+ {
+ auto token = c[1];
+ if (token.length == 0) continue;
+ if (token[0] == ';') continue;
+ tokens ~= token;
+ }
+ return tokens;
+}
+
+MalString parse_string(string token)
+{
+ string unescaped =
+ token[1..$-1] // Remove surrounding quotes
+ .replace("\\n", "\n")
+ .replace("\\\"", "\"")
+ .replace("\\\\", "\\");
+ return new MalString(unescaped);
+}
+
+auto integer_ctr = ctRegex!(r"^-?[0-9]+$");
+
+MalType read_atom(Reader reader)
+{
+ auto token = reader.next();
+ switch (token)
+ {
+ case "nil": return mal_nil;
+ case "false": return mal_false;
+ case "true": return mal_true;
+ default:
+ switch (token[0]) {
+ case ':':
+ return new MalString("\u029e" ~ token[1..$]);
+ case '"':
+ return parse_string(token);
+ default:
+ auto captures = matchFirst(token, integer_ctr);
+ if (!captures.empty())
+ {
+ return new MalInteger(token);
+ }
+
+ return new MalSymbol(token);
+ }
+ }
+}
+
+MalType[] read_items(Reader reader, string start, string end)
+{
+ auto open_paren = reader.next();
+ if (open_paren != start) throw new Exception("expected '" ~ start ~ "'");
+
+ string token;
+ MalType[] res;
+ while ((token = reader.peek()) != end)
+ {
+ if (token is null)
+ {
+ throw new Exception("expected '" ~ end ~ "'");
+ }
+ res ~= read_form(reader);
+ }
+ reader.next(); // consume the ')'
+ return res;
+}
+
+MalList read_list(Reader reader)
+{
+ return new MalList(read_items(reader, "(", ")"));
+}
+
+MalVector read_vector(Reader reader)
+{
+ return new MalVector(read_items(reader, "[", "]"));
+}
+
+MalHashmap read_hashmap(Reader reader)
+{
+ return new MalHashmap(read_items(reader, "{", "}"));
+}
+
+MalList read_quote_shortcut(Reader reader, MalSymbol sym)
+{
+ reader.next(); // consume the special quote char
+ return new MalList([sym, read_form(reader)]);
+}
+
+MalType read_form(Reader reader)
+{
+ auto token = reader.peek();
+ if (token is null) return new MalNil();
+ switch(token)
+ {
+ case "'":
+ return read_quote_shortcut(reader, sym_quote);
+ case "`":
+ return read_quote_shortcut(reader, sym_quasiquote);
+ case "~":
+ return read_quote_shortcut(reader, sym_unquote);
+ case "~@":
+ return read_quote_shortcut(reader, sym_splice_unquote);
+ case "@":
+ return read_quote_shortcut(reader, sym_deref);
+ case "^":
+ reader.next(); // consume the caret char
+ auto meta = read_form(reader);
+ return new MalList([sym_with_meta, read_form(reader), meta]);
+ case "(":
+ return read_list(reader);
+ case ")":
+ throw new Exception("unexpected ')'");
+ case "[":
+ return read_vector(reader);
+ case "]":
+ throw new Exception("unexpected ']'");
+ case "{":
+ return read_hashmap(reader);
+ case "}":
+ throw new Exception("unexpected '}'");
+ default:
+ return read_atom(reader);
+ }
+}
+
+MalType read_str(string str)
+{
+ auto tokens = tokenize(str);
+ auto reader = new Reader(tokens);
+ return read_form(reader);
+}
--- /dev/null
+import std.string;
+import std.path;
+
+// readline/readline.h
+extern (C) char* readline(const char* prompt);
+
+// readline/history.h
+extern (C) void using_history();
+extern (C) void add_history(const char *line);
+extern (C) int read_history(const char *filename);
+extern (C) int append_history(int nelement, const char *filename);
+
+bool history_loaded = false;
+const string history_file = "~/.mal-history";
+
+void load_history()
+{
+ if (history_loaded) return;
+ using_history();
+ string hf = expandTilde(history_file);
+ std.file.append(hf, ""); // Create the file if needed
+ read_history(toStringz(hf));
+ history_loaded = true;
+}
+
+void append_to_history()
+{
+ string hf = expandTilde(history_file);
+ append_history(1, toStringz(hf));
+}
+
+// Convert from C-string to D-string (making a copy)
+pure string fromCstr(char* cstr)
+{
+ auto len = core.stdc.string.strlen(cstr);
+ if (len == 0) return "";
+ string line = cstr[0..len].dup;
+ return line;
+}
+
+string _readline(in string prompt)
+{
+ load_history();
+
+ auto cstr = readline(toStringz(prompt));
+ if (cstr is null) return null;
+ scope(exit) { core.stdc.stdlib.free(cstr); }
+
+ if (cstr[0] != '\0')
+ {
+ add_history(cstr); // Add input to in-memory history
+ append_to_history(); // Flush new line of history to disk
+ }
+
+ return fromCstr(cstr);
+}
--- /dev/null
+import std.stdio;
+import std.string;
+import readline;
+
+string READ(string str)
+{
+ return str;
+}
+
+string EVAL(string ast)
+{
+ return ast;
+}
+
+string PRINT(string ast)
+{
+ return ast;
+}
+
+string rep(string str)
+{
+ return PRINT(EVAL(READ(str)));
+}
+
+void main()
+{
+ for (;;)
+ {
+ string line = _readline("user> ");
+ if (line is null) break;
+ if (line.length == 0) continue;
+ writeln(rep(line));
+ }
+ writeln("");
+}
--- /dev/null
+import std.stdio;
+import std.string;
+import readline;
+import reader;
+import printer;
+import types;
+
+MalType READ(string str)
+{
+ return read_str(str);
+}
+
+MalType EVAL(MalType ast)
+{
+ return ast;
+}
+
+string PRINT(MalType ast)
+{
+ return pr_str(ast);
+}
+
+string rep(string str)
+{
+ return PRINT(EVAL(READ(str)));
+}
+
+void main()
+{
+ for (;;)
+ {
+ string line = _readline("user> ");
+ if (line is null) break;
+ if (line.length == 0) continue;
+ try
+ {
+ writeln(rep(line));
+ }
+ catch (Exception e)
+ {
+ writeln("Error: ", e.msg);
+ }
+ }
+ writeln("");
+}
--- /dev/null
+import std.algorithm;
+import std.array;
+import std.stdio;
+import std.string;
+import readline;
+import reader;
+import printer;
+import types;
+
+alias Env = MalType[string];
+
+MalType READ(string str)
+{
+ return read_str(str);
+}
+
+MalType eval_ast(MalType ast, Env env)
+{
+ if (typeid(ast) == typeid(MalSymbol))
+ {
+ MalSymbol sym = verify_cast!MalSymbol(ast);
+ auto v = (sym.name in env);
+ if (v is null) throw new Exception("'" ~ sym.name ~ "' not found");
+ return *v;
+ }
+ else if (typeid(ast) == typeid(MalList))
+ {
+ auto lst = verify_cast!MalList(ast);
+ auto el = array(lst.elements.map!(e => EVAL(e, env)));
+ return new MalList(el);
+ }
+ else if (typeid(ast) == typeid(MalVector))
+ {
+ auto lst = verify_cast!MalVector(ast);
+ auto el = array(lst.elements.map!(e => EVAL(e, env)));
+ return new MalVector(el);
+ }
+ else if (typeid(ast) == typeid(MalHashmap))
+ {
+ auto hm = verify_cast!MalHashmap(ast);
+ typeof(hm.data) new_data;
+ foreach (string k, MalType v; hm.data)
+ {
+ new_data[k] = EVAL(v, env);
+ }
+ return new MalHashmap(new_data);
+ }
+ else
+ {
+ return ast;
+ }
+}
+
+MalType EVAL(MalType ast, Env env)
+{
+ if (typeid(ast) != typeid(MalList))
+ {
+ return eval_ast(ast, env);
+ }
+
+ auto el = verify_cast!MalList(eval_ast(ast, env));
+ auto fobj = verify_cast!MalBuiltinFunc(el.elements[0]);
+ auto args = el.elements[1..$];
+ return fobj.fn(args);
+}
+
+string PRINT(MalType ast)
+{
+ return pr_str(ast);
+}
+
+string rep(string str, Env env)
+{
+ return PRINT(EVAL(READ(str), env));
+}
+
+static MalType mal_add(MalType[] a ...)
+{
+ verify_args_count(a, 2);
+ MalInteger i0 = verify_cast!MalInteger(a[0]);
+ MalInteger i1 = verify_cast!MalInteger(a[1]);
+ return new MalInteger(i0.val + i1.val);
+}
+
+static MalType mal_sub(MalType[] a ...)
+{
+ verify_args_count(a, 2);
+ MalInteger i0 = verify_cast!MalInteger(a[0]);
+ MalInteger i1 = verify_cast!MalInteger(a[1]);
+ return new MalInteger(i0.val - i1.val);
+}
+
+static MalType mal_mul(MalType[] a ...)
+{
+ verify_args_count(a, 2);
+ MalInteger i0 = verify_cast!MalInteger(a[0]);
+ MalInteger i1 = verify_cast!MalInteger(a[1]);
+ return new MalInteger(i0.val * i1.val);
+}
+
+static MalType mal_div(MalType[] a ...)
+{
+ verify_args_count(a, 2);
+ MalInteger i0 = verify_cast!MalInteger(a[0]);
+ MalInteger i1 = verify_cast!MalInteger(a[1]);
+ return new MalInteger(i0.val / i1.val);
+}
+
+void main()
+{
+ Env repl_env;
+ repl_env["+"] = new MalBuiltinFunc(&mal_add, "+");
+ repl_env["-"] = new MalBuiltinFunc(&mal_sub, "-");
+ repl_env["*"] = new MalBuiltinFunc(&mal_mul, "*");
+ repl_env["/"] = new MalBuiltinFunc(&mal_div, "/");
+
+ for (;;)
+ {
+ string line = _readline("user> ");
+ if (line is null) break;
+ if (line.length == 0) continue;
+ try
+ {
+ writeln(rep(line, repl_env));
+ }
+ catch (Exception e)
+ {
+ writeln("Error: ", e.msg);
+ }
+ }
+ writeln("");
+}
--- /dev/null
+module main;
+
+import std.algorithm;
+import std.array;
+import std.range;
+import std.stdio;
+import std.string;
+import env;
+import readline;
+import reader;
+import printer;
+import types;
+
+MalType READ(string str)
+{
+ return read_str(str);
+}
+
+MalType eval_ast(MalType ast, Env env)
+{
+ if (typeid(ast) == typeid(MalSymbol))
+ {
+ auto sym = verify_cast!MalSymbol(ast);
+ return env.get(sym);
+ }
+ else if (typeid(ast) == typeid(MalList))
+ {
+ auto lst = verify_cast!MalList(ast);
+ auto el = array(lst.elements.map!(e => EVAL(e, env)));
+ return new MalList(el);
+ }
+ else if (typeid(ast) == typeid(MalVector))
+ {
+ auto lst = verify_cast!MalVector(ast);
+ auto el = array(lst.elements.map!(e => EVAL(e, env)));
+ return new MalVector(el);
+ }
+ else if (typeid(ast) == typeid(MalHashmap))
+ {
+ auto hm = verify_cast!MalHashmap(ast);
+ typeof(hm.data) new_data;
+ foreach (string k, MalType v; hm.data)
+ {
+ new_data[k] = EVAL(v, env);
+ }
+ return new MalHashmap(new_data);
+ }
+ else
+ {
+ return ast;
+ }
+}
+
+MalType EVAL(MalType ast, Env env)
+{
+ MalList ast_list = cast(MalList) ast;
+ if (ast_list is null)
+ {
+ return eval_ast(ast, env);
+ }
+
+ auto a0_sym = verify_cast!MalSymbol(ast_list.elements[0]);
+ switch (a0_sym.name)
+ {
+ case "def!":
+ auto a1 = verify_cast!MalSymbol(ast_list.elements[1]);
+ return env.set(a1, EVAL(ast_list.elements[2], env));
+
+ case "let*":
+ auto a1 = verify_cast!MalSequential(ast_list.elements[1]);
+ auto let_env = new Env(env);
+ foreach (kv; chunks(a1.elements, 2))
+ {
+ if (kv.length < 2) throw new Exception("let* requires even number of elements");
+ auto var_name = verify_cast!MalSymbol(kv[0]);
+ let_env.set(var_name, EVAL(kv[1], let_env));
+ }
+ return EVAL(ast_list.elements[2], let_env);
+
+ default:
+ auto el = verify_cast!MalList(eval_ast(ast_list, env));
+ auto fobj = verify_cast!MalBuiltinFunc(el.elements[0]);
+ auto args = el.elements[1..$];
+ return fobj.fn(args);
+ }
+}
+
+string PRINT(MalType ast)
+{
+ return pr_str(ast);
+}
+
+string rep(string str, Env env)
+{
+ return PRINT(EVAL(READ(str), env));
+}
+
+static MalType mal_add(MalType[] a ...)
+{
+ verify_args_count(a, 2);
+ MalInteger i0 = verify_cast!MalInteger(a[0]);
+ MalInteger i1 = verify_cast!MalInteger(a[1]);
+ return new MalInteger(i0.val + i1.val);
+}
+
+static MalType mal_sub(MalType[] a ...)
+{
+ verify_args_count(a, 2);
+ MalInteger i0 = verify_cast!MalInteger(a[0]);
+ MalInteger i1 = verify_cast!MalInteger(a[1]);
+ return new MalInteger(i0.val - i1.val);
+}
+
+static MalType mal_mul(MalType[] a ...)
+{
+ verify_args_count(a, 2);
+ MalInteger i0 = verify_cast!MalInteger(a[0]);
+ MalInteger i1 = verify_cast!MalInteger(a[1]);
+ return new MalInteger(i0.val * i1.val);
+}
+
+static MalType mal_div(MalType[] a ...)
+{
+ verify_args_count(a, 2);
+ MalInteger i0 = verify_cast!MalInteger(a[0]);
+ MalInteger i1 = verify_cast!MalInteger(a[1]);
+ return new MalInteger(i0.val / i1.val);
+}
+
+void main()
+{
+ auto repl_env = new Env(null);
+ repl_env.set(new MalSymbol("+"), new MalBuiltinFunc(&mal_add, "+"));
+ repl_env.set(new MalSymbol("-"), new MalBuiltinFunc(&mal_sub, "-"));
+ repl_env.set(new MalSymbol("*"), new MalBuiltinFunc(&mal_mul, "*"));
+ repl_env.set(new MalSymbol("/"), new MalBuiltinFunc(&mal_div, "/"));
+
+ for (;;)
+ {
+ string line = _readline("user> ");
+ if (line is null) break;
+ if (line.length == 0) continue;
+ try
+ {
+ writeln(rep(line, repl_env));
+ }
+ catch (Exception e)
+ {
+ writeln("Error: ", e.msg);
+ }
+ }
+ writeln("");
+}
--- /dev/null
+module main;
+
+import std.algorithm;
+import std.array;
+import std.range;
+import std.stdio;
+import std.string;
+import env;
+import mal_core;
+import readline;
+import reader;
+import printer;
+import types;
+
+MalType READ(string str)
+{
+ return read_str(str);
+}
+
+MalType eval_ast(MalType ast, Env env)
+{
+ if (typeid(ast) == typeid(MalSymbol))
+ {
+ auto sym = verify_cast!MalSymbol(ast);
+ return env.get(sym);
+ }
+ else if (typeid(ast) == typeid(MalList))
+ {
+ auto lst = verify_cast!MalList(ast);
+ auto el = array(lst.elements.map!(e => EVAL(e, env)));
+ return new MalList(el);
+ }
+ else if (typeid(ast) == typeid(MalVector))
+ {
+ auto lst = verify_cast!MalVector(ast);
+ auto el = array(lst.elements.map!(e => EVAL(e, env)));
+ return new MalVector(el);
+ }
+ else if (typeid(ast) == typeid(MalHashmap))
+ {
+ auto hm = verify_cast!MalHashmap(ast);
+ typeof(hm.data) new_data;
+ foreach (string k, MalType v; hm.data)
+ {
+ new_data[k] = EVAL(v, env);
+ }
+ return new MalHashmap(new_data);
+ }
+ else
+ {
+ return ast;
+ }
+}
+
+MalType EVAL(MalType ast, Env env)
+{
+ MalList ast_list = cast(MalList) ast;
+ if (ast_list is null)
+ {
+ return eval_ast(ast, env);
+ }
+
+ auto aste = ast_list.elements;
+ auto a0_sym = cast(MalSymbol) aste[0];
+ auto sym_name = a0_sym is null ? "" : a0_sym.name;
+ switch (sym_name)
+ {
+ case "def!":
+ auto a1 = verify_cast!MalSymbol(aste[1]);
+ return env.set(a1, EVAL(aste[2], env));
+
+ case "let*":
+ auto a1 = verify_cast!MalSequential(aste[1]);
+ auto let_env = new Env(env);
+ foreach (kv; chunks(a1.elements, 2))
+ {
+ if (kv.length < 2) throw new Exception("let* requires even number of elements");
+ auto var_name = verify_cast!MalSymbol(kv[0]);
+ let_env.set(var_name, EVAL(kv[1], let_env));
+ }
+ return EVAL(aste[2], let_env);
+
+ case "do":
+ auto rest = new MalList(aste[1..$]);
+ auto el = verify_cast!MalList(eval_ast(rest, env));
+ return el.elements[$-1];
+
+ case "if":
+ auto cond = EVAL(aste[1], env);
+ if (cond.is_truthy())
+ return EVAL(aste[2], env);
+ else
+ if (aste.length > 3)
+ return EVAL(aste[3], env);
+ else
+ return mal_nil;
+
+ case "fn*":
+ auto args_list = verify_cast!MalSequential(aste[1]);
+ return new MalFunc(args_list.elements, aste[2], env);
+
+ default:
+ auto el = verify_cast!MalList(eval_ast(ast, env));
+ if (el.elements.length == 0)
+ {
+ throw new Exception("Expected a non-empty list");
+ }
+ auto first = el.elements[0];
+ auto rest = el.elements[1..$];
+ if (typeid(first) == typeid(MalFunc))
+ {
+ auto funcobj = verify_cast!MalFunc(first);
+ auto callenv = new Env(funcobj.def_env, funcobj.arg_names, rest);
+ return EVAL(funcobj.func_body, callenv);
+ }
+ else if (typeid(first) == typeid(MalBuiltinFunc))
+ {
+ auto builtinfuncobj = verify_cast!MalBuiltinFunc(first);
+ return builtinfuncobj.fn(rest);
+ }
+ else
+ {
+ throw new Exception("Expected a function");
+ }
+ }
+}
+
+string PRINT(MalType ast)
+{
+ return pr_str(ast);
+}
+
+MalType re(string str, Env env)
+{
+ return EVAL(READ(str), env);
+}
+
+string rep(string str, Env env)
+{
+ return PRINT(re(str, env));
+}
+
+void main()
+{
+ auto repl_env = new Env(null);
+ foreach (string sym_name, BuiltinStaticFuncType f; core_ns)
+ {
+ repl_env.set(new MalSymbol(sym_name), new MalBuiltinFunc(f, sym_name));
+ }
+
+ // core.mal: defined using the language itself
+ re("(def! not (fn* (a) (if a false true)))", repl_env);
+
+ for (;;)
+ {
+ string line = _readline("user> ");
+ if (line is null) break;
+ if (line.length == 0) continue;
+ try
+ {
+ writeln(rep(line, repl_env));
+ }
+ catch (Exception e)
+ {
+ writeln("Error: ", e.msg);
+ }
+ }
+ writeln("");
+}
--- /dev/null
+module main;
+
+import std.algorithm;
+import std.array;
+import std.range;
+import std.stdio;
+import std.string;
+import env;
+import mal_core;
+import readline;
+import reader;
+import printer;
+import types;
+
+MalType READ(string str)
+{
+ return read_str(str);
+}
+
+MalType eval_ast(MalType ast, Env env)
+{
+ if (typeid(ast) == typeid(MalSymbol))
+ {
+ auto sym = verify_cast!MalSymbol(ast);
+ return env.get(sym);
+ }
+ else if (typeid(ast) == typeid(MalList))
+ {
+ auto lst = verify_cast!MalList(ast);
+ auto el = array(lst.elements.map!(e => EVAL(e, env)));
+ return new MalList(el);
+ }
+ else if (typeid(ast) == typeid(MalVector))
+ {
+ auto lst = verify_cast!MalVector(ast);
+ auto el = array(lst.elements.map!(e => EVAL(e, env)));
+ return new MalVector(el);
+ }
+ else if (typeid(ast) == typeid(MalHashmap))
+ {
+ auto hm = verify_cast!MalHashmap(ast);
+ typeof(hm.data) new_data;
+ foreach (string k, MalType v; hm.data)
+ {
+ new_data[k] = EVAL(v, env);
+ }
+ return new MalHashmap(new_data);
+ }
+ else
+ {
+ return ast;
+ }
+}
+
+MalType EVAL(MalType ast, Env env)
+{
+ for (;;)
+ {
+ MalList ast_list = cast(MalList) ast;
+ if (ast_list is null)
+ {
+ return eval_ast(ast, env);
+ }
+
+ auto aste = ast_list.elements;
+ auto a0_sym = cast(MalSymbol) aste[0];
+ auto sym_name = a0_sym is null ? "" : a0_sym.name;
+ switch (sym_name)
+ {
+ case "def!":
+ auto a1 = verify_cast!MalSymbol(aste[1]);
+ return env.set(a1, EVAL(aste[2], env));
+
+ case "let*":
+ auto a1 = verify_cast!MalSequential(aste[1]);
+ auto let_env = new Env(env);
+ foreach (kv; chunks(a1.elements, 2))
+ {
+ if (kv.length < 2) throw new Exception("let* requires even number of elements");
+ auto var_name = verify_cast!MalSymbol(kv[0]);
+ let_env.set(var_name, EVAL(kv[1], let_env));
+ }
+ ast = aste[2];
+ env = let_env;
+ continue; // TCO
+
+ case "do":
+ auto all_but_last = new MalList(aste[1..$-1]);
+ eval_ast(all_but_last, env);
+ ast = aste[$-1];
+ continue; // TCO
+
+ case "if":
+ auto cond = EVAL(aste[1], env);
+ if (cond.is_truthy())
+ {
+ ast = aste[2];
+ continue; // TCO
+ }
+ else
+ if (aste.length > 3)
+ {
+ ast = aste[3];
+ continue; // TCO
+ }
+ else
+ {
+ return mal_nil;
+ }
+
+ case "fn*":
+ auto args_list = verify_cast!MalSequential(aste[1]);
+ return new MalFunc(args_list.elements, aste[2], env);
+
+ default:
+ auto el = verify_cast!MalList(eval_ast(ast, env));
+ if (el.elements.length == 0)
+ {
+ throw new Exception("Expected a non-empty list");
+ }
+ auto first = el.elements[0];
+ auto rest = el.elements[1..$];
+ if (typeid(first) == typeid(MalFunc))
+ {
+ auto funcobj = verify_cast!MalFunc(first);
+ auto callenv = new Env(funcobj.def_env, funcobj.arg_names, rest);
+ ast = funcobj.func_body;
+ env = callenv;
+ continue; // TCO
+ }
+ else if (typeid(first) == typeid(MalBuiltinFunc))
+ {
+ auto builtinfuncobj = verify_cast!MalBuiltinFunc(first);
+ return builtinfuncobj.fn(rest);
+ }
+ else
+ {
+ throw new Exception("Expected a function");
+ }
+ }
+ }
+}
+
+string PRINT(MalType ast)
+{
+ return pr_str(ast);
+}
+
+MalType re(string str, Env env)
+{
+ return EVAL(READ(str), env);
+}
+
+string rep(string str, Env env)
+{
+ return PRINT(re(str, env));
+}
+
+void main()
+{
+ auto repl_env = new Env(null);
+ foreach (string sym_name, BuiltinStaticFuncType f; core_ns)
+ {
+ repl_env.set(new MalSymbol(sym_name), new MalBuiltinFunc(f, sym_name));
+ }
+
+ // core.mal: defined using the language itself
+ re("(def! not (fn* (a) (if a false true)))", repl_env);
+
+ for (;;)
+ {
+ string line = _readline("user> ");
+ if (line is null) break;
+ if (line.length == 0) continue;
+ try
+ {
+ writeln(rep(line, repl_env));
+ }
+ catch (Exception e)
+ {
+ writeln("Error: ", e.msg);
+ }
+ }
+ writeln("");
+}
--- /dev/null
+module main;
+
+import std.algorithm;
+import std.array;
+import std.range;
+import std.stdio;
+import std.string;
+import std.c.process;
+import env;
+import mal_core;
+import readline;
+import reader;
+import printer;
+import types;
+
+MalType READ(string str)
+{
+ return read_str(str);
+}
+
+MalType eval_ast(MalType ast, Env env)
+{
+ if (typeid(ast) == typeid(MalSymbol))
+ {
+ auto sym = verify_cast!MalSymbol(ast);
+ return env.get(sym);
+ }
+ else if (typeid(ast) == typeid(MalList))
+ {
+ auto lst = verify_cast!MalList(ast);
+ auto el = array(lst.elements.map!(e => EVAL(e, env)));
+ return new MalList(el);
+ }
+ else if (typeid(ast) == typeid(MalVector))
+ {
+ auto lst = verify_cast!MalVector(ast);
+ auto el = array(lst.elements.map!(e => EVAL(e, env)));
+ return new MalVector(el);
+ }
+ else if (typeid(ast) == typeid(MalHashmap))
+ {
+ auto hm = verify_cast!MalHashmap(ast);
+ typeof(hm.data) new_data;
+ foreach (string k, MalType v; hm.data)
+ {
+ new_data[k] = EVAL(v, env);
+ }
+ return new MalHashmap(new_data);
+ }
+ else
+ {
+ return ast;
+ }
+}
+
+MalType EVAL(MalType ast, Env env)
+{
+ for (;;)
+ {
+ MalList ast_list = cast(MalList) ast;
+ if (ast_list is null)
+ {
+ return eval_ast(ast, env);
+ }
+
+ auto aste = ast_list.elements;
+ auto a0_sym = cast(MalSymbol) aste[0];
+ auto sym_name = a0_sym is null ? "" : a0_sym.name;
+ switch (sym_name)
+ {
+ case "def!":
+ auto a1 = verify_cast!MalSymbol(aste[1]);
+ return env.set(a1, EVAL(aste[2], env));
+
+ case "let*":
+ auto a1 = verify_cast!MalSequential(aste[1]);
+ auto let_env = new Env(env);
+ foreach (kv; chunks(a1.elements, 2))
+ {
+ if (kv.length < 2) throw new Exception("let* requires even number of elements");
+ auto var_name = verify_cast!MalSymbol(kv[0]);
+ let_env.set(var_name, EVAL(kv[1], let_env));
+ }
+ ast = aste[2];
+ env = let_env;
+ continue; // TCO
+
+ case "do":
+ auto all_but_last = new MalList(aste[1..$-1]);
+ eval_ast(all_but_last, env);
+ ast = aste[$-1];
+ continue; // TCO
+
+ case "if":
+ auto cond = EVAL(aste[1], env);
+ if (cond.is_truthy())
+ {
+ ast = aste[2];
+ continue; // TCO
+ }
+ else
+ if (aste.length > 3)
+ {
+ ast = aste[3];
+ continue; // TCO
+ }
+ else
+ {
+ return mal_nil;
+ }
+
+ case "fn*":
+ auto args_list = verify_cast!MalSequential(aste[1]);
+ return new MalFunc(args_list.elements, aste[2], env);
+
+ default:
+ auto el = verify_cast!MalList(eval_ast(ast, env));
+ if (el.elements.length == 0)
+ {
+ throw new Exception("Expected a non-empty list");
+ }
+ auto first = el.elements[0];
+ auto rest = el.elements[1..$];
+ if (typeid(first) == typeid(MalFunc))
+ {
+ auto funcobj = verify_cast!MalFunc(first);
+ auto callenv = new Env(funcobj.def_env, funcobj.arg_names, rest);
+ ast = funcobj.func_body;
+ env = callenv;
+ continue; // TCO
+ }
+ else if (typeid(first) == typeid(MalBuiltinFunc))
+ {
+ auto builtinfuncobj = verify_cast!MalBuiltinFunc(first);
+ return builtinfuncobj.fn(rest);
+ }
+ else
+ {
+ throw new Exception("Expected a function");
+ }
+ }
+ }
+}
+
+string PRINT(MalType ast)
+{
+ return pr_str(ast);
+}
+
+MalType re(string str, Env env)
+{
+ return EVAL(READ(str), env);
+}
+
+string rep(string str, Env env)
+{
+ return PRINT(re(str, env));
+}
+
+static MalList create_argv_list(string[] args)
+{
+ if (args.length <= 2) return new MalList([]);
+ return new MalList(array(args[2..$].map!(s => cast(MalType)(new MalString(s)))));
+}
+
+void main(string[] args)
+{
+ Env repl_env = new Env(null);
+ foreach (string sym_name, BuiltinStaticFuncType f; core_ns)
+ {
+ repl_env.set(new MalSymbol(sym_name), new MalBuiltinFunc(f, sym_name));
+ }
+
+ BuiltinFuncType eval_func = (a ...) {
+ verify_args_count(a, 1);
+ return EVAL(a[0], repl_env);
+ };
+ repl_env.set(new MalSymbol("eval"), new MalBuiltinFunc(eval_func, "eval"));
+ repl_env.set(new MalSymbol("*ARGV*"), create_argv_list(args));
+
+ // core.mal: defined using the language itself
+ re("(def! not (fn* (a) (if a false true)))", repl_env);
+ re("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", repl_env);
+
+ if (args.length > 1)
+ {
+ try
+ {
+ rep("(load-file \"" ~ args[1] ~ "\")", repl_env);
+ return;
+ }
+ catch (Exception e)
+ {
+ writeln("Error: ", e.msg);
+ std.c.process.exit(1);
+ }
+ }
+
+ for (;;)
+ {
+ string line = _readline("user> ");
+ if (line is null) break;
+ if (line.length == 0) continue;
+ try
+ {
+ writeln(rep(line, repl_env));
+ }
+ catch (Exception e)
+ {
+ writeln("Error: ", e.msg);
+ }
+ }
+ writeln("");
+}
--- /dev/null
+module main;
+
+import std.algorithm;
+import std.array;
+import std.range;
+import std.stdio;
+import std.string;
+import std.c.process;
+import env;
+import mal_core;
+import readline;
+import reader;
+import printer;
+import types;
+
+bool is_pair(MalType ast)
+{
+ auto lst = cast(MalSequential) ast;
+ if (lst is null) return false;
+ return lst.elements.length > 0;
+}
+
+MalType quasiquote(MalType ast)
+{
+ if (!is_pair(ast))
+ {
+ return new MalList([sym_quote, ast]);
+ }
+ auto ast_seq = verify_cast!MalSequential(ast);
+ auto aste = ast_seq.elements;
+ if (aste[0] == sym_unquote)
+ {
+ return aste[1];
+ }
+
+ if (is_pair(aste[0]))
+ {
+ auto ast0_seq = verify_cast!MalSequential(aste[0]);
+ if (ast0_seq.elements[0] == sym_splice_unquote)
+ {
+ return new MalList([new MalSymbol("concat"), ast0_seq.elements[1], quasiquote(new MalList(aste[1..$]))]);
+ }
+ }
+
+ return new MalList([new MalSymbol("cons"), quasiquote(aste[0]), quasiquote(new MalList(aste[1..$]))]);
+}
+
+MalType READ(string str)
+{
+ return read_str(str);
+}
+
+MalType eval_ast(MalType ast, Env env)
+{
+ if (typeid(ast) == typeid(MalSymbol))
+ {
+ auto sym = verify_cast!MalSymbol(ast);
+ return env.get(sym);
+ }
+ else if (typeid(ast) == typeid(MalList))
+ {
+ auto lst = verify_cast!MalList(ast);
+ auto el = array(lst.elements.map!(e => EVAL(e, env)));
+ return new MalList(el);
+ }
+ else if (typeid(ast) == typeid(MalVector))
+ {
+ auto lst = verify_cast!MalVector(ast);
+ auto el = array(lst.elements.map!(e => EVAL(e, env)));
+ return new MalVector(el);
+ }
+ else if (typeid(ast) == typeid(MalHashmap))
+ {
+ auto hm = verify_cast!MalHashmap(ast);
+ typeof(hm.data) new_data;
+ foreach (string k, MalType v; hm.data)
+ {
+ new_data[k] = EVAL(v, env);
+ }
+ return new MalHashmap(new_data);
+ }
+ else
+ {
+ return ast;
+ }
+}
+
+MalType EVAL(MalType ast, Env env)
+{
+ for (;;)
+ {
+ MalList ast_list = cast(MalList) ast;
+ if (ast_list is null)
+ {
+ return eval_ast(ast, env);
+ }
+
+ auto aste = ast_list.elements;
+ auto a0_sym = cast(MalSymbol) aste[0];
+ auto sym_name = a0_sym is null ? "" : a0_sym.name;
+ switch (sym_name)
+ {
+ case "def!":
+ auto a1 = verify_cast!MalSymbol(aste[1]);
+ return env.set(a1, EVAL(aste[2], env));
+
+ case "let*":
+ auto a1 = verify_cast!MalSequential(aste[1]);
+ auto let_env = new Env(env);
+ foreach (kv; chunks(a1.elements, 2))
+ {
+ if (kv.length < 2) throw new Exception("let* requires even number of elements");
+ auto var_name = verify_cast!MalSymbol(kv[0]);
+ let_env.set(var_name, EVAL(kv[1], let_env));
+ }
+ ast = aste[2];
+ env = let_env;
+ continue; // TCO
+
+ case "quote":
+ return aste[1];
+
+ case "quasiquote":
+ ast = quasiquote(aste[1]);
+ continue; // TCO
+
+ case "do":
+ auto all_but_last = new MalList(aste[1..$-1]);
+ eval_ast(all_but_last, env);
+ ast = aste[$-1];
+ continue; // TCO
+
+ case "if":
+ auto cond = EVAL(aste[1], env);
+ if (cond.is_truthy())
+ {
+ ast = aste[2];
+ continue; // TCO
+ }
+ else
+ if (aste.length > 3)
+ {
+ ast = aste[3];
+ continue; // TCO
+ }
+ else
+ {
+ return mal_nil;
+ }
+
+ case "fn*":
+ auto args_list = verify_cast!MalSequential(aste[1]);
+ return new MalFunc(args_list.elements, aste[2], env);
+
+ default:
+ auto el = verify_cast!MalList(eval_ast(ast, env));
+ if (el.elements.length == 0)
+ {
+ throw new Exception("Expected a non-empty list");
+ }
+ auto first = el.elements[0];
+ auto rest = el.elements[1..$];
+ if (typeid(first) == typeid(MalFunc))
+ {
+ auto funcobj = verify_cast!MalFunc(first);
+ auto callenv = new Env(funcobj.def_env, funcobj.arg_names, rest);
+ ast = funcobj.func_body;
+ env = callenv;
+ continue; // TCO
+ }
+ else if (typeid(first) == typeid(MalBuiltinFunc))
+ {
+ auto builtinfuncobj = verify_cast!MalBuiltinFunc(first);
+ return builtinfuncobj.fn(rest);
+ }
+ else
+ {
+ throw new Exception("Expected a function");
+ }
+ }
+ }
+}
+
+string PRINT(MalType ast)
+{
+ return pr_str(ast);
+}
+
+MalType re(string str, Env env)
+{
+ return EVAL(READ(str), env);
+}
+
+string rep(string str, Env env)
+{
+ return PRINT(re(str, env));
+}
+
+static MalList create_argv_list(string[] args)
+{
+ if (args.length <= 2) return new MalList([]);
+ return new MalList(array(args[2..$].map!(s => cast(MalType)(new MalString(s)))));
+}
+
+void main(string[] args)
+{
+ Env repl_env = new Env(null);
+ foreach (string sym_name, BuiltinStaticFuncType f; core_ns)
+ {
+ repl_env.set(new MalSymbol(sym_name), new MalBuiltinFunc(f, sym_name));
+ }
+
+ BuiltinFuncType eval_func = (a ...) {
+ verify_args_count(a, 1);
+ return EVAL(a[0], repl_env);
+ };
+ repl_env.set(new MalSymbol("eval"), new MalBuiltinFunc(eval_func, "eval"));
+ repl_env.set(new MalSymbol("*ARGV*"), create_argv_list(args));
+
+ // core.mal: defined using the language itself
+ re("(def! not (fn* (a) (if a false true)))", repl_env);
+ re("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", repl_env);
+
+ if (args.length > 1)
+ {
+ try
+ {
+ rep("(load-file \"" ~ args[1] ~ "\")", repl_env);
+ return;
+ }
+ catch (Exception e)
+ {
+ writeln("Error: ", e.msg);
+ std.c.process.exit(1);
+ }
+ }
+
+ for (;;)
+ {
+ string line = _readline("user> ");
+ if (line is null) break;
+ if (line.length == 0) continue;
+ try
+ {
+ writeln(rep(line, repl_env));
+ }
+ catch (Exception e)
+ {
+ writeln("Error: ", e.msg);
+ }
+ }
+ writeln("");
+}
--- /dev/null
+module main;
+
+import std.algorithm;
+import std.array;
+import std.range;
+import std.stdio;
+import std.string;
+import std.c.process;
+import env;
+import mal_core;
+import readline;
+import reader;
+import printer;
+import types;
+
+bool is_pair(MalType ast)
+{
+ auto lst = cast(MalSequential) ast;
+ if (lst is null) return false;
+ return lst.elements.length > 0;
+}
+
+MalType quasiquote(MalType ast)
+{
+ if (!is_pair(ast))
+ {
+ return new MalList([sym_quote, ast]);
+ }
+ auto ast_seq = verify_cast!MalSequential(ast);
+ auto aste = ast_seq.elements;
+ if (aste[0] == sym_unquote)
+ {
+ return aste[1];
+ }
+
+ if (is_pair(aste[0]))
+ {
+ auto ast0_seq = verify_cast!MalSequential(aste[0]);
+ if (ast0_seq.elements[0] == sym_splice_unquote)
+ {
+ return new MalList([new MalSymbol("concat"), ast0_seq.elements[1], quasiquote(new MalList(aste[1..$]))]);
+ }
+ }
+
+ return new MalList([new MalSymbol("cons"), quasiquote(aste[0]), quasiquote(new MalList(aste[1..$]))]);
+}
+
+bool is_macro_call(MalType ast, Env env)
+{
+ auto lst = cast(MalList) ast;
+ if (lst is null) return false;
+ if (lst.elements.length == 0) return false;
+ auto sym0 = cast(MalSymbol) lst.elements[0];
+ if (sym0 is null) return false;
+ if (env.find(sym0) is null) return false;
+ auto val = env.get(sym0);
+ auto val_func = cast(MalFunc) val;
+ if (val_func is null) return false;
+ return val_func.is_macro;
+}
+
+MalType macroexpand(MalType ast, Env env)
+{
+ while (is_macro_call(ast, env))
+ {
+ auto ast_list = verify_cast!MalList(ast);
+ auto sym0 = verify_cast!MalSymbol(ast_list.elements[0]);
+ auto macrofunc = verify_cast!MalFunc(env.get(sym0));
+ auto rest = ast_list.elements[1..$];
+ auto callenv = new Env(macrofunc.def_env, macrofunc.arg_names, rest);
+ ast = EVAL(macrofunc.func_body, callenv);
+ }
+ return ast;
+}
+
+MalType READ(string str)
+{
+ return read_str(str);
+}
+
+MalType eval_ast(MalType ast, Env env)
+{
+ if (typeid(ast) == typeid(MalSymbol))
+ {
+ auto sym = verify_cast!MalSymbol(ast);
+ return env.get(sym);
+ }
+ else if (typeid(ast) == typeid(MalList))
+ {
+ auto lst = verify_cast!MalList(ast);
+ auto el = array(lst.elements.map!(e => EVAL(e, env)));
+ return new MalList(el);
+ }
+ else if (typeid(ast) == typeid(MalVector))
+ {
+ auto lst = verify_cast!MalVector(ast);
+ auto el = array(lst.elements.map!(e => EVAL(e, env)));
+ return new MalVector(el);
+ }
+ else if (typeid(ast) == typeid(MalHashmap))
+ {
+ auto hm = verify_cast!MalHashmap(ast);
+ typeof(hm.data) new_data;
+ foreach (string k, MalType v; hm.data)
+ {
+ new_data[k] = EVAL(v, env);
+ }
+ return new MalHashmap(new_data);
+ }
+ else
+ {
+ return ast;
+ }
+}
+
+MalType EVAL(MalType ast, Env env)
+{
+ for (;;)
+ {
+ MalList ast_list = cast(MalList) ast;
+ if (ast_list is null)
+ {
+ return eval_ast(ast, env);
+ }
+
+ ast = macroexpand(ast, env);
+ ast_list = cast(MalList) ast;
+ if (ast_list is null)
+ {
+ return ast;
+ }
+
+ auto aste = ast_list.elements;
+ auto a0_sym = cast(MalSymbol) aste[0];
+ auto sym_name = a0_sym is null ? "" : a0_sym.name;
+ switch (sym_name)
+ {
+ case "def!":
+ auto a1 = verify_cast!MalSymbol(aste[1]);
+ return env.set(a1, EVAL(aste[2], env));
+
+ case "let*":
+ auto a1 = verify_cast!MalSequential(aste[1]);
+ auto let_env = new Env(env);
+ foreach (kv; chunks(a1.elements, 2))
+ {
+ if (kv.length < 2) throw new Exception("let* requires even number of elements");
+ auto var_name = verify_cast!MalSymbol(kv[0]);
+ let_env.set(var_name, EVAL(kv[1], let_env));
+ }
+ ast = aste[2];
+ env = let_env;
+ continue; // TCO
+
+ case "quote":
+ return aste[1];
+
+ case "quasiquote":
+ ast = quasiquote(aste[1]);
+ continue; // TCO
+
+ case "defmacro!":
+ auto a1 = verify_cast!MalSymbol(aste[1]);
+ auto mac = verify_cast!MalFunc(EVAL(aste[2], env));
+ mac.is_macro = true;
+ return env.set(a1, mac);
+
+ case "macroexpand":
+ return macroexpand(aste[1], env);
+
+ case "do":
+ auto all_but_last = new MalList(aste[1..$-1]);
+ eval_ast(all_but_last, env);
+ ast = aste[$-1];
+ continue; // TCO
+
+ case "if":
+ auto cond = EVAL(aste[1], env);
+ if (cond.is_truthy())
+ {
+ ast = aste[2];
+ continue; // TCO
+ }
+ else
+ if (aste.length > 3)
+ {
+ ast = aste[3];
+ continue; // TCO
+ }
+ else
+ {
+ return mal_nil;
+ }
+
+ case "fn*":
+ auto args_list = verify_cast!MalSequential(aste[1]);
+ return new MalFunc(args_list.elements, aste[2], env);
+
+ default:
+ auto el = verify_cast!MalList(eval_ast(ast, env));
+ if (el.elements.length == 0)
+ {
+ throw new Exception("Expected a non-empty list");
+ }
+ auto first = el.elements[0];
+ auto rest = el.elements[1..$];
+ if (typeid(first) == typeid(MalFunc))
+ {
+ auto funcobj = verify_cast!MalFunc(first);
+ auto callenv = new Env(funcobj.def_env, funcobj.arg_names, rest);
+ ast = funcobj.func_body;
+ env = callenv;
+ continue; // TCO
+ }
+ else if (typeid(first) == typeid(MalBuiltinFunc))
+ {
+ auto builtinfuncobj = verify_cast!MalBuiltinFunc(first);
+ return builtinfuncobj.fn(rest);
+ }
+ else
+ {
+ throw new Exception("Expected a function");
+ }
+ }
+ }
+}
+
+string PRINT(MalType ast)
+{
+ return pr_str(ast);
+}
+
+MalType re(string str, Env env)
+{
+ return EVAL(READ(str), env);
+}
+
+string rep(string str, Env env)
+{
+ return PRINT(re(str, env));
+}
+
+static MalList create_argv_list(string[] args)
+{
+ if (args.length <= 2) return new MalList([]);
+ return new MalList(array(args[2..$].map!(s => cast(MalType)(new MalString(s)))));
+}
+
+void main(string[] args)
+{
+ Env repl_env = new Env(null);
+ foreach (string sym_name, BuiltinStaticFuncType f; core_ns)
+ {
+ repl_env.set(new MalSymbol(sym_name), new MalBuiltinFunc(f, sym_name));
+ }
+
+ BuiltinFuncType eval_func = (a ...) {
+ verify_args_count(a, 1);
+ return EVAL(a[0], repl_env);
+ };
+ repl_env.set(new MalSymbol("eval"), new MalBuiltinFunc(eval_func, "eval"));
+ repl_env.set(new MalSymbol("*ARGV*"), create_argv_list(args));
+
+ // core.mal: defined using the language itself
+ re("(def! not (fn* (a) (if a false true)))", repl_env);
+ re("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", repl_env);
+ re("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))", repl_env);
+ re("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))", repl_env);
+
+ if (args.length > 1)
+ {
+ try
+ {
+ rep("(load-file \"" ~ args[1] ~ "\")", repl_env);
+ return;
+ }
+ catch (Exception e)
+ {
+ writeln("Error: ", e.msg);
+ std.c.process.exit(1);
+ }
+ }
+
+ for (;;)
+ {
+ string line = _readline("user> ");
+ if (line is null) break;
+ if (line.length == 0) continue;
+ try
+ {
+ writeln(rep(line, repl_env));
+ }
+ catch (Exception e)
+ {
+ writeln("Error: ", e.msg);
+ }
+ }
+ writeln("");
+}
--- /dev/null
+module main;
+
+import std.algorithm;
+import std.array;
+import std.range;
+import std.stdio;
+import std.string;
+import std.c.process;
+import env;
+import mal_core;
+import readline;
+import reader;
+import printer;
+import types;
+
+bool is_pair(MalType ast)
+{
+ auto lst = cast(MalSequential) ast;
+ if (lst is null) return false;
+ return lst.elements.length > 0;
+}
+
+MalType quasiquote(MalType ast)
+{
+ if (!is_pair(ast))
+ {
+ return new MalList([sym_quote, ast]);
+ }
+ auto ast_seq = verify_cast!MalSequential(ast);
+ auto aste = ast_seq.elements;
+ if (aste[0] == sym_unquote)
+ {
+ return aste[1];
+ }
+
+ if (is_pair(aste[0]))
+ {
+ auto ast0_seq = verify_cast!MalSequential(aste[0]);
+ if (ast0_seq.elements[0] == sym_splice_unquote)
+ {
+ return new MalList([new MalSymbol("concat"), ast0_seq.elements[1], quasiquote(new MalList(aste[1..$]))]);
+ }
+ }
+
+ return new MalList([new MalSymbol("cons"), quasiquote(aste[0]), quasiquote(new MalList(aste[1..$]))]);
+}
+
+bool is_macro_call(MalType ast, Env env)
+{
+ auto lst = cast(MalList) ast;
+ if (lst is null) return false;
+ if (lst.elements.length == 0) return false;
+ auto sym0 = cast(MalSymbol) lst.elements[0];
+ if (sym0 is null) return false;
+ if (env.find(sym0) is null) return false;
+ auto val = env.get(sym0);
+ auto val_func = cast(MalFunc) val;
+ if (val_func is null) return false;
+ return val_func.is_macro;
+}
+
+MalType macroexpand(MalType ast, Env env)
+{
+ while (is_macro_call(ast, env))
+ {
+ auto ast_list = verify_cast!MalList(ast);
+ auto sym0 = verify_cast!MalSymbol(ast_list.elements[0]);
+ auto macrofunc = verify_cast!MalFunc(env.get(sym0));
+ auto rest = ast_list.elements[1..$];
+ auto callenv = new Env(macrofunc.def_env, macrofunc.arg_names, rest);
+ ast = EVAL(macrofunc.func_body, callenv);
+ }
+ return ast;
+}
+
+MalType READ(string str)
+{
+ return read_str(str);
+}
+
+MalType eval_ast(MalType ast, Env env)
+{
+ if (typeid(ast) == typeid(MalSymbol))
+ {
+ auto sym = verify_cast!MalSymbol(ast);
+ return env.get(sym);
+ }
+ else if (typeid(ast) == typeid(MalList))
+ {
+ auto lst = verify_cast!MalList(ast);
+ auto el = array(lst.elements.map!(e => EVAL(e, env)));
+ return new MalList(el);
+ }
+ else if (typeid(ast) == typeid(MalVector))
+ {
+ auto lst = verify_cast!MalVector(ast);
+ auto el = array(lst.elements.map!(e => EVAL(e, env)));
+ return new MalVector(el);
+ }
+ else if (typeid(ast) == typeid(MalHashmap))
+ {
+ auto hm = verify_cast!MalHashmap(ast);
+ typeof(hm.data) new_data;
+ foreach (string k, MalType v; hm.data)
+ {
+ new_data[k] = EVAL(v, env);
+ }
+ return new MalHashmap(new_data);
+ }
+ else
+ {
+ return ast;
+ }
+}
+
+MalType EVAL(MalType ast, Env env)
+{
+ for (;;)
+ {
+ MalList ast_list = cast(MalList) ast;
+ if (ast_list is null)
+ {
+ return eval_ast(ast, env);
+ }
+
+ ast = macroexpand(ast, env);
+ ast_list = cast(MalList) ast;
+ if (ast_list is null)
+ {
+ return ast;
+ }
+
+ auto aste = ast_list.elements;
+ auto a0_sym = cast(MalSymbol) aste[0];
+ auto sym_name = a0_sym is null ? "" : a0_sym.name;
+ switch (sym_name)
+ {
+ case "def!":
+ auto a1 = verify_cast!MalSymbol(aste[1]);
+ return env.set(a1, EVAL(aste[2], env));
+
+ case "let*":
+ auto a1 = verify_cast!MalSequential(aste[1]);
+ auto let_env = new Env(env);
+ foreach (kv; chunks(a1.elements, 2))
+ {
+ if (kv.length < 2) throw new Exception("let* requires even number of elements");
+ auto var_name = verify_cast!MalSymbol(kv[0]);
+ let_env.set(var_name, EVAL(kv[1], let_env));
+ }
+ ast = aste[2];
+ env = let_env;
+ continue; // TCO
+
+ case "quote":
+ return aste[1];
+
+ case "quasiquote":
+ ast = quasiquote(aste[1]);
+ continue; // TCO
+
+ case "defmacro!":
+ auto a1 = verify_cast!MalSymbol(aste[1]);
+ auto mac = verify_cast!MalFunc(EVAL(aste[2], env));
+ mac.is_macro = true;
+ return env.set(a1, mac);
+
+ case "macroexpand":
+ return macroexpand(aste[1], env);
+
+ case "try*":
+ MalType exc;
+ try
+ {
+ return EVAL(aste[1], env);
+ }
+ catch (MalException e)
+ {
+ exc = e.data;
+ }
+ catch (Exception e)
+ {
+ exc = new MalString(e.msg);
+ }
+ if (aste.length < 3) return mal_nil;
+ auto catch_clause = verify_cast!MalList(aste[2]);
+ auto catch_env = new Env(env, [catch_clause.elements[1]], [exc]);
+ return EVAL(catch_clause.elements[2], catch_env);
+
+ case "do":
+ auto all_but_last = new MalList(aste[1..$-1]);
+ eval_ast(all_but_last, env);
+ ast = aste[$-1];
+ continue; // TCO
+
+ case "if":
+ auto cond = EVAL(aste[1], env);
+ if (cond.is_truthy())
+ {
+ ast = aste[2];
+ continue; // TCO
+ }
+ else
+ if (aste.length > 3)
+ {
+ ast = aste[3];
+ continue; // TCO
+ }
+ else
+ {
+ return mal_nil;
+ }
+
+ case "fn*":
+ auto args_list = verify_cast!MalSequential(aste[1]);
+ return new MalFunc(args_list.elements, aste[2], env);
+
+ default:
+ auto el = verify_cast!MalList(eval_ast(ast, env));
+ if (el.elements.length == 0)
+ {
+ throw new Exception("Expected a non-empty list");
+ }
+ auto first = el.elements[0];
+ auto rest = el.elements[1..$];
+ if (typeid(first) == typeid(MalFunc))
+ {
+ auto funcobj = verify_cast!MalFunc(first);
+ auto callenv = new Env(funcobj.def_env, funcobj.arg_names, rest);
+ ast = funcobj.func_body;
+ env = callenv;
+ continue; // TCO
+ }
+ else if (typeid(first) == typeid(MalBuiltinFunc))
+ {
+ auto builtinfuncobj = verify_cast!MalBuiltinFunc(first);
+ return builtinfuncobj.fn(rest);
+ }
+ else
+ {
+ throw new Exception("Expected a function");
+ }
+ }
+ }
+}
+
+string PRINT(MalType ast)
+{
+ return pr_str(ast);
+}
+
+MalType re(string str, Env env)
+{
+ return EVAL(READ(str), env);
+}
+
+string rep(string str, Env env)
+{
+ return PRINT(re(str, env));
+}
+
+static MalList create_argv_list(string[] args)
+{
+ if (args.length <= 2) return new MalList([]);
+ return new MalList(array(args[2..$].map!(s => cast(MalType)(new MalString(s)))));
+}
+
+void main(string[] args)
+{
+ Env repl_env = new Env(null);
+ foreach (string sym_name, BuiltinStaticFuncType f; core_ns)
+ {
+ repl_env.set(new MalSymbol(sym_name), new MalBuiltinFunc(f, sym_name));
+ }
+
+ BuiltinFuncType eval_func = (a ...) {
+ verify_args_count(a, 1);
+ return EVAL(a[0], repl_env);
+ };
+ repl_env.set(new MalSymbol("eval"), new MalBuiltinFunc(eval_func, "eval"));
+ repl_env.set(new MalSymbol("*ARGV*"), create_argv_list(args));
+
+ // core.mal: defined using the language itself
+ re("(def! not (fn* (a) (if a false true)))", repl_env);
+ re("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", repl_env);
+ re("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))", repl_env);
+ re("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))", repl_env);
+
+ if (args.length > 1)
+ {
+ try
+ {
+ rep("(load-file \"" ~ args[1] ~ "\")", repl_env);
+ return;
+ }
+ catch (Exception e)
+ {
+ writeln("Error: ", e.msg);
+ std.c.process.exit(1);
+ }
+ }
+
+ for (;;)
+ {
+ string line = _readline("user> ");
+ if (line is null) break;
+ if (line.length == 0) continue;
+ try
+ {
+ writeln(rep(line, repl_env));
+ }
+ catch (Exception e)
+ {
+ writeln("Error: ", e.msg);
+ }
+ }
+ writeln("");
+}
--- /dev/null
+module main;
+
+import std.algorithm;
+import std.array;
+import std.range;
+import std.stdio;
+import std.string;
+import std.c.process;
+import env;
+import mal_core;
+import readline;
+import reader;
+import printer;
+import types;
+
+bool is_pair(MalType ast)
+{
+ auto lst = cast(MalSequential) ast;
+ if (lst is null) return false;
+ return lst.elements.length > 0;
+}
+
+MalType quasiquote(MalType ast)
+{
+ if (!is_pair(ast))
+ {
+ return new MalList([sym_quote, ast]);
+ }
+ auto ast_seq = verify_cast!MalSequential(ast);
+ auto aste = ast_seq.elements;
+ if (aste[0] == sym_unquote)
+ {
+ return aste[1];
+ }
+
+ if (is_pair(aste[0]))
+ {
+ auto ast0_seq = verify_cast!MalSequential(aste[0]);
+ if (ast0_seq.elements[0] == sym_splice_unquote)
+ {
+ return new MalList([new MalSymbol("concat"), ast0_seq.elements[1], quasiquote(new MalList(aste[1..$]))]);
+ }
+ }
+
+ return new MalList([new MalSymbol("cons"), quasiquote(aste[0]), quasiquote(new MalList(aste[1..$]))]);
+}
+
+bool is_macro_call(MalType ast, Env env)
+{
+ auto lst = cast(MalList) ast;
+ if (lst is null) return false;
+ if (lst.elements.length == 0) return false;
+ auto sym0 = cast(MalSymbol) lst.elements[0];
+ if (sym0 is null) return false;
+ if (env.find(sym0) is null) return false;
+ auto val = env.get(sym0);
+ auto val_func = cast(MalFunc) val;
+ if (val_func is null) return false;
+ return val_func.is_macro;
+}
+
+MalType macroexpand(MalType ast, Env env)
+{
+ while (is_macro_call(ast, env))
+ {
+ auto ast_list = verify_cast!MalList(ast);
+ auto sym0 = verify_cast!MalSymbol(ast_list.elements[0]);
+ auto macrofunc = verify_cast!MalFunc(env.get(sym0));
+ auto rest = ast_list.elements[1..$];
+ auto callenv = new Env(macrofunc.def_env, macrofunc.arg_names, rest);
+ ast = EVAL(macrofunc.func_body, callenv);
+ }
+ return ast;
+}
+
+MalType READ(string str)
+{
+ return read_str(str);
+}
+
+MalType eval_ast(MalType ast, Env env)
+{
+ if (typeid(ast) == typeid(MalSymbol))
+ {
+ auto sym = verify_cast!MalSymbol(ast);
+ return env.get(sym);
+ }
+ else if (typeid(ast) == typeid(MalList))
+ {
+ auto lst = verify_cast!MalList(ast);
+ auto el = array(lst.elements.map!(e => EVAL(e, env)));
+ return new MalList(el);
+ }
+ else if (typeid(ast) == typeid(MalVector))
+ {
+ auto lst = verify_cast!MalVector(ast);
+ auto el = array(lst.elements.map!(e => EVAL(e, env)));
+ return new MalVector(el);
+ }
+ else if (typeid(ast) == typeid(MalHashmap))
+ {
+ auto hm = verify_cast!MalHashmap(ast);
+ typeof(hm.data) new_data;
+ foreach (string k, MalType v; hm.data)
+ {
+ new_data[k] = EVAL(v, env);
+ }
+ return new MalHashmap(new_data);
+ }
+ else
+ {
+ return ast;
+ }
+}
+
+MalType EVAL(MalType ast, Env env)
+{
+ for (;;)
+ {
+ MalList ast_list = cast(MalList) ast;
+ if (ast_list is null)
+ {
+ return eval_ast(ast, env);
+ }
+
+ ast = macroexpand(ast, env);
+ ast_list = cast(MalList) ast;
+ if (ast_list is null)
+ {
+ return ast;
+ }
+
+ auto aste = ast_list.elements;
+ auto a0_sym = cast(MalSymbol) aste[0];
+ auto sym_name = a0_sym is null ? "" : a0_sym.name;
+ switch (sym_name)
+ {
+ case "def!":
+ auto a1 = verify_cast!MalSymbol(aste[1]);
+ return env.set(a1, EVAL(aste[2], env));
+
+ case "let*":
+ auto a1 = verify_cast!MalSequential(aste[1]);
+ auto let_env = new Env(env);
+ foreach (kv; chunks(a1.elements, 2))
+ {
+ if (kv.length < 2) throw new Exception("let* requires even number of elements");
+ auto var_name = verify_cast!MalSymbol(kv[0]);
+ let_env.set(var_name, EVAL(kv[1], let_env));
+ }
+ ast = aste[2];
+ env = let_env;
+ continue; // TCO
+
+ case "quote":
+ return aste[1];
+
+ case "quasiquote":
+ ast = quasiquote(aste[1]);
+ continue; // TCO
+
+ case "defmacro!":
+ auto a1 = verify_cast!MalSymbol(aste[1]);
+ auto mac = verify_cast!MalFunc(EVAL(aste[2], env));
+ mac.is_macro = true;
+ return env.set(a1, mac);
+
+ case "macroexpand":
+ return macroexpand(aste[1], env);
+
+ case "try*":
+ MalType exc;
+ try
+ {
+ return EVAL(aste[1], env);
+ }
+ catch (MalException e)
+ {
+ exc = e.data;
+ }
+ catch (Exception e)
+ {
+ exc = new MalString(e.msg);
+ }
+ if (aste.length < 3) return mal_nil;
+ auto catch_clause = verify_cast!MalList(aste[2]);
+ auto catch_env = new Env(env, [catch_clause.elements[1]], [exc]);
+ return EVAL(catch_clause.elements[2], catch_env);
+
+ case "do":
+ auto all_but_last = new MalList(aste[1..$-1]);
+ eval_ast(all_but_last, env);
+ ast = aste[$-1];
+ continue; // TCO
+
+ case "if":
+ auto cond = EVAL(aste[1], env);
+ if (cond.is_truthy())
+ {
+ ast = aste[2];
+ continue; // TCO
+ }
+ else
+ if (aste.length > 3)
+ {
+ ast = aste[3];
+ continue; // TCO
+ }
+ else
+ {
+ return mal_nil;
+ }
+
+ case "fn*":
+ auto args_list = verify_cast!MalSequential(aste[1]);
+ return new MalFunc(args_list.elements, aste[2], env);
+
+ default:
+ auto el = verify_cast!MalList(eval_ast(ast, env));
+ if (el.elements.length == 0)
+ {
+ throw new Exception("Expected a non-empty list");
+ }
+ auto first = el.elements[0];
+ auto rest = el.elements[1..$];
+ if (typeid(first) == typeid(MalFunc))
+ {
+ auto funcobj = verify_cast!MalFunc(first);
+ auto callenv = new Env(funcobj.def_env, funcobj.arg_names, rest);
+ ast = funcobj.func_body;
+ env = callenv;
+ continue; // TCO
+ }
+ else if (typeid(first) == typeid(MalBuiltinFunc))
+ {
+ auto builtinfuncobj = verify_cast!MalBuiltinFunc(first);
+ return builtinfuncobj.fn(rest);
+ }
+ else
+ {
+ throw new Exception("Expected a function");
+ }
+ }
+ }
+}
+
+string PRINT(MalType ast)
+{
+ return pr_str(ast);
+}
+
+MalType re(string str, Env env)
+{
+ return EVAL(READ(str), env);
+}
+
+string rep(string str, Env env)
+{
+ return PRINT(re(str, env));
+}
+
+static MalList create_argv_list(string[] args)
+{
+ if (args.length <= 2) return new MalList([]);
+ return new MalList(array(args[2..$].map!(s => cast(MalType)(new MalString(s)))));
+}
+
+void main(string[] args)
+{
+ Env repl_env = new Env(null);
+ foreach (string sym_name, BuiltinStaticFuncType f; core_ns)
+ {
+ repl_env.set(new MalSymbol(sym_name), new MalBuiltinFunc(f, sym_name));
+ }
+
+ BuiltinFuncType eval_func = (a ...) {
+ verify_args_count(a, 1);
+ return EVAL(a[0], repl_env);
+ };
+ repl_env.set(new MalSymbol("eval"), new MalBuiltinFunc(eval_func, "eval"));
+ repl_env.set(new MalSymbol("*ARGV*"), create_argv_list(args));
+
+ // core.mal: defined using the language itself
+ re("(def! *host-language* \"d\")", repl_env);
+ re("(def! not (fn* (a) (if a false true)))", repl_env);
+ re("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))", repl_env);
+ re("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))", repl_env);
+ re("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))", repl_env);
+
+ if (args.length > 1)
+ {
+ try
+ {
+ rep("(load-file \"" ~ args[1] ~ "\")", repl_env);
+ return;
+ }
+ catch (Exception e)
+ {
+ writeln("Error: ", e.msg);
+ std.c.process.exit(1);
+ }
+ }
+
+ re("(println (str \"Mal [\" *host-language* \"]\"))", repl_env);
+ for (;;)
+ {
+ string line = _readline("user> ");
+ if (line is null) break;
+ if (line.length == 0) continue;
+ try
+ {
+ writeln(rep(line, repl_env));
+ }
+ catch (Exception e)
+ {
+ writeln("Error: ", e.msg);
+ }
+ }
+ writeln("");
+}
--- /dev/null
+import std.algorithm;
+import std.array;
+import std.conv;
+import std.functional;
+import std.range;
+import env;
+
+abstract class MalType
+{
+ string print(bool readable) const;
+ bool is_truthy() const { return true; }
+}
+
+interface MalMeta
+{
+ MalType meta();
+ MalType with_meta(MalType new_meta);
+}
+
+class MalNil : MalType
+{
+ override string print(bool readable) const { return "nil"; }
+ override bool is_truthy() const { return false; }
+ override bool opEquals(Object o) { return (cast(MalNil)(o) !is null); }
+}
+
+class MalFalse : MalType
+{
+ override string print(bool readable) const { return "false"; }
+ override bool is_truthy() const { return false; }
+ override bool opEquals(Object o) { return (cast(MalFalse)(o) !is null); }
+}
+
+class MalTrue : MalType
+{
+ override string print(bool readable) const { return "true"; }
+ override bool opEquals(Object o) { return (cast(MalTrue)(o) !is null); }
+}
+
+MalNil mal_nil;
+MalFalse mal_false;
+MalTrue mal_true;
+
+static this()
+{
+ mal_nil = new MalNil;
+ mal_false = new MalFalse;
+ mal_true = new MalTrue;
+}
+
+MalType bool_to_mal(in bool b)
+{
+ return b ? mal_true : mal_false;
+}
+
+class MalSymbol : MalType
+{
+ const string name;
+ this(in string token) { name = token; }
+ override string print(bool readable) const { return name; }
+
+ override size_t toHash()
+ {
+ return typeid(name).getHash(&name);
+ }
+
+ override int opCmp(Object other)
+ {
+ MalSymbol o = cast(MalSymbol) other;
+ return cmp(name, o.name);
+ }
+
+ override bool opEquals(Object other)
+ {
+ auto o = cast(MalSymbol) other;
+ return (o !is null && name == o.name);
+ }
+}
+
+class MalInteger : MalType
+{
+ const long val;
+ this(string token) { val = to!long(token); }
+ this(long v) { val = v; }
+ override string print(bool readable) const { return to!string(val); }
+
+ override bool opEquals(Object o)
+ {
+ auto oint = cast(MalInteger)(o);
+ return (oint !is null && val == oint.val);
+ }
+}
+
+class MalString : MalType
+{
+ const string val;
+ this(in string token) { val = token; }
+ override string print(bool readable) const
+ {
+ if (is_keyword()) return ":" ~ val[2..$];
+ if (readable)
+ {
+ string escaped = val.replace("\\", "\\\\")
+ .replace("\"", "\\\"")
+ .replace("\n", "\\n");
+ return "\"" ~ escaped ~ "\"";
+ }
+ else
+ {
+ return val;
+ }
+ }
+
+ bool is_keyword() const
+ {
+ return val.length > 1 && val[0..2] == "\u029e";
+ }
+
+ override bool opEquals(Object o)
+ {
+ auto ostr = cast(MalString)(o);
+ return (ostr !is null && val == ostr.val);
+ }
+}
+
+abstract class MalSequential : MalType, MalMeta
+{
+ MalType[] elements;
+ MalType meta_val;
+
+ this(MalType[] lst) {
+ elements = lst;
+ meta_val = mal_nil;
+ }
+
+ override bool opEquals(Object o)
+ {
+ auto oseq = cast(MalSequential)(o);
+ return (oseq !is null && elements == oseq.elements);
+ }
+
+ MalSequential conj(MalType element);
+}
+
+class MalList : MalSequential, MalMeta
+{
+ this(MalType[] lst) { super(lst); }
+ this(MalList that, MalType new_meta)
+ {
+ super(that.elements);
+ meta_val = new_meta;
+ }
+
+ override string print(bool readable) const
+ {
+ auto items_strs = elements.map!(e => e.print(readable));
+ return "(" ~ array(items_strs).join(" ") ~ ")";
+ }
+
+ override MalSequential conj(MalType element)
+ {
+ return new MalList([element] ~ elements);
+ }
+
+ override MalType meta() { return meta_val; }
+ override MalType with_meta(MalType new_meta)
+ {
+ return new MalList(this, new_meta);
+ }
+}
+
+class MalVector : MalSequential, MalMeta
+{
+ this(MalType[] lst) { super(lst); }
+ this(MalVector that, MalType new_meta)
+ {
+ super(that.elements);
+ meta_val = new_meta;
+ }
+
+ override string print(bool readable) const
+ {
+ auto items_strs = elements.map!(e => e.print(readable));
+ return "[" ~ array(items_strs).join(" ") ~ "]";
+ }
+
+ override MalSequential conj(MalType element)
+ {
+ return new MalVector(elements ~ [element]);
+ }
+
+ override MalType meta() { return meta_val; }
+ override MalType with_meta(MalType new_meta)
+ {
+ return new MalVector(this, new_meta);
+ }
+}
+
+class MalHashmap : MalType, MalMeta
+{
+ MalType[string] data;
+ MalType meta_val;
+
+ this(MalType[string] map)
+ {
+ data = map;
+ meta_val = mal_nil;
+ }
+ this(MalType[] lst)
+ {
+ put_kv_list(lst);
+ meta_val = mal_nil;
+ }
+ this(MalHashmap that, MalType new_meta)
+ {
+ data = that.data;
+ meta_val = new_meta;
+ }
+
+ bool contains(in MalType key)
+ {
+ auto valp = (make_hash_key(key) in data);
+ return valp !is null;
+ }
+
+ MalType get(in MalType key)
+ {
+ auto valp = (make_hash_key(key) in data);
+ return valp is null ? mal_nil : *valp;
+ }
+
+ void remove(in MalType key)
+ {
+ data.remove(make_hash_key(key));
+ }
+
+ void put(in MalType key, MalType val)
+ {
+ data[make_hash_key(key)] = val;
+ }
+
+ void put_kv_list(MalType[] lst)
+ {
+ foreach (kv; chunks(lst, 2))
+ {
+ if (kv.length < 2) throw new Exception("requires even number of elements");
+ put(kv[0], kv[1]);
+ }
+ }
+
+ private string make_hash_key(in MalType key)
+ {
+ return verify_cast!MalString(key).val;
+ }
+
+ override string print(bool readable) const
+ {
+ string[] parts;
+ foreach (k, v; data)
+ {
+ parts ~= (new MalString(k)).print(readable);
+ parts ~= v.print(readable);
+ }
+ return "{" ~ parts.join(" ") ~ "}";
+ }
+
+ override bool opEquals(Object o)
+ {
+ auto ohm = cast(MalHashmap)(o);
+ return (ohm !is null && data == ohm.data);
+ }
+
+ override MalType meta() { return meta_val; }
+ override MalType with_meta(MalType new_meta)
+ {
+ return new MalHashmap(this, new_meta);
+ }
+}
+
+alias BuiltinStaticFuncType = MalType function(MalType[] a ...);
+alias BuiltinFuncType = MalType delegate(MalType[] a ...);
+
+class MalBuiltinFunc : MalType, MalMeta
+{
+ const BuiltinFuncType fn;
+ const string name;
+ MalType meta_val;
+
+ this(in BuiltinFuncType fn_v, in string name_v)
+ {
+ fn = fn_v;
+ name = name_v;
+ meta_val = mal_nil;
+ }
+
+ this(in BuiltinStaticFuncType static_fn_v, in string name_v)
+ {
+ fn = toDelegate(static_fn_v);
+ name = name_v;
+ meta_val = mal_nil;
+ }
+
+ this(MalBuiltinFunc that, MalType new_meta)
+ {
+ fn = that.fn;
+ name = that.name;
+ meta_val = new_meta;
+ }
+
+ override string print(bool readable) const
+ {
+ return "<BuiltinFunc:" ~ name ~ ">";
+ }
+
+ override MalType meta() { return meta_val; }
+
+ override MalType with_meta(MalType new_meta)
+ {
+ return new MalBuiltinFunc(this, new_meta);
+ }
+}
+
+class MalFunc : MalType, MalMeta
+{
+ MalType[] arg_names;
+ MalType func_body;
+ Env def_env;
+ bool is_macro;
+ MalType meta_val;
+
+ this(MalType[] arg_names_v, MalType func_body_v, Env def_env_v)
+ {
+ arg_names = arg_names_v;
+ func_body = func_body_v;
+ def_env = def_env_v;
+ is_macro = false;
+ meta_val = mal_nil;
+ }
+
+ this(MalFunc that, MalType new_meta)
+ {
+ arg_names = that.arg_names;
+ func_body = that.func_body;
+ def_env = that.def_env;
+ is_macro = that.is_macro;
+ meta_val = new_meta;
+ }
+
+ override string print(bool readable) const
+ {
+ return "<Function:args=" ~ array(arg_names.map!(e => e.print(true))).join(",") ~ ">";
+ }
+
+ override MalType meta() { return meta_val; }
+
+ override MalType with_meta(MalType new_meta)
+ {
+ return new MalFunc(this, new_meta);
+ }
+}
+
+class MalAtom : MalType, MalMeta
+{
+ MalType val;
+ MalType meta_val;
+
+ this(MalType v)
+ {
+ val = v;
+ meta_val = mal_nil;
+ }
+
+ this(MalAtom that, MalType new_meta)
+ {
+ val = that.val;
+ meta_val = new_meta;
+ }
+
+ override string print(bool readable) const
+ {
+ return "(atom " ~ val.print(readable) ~ ")";
+ }
+
+ override bool opEquals(Object other)
+ {
+ auto o = cast(MalAtom) other;
+ return (o !is null && val == o.val);
+ }
+
+ override MalType meta() { return meta_val; }
+
+ override MalType with_meta(MalType new_meta)
+ {
+ return new MalAtom(this, new_meta);
+ }
+}
+
+class MalException : Exception
+{
+ MalType data;
+
+ this(MalType val)
+ {
+ super("MalException");
+ data = val;
+ }
+}
+
+T verify_cast(T)(in MalType v)
+{
+ T res = cast(T) v;
+ if (res is null) throw new Exception("Expected " ~ typeid(T).name);
+ return res;
+}
+
+MalType mal_type_q(T)(in MalType[] a)
+{
+ verify_args_count(a, 1);
+ T res = cast(T) a[0];
+ return bool_to_mal(res !is null);
+}
+
+inout(MalType[]) verify_args_count(inout MalType[] args, in int expected_length)
+{
+ if (args.length != expected_length)
+ {
+ throw new Exception("Expected " ~ to!string(expected_length) ~ " arguments");
+ }
+ return args;
+}
+
+void verify_min_args_count(in MalType[] args, in int min_expected_length)
+{
+ if (args.length < min_expected_length)
+ {
+ throw new Exception("Expected at least " ~ to!string(min_expected_length) ~ " arguments");
+ }
+}