1
1
mirror of https://github.com/kanaka/mal.git synced 2024-09-11 13:55:55 +03:00

D implementation

This commit is contained in:
Dov Murik 2015-11-16 22:17:13 -05:00
parent 6496cd1d17
commit f82cb96552
23 changed files with 3345 additions and 2 deletions

1
.gitignore vendored
View File

@ -6,6 +6,7 @@ bash/mal.sh
c/mal
coffee/mal.coffee
crystal/mal
d/mal
haskell/mal
js/mal.js
js/web/mal.js

View File

@ -16,7 +16,7 @@ mal_TEST_OPTS = --start-timeout 60 --test-timeout 120
# 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
@ -37,6 +37,7 @@ EXCLUDE_TESTS += test^bash^step5 # no stack exhaustion or completion
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
@ -65,6 +66,7 @@ STEP_TEST_FILES = $(strip $(wildcard $(1)/tests/$($(2)).mal) $(wildcard tests/$(
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))
@ -115,6 +117,7 @@ export FACTOR_ROOTS := .
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)

View File

@ -6,7 +6,7 @@
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
@ -16,6 +16,7 @@ Mal is implemented in 43 different languages:
* Clojure
* CoffeeScript
* Crystal
* D
* Elixir
* Erlang
* ES6 (ECMAScript 6 / ECMAScript 2015)
@ -175,6 +176,19 @@ make # needed to run tests
./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)*

26
d/Dockerfile Normal file
View File

@ -0,0 +1,26 @@
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

57
d/Makefile Normal file
View File

@ -0,0 +1,57 @@
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; \

53
d/env.d Normal file
View File

@ -0,0 +1,53 @@
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];
}
}

4
d/main.di Normal file
View File

@ -0,0 +1,4 @@
import types : MalType;
import env : Env;
MalType EVAL(MalType ast, Env env);

380
d/mal_core.d Normal file
View File

@ -0,0 +1,380 @@
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
];
}

6
d/printer.d Normal file
View File

@ -0,0 +1,6 @@
import types;
string pr_str(MalType obj, bool readable = true)
{
return obj.print(readable);
}

182
d/reader.d Normal file
View File

@ -0,0 +1,182 @@
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);
}

56
d/readline.d Normal file
View File

@ -0,0 +1,56 @@
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);
}

35
d/step0_repl.d Normal file
View File

@ -0,0 +1,35 @@
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("");
}

45
d/step1_read_print.d Normal file
View File

@ -0,0 +1,45 @@
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("");
}

132
d/step2_eval.d Normal file
View File

@ -0,0 +1,132 @@
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("");
}

153
d/step3_env.d Normal file
View File

@ -0,0 +1,153 @@
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("");
}

169
d/step4_if_fn_do.d Normal file
View File

@ -0,0 +1,169 @@
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("");
}

185
d/step5_tco.d Normal file
View File

@ -0,0 +1,185 @@
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("");
}

214
d/step6_file.d Normal file
View File

@ -0,0 +1,214 @@
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("");
}

253
d/step7_quote.d Normal file
View File

@ -0,0 +1,253 @@
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("");
}

299
d/step8_macros.d Normal file
View File

@ -0,0 +1,299 @@
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("");
}

318
d/step9_try.d Normal file
View File

@ -0,0 +1,318 @@
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("");
}

320
d/stepA_mal.d Normal file
View File

@ -0,0 +1,320 @@
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("");
}

438
d/types.d Normal file
View File

@ -0,0 +1,438 @@
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");
}
}