mirror of
https://github.com/kanaka/mal.git
synced 2024-09-11 13:55:55 +03:00
D implementation
This commit is contained in:
parent
6496cd1d17
commit
f82cb96552
1
.gitignore
vendored
1
.gitignore
vendored
@ -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
|
||||
|
5
Makefile
5
Makefile
@ -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)
|
||||
|
16
README.md
16
README.md
@ -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
26
d/Dockerfile
Normal 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
57
d/Makefile
Normal 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
53
d/env.d
Normal 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
4
d/main.di
Normal file
@ -0,0 +1,4 @@
|
||||
import types : MalType;
|
||||
import env : Env;
|
||||
|
||||
MalType EVAL(MalType ast, Env env);
|
380
d/mal_core.d
Normal file
380
d/mal_core.d
Normal 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
6
d/printer.d
Normal 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
182
d/reader.d
Normal 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
56
d/readline.d
Normal 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
35
d/step0_repl.d
Normal 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
45
d/step1_read_print.d
Normal 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
132
d/step2_eval.d
Normal 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
153
d/step3_env.d
Normal 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
169
d/step4_if_fn_do.d
Normal 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
185
d/step5_tco.d
Normal 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
214
d/step6_file.d
Normal 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
253
d/step7_quote.d
Normal 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
299
d/step8_macros.d
Normal 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
318
d/step9_try.d
Normal 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
320
d/stepA_mal.d
Normal 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
438
d/types.d
Normal 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");
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user