1
1
mirror of https://github.com/kanaka/mal.git synced 2024-09-19 17:47:53 +03:00

Merge branch 'master' of https://github.com/kanaka/mal into fsharp

Conflicts:
	Makefile
This commit is contained in:
Peter Stephens 2015-03-27 04:31:38 -05:00
commit d993b22f43
116 changed files with 12893 additions and 178 deletions

View File

@ -10,9 +10,9 @@ PYTHON = python
# Settings
#
IMPLS = bash c clojure coffee cs forth fsharp go haskell java js lua make mal \
ocaml matlab miniMAL nim perl php ps python r racket ruby rust \
scala vb
IMPLS = bash c clojure coffee cpp cs forth fsharp go haskell java \
julia js lua make mal ocaml matlab miniMAL nim perl php ps \
python r racket ruby rust scala swift vb
step0 = step0_repl
step1 = step1_read_print
@ -28,6 +28,7 @@ stepA = stepA_mal
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^haskell^step5 # test completes
EXCLUDE_TESTS += test^make^step5 # no TCO capability/step
@ -54,12 +55,14 @@ bash_STEP_TO_PROG = bash/$($(1)).sh
c_STEP_TO_PROG = c/$($(1))
clojure_STEP_TO_PROG = clojure/src/$($(1)).clj
coffee_STEP_TO_PROG = coffee/$($(1)).coffee
cpp_STEP_TO_PROG = cpp/$($(1))
cs_STEP_TO_PROG = cs/$($(1)).exe
forth_STEP_TO_PROG = forth/$($(1)).fs
fsharp_STEP_TO_PROG = fsharp/$($(1)).exe
go_STEP_TO_PROG = go/$($(1))
java_STEP_TO_PROG = java/src/main/java/mal/$($(1)).java
haskell_STEP_TO_PROG = haskell/$($(1))
julia_STEP_TO_PROG = julia/$($(1)).jl
js_STEP_TO_PROG = js/$($(1)).js
lua_STEP_TO_PROG = lua/$($(1)).lua
make_STEP_TO_PROG = make/$($(1)).mk
@ -77,6 +80,7 @@ racket_STEP_TO_PROG = racket/$($(1)).rkt
ruby_STEP_TO_PROG = ruby/$($(1)).rb
rust_STEP_TO_PROG = rust/target/release/$($(1))
scala_STEP_TO_PROG = scala/$($(1)).scala
swift_STEP_TO_PROG = swift/$($(1))
vb_STEP_TO_PROG = vb/$($(1)).exe
# Needed some argument munging
@ -88,14 +92,16 @@ bash_RUNSTEP = bash ../$(2) $(3)
c_RUNSTEP = ../$(2) $(3)
clojure_RUNSTEP = lein with-profile +$(1) trampoline run $(3)
coffee_RUNSTEP = coffee ../$(2) $(3)
cpp_RUNSTEP = ../$(2) $(3)
cs_RUNSTEP = mono ../$(2) --raw $(3)
forth_RUNSTEP = gforth ../$(2) $(3)
fsharp_RUNSTEP = mono ../$(2) --raw $(3)
go_RUNSTEP = ../$(2) $(3)
haskell_RUNSTEP = ../$(2) $(3)
java_RUNSTEP = mvn -quiet exec:java -Dexec.mainClass="mal.$($(1))" -Dexec.args="--raw$(if $(3), $(3),)"
java_RUNSTEP = mvn -quiet exec:java -Dexec.mainClass="mal.$($(1))" $(if $(3), -Dexec.args="$(3)",)
julia_RUNSTEP = ../$(2) $(3)
js_RUNSTEP = node ../$(2) $(3)
lua_RUNSTEP = ../$(2) --raw $(3)
lua_RUNSTEP = ../$(2) $(3)
make_RUNSTEP = make -f ../$(2) $(3)
mal_RUNSTEP = $(call $(MAL_IMPL)_RUNSTEP,$(1),$(call $(MAL_IMPL)_STEP_TO_PROG,stepA),../$(2),") #"
ocaml_RUNSTEP = ../$(2) $(3)
@ -103,7 +109,7 @@ matlab_args = $(subst $(SPACE),$(COMMA),$(foreach x,$(strip $(1)),'$(x)'))
matlab_RUNSTEP = matlab -nodisplay -nosplash -nodesktop -nojvm -r "$($(1))($(call matlab_args,$(3)));quit;"
miniMAL_RUNSTEP = miniMAL ../$(2) $(3)
nim_RUNSTEP = ../$(2) $(3)
perl_RUNSTEP = perl ../$(2) --raw $(3)
perl_RUNSTEP = perl ../$(2) $(3)
php_RUNSTEP = php ../$(2) $(3)
ps_RUNSTEP = $(4)gs -q -I./ -dNODISPLAY -- ../$(2) $(3)$(4)
python_RUNSTEP = $(PYTHON) ../$(2) $(3)
@ -112,13 +118,11 @@ racket_RUNSTEP = ../$(2) $(3)
ruby_RUNSTEP = ruby ../$(2) $(3)
rust_RUNSTEP = ../$(2) $(3)
scala_RUNSTEP = sbt 'run-main $($(1))$(if $(3), $(3),)'
swift_RUNSTEP = ../$(2) $(3)
vb_RUNSTEP = mono ../$(2) --raw $(3)
# Extra options to pass to runtest.py
cs_TEST_OPTS = --mono
fsharp_TEST_OPTS = --mono
mal_TEST_OPTS = --start-timeout 60 --test-timeout 120
vb_TEST_OPTS = --mono
mal_TEST_OPTS = --start-timeout 60 --test-timeout 120
# Derived lists
@ -144,6 +148,10 @@ IMPL_PERF = $(filter-out $(EXCLUDE_PERFS),$(foreach impl,$(DO_IMPLS),perf^$(impl
c/%:
$(MAKE) -C $(dir $(@)) $(notdir $(@))
# Build a program in 'cpp' directory
cpp/%:
$(MAKE) -C $(dir $(@)) $(notdir $(@))
# Allow test, test^STEP, test^IMPL, and test^IMPL^STEP
.SECONDEXPANSION:
$(IMPL_TESTS): $$(filter $$@^%,$$(ALL_TESTS))

View File

@ -4,10 +4,11 @@
Mal is a Clojure inspired Lisp interpreter.
Mal is implemented in 27 different languages:
Mal is implemented in 30 different languages:
* Bash shell
* C
* C++
* C#
* Clojure
* CoffeeScript
@ -15,7 +16,8 @@ Mal is implemented in 27 different languages:
* Go
* Haskell
* Java
* Javascript ([Online Demo](http://kanaka.github.io/mal))
* JavaScript ([Online Demo](http://kanaka.github.io/mal))
* Julia
* Lua
* GNU Make
* mal itself
@ -32,6 +34,7 @@ Mal is implemented in 27 different languages:
* Ruby
* Rust
* Scala
* Swift
* Visual Basic.NET
@ -62,7 +65,7 @@ mal/clojurewest2014.mal for the presentation that was given at the
conference (yes the presentation is a mal program).
If you are interesting in creating a mal implementation (or just
interested in using mal for something), please stop by the #mal
interested in using mal for something), please drop by the #mal
channel on freenode. In addition to the [make-a-lisp process
guide](process/guide.md) there is also a [mal/make-a-lisp
FAQ](docs/FAQ.md) where I attempt to answer some common questions.
@ -87,6 +90,23 @@ make
./stepX_YYY
```
### C++
*The C++ implementation was created by [Stephen Thirlwall (sdt)](https://github.com/sdt)*
The C++ implementation of mal requires g++-4.9 or clang++-3.5 and
a readline compatible library to build. See the `cpp/README.md` for
more details:
```
cd cpp
make
# OR
make CXX=clang++-3.5
./stepX_YYY
```
### C# ###
The C# implementation of mal has been tested on Linux using the Mono
@ -117,6 +137,8 @@ coffee ./stepX_YYY
### Forth
*The Forth implementation was created by [Chris Houser (chouser)](https://github.com/chouser)*
```
cd forth
gforth stepX_YYY.fs
@ -160,7 +182,7 @@ mvn -quiet exec:java -Dexec.mainClass=mal.stepX_YYY
mvn -quiet exec:java -Dexec.mainClass=mal.stepX_YYY -Dexec.args="CMDLINE_ARGS"
```
### Javascript/Node
### JavaScript/Node
```
cd js
@ -168,6 +190,15 @@ npm update
node stepX_YYY.js
```
### Julia
The Julia implementation of mal has been tested with Julia 0.3.7.
```
cd julia
julia stepX_YYY.jl
```
### Lua
Running the Lua implementation of mal requires lua 5.1 or later,
@ -200,6 +231,8 @@ make -f stepX_YYY.mk
### Nim 0.10.3
*The Nim implementation was created by [Dennis Felsing (def-)](https://github.com/def-)*
Running the Nim implementation of mal requires Nim's current devel branch
(0.10.3) or later, and the nre library installed.
@ -213,6 +246,8 @@ nimble build
### OCaml 4.01.0
*The OCaml implementation was created by [Chris Houser (chouser)](https://github.com/chouser)*
```
cd ocaml
make
@ -336,6 +371,19 @@ sbt compile
scala -classpath target/scala*/classes stepX_YYY
```
### Swift
*The Swift implementation was created by [Keith Rollin](https://github.com/keith-rollin)*
The Swift implemenation of mal requires the Swift compiler (XCode) to
build.
```
cd swift
make
./stepX_YYY
```
### Visual Basic.NET ###
The VB.NET implementation of mal has been tested on Linux using the Mono
@ -357,9 +405,9 @@ mono ./stepX_YYY.exe
The are nearly 500 generic functional tests (for all implementations)
in the `tests/` directory. Each step has a corresponding test file
containing tests specific to that step. The `runtest.py` test harness
uses pexpect to launch a Mal step implementation and then feeds the
tests one at a time to the implementation and compares the
output/return value to the expected output/return value.
launches a Mal step implementation and then feeds the tests one at
a time to the implementation and compares the output/return value to
the expected output/return value.
To simplify the process of running tests, a top level Makefile is
provided with convenient test targets.
@ -488,7 +536,7 @@ example, to run step2 tests for every implementation (except MATLAB):
run tests because runtime dependencies need to be downloaded to
avoid the tests timing out. These dependencies are download to
dot-files in the /mal directory so they will persist between runs.
* Compiled languages: if you host system is different enough from
* Compiled languages: if your host system is different enough from
Ubuntu Utopic then you may need to re-compile your compiled
languages from within the container to avoid linker version
mismatches.

View File

@ -26,6 +26,11 @@ OTHER_HDRS = types.h readline.h reader.h printer.h core.h interop.h
GLIB_CFLAGS ?= $(shell pkg-config --cflags glib-2.0)
GLIB_LDFLAGS ?= $(shell pkg-config --libs glib-2.0)
ifeq ($(shell uname -s),Darwin)
CFLAGS +=-DOSX=1
endif
ifeq (,$(USE_READLINE))
RL_LIBRARY ?= edit
else

View File

@ -3,7 +3,9 @@
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <fcntl.h>
#include <unistd.h>
#include "types.h"
#include "core.h"

View File

@ -1,11 +1,18 @@
#include <string.h>
#include <dlfcn.h>
#include <ffi.h>
#if OSX
#include <ffi/ffi.h>
#else
#include <ffi.h>
#endif
#include "types.h"
GHashTable *loaded_dls = NULL;
int get_byte_size(char *type) {
return 0;
}
typedef struct Raw64 {

View File

@ -8,15 +8,14 @@
#include <readline/tilde.h>
#else
#include <editline/readline.h>
#include <editline/history.h>
#endif
int history_loaded = 0;
char HISTORY_FILE[] = "~/.mal-history";
int load_history() {
if (history_loaded) { return 0; }
void load_history() {
if (history_loaded) { return; }
int ret;
char *hf = tilde_expand(HISTORY_FILE);
if (access(hf, F_OK) != -1) {
@ -38,7 +37,7 @@ int load_history() {
free(hf);
}
int append_to_history() {
void append_to_history() {
char *hf = tilde_expand(HISTORY_FILE);
#ifdef USE_READLINE
append_history(1, hf);

View File

@ -1,5 +1,6 @@
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "types.h"

View File

@ -1,5 +1,6 @@
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "types.h"
@ -109,15 +110,14 @@ MalVal *RE(GHashTable *env, char *prompt, char *str) {
// Setup the initial REPL environment
GHashTable *repl_env;
WRAP_INTEGER_OP(plus,+)
WRAP_INTEGER_OP(minus,-)
WRAP_INTEGER_OP(multiply,*)
WRAP_INTEGER_OP(divide,/)
void init_repl_env() {
repl_env = g_hash_table_new(g_str_hash, g_str_equal);
WRAP_INTEGER_OP(plus,+)
WRAP_INTEGER_OP(minus,-)
WRAP_INTEGER_OP(multiply,*)
WRAP_INTEGER_OP(divide,/)
g_hash_table_insert(repl_env, "+", int_plus);
g_hash_table_insert(repl_env, "-", int_minus);
g_hash_table_insert(repl_env, "*", int_multiply);

View File

@ -1,5 +1,6 @@
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "types.h"
@ -135,14 +136,14 @@ MalVal *RE(Env *env, char *prompt, char *str) {
// Setup the initial REPL environment
Env *repl_env;
WRAP_INTEGER_OP(plus,+)
WRAP_INTEGER_OP(minus,-)
WRAP_INTEGER_OP(multiply,*)
WRAP_INTEGER_OP(divide,/)
void init_repl_env() {
repl_env = new_env(NULL, NULL, NULL);
WRAP_INTEGER_OP(plus,+)
WRAP_INTEGER_OP(minus,-)
WRAP_INTEGER_OP(multiply,*)
WRAP_INTEGER_OP(divide,/)
env_set(repl_env, malval_new_symbol("+"), (MalVal *)int_plus);
env_set(repl_env, malval_new_symbol("-"), (MalVal *)int_minus);
env_set(repl_env, malval_new_symbol("*"), (MalVal *)int_multiply);

View File

@ -1,5 +1,6 @@
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "types.h"

View File

@ -1,5 +1,6 @@
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "types.h"

View File

@ -1,5 +1,6 @@
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "types.h"
@ -186,6 +187,8 @@ MalVal *RE(Env *env, char *prompt, char *str) {
// Setup the initial REPL environment
Env *repl_env;
MalVal *do_eval(MalVal *ast) { return EVAL(ast, repl_env); }
void init_repl_env(int argc, char *argv[]) {
repl_env = new_env(NULL, NULL, NULL);
@ -196,7 +199,6 @@ void init_repl_env(int argc, char *argv[]) {
malval_new_symbol(core_ns[i].name),
malval_new_function(core_ns[i].func, core_ns[i].arg_cnt));
}
MalVal *do_eval(MalVal *ast) { return EVAL(ast, repl_env); }
env_set(repl_env,
malval_new_symbol("eval"),
malval_new_function((void*(*)(void *))do_eval, 1));

View File

@ -1,5 +1,6 @@
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "types.h"
@ -223,6 +224,8 @@ MalVal *RE(Env *env, char *prompt, char *str) {
// Setup the initial REPL environment
Env *repl_env;
MalVal *do_eval(MalVal *ast) { return EVAL(ast, repl_env); }
void init_repl_env(int argc, char *argv[]) {
repl_env = new_env(NULL, NULL, NULL);
@ -233,7 +236,6 @@ void init_repl_env(int argc, char *argv[]) {
malval_new_symbol(core_ns[i].name),
malval_new_function(core_ns[i].func, core_ns[i].arg_cnt));
}
MalVal *do_eval(MalVal *ast) { return EVAL(ast, repl_env); }
env_set(repl_env,
malval_new_symbol("eval"),
malval_new_function((void*(*)(void *))do_eval, 1));

View File

@ -1,5 +1,6 @@
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "types.h"
@ -263,6 +264,8 @@ MalVal *RE(Env *env, char *prompt, char *str) {
// Setup the initial REPL environment
Env *repl_env;
MalVal *do_eval(MalVal *ast) { return EVAL(ast, repl_env); }
void init_repl_env(int argc, char *argv[]) {
repl_env = new_env(NULL, NULL, NULL);
@ -273,7 +276,6 @@ void init_repl_env(int argc, char *argv[]) {
malval_new_symbol(core_ns[i].name),
malval_new_function(core_ns[i].func, core_ns[i].arg_cnt));
}
MalVal *do_eval(MalVal *ast) { return EVAL(ast, repl_env); }
env_set(repl_env,
malval_new_symbol("eval"),
malval_new_function((void*(*)(void *))do_eval, 1));

View File

@ -1,5 +1,6 @@
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "types.h"
@ -285,6 +286,8 @@ MalVal *RE(Env *env, char *prompt, char *str) {
// Setup the initial REPL environment
Env *repl_env;
MalVal *do_eval(MalVal *ast) { return EVAL(ast, repl_env); }
void init_repl_env(int argc, char *argv[]) {
repl_env = new_env(NULL, NULL, NULL);
@ -295,7 +298,6 @@ void init_repl_env(int argc, char *argv[]) {
malval_new_symbol(core_ns[i].name),
malval_new_function(core_ns[i].func, core_ns[i].arg_cnt));
}
MalVal *do_eval(MalVal *ast) { return EVAL(ast, repl_env); }
env_set(repl_env,
malval_new_symbol("eval"),
malval_new_function((void*(*)(void *))do_eval, 1));

View File

@ -1,5 +1,6 @@
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "types.h"
@ -289,6 +290,7 @@ MalVal *RE(Env *env, char *prompt, char *str) {
// Setup the initial REPL environment
Env *repl_env;
MalVal *do_eval(MalVal *ast) { return EVAL(ast, repl_env); }
void init_repl_env(int argc, char *argv[]) {
repl_env = new_env(NULL, NULL, NULL);
@ -300,7 +302,6 @@ void init_repl_env(int argc, char *argv[]) {
malval_new_symbol(core_ns[i].name),
malval_new_function(core_ns[i].func, core_ns[i].arg_cnt));
}
MalVal *do_eval(MalVal *ast) { return EVAL(ast, repl_env); }
env_set(repl_env,
malval_new_symbol("eval"),
malval_new_function((void*(*)(void *))do_eval, 1));

View File

@ -74,7 +74,7 @@ MalVal *malval_new(MalType type, MalVal *metadata) {
}
//
int malval_free(MalVal *mv) {
void malval_free(MalVal *mv) {
// TODO: free collection items
if (!(mv->type & (MAL_NIL|MAL_TRUE|MAL_FALSE))) {
free(mv);

View File

@ -125,7 +125,7 @@ extern MalVal mal_false;
// Mal visible functions are "exported" in types_ns
MalVal *malval_new(MalType type, MalVal *metadata);
int malval_free(MalVal *mv);
void malval_free(MalVal *mv);
MalVal *malval_new_integer(gint64 val);
MalVal *malval_new_float(gdouble val);
MalVal *malval_new_string(char *val);
@ -138,11 +138,11 @@ MalVal *malval_new_function(void *(*func)(void *), int arg_cnt);
// Numbers
#define WRAP_INTEGER_OP(name, op) \
MalVal *int_ ## name(MalVal *a, MalVal *b) { \
static MalVal *int_ ## name(MalVal *a, MalVal *b) { \
return malval_new_integer(a->val.intnum op b->val.intnum); \
}
#define WRAP_INTEGER_CMP_OP(name, op) \
MalVal *int_ ## name(MalVal *a, MalVal *b) { \
static MalVal *int_ ## name(MalVal *a, MalVal *b) { \
return a->val.intnum op b->val.intnum ? &mal_true : &mal_false; \
}
@ -163,6 +163,14 @@ MalVal *_nth(MalVal *seq, int idx);
MalVal *_first(MalVal *seq);
MalVal *_rest(MalVal *seq);
MalVal *_last(MalVal *seq);
int _count(MalVal *obj);
int _atom_Q(MalVal *exp);
int _sequential_Q(MalVal *seq);
int _list_Q(MalVal *seq);
int _vector_Q(MalVal *seq);
int _hash_map_Q(MalVal *seq);
int _equal_Q(MalVal *a, MalVal *b);
MalVal *_map2(MalVal *(*func)(void*, void*), MalVal *lst, void *arg2);

5
cpp/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
.deps
*.o
*.a
step0_repl
step1_read_print

428
cpp/Core.cpp Normal file
View File

@ -0,0 +1,428 @@
#include "MAL.h"
#include "Environment.h"
#include "StaticList.h"
#include "Types.h"
#include <chrono>
#include <fstream>
#include <iostream>
#define CHECK_ARGS_IS(expected) \
checkArgsIs(name.c_str(), expected, \
std::distance(argsBegin, argsEnd))
#define CHECK_ARGS_BETWEEN(min, max) \
checkArgsBetween(name.c_str(), min, max, \
std::distance(argsBegin, argsEnd))
#define CHECK_ARGS_AT_LEAST(expected) \
checkArgsAtLeast(name.c_str(), expected, \
std::distance(argsBegin, argsEnd))
static String printValues(malValueIter begin, malValueIter end,
const String& sep, bool readably);
static StaticList<malBuiltIn*> handlers;
#define ARG(type, name) type* name = VALUE_CAST(type, *argsBegin++)
#define FUNCNAME(uniq) builtIn ## uniq
#define HRECNAME(uniq) handler ## uniq
#define BUILTIN_DEF(uniq, symbol) \
static malBuiltIn::ApplyFunc FUNCNAME(uniq); \
static StaticList<malBuiltIn*>::Node HRECNAME(uniq) \
(handlers, new malBuiltIn(symbol, FUNCNAME(uniq))); \
malValuePtr FUNCNAME(uniq)(const String& name, \
malValueIter argsBegin, malValueIter argsEnd, malEnvPtr env)
#define BUILTIN(symbol) BUILTIN_DEF(__LINE__, symbol)
#define BUILTIN_ISA(symbol, type) \
BUILTIN(symbol) { \
CHECK_ARGS_IS(1); \
return mal::boolean(DYNAMIC_CAST(type, *argsBegin)); \
}
#define BUILTIN_IS(op, constant) \
BUILTIN(op) { \
CHECK_ARGS_IS(1); \
return mal::boolean(*argsBegin == mal::constant()); \
}
#define BUILTIN_INTOP(op, checkDivByZero) \
BUILTIN(#op) { \
CHECK_ARGS_IS(2); \
ARG(malInteger, lhs); \
ARG(malInteger, rhs); \
if (checkDivByZero) { \
MAL_CHECK(rhs->value() != 0, "Division by zero"); \
} \
return mal::integer(lhs->value() op rhs->value()); \
}
BUILTIN_ISA("atom?", malAtom);
BUILTIN_ISA("keyword?", malKeyword);
BUILTIN_ISA("list?", malList);
BUILTIN_ISA("map?", malHash);
BUILTIN_ISA("sequential?", malSequence);
BUILTIN_ISA("symbol?", malSymbol);
BUILTIN_ISA("vector?", malVector);
BUILTIN_INTOP(+, false);
BUILTIN_INTOP(/, true);
BUILTIN_INTOP(*, false);
BUILTIN_INTOP(%, true);
BUILTIN_IS("true?", trueValue);
BUILTIN_IS("false?", falseValue);
BUILTIN_IS("nil?", nilValue);
BUILTIN("-")
{
int argCount = CHECK_ARGS_BETWEEN(1, 2);
ARG(malInteger, lhs);
if (argCount == 1) {
return mal::integer(- lhs->value());
}
ARG(malInteger, rhs);
return mal::integer(lhs->value() - rhs->value());
}
BUILTIN("<=")
{
CHECK_ARGS_IS(2);
ARG(malInteger, lhs);
ARG(malInteger, rhs);
return mal::boolean(lhs->value() <= rhs->value());
}
BUILTIN("=")
{
CHECK_ARGS_IS(2);
const malValue* lhs = (*argsBegin++).ptr();
const malValue* rhs = (*argsBegin++).ptr();
return mal::boolean(lhs->isEqualTo(rhs));
}
BUILTIN("apply")
{
CHECK_ARGS_AT_LEAST(2);
malValuePtr op = *argsBegin++; // this gets checked in APPLY
// Copy the first N-1 arguments in.
malValueVec args(argsBegin, argsEnd-1);
// Then append the argument as a list.
const malSequence* lastArg = VALUE_CAST(malSequence, *(argsEnd-1));
for (int i = 0; i < lastArg->count(); i++) {
args.push_back(lastArg->item(i));
}
return APPLY(op, args.begin(), args.end(), env->getRoot());
}
BUILTIN("assoc")
{
CHECK_ARGS_AT_LEAST(1);
ARG(malHash, hash);
return hash->assoc(argsBegin, argsEnd);
}
BUILTIN("atom")
{
CHECK_ARGS_IS(1);
return mal::atom(*argsBegin);
}
BUILTIN("concat")
{
int count = 0;
for (auto it = argsBegin; it != argsEnd; ++it) {
const malSequence* seq = VALUE_CAST(malSequence, *it);
count += seq->count();
}
malValueVec* items = new malValueVec(count);
int offset = 0;
for (auto it = argsBegin; it != argsEnd; ++it) {
const malSequence* seq = STATIC_CAST(malSequence, *it);
std::copy(seq->begin(), seq->end(), items->begin() + offset);
offset += seq->count();
}
return mal::list(items);
}
BUILTIN("conj")
{
CHECK_ARGS_AT_LEAST(1);
ARG(malSequence, seq);
return seq->conj(argsBegin, argsEnd);
}
BUILTIN("cons")
{
CHECK_ARGS_IS(2);
malValuePtr first = *argsBegin++;
ARG(malSequence, rest);
malValueVec* items = new malValueVec(1 + rest->count());
items->at(0) = first;
std::copy(rest->begin(), rest->end(), items->begin() + 1);
return mal::list(items);
}
BUILTIN("contains?")
{
CHECK_ARGS_IS(2);
if (*argsBegin == mal::nilValue()) {
return *argsBegin;
}
ARG(malHash, hash);
return mal::boolean(hash->contains(*argsBegin));
}
BUILTIN("count")
{
CHECK_ARGS_IS(1);
if (*argsBegin == mal::nilValue()) {
return mal::integer(0);
}
ARG(malSequence, seq);
return mal::integer(seq->count());
}
BUILTIN("deref")
{
CHECK_ARGS_IS(1);
ARG(malAtom, atom);
return atom->deref();
}
BUILTIN("dissoc")
{
CHECK_ARGS_AT_LEAST(1);
ARG(malHash, hash);
return hash->dissoc(argsBegin, argsEnd);
}
BUILTIN("empty?")
{
CHECK_ARGS_IS(1);
ARG(malSequence, seq);
return mal::boolean(seq->isEmpty());
}
BUILTIN("eval")
{
CHECK_ARGS_IS(1);
return EVAL(*argsBegin, env->getRoot());
}
BUILTIN("first")
{
CHECK_ARGS_IS(1);
ARG(malSequence, seq);
return seq->first();
}
BUILTIN("get")
{
CHECK_ARGS_IS(2);
if (*argsBegin == mal::nilValue()) {
return *argsBegin;
}
ARG(malHash, hash);
return hash->get(*argsBegin);
}
BUILTIN("hash-map")
{
return mal::hash(argsBegin, argsEnd, true);
}
BUILTIN("keys")
{
CHECK_ARGS_IS(1);
ARG(malHash, hash);
return hash->keys();
}
BUILTIN("keyword")
{
CHECK_ARGS_IS(1);
ARG(malString, token);
return mal::keyword(":" + token->value());
}
BUILTIN("meta")
{
CHECK_ARGS_IS(1);
malValuePtr obj = *argsBegin++;
return obj->meta();
}
BUILTIN("nth")
{
CHECK_ARGS_IS(2);
ARG(malSequence, seq);
ARG(malInteger, index);
int i = index->value();
MAL_CHECK(i >= 0 && i < seq->count(), "Index out of range");
return seq->item(i);
}
BUILTIN("pr-str")
{
return mal::string(printValues(argsBegin, argsEnd, " ", true));
}
BUILTIN("println")
{
std::cout << printValues(argsBegin, argsEnd, " ", false) << "\n";
return mal::nilValue();
}
BUILTIN("prn")
{
std::cout << printValues(argsBegin, argsEnd, " ", true) << "\n";
return mal::nilValue();
}
BUILTIN("read-string")
{
CHECK_ARGS_IS(1);
ARG(malString, str);
return readStr(str->value());
}
BUILTIN("readline")
{
CHECK_ARGS_IS(1);
ARG(malString, str);
return readline(str->value());
}
BUILTIN("reset!")
{
CHECK_ARGS_IS(2);
ARG(malAtom, atom);
return atom->reset(*argsBegin);
}
BUILTIN("rest")
{
CHECK_ARGS_IS(1);
ARG(malSequence, seq);
return seq->rest();
}
BUILTIN("slurp")
{
CHECK_ARGS_IS(1);
ARG(malString, filename);
std::ios_base::openmode openmode =
std::ios::ate | std::ios::in | std::ios::binary;
std::ifstream file(filename->value().c_str(), openmode);
MAL_CHECK(!file.fail(), "Cannot open %s", filename->value().c_str());
String data;
data.reserve(file.tellg());
file.seekg(0, std::ios::beg);
data.append(std::istreambuf_iterator<char>(file.rdbuf()),
std::istreambuf_iterator<char>());
return mal::string(data);
}
BUILTIN("str")
{
return mal::string(printValues(argsBegin, argsEnd, "", false));
}
BUILTIN("symbol")
{
CHECK_ARGS_IS(1);
ARG(malString, token);
return mal::symbol(token->value());
}
BUILTIN("throw")
{
CHECK_ARGS_IS(1);
throw *argsBegin;
}
BUILTIN("time-ms")
{
CHECK_ARGS_IS(0);
using namespace std::chrono;
milliseconds ms = duration_cast<milliseconds>(
high_resolution_clock::now().time_since_epoch()
);
return mal::integer(ms.count());
}
BUILTIN("vals")
{
CHECK_ARGS_IS(1);
ARG(malHash, hash);
return hash->values();
}
BUILTIN("vector")
{
return mal::vector(argsBegin, argsEnd);
}
BUILTIN("with-meta")
{
CHECK_ARGS_IS(2);
malValuePtr obj = *argsBegin++;
malValuePtr meta = *argsBegin++;
return obj->withMeta(meta);
}
void installCore(malEnvPtr env) {
for (auto it = handlers.begin(), end = handlers.end(); it != end; ++it) {
malBuiltIn* handler = *it;
env->set(handler->name(), handler);
}
}
static String printValues(malValueIter begin, malValueIter end,
const String& sep, bool readably)
{
String out;
if (begin != end) {
out += (*begin)->print(readably);
++begin;
}
for ( ; begin != end; ++begin) {
out += sep;
out += (*begin)->print(readably);
}
return out;
}

45
cpp/Debug.h Normal file
View File

@ -0,0 +1,45 @@
#ifndef INCLUDE_DEBUG_H
#define INCLUDE_DEBUG_H
#include <stdio.h>
#include <stdlib.h>
#define DEBUG_TRACE 1
//#define DEBUG_OBJECT_LIFETIMES 1
//#define DEBUG_ENV_LIFETIMES 1
#define DEBUG_TRACE_FILE stderr
#define NOOP do { } while (false)
#define NOTRACE(...) NOOP
#if DEBUG_TRACE
#define TRACE(...) fprintf(DEBUG_TRACE_FILE, __VA_ARGS__)
#else
#define TRACE NOTRACE
#endif
#if DEBUG_OBJECT_LIFETIMES
#define TRACE_OBJECT TRACE
#else
#define TRACE_OBJECT NOTRACE
#endif
#if DEBUG_ENV_LIFETIMES
#define TRACE_ENV TRACE
#else
#define TRACE_ENV NOTRACE
#endif
#define _ASSERT(file, line, condition, ...) \
if (!(condition)) { \
printf("Assertion failed at %s(%d): ", file, line); \
printf(__VA_ARGS__); \
exit(1); \
} else { }
#define ASSERT(condition, ...) \
_ASSERT(__FILE__, __LINE__, condition, __VA_ARGS__)
#endif // INCLUDE_DEBUG_H

11
cpp/Dockerfile Normal file
View File

@ -0,0 +1,11 @@
FROM ubuntu:utopic
MAINTAINER Stephen Thirlwall <sdt@dr.com>
RUN apt-get -y update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y \
g++-4.9 \
libreadline-dev \
make \
&& /bin/true
WORKDIR /mal

73
cpp/Environment.cpp Normal file
View File

@ -0,0 +1,73 @@
#include "Environment.h"
#include "Types.h"
#include <algorithm>
malEnv::malEnv(malEnvPtr outer)
: m_outer(outer)
{
TRACE_ENV("Creating malEnv %p, outer=%p\n", this, m_outer.ptr());
}
malEnv::malEnv(malEnvPtr outer, const StringVec& bindings,
malValueIter argsBegin, malValueIter argsEnd)
: m_outer(outer)
{
TRACE_ENV("Creating malEnv %p, outer=%p\n", this, m_outer.ptr());
int n = bindings.size();
auto it = argsBegin;
for (int i = 0; i < n; i++) {
if (bindings[i] == "&") {
MAL_CHECK(i == n - 2, "There must be one parameter after the &");
set(bindings[n-1], mal::list(it, argsEnd));
return;
}
MAL_CHECK(it != argsEnd, "Not enough parameters");
set(bindings[i], *it);
++it;
}
MAL_CHECK(it == argsEnd, "Too many parameters");
}
malEnv::~malEnv()
{
TRACE_ENV("Destroying malEnv %p, outer=%p\n", this, m_outer.ptr());
}
malEnvPtr malEnv::find(const String& symbol)
{
for (malEnvPtr env = this; env; env = env->m_outer) {
if (env->m_map.find(symbol) != env->m_map.end()) {
return env;
}
}
return NULL;
}
malValuePtr malEnv::get(const String& symbol)
{
for (malEnvPtr env = this; env; env = env->m_outer) {
auto it = env->m_map.find(symbol);
if (it != env->m_map.end()) {
return it->second;
}
}
MAL_FAIL("'%s' not found", symbol.c_str());
}
malValuePtr malEnv::set(const String& symbol, malValuePtr value)
{
m_map[symbol] = value;
return value;
}
malEnvPtr malEnv::getRoot()
{
// Work our way down the the global environment.
for (malEnvPtr env = this; ; env = env->m_outer) {
if (!env->m_outer) {
return env;
}
}
}

29
cpp/Environment.h Normal file
View File

@ -0,0 +1,29 @@
#ifndef INCLUDE_ENVIRONMENT_H
#define INCLUDE_ENVIRONMENT_H
#include "MAL.h"
#include <map>
class malEnv : public RefCounted {
public:
malEnv(malEnvPtr outer = NULL);
malEnv(malEnvPtr outer,
const StringVec& bindings,
malValueIter argsBegin,
malValueIter argsEnd);
~malEnv();
malValuePtr get(const String& symbol);
malEnvPtr find(const String& symbol);
malValuePtr set(const String& symbol, malValuePtr value);
malEnvPtr getRoot();
private:
typedef std::map<String, malValuePtr> Map;
Map m_map;
malEnvPtr m_outer;
};
#endif // INCLUDE_ENVIRONMENT_H

33
cpp/MAL.h Normal file
View File

@ -0,0 +1,33 @@
#ifndef INCLUDE_MAL_H
#define INCLUDE_MAL_H
#include "Debug.h"
#include "RefCountedPtr.h"
#include "String.h"
#include "Validation.h"
#include <vector>
class malValue;
typedef RefCountedPtr<malValue> malValuePtr;
typedef std::vector<malValuePtr> malValueVec;
typedef malValueVec::iterator malValueIter;
class malEnv;
typedef RefCountedPtr<malEnv> malEnvPtr;
// step*.cpp
extern malValuePtr APPLY(malValuePtr op,
malValueIter argsBegin, malValueIter argsEnd,
malEnvPtr env);
extern malValuePtr EVAL(malValuePtr ast, malEnvPtr env);
extern malValuePtr readline(const String& prompt);
extern String rep(const String& input, malEnvPtr env);
// Core.cpp
extern void installCore(malEnvPtr env);
// Reader.cpp
extern malValuePtr readStr(const String& input);
#endif // INCLUDE_MAL_H

60
cpp/Makefile Normal file
View File

@ -0,0 +1,60 @@
uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
ifeq ($(uname_S),Darwin)
# Native build on yosemite. Requires: brew install readline
CXX=g++
READLINE=/usr/local/Cellar/readline/6.3.8
INCPATHS=-I$(READLINE)/include
LIBPATHS=-L$(READLINE)/lib
else
# Ubuntu 14.10 / docker
CXX=g++-4.9
endif
LD=$(CXX)
AR=ar
DEBUG=-ggdb
CXXFLAGS=-O3 -Wall $(DEBUG) $(INCPATHS) -std=c++11
LDFLAGS=-O3 $(DEBUG) $(LIBPATHS) -L. -lreadline -lhistory
LIBSOURCES=Core.cpp Environment.cpp Reader.cpp ReadLine.cpp String.cpp \
Types.cpp Validation.cpp
LIBOBJS=$(LIBSOURCES:%.cpp=%.o)
MAINS=$(wildcard step*.cpp)
TARGETS=$(MAINS:%.cpp=%)
.PHONY: all clean
.SUFFIXES: .cpp .o
all: $(TARGETS)
.deps: *.cpp *.h
$(CXX) $(CXXFLAGS) -MM *.cpp > .deps
$(TARGETS): %: %.o libmal.a
$(LD) $^ -o $@ $(LDFLAGS)
libmal.a: $(LIBOBJS)
$(AR) rcs $@ $^
.cpp.o:
$(CXX) $(CXXFLAGS) -c $< -o $@
clean:
rm -rf *.o $(TARGETS) libmal.a .deps
-include .deps
### Stats
.PHONY: stats stats-lisp
stats: $(LIBSOURCES) stepA_mal.cpp
@wc $^
stats-lisp: Core.cpp Environment.cpp stepA_mal.cpp
@wc $^

39
cpp/README.md Normal file
View File

@ -0,0 +1,39 @@
# Compilation notes
## Mac OSX
This C++ implementation was developed on Mac OS X Yosemite, and uses the
stock g++ compiler.
The only other requirement is GNU Readline, which I got from homebrew.
brew install readline
You may need to edit the READLINE path in the Makefile.
## Ubuntu 14.10/15.04
This should compile on Ubuntu 14.10 and 15.04 with the following packages
apt-get install clang-3.5 libreadline-dev make
## Docker
For everyone else, there is a Dockerfile and associated docker.sh script which
can be used to make and run this implementation.
* build the docker image
./docker build
* make the MAL binaries:
./docker make
* run one of the implemenations:
./docker run ./stepA_mal
* open a shell inside the docker container:
./docker run

34
cpp/ReadLine.cpp Normal file
View File

@ -0,0 +1,34 @@
#include "ReadLine.h"
#include "String.h"
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <readline/readline.h>
#include <readline/history.h>
#include <readline/tilde.h>
ReadLine::ReadLine(const String& historyFile)
: m_historyPath(copyAndFree(tilde_expand(historyFile.c_str())))
{
read_history(m_historyPath.c_str());
}
ReadLine::~ReadLine()
{
}
bool ReadLine::get(const String& prompt, String& out)
{
char *line = readline(prompt.c_str());
if (line == NULL) {
return false;
}
add_history(line); // Add input to in-memory history
append_history(1, m_historyPath.c_str());
out = line;
return true;
}

17
cpp/ReadLine.h Normal file
View File

@ -0,0 +1,17 @@
#ifndef INCLUDE_READLINE_H
#define INCLUDE_READLINE_H
#include "String.h"
class ReadLine {
public:
ReadLine(const String& historyFile);
~ReadLine();
bool get(const String& prompt, String& line);
private:
String m_historyPath;
};
#endif // INCLUDE_READLINE_H

228
cpp/Reader.cpp Normal file
View File

@ -0,0 +1,228 @@
#include "MAL.h"
#include "Types.h"
#include <regex>
typedef std::regex Regex;
static const Regex intRegex("^[-+]?\\d+$");
static const Regex closeRegex("[\\)\\]}]");
static const Regex whitespaceRegex("[\\s,]+|;.*");
static const Regex tokenRegexes[] = {
Regex("~@"),
Regex("[\\[\\]{}()'`~^@]"),
Regex("\"(?:\\\\.|[^\\\\\"])*\""),
Regex("[^\\s\\[\\]{}('\"`,;)]+"),
};
class Tokeniser
{
public:
Tokeniser(const String& input);
String peek() const {
ASSERT(!eof(), "Tokeniser reading past EOF in peek\n");
return m_token;
}
String next() {
ASSERT(!eof(), "Tokeniser reading past EOF in next\n");
String ret = peek();
nextToken();
return ret;
}
bool eof() const {
return m_iter == m_end;
}
private:
void skipWhitespace();
void nextToken();
bool matchRegex(const Regex& regex);
typedef String::const_iterator StringIter;
String m_token;
StringIter m_iter;
StringIter m_end;
};
Tokeniser::Tokeniser(const String& input)
: m_iter(input.begin())
, m_end(input.end())
{
nextToken();
}
bool Tokeniser::matchRegex(const Regex& regex)
{
if (eof()) {
return false;
}
std::smatch match;
auto flags = std::regex_constants::match_continuous;
if (!std::regex_search(m_iter, m_end, match, regex, flags)) {
return false;
}
ASSERT(match.size() == 1, "Should only have one submatch, not %lu\n",
match.size());
ASSERT(match.position(0) == 0, "Need to match first character\n");
ASSERT(match.length(0) > 0, "Need to match a non-empty string\n");
// Don't advance m_iter now, do it after we've consumed the token in
// next(). If we do it now, we hit eof() when there's still one token left.
m_token = match.str(0);
return true;
}
void Tokeniser::nextToken()
{
m_iter += m_token.size();
skipWhitespace();
if (eof()) {
return;
}
for (auto &it : tokenRegexes) {
if (matchRegex(it)) {
return;
}
}
String mismatch(m_iter, m_end);
if (mismatch[0] == '"') {
MAL_CHECK(false, "Expected \", got EOF");
}
else {
MAL_CHECK(false, "Unexpected \"%s\"", mismatch.c_str());
}
}
void Tokeniser::skipWhitespace()
{
while (matchRegex(whitespaceRegex)) {
m_iter += m_token.size();
}
}
static malValuePtr readAtom(Tokeniser& tokeniser);
static malValuePtr readForm(Tokeniser& tokeniser);
static void readList(Tokeniser& tokeniser, malValueVec* items,
const String& end);
static malValuePtr processMacro(Tokeniser& tokeniser, const String& symbol);
malValuePtr readStr(const String& input)
{
Tokeniser tokeniser(input);
if (tokeniser.eof()) {
throw malEmptyInputException();
}
return readForm(tokeniser);
}
static malValuePtr readForm(Tokeniser& tokeniser)
{
MAL_CHECK(!tokeniser.eof(), "Expected form, got EOF");
String token = tokeniser.peek();
MAL_CHECK(!std::regex_match(token, closeRegex),
"Unexpected \"%s\"", token.c_str());
if (token == "(") {
tokeniser.next();
std::unique_ptr<malValueVec> items(new malValueVec);
readList(tokeniser, items.get(), ")");
return mal::list(items.release());
}
if (token == "[") {
tokeniser.next();
std::unique_ptr<malValueVec> items(new malValueVec);
readList(tokeniser, items.get(), "]");
return mal::vector(items.release());
}
if (token == "{") {
tokeniser.next();
malValueVec items;
readList(tokeniser, &items, "}");
return mal::hash(items.begin(), items.end(), false);
}
return readAtom(tokeniser);
}
static malValuePtr readAtom(Tokeniser& tokeniser)
{
struct ReaderMacro {
const char* token;
const char* symbol;
};
ReaderMacro macroTable[] = {
{ "@", "deref" },
{ "`", "quasiquote" },
{ "'", "quote" },
{ "~@", "splice-unquote" },
{ "~", "unquote" },
};
struct Constant {
const char* token;
malValuePtr value;
};
Constant constantTable[] = {
{ "false", mal::falseValue() },
{ "nil", mal::nilValue() },
{ "true", mal::trueValue() },
};
String token = tokeniser.next();
if (token[0] == '"') {
return mal::string(unescape(token));
}
if (token[0] == ':') {
return mal::keyword(token);
}
if (token == "^") {
malValuePtr meta = readForm(tokeniser);
malValuePtr value = readForm(tokeniser);
// Note that meta and value switch places
return mal::list(mal::symbol("with-meta"), value, meta);
}
for (auto &constant : constantTable) {
if (token == constant.token) {
return constant.value;
}
}
for (auto &macro : macroTable) {
if (token == macro.token) {
return processMacro(tokeniser, macro.symbol);
}
}
if (std::regex_match(token, intRegex)) {
return mal::integer(token);
}
return mal::symbol(token);
}
static void readList(Tokeniser& tokeniser, malValueVec* items,
const String& end)
{
while (1) {
MAL_CHECK(!tokeniser.eof(), "Expected \"%s\", got EOF", end.c_str());
if (tokeniser.peek() == end) {
tokeniser.next();
return;
}
items->push_back(readForm(tokeniser));
}
}
static malValuePtr processMacro(Tokeniser& tokeniser, const String& symbol)
{
return mal::list(mal::symbol(symbol), readForm(tokeniser));
}

77
cpp/RefCountedPtr.h Normal file
View File

@ -0,0 +1,77 @@
#ifndef INCLUDE_REFCOUNTEDPTR_H
#define INCLUDE_REFCOUNTEDPTR_H
#include "Debug.h"
#include <cstddef>
class RefCounted {
public:
RefCounted() : m_refCount(0) { }
virtual ~RefCounted() { }
const RefCounted* acquire() const { m_refCount++; return this; }
int release() const { return --m_refCount; }
int refCount() const { return m_refCount; }
private:
RefCounted(const RefCounted&); // no copy ctor
RefCounted& operator = (const RefCounted&); // no assignments
mutable int m_refCount;
};
template<class T>
class RefCountedPtr {
public:
RefCountedPtr() : m_object(0) { }
RefCountedPtr(T* object) : m_object(0)
{ acquire(object); }
RefCountedPtr(const RefCountedPtr& rhs) : m_object(0)
{ acquire(rhs.m_object); }
const RefCountedPtr& operator = (const RefCountedPtr& rhs) {
acquire(rhs.m_object);
return *this;
}
bool operator == (const RefCountedPtr& rhs) const {
return m_object == rhs.m_object;
}
bool operator != (const RefCountedPtr& rhs) const {
return m_object != rhs.m_object;
}
operator bool () const {
return m_object != NULL;
}
~RefCountedPtr() {
release();
}
T* operator -> () const { return m_object; }
T* ptr() const { return m_object; }
private:
void acquire(T* object) {
if (object != NULL) {
object->acquire();
}
release();
m_object = object;
}
void release() {
if ((m_object != NULL) && (m_object->release() == 0)) {
delete m_object;
}
}
T* m_object;
};
#endif // INCLUDE_REFCOUNTEDPTR_H

50
cpp/StaticList.h Normal file
View File

@ -0,0 +1,50 @@
#ifndef INCLUDE_STATICLIST_H
#define INCLUDE_STATICLIST_H
template<typename T>
class StaticList
{
public:
StaticList() : m_head(NULL) { }
class Iterator;
Iterator begin() { return Iterator(m_head); }
Iterator end() { return Iterator(NULL); }
class Node {
public:
Node(StaticList<T>& list, T item)
: m_item(item), m_next(list.m_head) {
list.m_head = this;
}
private:
friend class Iterator;
T m_item;
Node* m_next;
};
class Iterator {
public:
Iterator& operator ++ () {
m_node = m_node->m_next;
return *this;
}
T& operator * () { return m_node->m_item; }
bool operator != (const Iterator& that) {
return m_node != that.m_node;
}
private:
friend class StaticList<T>;
Iterator(Node* node) : m_node(node) { }
Node* m_node;
};
private:
friend class Node;
Node* m_head;
};
#endif // INCLUDE_STATICLIST_H

88
cpp/String.cpp Normal file
View File

@ -0,0 +1,88 @@
#include "Debug.h"
#include "String.h"
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Adapted from: http://stackoverflow.com/questions/2342162
String stringPrintf(const char* fmt, ...) {
int size = strlen(fmt); // make a guess
String str;
va_list ap;
while (1) {
str.resize(size);
va_start(ap, fmt);
int n = vsnprintf((char *)str.data(), size, fmt, ap);
va_end(ap);
if (n > -1 && n < size) { // Everything worked
str.resize(n);
return str;
}
if (n > -1) // Needed size returned
size = n + 1; // For null char
else
size *= 2; // Guess at a larger size (OS specific)
}
return str;
}
String copyAndFree(char* mallocedString)
{
String ret(mallocedString);
free(mallocedString);
return ret;
}
String escape(const String& in)
{
String out;
out.reserve(in.size() * 2 + 2); // each char may get escaped + two "'s
out += '"';
for (auto it = in.begin(), end = in.end(); it != end; ++it) {
char c = *it;
switch (c) {
case '\\': out += "\\\\"; break;
case '\n': out += "\\n"; break;
case '"': out += "\\\""; break;
default: out += c; break;
};
}
out += '"';
out.shrink_to_fit();
return out;
}
static char unescape(char c)
{
switch (c) {
case '\\': return '\\';
case 'n': return '\n';
case '"': return '"';
default: return c;
}
}
String unescape(const String& in)
{
String out;
out.reserve(in.size()); // unescaped string will always be shorter
// in will have double-quotes at either end, so move the iterators in
for (auto it = in.begin()+1, end = in.end()-1; it != end; ++it) {
char c = *it;
if (c == '\\') {
++it;
if (it != end) {
out += unescape(*it);
}
}
else {
out += c;
}
}
out.shrink_to_fit();
return out;
}

18
cpp/String.h Normal file
View File

@ -0,0 +1,18 @@
#ifndef INCLUDE_STRING_H
#define INCLUDE_STRING_H
#include <string>
#include <vector>
typedef std::string String;
typedef std::vector<String> StringVec;
#define STRF stringPrintf
#define PLURAL(n) &("s"[(n)==1])
extern String stringPrintf(const char* fmt, ...);
extern String copyAndFree(char* mallocedString);
extern String escape(const String& s);
extern String unescape(const String& s);
#endif // INCLUDE_STRING_H

500
cpp/Types.cpp Normal file
View File

@ -0,0 +1,500 @@
#include "Debug.h"
#include "Environment.h"
#include "Types.h"
#include <algorithm>
#include <memory>
#include <typeinfo>
namespace mal {
malValuePtr atom(malValuePtr value) {
return malValuePtr(new malAtom(value));
};
malValuePtr boolean(bool value) {
return value ? trueValue() : falseValue();
}
malValuePtr builtin(const String& name, malBuiltIn::ApplyFunc handler) {
return malValuePtr(new malBuiltIn(name, handler));
};
malValuePtr falseValue() {
static malValuePtr c(new malConstant("false"));
return malValuePtr(c);
};
malValuePtr hash(const malHash::Map& map) {
return malValuePtr(new malHash(map));
}
malValuePtr hash(malValueIter argsBegin, malValueIter argsEnd,
bool isEvaluated) {
return malValuePtr(new malHash(argsBegin, argsEnd, isEvaluated));
}
malValuePtr integer(int value) {
return malValuePtr(new malInteger(value));
};
malValuePtr integer(const String& token) {
return integer(std::stoi(token));
};
malValuePtr keyword(const String& token) {
return malValuePtr(new malKeyword(token));
};
malValuePtr lambda(const StringVec& bindings,
malValuePtr body, malEnvPtr env) {
return malValuePtr(new malLambda(bindings, body, env));
}
malValuePtr list(malValueVec* items) {
return malValuePtr(new malList(items));
};
malValuePtr list(malValueIter begin, malValueIter end) {
return malValuePtr(new malList(begin, end));
};
malValuePtr list(malValuePtr a) {
malValueVec* items = new malValueVec(1);
items->at(0) = a;
return malValuePtr(new malList(items));
}
malValuePtr list(malValuePtr a, malValuePtr b) {
malValueVec* items = new malValueVec(2);
items->at(0) = a;
items->at(1) = b;
return malValuePtr(new malList(items));
}
malValuePtr list(malValuePtr a, malValuePtr b, malValuePtr c) {
malValueVec* items = new malValueVec(3);
items->at(0) = a;
items->at(1) = b;
items->at(2) = c;
return malValuePtr(new malList(items));
}
malValuePtr macro(const malLambda& lambda) {
return malValuePtr(new malLambda(lambda, true));
};
malValuePtr nilValue() {
static malValuePtr c(new malConstant("nil"));
return malValuePtr(c);
};
malValuePtr string(const String& token) {
return malValuePtr(new malString(token));
}
malValuePtr symbol(const String& token) {
return malValuePtr(new malSymbol(token));
};
malValuePtr trueValue() {
static malValuePtr c(new malConstant("true"));
return malValuePtr(c);
};
malValuePtr vector(malValueVec* items) {
return malValuePtr(new malVector(items));
};
malValuePtr vector(malValueIter begin, malValueIter end) {
return malValuePtr(new malVector(begin, end));
};
};
malValuePtr malBuiltIn::apply(malValueIter argsBegin,
malValueIter argsEnd,
malEnvPtr env) const
{
return m_handler(m_name, argsBegin, argsEnd, env);
}
static String makeHashKey(malValuePtr key)
{
if (const malString* skey = DYNAMIC_CAST(malString, key)) {
return skey->print(true);
}
else if (const malKeyword* kkey = DYNAMIC_CAST(malKeyword, key)) {
return kkey->print(true);
}
MAL_FAIL("%s is not a string or keyword", key->print(true).c_str());
}
static malHash::Map addToMap(malHash::Map& map,
malValueIter argsBegin, malValueIter argsEnd)
{
// This is intended to be called with pre-evaluated arguments.
for (auto it = argsBegin; it != argsEnd; ++it) {
String key = makeHashKey(*it++);
map[key] = *it;
}
return map;
}
static malHash::Map createMap(malValueIter argsBegin, malValueIter argsEnd)
{
MAL_CHECK(std::distance(argsBegin, argsEnd) % 2 == 0,
"hash-map requires an even-sized list");
malHash::Map map;
return addToMap(map, argsBegin, argsEnd);
}
malHash::malHash(malValueIter argsBegin, malValueIter argsEnd, bool isEvaluated)
: m_map(createMap(argsBegin, argsEnd))
, m_isEvaluated(isEvaluated)
{
}
malHash::malHash(const malHash::Map& map)
: m_map(map)
, m_isEvaluated(true)
{
}
malValuePtr
malHash::assoc(malValueIter argsBegin, malValueIter argsEnd) const
{
MAL_CHECK(std::distance(argsBegin, argsEnd) % 2 == 0,
"assoc requires an even-sized list");
malHash::Map map(m_map);
return mal::hash(addToMap(map, argsBegin, argsEnd));
}
bool malHash::contains(malValuePtr key) const
{
auto it = m_map.find(makeHashKey(key));
return it != m_map.end();
}
malValuePtr
malHash::dissoc(malValueIter argsBegin, malValueIter argsEnd) const
{
malHash::Map map(m_map);
for (auto it = argsBegin; it != argsEnd; ++it) {
String key = makeHashKey(*it);
map.erase(key);
}
return mal::hash(map);
}
malValuePtr malHash::eval(malEnvPtr env)
{
if (m_isEvaluated) {
return malValuePtr(this);
}
malHash::Map map;
for (auto it = m_map.begin(), end = m_map.end(); it != end; ++it) {
map[it->first] = EVAL(it->second, env);
}
return mal::hash(map);
}
malValuePtr malHash::get(malValuePtr key) const
{
auto it = m_map.find(makeHashKey(key));
return it == m_map.end() ? mal::nilValue() : it->second;
}
malValuePtr malHash::keys() const
{
malValueVec* keys = new malValueVec();
keys->reserve(m_map.size());
for (auto it = m_map.begin(), end = m_map.end(); it != end; ++it) {
if (it->first[0] == '"') {
keys->push_back(mal::string(unescape(it->first)));
}
else {
keys->push_back(mal::keyword(it->first));
}
}
return mal::list(keys);
}
malValuePtr malHash::values() const
{
malValueVec* keys = new malValueVec();
keys->reserve(m_map.size());
for (auto it = m_map.begin(), end = m_map.end(); it != end; ++it) {
keys->push_back(it->second);
}
return mal::list(keys);
}
String malHash::print(bool readably) const
{
String s = "{";
auto it = m_map.begin(), end = m_map.end();
if (it != end) {
s += it->first + " " + it->second->print(readably);
++it;
}
for ( ; it != end; ++it) {
s += " " + it->first + " " + it->second->print(readably);
}
return s + "}";
}
bool malHash::doIsEqualTo(const malValue* rhs) const
{
const malHash::Map& r_map = static_cast<const malHash*>(rhs)->m_map;
if (m_map.size() != r_map.size()) {
return false;
}
for (auto it0 = m_map.begin(), end0 = m_map.end(), it1 = r_map.begin();
it0 != end0; ++it0, ++it1) {
if (it0->first != it1->first) {
return false;
}
if (!it0->second->isEqualTo(it1->second.ptr())) {
return false;
}
}
return true;
}
malLambda::malLambda(const StringVec& bindings,
malValuePtr body, malEnvPtr env)
: m_bindings(bindings)
, m_body(body)
, m_env(env)
, m_isMacro(false)
{
}
malLambda::malLambda(const malLambda& that, malValuePtr meta)
: malApplicable(meta)
, m_bindings(that.m_bindings)
, m_body(that.m_body)
, m_env(that.m_env)
, m_isMacro(that.m_isMacro)
{
}
malLambda::malLambda(const malLambda& that, bool isMacro)
: malApplicable(that.m_meta)
, m_bindings(that.m_bindings)
, m_body(that.m_body)
, m_env(that.m_env)
, m_isMacro(isMacro)
{
}
malValuePtr malLambda::apply(malValueIter argsBegin,
malValueIter argsEnd,
malEnvPtr) const
{
return EVAL(m_body, makeEnv(argsBegin, argsEnd));
}
malValuePtr malLambda::doWithMeta(malValuePtr meta) const
{
return new malLambda(*this, meta);
}
malEnvPtr malLambda::makeEnv(malValueIter argsBegin, malValueIter argsEnd) const
{
return malEnvPtr(new malEnv(m_env, m_bindings, argsBegin, argsEnd));
}
malValuePtr malList::conj(malValueIter argsBegin,
malValueIter argsEnd) const
{
int oldItemCount = std::distance(begin(), end());
int newItemCount = std::distance(argsBegin, argsEnd);
malValueVec* items = new malValueVec(oldItemCount + newItemCount);
std::reverse_copy(argsBegin, argsEnd, items->begin());
std::copy(begin(), end(), items->begin() + newItemCount);
return mal::list(items);
}
malValuePtr malList::eval(malEnvPtr env)
{
// Note, this isn't actually called since the TCO updates, but
// is required for the earlier steps, so don't get rid of it.
if (count() == 0) {
return malValuePtr(this);
}
std::unique_ptr<malValueVec> items(evalItems(env));
auto it = items->begin();
malValuePtr op = *it;
return APPLY(op, ++it, items->end(), env);
}
String malList::print(bool readably) const
{
return '(' + malSequence::print(readably) + ')';
}
malValuePtr malValue::eval(malEnvPtr env)
{
// Default case of eval is just to return the object itself.
return malValuePtr(this);
}
bool malValue::isEqualTo(const malValue* rhs) const
{
// Special-case. Vectors and Lists can be compared.
bool matchingTypes = (typeid(*this) == typeid(*rhs)) ||
(dynamic_cast<const malSequence*>(this) &&
dynamic_cast<const malSequence*>(rhs));
return matchingTypes && doIsEqualTo(rhs);
}
bool malValue::isTrue() const
{
return (this != mal::falseValue().ptr())
&& (this != mal::nilValue().ptr());
}
malValuePtr malValue::meta() const
{
return m_meta.ptr() == NULL ? mal::nilValue() : m_meta;
}
malValuePtr malValue::withMeta(malValuePtr meta) const
{
return doWithMeta(meta);
}
malSequence::malSequence(malValueVec* items)
: m_items(items)
{
}
malSequence::malSequence(malValueIter begin, malValueIter end)
: m_items(new malValueVec(begin, end))
{
}
malSequence::malSequence(const malSequence& that, malValuePtr meta)
: malValue(meta)
, m_items(new malValueVec(*(that.m_items)))
{
}
malSequence::~malSequence()
{
delete m_items;
}
bool malSequence::doIsEqualTo(const malValue* rhs) const
{
const malSequence* rhsSeq = static_cast<const malSequence*>(rhs);
if (count() != rhsSeq->count()) {
return false;
}
for (malValueIter it0 = m_items->begin(),
it1 = rhsSeq->begin(),
end = m_items->end(); it0 != end; ++it0, ++it1) {
if (! (*it0)->isEqualTo((*it1).ptr())) {
return false;
}
}
return true;
}
malValueVec* malSequence::evalItems(malEnvPtr env) const
{
malValueVec* items = new malValueVec;;
items->reserve(count());
for (auto it = m_items->begin(), end = m_items->end(); it != end; ++it) {
items->push_back(EVAL(*it, env));
}
return items;
}
malValuePtr malSequence::first() const
{
return count() == 0 ? mal::nilValue() : item(0);
}
String malSequence::print(bool readably) const
{
String str;
auto end = m_items->cend();
auto it = m_items->cbegin();
if (it != end) {
str += (*it)->print(readably);
++it;
}
for ( ; it != end; ++it) {
str += " ";
str += (*it)->print(readably);
}
return str;
}
malValuePtr malSequence::rest() const
{
malValueIter start = (count() > 0) ? begin() + 1 : end();
return mal::list(start, end());
}
String malString::escapedValue() const
{
return escape(value());
}
String malString::print(bool readably) const
{
return readably ? escapedValue() : value();
}
malValuePtr malSymbol::eval(malEnvPtr env)
{
return env->get(value());
}
malValuePtr malVector::conj(malValueIter argsBegin,
malValueIter argsEnd) const
{
int oldItemCount = std::distance(begin(), end());
int newItemCount = std::distance(argsBegin, argsEnd);
malValueVec* items = new malValueVec(oldItemCount + newItemCount);
std::copy(begin(), end(), items->begin());
std::copy(argsBegin, argsEnd, items->begin() + oldItemCount);
return mal::vector(items);
}
malValuePtr malVector::eval(malEnvPtr env)
{
return mal::vector(evalItems(env));
}
String malVector::print(bool readably) const
{
return '[' + malSequence::print(readably) + ']';
}

378
cpp/Types.h Normal file
View File

@ -0,0 +1,378 @@
#ifndef INCLUDE_TYPES_H
#define INCLUDE_TYPES_H
#include "MAL.h"
#include <exception>
#include <map>
class malEmptyInputException : public std::exception { };
class malValue : public RefCounted {
public:
malValue() {
TRACE_OBJECT("Creating malValue %p\n", this);
}
malValue(malValuePtr meta) : m_meta(meta) {
TRACE_OBJECT("Creating malValue %p\n", this);
}
virtual ~malValue() {
TRACE_OBJECT("Destroying malValue %p\n", this);
}
malValuePtr withMeta(malValuePtr meta) const;
virtual malValuePtr doWithMeta(malValuePtr meta) const = 0;
malValuePtr meta() const;
bool isTrue() const;
bool isEqualTo(const malValue* rhs) const;
virtual malValuePtr eval(malEnvPtr env);
virtual String print(bool readably) const = 0;
protected:
virtual bool doIsEqualTo(const malValue* rhs) const = 0;
malValuePtr m_meta;
};
template<class T>
T* value_cast(malValuePtr obj, const char* typeName) {
T* dest = dynamic_cast<T*>(obj.ptr());
MAL_CHECK(dest != NULL, "%s is not a %s",
obj->print(true).c_str(), typeName);
return dest;
}
#define VALUE_CAST(Type, Value) value_cast<Type>(Value, #Type)
#define DYNAMIC_CAST(Type, Value) (dynamic_cast<Type*>((Value).ptr()))
#define STATIC_CAST(Type, Value) (static_cast<Type*>((Value).ptr()))
#define WITH_META(Type) \
virtual malValuePtr doWithMeta(malValuePtr meta) const { \
return new Type(*this, meta); \
} \
class malConstant : public malValue {
public:
malConstant(String name) : m_name(name) { }
malConstant(const malConstant& that, malValuePtr meta)
: malValue(meta), m_name(that.m_name) { }
virtual String print(bool readably) const { return m_name; }
virtual bool doIsEqualTo(const malValue* rhs) const {
return this == rhs; // these are singletons
}
WITH_META(malConstant);
private:
const String m_name;
};
class malInteger : public malValue {
public:
malInteger(int value) : m_value(value) { }
malInteger(const malInteger& that, malValuePtr meta)
: malValue(meta), m_value(that.m_value) { }
virtual String print(bool readably) const {
return std::to_string(m_value);
}
int value() const { return m_value; }
virtual bool doIsEqualTo(const malValue* rhs) const {
return m_value == static_cast<const malInteger*>(rhs)->m_value;
}
WITH_META(malInteger);
private:
const int m_value;
};
class malStringBase : public malValue {
public:
malStringBase(const String& token)
: m_value(token) { }
malStringBase(const malStringBase& that, malValuePtr meta)
: malValue(meta), m_value(that.value()) { }
virtual String print(bool readably) const { return m_value; }
String value() const { return m_value; }
private:
const String m_value;
};
class malString : public malStringBase {
public:
malString(const String& token)
: malStringBase(token) { }
malString(const malString& that, malValuePtr meta)
: malStringBase(that, meta) { }
virtual String print(bool readably) const;
String escapedValue() const;
virtual bool doIsEqualTo(const malValue* rhs) const {
return value() == static_cast<const malString*>(rhs)->value();
}
WITH_META(malString);
};
class malKeyword : public malStringBase {
public:
malKeyword(const String& token)
: malStringBase(token) { }
malKeyword(const malKeyword& that, malValuePtr meta)
: malStringBase(that, meta) { }
virtual bool doIsEqualTo(const malValue* rhs) const {
return value() == static_cast<const malKeyword*>(rhs)->value();
}
WITH_META(malKeyword);
};
class malSymbol : public malStringBase {
public:
malSymbol(const String& token)
: malStringBase(token) { }
malSymbol(const malSymbol& that, malValuePtr meta)
: malStringBase(that, meta) { }
virtual malValuePtr eval(malEnvPtr env);
virtual bool doIsEqualTo(const malValue* rhs) const {
return value() == static_cast<const malSymbol*>(rhs)->value();
}
WITH_META(malSymbol);
};
class malSequence : public malValue {
public:
malSequence(malValueVec* items);
malSequence(malValueIter begin, malValueIter end);
malSequence(const malSequence& that, malValuePtr meta);
virtual ~malSequence();
virtual String print(bool readably) const;
malValueVec* evalItems(malEnvPtr env) const;
int count() const { return m_items->size(); }
bool isEmpty() const { return m_items->empty(); }
malValuePtr item(int index) const { return (*m_items)[index]; }
malValueIter begin() const { return m_items->begin(); }
malValueIter end() const { return m_items->end(); }
virtual bool doIsEqualTo(const malValue* rhs) const;
virtual malValuePtr conj(malValueIter argsBegin,
malValueIter argsEnd) const = 0;
malValuePtr first() const;
virtual malValuePtr rest() const;
private:
malValueVec* const m_items;
};
class malList : public malSequence {
public:
malList(malValueVec* items) : malSequence(items) { }
malList(malValueIter begin, malValueIter end)
: malSequence(begin, end) { }
malList(const malList& that, malValuePtr meta)
: malSequence(that, meta) { }
virtual String print(bool readably) const;
virtual malValuePtr eval(malEnvPtr env);
virtual malValuePtr conj(malValueIter argsBegin,
malValueIter argsEnd) const;
WITH_META(malList);
};
class malVector : public malSequence {
public:
malVector(malValueVec* items) : malSequence(items) { }
malVector(malValueIter begin, malValueIter end)
: malSequence(begin, end) { }
malVector(const malVector& that, malValuePtr meta)
: malSequence(that, meta) { }
virtual malValuePtr eval(malEnvPtr env);
virtual String print(bool readably) const;
virtual malValuePtr conj(malValueIter argsBegin,
malValueIter argsEnd) const;
WITH_META(malVector);
};
class malApplicable : public malValue {
public:
malApplicable() { }
malApplicable(malValuePtr meta) : malValue(meta) { }
virtual malValuePtr apply(malValueIter argsBegin,
malValueIter argsEnd,
malEnvPtr env) const = 0;
};
class malHash : public malValue {
public:
typedef std::map<String, malValuePtr> Map;
malHash(malValueIter argsBegin, malValueIter argsEnd, bool isEvaluated);
malHash(const malHash::Map& map);
malHash(const malHash& that, malValuePtr meta)
: malValue(meta), m_map(that.m_map), m_isEvaluated(that.m_isEvaluated) { }
malValuePtr assoc(malValueIter argsBegin, malValueIter argsEnd) const;
malValuePtr dissoc(malValueIter argsBegin, malValueIter argsEnd) const;
bool contains(malValuePtr key) const;
malValuePtr eval(malEnvPtr env);
malValuePtr get(malValuePtr key) const;
malValuePtr keys() const;
malValuePtr values() const;
virtual String print(bool readably) const;
virtual bool doIsEqualTo(const malValue* rhs) const;
WITH_META(malHash);
private:
const Map m_map;
const bool m_isEvaluated;
};
class malBuiltIn : public malApplicable {
public:
typedef malValuePtr (ApplyFunc)(const String& name,
malValueIter argsBegin,
malValueIter argsEnd,
malEnvPtr env);
malBuiltIn(const String& name, ApplyFunc* handler)
: m_name(name), m_handler(handler) { }
malBuiltIn(const malBuiltIn& that, malValuePtr meta)
: malApplicable(meta), m_name(that.m_name), m_handler(that.m_handler) { }
virtual malValuePtr apply(malValueIter argsBegin,
malValueIter argsEnd,
malEnvPtr env) const;
virtual String print(bool readably) const {
return STRF("#builtin-function(%s)", m_name.c_str());
}
virtual bool doIsEqualTo(const malValue* rhs) const {
return this == rhs; // these are singletons
}
String name() const { return m_name; }
WITH_META(malBuiltIn);
private:
const String m_name;
ApplyFunc* m_handler;
};
class malLambda : public malApplicable {
public:
malLambda(const StringVec& bindings, malValuePtr body, malEnvPtr env);
malLambda(const malLambda& that, malValuePtr meta);
malLambda(const malLambda& that, bool isMacro);
virtual malValuePtr apply(malValueIter argsBegin,
malValueIter argsEnd,
malEnvPtr env) const;
malValuePtr getBody() const { return m_body; }
malEnvPtr makeEnv(malValueIter argsBegin, malValueIter argsEnd) const;
virtual bool doIsEqualTo(const malValue* rhs) const {
return this == rhs; // do we need to do a deep inspection?
}
virtual String print(bool readably) const {
return STRF("#user-%s(%p)", m_isMacro ? "macro" : "function", this);
}
bool isMacro() const { return m_isMacro; }
virtual malValuePtr doWithMeta(malValuePtr meta) const;
private:
const StringVec m_bindings;
const malValuePtr m_body;
const malEnvPtr m_env;
const bool m_isMacro;
};
class malAtom : public malValue {
public:
malAtom(malValuePtr value) : m_value(value) { }
malAtom(const malAtom& that, malValuePtr meta)
: malValue(meta), m_value(that.m_value) { }
virtual bool doIsEqualTo(const malValue* rhs) const {
return this->m_value->isEqualTo(rhs);
}
virtual String print(bool readably) const {
return "(atom " + m_value->print(readably) + ")";
};
malValuePtr deref() const { return m_value; }
malValuePtr reset(malValuePtr value) { return m_value = value; }
WITH_META(malAtom);
private:
malValuePtr m_value;
};
namespace mal {
malValuePtr atom(malValuePtr value);
malValuePtr boolean(bool value);
malValuePtr builtin(const String& name, malBuiltIn::ApplyFunc handler);
malValuePtr falseValue();
malValuePtr hash(malValueIter argsBegin, malValueIter argsEnd,
bool isEvaluated);
malValuePtr hash(const malHash::Map& map);
malValuePtr integer(int value);
malValuePtr integer(const String& token);
malValuePtr keyword(const String& token);
malValuePtr lambda(const StringVec&, malValuePtr, malEnvPtr);
malValuePtr list(malValueVec* items);
malValuePtr list(malValueIter begin, malValueIter end);
malValuePtr list(malValuePtr a);
malValuePtr list(malValuePtr a, malValuePtr b);
malValuePtr list(malValuePtr a, malValuePtr b, malValuePtr c);
malValuePtr macro(const malLambda& lambda);
malValuePtr nilValue();
malValuePtr string(const String& token);
malValuePtr symbol(const String& token);
malValuePtr trueValue();
malValuePtr vector(malValueVec* items);
malValuePtr vector(malValueIter begin, malValueIter end);
};
#endif // INCLUDE_TYPES_H

33
cpp/Validation.cpp Normal file
View File

@ -0,0 +1,33 @@
#include "Validation.h"
int checkArgsIs(const char* name, int expected, int got)
{
MAL_CHECK(got == expected,
"\"%s\" expects %d arg%s, %d supplied",
name, expected, PLURAL(expected), got);
return got;
}
int checkArgsBetween(const char* name, int min, int max, int got)
{
MAL_CHECK((got >= min) && (got <= max),
"\"%s\" expects between %d and %d arg%s, %d supplied",
name, min, max, PLURAL(max), got);
return got;
}
int checkArgsAtLeast(const char* name, int min, int got)
{
MAL_CHECK(got >= min,
"\"%s\" expects at least %d arg%s, %d supplied",
name, min, PLURAL(min), got);
return got;
}
int checkArgsEven(const char* name, int got)
{
MAL_CHECK(got % 2 == 0,
"\"%s\" expects an even number of args, %d supplied",
name, got);
return got;
}

16
cpp/Validation.h Normal file
View File

@ -0,0 +1,16 @@
#ifndef INCLUDE_VALIDATION_H
#define INCLUDE_VALIDATION_H
#include "String.h"
#define MAL_CHECK(condition, ...) \
if (!(condition)) { throw STRF(__VA_ARGS__); } else { }
#define MAL_FAIL(...) MAL_CHECK(false, __VA_ARGS__)
extern int checkArgsIs(const char* name, int expected, int got);
extern int checkArgsBetween(const char* name, int min, int max, int got);
extern int checkArgsAtLeast(const char* name, int min, int got);
extern int checkArgsEven(const char* name, int got);
#endif // INCLUDE_VALIDATION_H

34
cpp/docker.sh Executable file
View File

@ -0,0 +1,34 @@
#!/bin/bash
IMAGE_NAME=mal-cpp
CONTAINER_NAME=mal-cpp-running
run() {
docker rm -f $CONTAINER_NAME > /dev/null 2>/dev/null
docker run -v $PWD:/mal -ti --name $CONTAINER_NAME $IMAGE_NAME "$@"
}
case $1 in
build)
docker build -t $IMAGE_NAME .
;;
run)
shift
run "$@"
;;
make)
shift
run make "$@"
;;
*)
echo "usage: $0 [build|run|make]"
exit 1
;;
esac

42
cpp/step0_repl.cpp Normal file
View File

@ -0,0 +1,42 @@
#include "String.h"
#include "ReadLine.h"
#include <iostream>
#include <memory>
String READ(const String& input);
String EVAL(const String& ast);
String PRINT(const String& ast);
String rep(const String& input);
static ReadLine s_readLine("~/.mal-history");
int main(int argc, char* argv[])
{
String prompt = "user> ";
String input;
while (s_readLine.get(prompt, input)) {
std::cout << rep(input) << "\n";
}
return 0;
}
String rep(const String& input)
{
return PRINT(EVAL(READ(input)));
}
String READ(const String& input)
{
return input;
}
String EVAL(const String& ast)
{
return ast;
}
String PRINT(const String& ast)
{
return ast;
}

66
cpp/step1_read_print.cpp Normal file
View File

@ -0,0 +1,66 @@
#include "MAL.h"
#include "ReadLine.h"
#include "Types.h"
#include <iostream>
#include <memory>
malValuePtr READ(const String& input);
String PRINT(malValuePtr ast);
static ReadLine s_readLine("~/.mal-history");
static String rep(const String& input);
static malValuePtr EVAL(malValuePtr ast);
int main(int argc, char* argv[])
{
String prompt = "user> ";
String input;
while (s_readLine.get(prompt, input)) {
String out;
try {
out = rep(input);
}
catch (malEmptyInputException&) {
continue; // no output
}
catch (String& s) {
out = s;
};
std::cout << out << "\n";
}
return 0;
}
static String rep(const String& input)
{
return PRINT(EVAL(READ(input)));
}
malValuePtr READ(const String& input)
{
return readStr(input);
}
static malValuePtr EVAL(malValuePtr ast)
{
return ast;
}
String PRINT(malValuePtr ast)
{
return ast->print(true);
}
// These have been added after step 1 to keep the linker happy.
malValuePtr EVAL(malValuePtr ast, malEnvPtr)
{
return ast;
}
malValuePtr APPLY(malValuePtr ast, malValueIter, malValueIter, malEnvPtr)
{
return ast;
}

119
cpp/step2_eval.cpp Normal file
View File

@ -0,0 +1,119 @@
#include "MAL.h"
#include "Environment.h"
#include "ReadLine.h"
#include "Types.h"
#include <iostream>
#include <memory>
malValuePtr READ(const String& input);
String PRINT(malValuePtr ast);
static ReadLine s_readLine("~/.mal-history");
static malBuiltIn::ApplyFunc
builtIn_add, builtIn_sub, builtIn_mul, builtIn_div;
int main(int argc, char* argv[])
{
String prompt = "user> ";
String input;
malEnvPtr replEnv(new malEnv);
replEnv->set("+", mal::builtin("+", &builtIn_add));
replEnv->set("-", mal::builtin("-", &builtIn_sub));
replEnv->set("*", mal::builtin("+", &builtIn_mul));
replEnv->set("/", mal::builtin("/", &builtIn_div));
while (s_readLine.get(prompt, input)) {
String out;
try {
out = rep(input, replEnv);
}
catch (malEmptyInputException&) {
continue; // no output
}
catch (String& s) {
out = s;
};
std::cout << out << "\n";
}
return 0;
}
String rep(const String& input, malEnvPtr env)
{
return PRINT(EVAL(READ(input), env));
}
malValuePtr READ(const String& input)
{
return readStr(input);
}
malValuePtr EVAL(malValuePtr ast, malEnvPtr env)
{
return ast->eval(env);
}
String PRINT(malValuePtr ast)
{
return ast->print(true);
}
malValuePtr APPLY(malValuePtr op, malValueIter argsBegin, malValueIter argsEnd,
malEnvPtr env)
{
const malApplicable* handler = DYNAMIC_CAST(malApplicable, op);
MAL_CHECK(handler != NULL,
"\"%s\" is not applicable", op->print(true).c_str());
return handler->apply(argsBegin, argsEnd, env);
}
#define ARG(type, name) type* name = VALUE_CAST(type, *argsBegin++)
#define CHECK_ARGS_IS(expected) \
checkArgsIs(name.c_str(), expected, std::distance(argsBegin, argsEnd))
#define CHECK_ARGS_BETWEEN(min, max) \
checkArgsBetween(name.c_str(), min, max, std::distance(argsBegin, argsEnd))
static malValuePtr builtIn_add(const String& name,
malValueIter argsBegin, malValueIter argsEnd, malEnvPtr env)
{
CHECK_ARGS_IS(2);
ARG(malInteger, lhs);
ARG(malInteger, rhs);
return mal::integer(lhs->value() + rhs->value());
}
static malValuePtr builtIn_sub(const String& name,
malValueIter argsBegin, malValueIter argsEnd, malEnvPtr env)
{
int argCount = CHECK_ARGS_BETWEEN(1, 2);
ARG(malInteger, lhs);
if (argCount == 1) {
return mal::integer(- lhs->value());
}
ARG(malInteger, rhs);
return mal::integer(lhs->value() - rhs->value());
}
static malValuePtr builtIn_mul(const String& name,
malValueIter argsBegin, malValueIter argsEnd, malEnvPtr env)
{
CHECK_ARGS_IS(2);
ARG(malInteger, lhs);
ARG(malInteger, rhs);
return mal::integer(lhs->value() * rhs->value());
}
static malValuePtr builtIn_div(const String& name,
malValueIter argsBegin, malValueIter argsEnd, malEnvPtr env)
{
CHECK_ARGS_IS(2);
ARG(malInteger, lhs);
ARG(malInteger, rhs);
MAL_CHECK(rhs->value() != 0, "Division by zero"); \
return mal::integer(lhs->value() / rhs->value());
}

111
cpp/step3_env.cpp Normal file
View File

@ -0,0 +1,111 @@
#include "MAL.h"
#include "Environment.h"
#include "ReadLine.h"
#include "Types.h"
#include <iostream>
#include <memory>
malValuePtr READ(const String& input);
String PRINT(malValuePtr ast);
static ReadLine s_readLine("~/.mal-history");
int main(int argc, char* argv[])
{
String prompt = "user> ";
String input;
malEnvPtr replEnv(new malEnv);
installCore(replEnv);
while (s_readLine.get(prompt, input)) {
String out;
try {
out = rep(input, replEnv);
}
catch (malEmptyInputException&) {
continue; // no output
}
catch (String& s) {
out = s;
};
std::cout << out << "\n";
}
return 0;
}
String rep(const String& input, malEnvPtr env)
{
return PRINT(EVAL(READ(input), env));
}
malValuePtr READ(const String& input)
{
return readStr(input);
}
malValuePtr EVAL(malValuePtr ast, malEnvPtr env)
{
const malList* list = DYNAMIC_CAST(malList, ast);
if (!list || (list->count() == 0)) {
return ast->eval(env);
}
// From here on down we are evaluating a non-empty list.
// First handle the special forms.
if (const malSymbol* symbol = DYNAMIC_CAST(malSymbol, list->item(0))) {
String special = symbol->value();
int argCount = list->count() - 1;
if (special == "def!") {
checkArgsIs("def!", 2, argCount);
const malSymbol* id = VALUE_CAST(malSymbol, list->item(1));
return env->set(id->value(), EVAL(list->item(2), env));
}
if (special == "let*") {
checkArgsIs("let*", 2, argCount);
const malSequence* bindings =
VALUE_CAST(malSequence, list->item(1));
int count = checkArgsEven("let*", bindings->count());
malEnvPtr inner(new malEnv(env));
for (int i = 0; i < count; i += 2) {
const malSymbol* var =
VALUE_CAST(malSymbol, bindings->item(i));
inner->set(var->value(), EVAL(bindings->item(i+1), inner));
}
return EVAL(list->item(2), inner);
}
}
// Now we're left with the case of a regular list to be evaluated.
std::unique_ptr<malValueVec> items(list->evalItems(env));
malValuePtr op = items->at(0);
return APPLY(op, items->begin()+1, items->end(), env);
}
String PRINT(malValuePtr ast)
{
return ast->print(true);
}
malValuePtr APPLY(malValuePtr op, malValueIter argsBegin, malValueIter argsEnd,
malEnvPtr env)
{
const malApplicable* handler = DYNAMIC_CAST(malApplicable, op);
MAL_CHECK(handler != NULL,
"\"%s\" is not applicable", op->print(true).c_str());
return handler->apply(argsBegin, argsEnd, env);
}
// Added to keep the linker happy at step A
malValuePtr readline(const String& prompt)
{
String input;
if (s_readLine.get(prompt, input)) {
return mal::string(input);
}
return mal::nilValue();
}

167
cpp/step4_if_fn_do.cpp Normal file
View File

@ -0,0 +1,167 @@
#include "MAL.h"
#include "Environment.h"
#include "ReadLine.h"
#include "Types.h"
#include <iostream>
#include <memory>
malValuePtr READ(const String& input);
String PRINT(malValuePtr ast);
static void installFunctions(malEnvPtr env);
static ReadLine s_readLine("~/.mal-history");
int main(int argc, char* argv[])
{
String prompt = "user> ";
String input;
malEnvPtr replEnv(new malEnv);
installCore(replEnv);
installFunctions(replEnv);
while (s_readLine.get(prompt, input)) {
String out;
try {
out = rep(input, replEnv);
}
catch (malEmptyInputException&) {
continue; // no output
}
catch (String& s) {
out = s;
};
std::cout << out << "\n";
}
return 0;
}
String rep(const String& input, malEnvPtr env)
{
return PRINT(EVAL(READ(input), env));
}
malValuePtr READ(const String& input)
{
return readStr(input);
}
malValuePtr EVAL(malValuePtr ast, malEnvPtr env)
{
const malList* list = DYNAMIC_CAST(malList, ast);
if (!list || (list->count() == 0)) {
return ast->eval(env);
}
// From here on down we are evaluating a non-empty list.
// First handle the special forms.
if (const malSymbol* symbol = DYNAMIC_CAST(malSymbol, list->item(0))) {
String special = symbol->value();
int argCount = list->count() - 1;
if (special == "def!") {
checkArgsIs("def!", 2, argCount);
const malSymbol* id = VALUE_CAST(malSymbol, list->item(1));
return env->set(id->value(), EVAL(list->item(2), env));
}
if (special == "do") {
checkArgsAtLeast("do", 1, argCount);
for (int i = 1; i < argCount; i++) {
EVAL(list->item(i), env);
}
return EVAL(list->item(argCount), env);
}
if (special == "fn*") {
checkArgsIs("fn*", 2, argCount);
const malSequence* bindings =
VALUE_CAST(malSequence, list->item(1));
StringVec params;
for (int i = 0; i < bindings->count(); i++) {
const malSymbol* sym =
VALUE_CAST(malSymbol, bindings->item(i));
params.push_back(sym->value());
}
return mal::lambda(params, list->item(2), env);
}
if (special == "if") {
checkArgsBetween("if", 2, 3, argCount);
bool isTrue = EVAL(list->item(1), env)->isTrue();
if (!isTrue && (argCount == 2)) {
return mal::nilValue();
}
return EVAL(list->item(isTrue ? 2 : 3), env);
}
if (special == "let*") {
checkArgsIs("let*", 2, argCount);
const malSequence* bindings =
VALUE_CAST(malSequence, list->item(1));
int count = checkArgsEven("let*", bindings->count());
malEnvPtr inner(new malEnv(env));
for (int i = 0; i < count; i += 2) {
const malSymbol* var =
VALUE_CAST(malSymbol, bindings->item(i));
inner->set(var->value(), EVAL(bindings->item(i+1), inner));
}
return EVAL(list->item(2), inner);
}
}
// Now we're left with the case of a regular list to be evaluated.
std::unique_ptr<malValueVec> items(list->evalItems(env));
malValuePtr op = items->at(0);
if (const malLambda* lambda = DYNAMIC_CAST(malLambda, op)) {
return EVAL(lambda->getBody(),
lambda->makeEnv(items->begin()+1, items->end()));
}
else {
return APPLY(op, items->begin()+1, items->end(), env);
}
}
String PRINT(malValuePtr ast)
{
return ast->print(true);
}
malValuePtr APPLY(malValuePtr op, malValueIter argsBegin, malValueIter argsEnd,
malEnvPtr env)
{
const malApplicable* handler = DYNAMIC_CAST(malApplicable, op);
MAL_CHECK(handler != NULL,
"\"%s\" is not applicable", op->print(true).c_str());
return handler->apply(argsBegin, argsEnd, env);
}
static const char* malFunctionTable[] = {
"(def! list (fn* (& items) items))",
"(def! not (fn* (cond) (if cond false true)))",
"(def! >= (fn* (a b) (<= b a)))",
"(def! < (fn* (a b) (not (<= b a))))",
"(def! > (fn* (a b) (not (<= a b))))",
};
static void installFunctions(malEnvPtr env) {
for (auto &function : malFunctionTable) {
rep(function, env);
}
}
// Added to keep the linker happy at step A
malValuePtr readline(const String& prompt)
{
String input;
if (s_readLine.get(prompt, input)) {
return mal::string(input);
}
return mal::nilValue();
}

174
cpp/step5_tco.cpp Normal file
View File

@ -0,0 +1,174 @@
#include "MAL.h"
#include "Environment.h"
#include "ReadLine.h"
#include "Types.h"
#include <iostream>
#include <memory>
malValuePtr READ(const String& input);
String PRINT(malValuePtr ast);
static void installFunctions(malEnvPtr env);
static ReadLine s_readLine("~/.mal-history");
int main(int argc, char* argv[])
{
String prompt = "user> ";
String input;
malEnvPtr replEnv(new malEnv);
installCore(replEnv);
installFunctions(replEnv);
while (s_readLine.get(prompt, input)) {
String out;
try {
out = rep(input, replEnv);
}
catch (malEmptyInputException&) {
continue; // no output
}
catch (String& s) {
out = s;
};
std::cout << out << "\n";
}
return 0;
}
String rep(const String& input, malEnvPtr env)
{
return PRINT(EVAL(READ(input), env));
}
malValuePtr READ(const String& input)
{
return readStr(input);
}
malValuePtr EVAL(malValuePtr ast, malEnvPtr env)
{
while (1) {
const malList* list = DYNAMIC_CAST(malList, ast);
if (!list || (list->count() == 0)) {
return ast->eval(env);
}
// From here on down we are evaluating a non-empty list.
// First handle the special forms.
if (const malSymbol* symbol = DYNAMIC_CAST(malSymbol, list->item(0))) {
String special = symbol->value();
int argCount = list->count() - 1;
if (special == "def!") {
checkArgsIs("def!", 2, argCount);
const malSymbol* id = VALUE_CAST(malSymbol, list->item(1));
return env->set(id->value(), EVAL(list->item(2), env));
}
if (special == "do") {
checkArgsAtLeast("do", 1, argCount);
for (int i = 1; i < argCount; i++) {
EVAL(list->item(i), env);
}
ast = list->item(argCount);
continue; // TCO
}
if (special == "fn*") {
checkArgsIs("fn*", 2, argCount);
const malSequence* bindings =
VALUE_CAST(malSequence, list->item(1));
StringVec params;
for (int i = 0; i < bindings->count(); i++) {
const malSymbol* sym =
VALUE_CAST(malSymbol, bindings->item(i));
params.push_back(sym->value());
}
return mal::lambda(params, list->item(2), env);
}
if (special == "if") {
checkArgsBetween("if", 2, 3, argCount);
bool isTrue = EVAL(list->item(1), env)->isTrue();
if (!isTrue && (argCount == 2)) {
return mal::nilValue();
}
ast = list->item(isTrue ? 2 : 3);
continue; // TCO
}
if (special == "let*") {
checkArgsIs("let*", 2, argCount);
const malSequence* bindings =
VALUE_CAST(malSequence, list->item(1));
int count = checkArgsEven("let*", bindings->count());
malEnvPtr inner(new malEnv(env));
for (int i = 0; i < count; i += 2) {
const malSymbol* var =
VALUE_CAST(malSymbol, bindings->item(i));
inner->set(var->value(), EVAL(bindings->item(i+1), inner));
}
ast = list->item(2);
env = inner;
continue; // TCO
}
}
// Now we're left with the case of a regular list to be evaluated.
std::unique_ptr<malValueVec> items(list->evalItems(env));
malValuePtr op = items->at(0);
if (const malLambda* lambda = DYNAMIC_CAST(malLambda, op)) {
ast = lambda->getBody();
env = lambda->makeEnv(items->begin()+1, items->end());
continue; // TCO
}
else {
return APPLY(op, items->begin()+1, items->end(), env);
}
}
}
String PRINT(malValuePtr ast)
{
return ast->print(true);
}
malValuePtr APPLY(malValuePtr op, malValueIter argsBegin, malValueIter argsEnd,
malEnvPtr env)
{
const malApplicable* handler = DYNAMIC_CAST(malApplicable, op);
MAL_CHECK(handler != NULL,
"\"%s\" is not applicable", op->print(true).c_str());
return handler->apply(argsBegin, argsEnd, env);
}
static const char* malFunctionTable[] = {
"(def! list (fn* (& items) items))",
"(def! not (fn* (cond) (if cond false true)))",
"(def! >= (fn* (a b) (<= b a)))",
"(def! < (fn* (a b) (not (<= b a))))",
"(def! > (fn* (a b) (not (<= a b))))",
};
static void installFunctions(malEnvPtr env) {
for (auto &function : malFunctionTable) {
rep(function, env);
}
}
// Added to keep the linker happy at step A
malValuePtr readline(const String& prompt)
{
String input;
if (s_readLine.get(prompt, input)) {
return mal::string(input);
}
return mal::nilValue();
}

199
cpp/step6_file.cpp Normal file
View File

@ -0,0 +1,199 @@
#include "MAL.h"
#include "Environment.h"
#include "ReadLine.h"
#include "Types.h"
#include <iostream>
#include <memory>
malValuePtr READ(const String& input);
String PRINT(malValuePtr ast);
static void installFunctions(malEnvPtr env);
static void makeArgv(malEnvPtr env, int argc, char* argv[]);
static void safeRep(const String& input, malEnvPtr env);
static ReadLine s_readLine("~/.mal-history");
int main(int argc, char* argv[])
{
String prompt = "user> ";
String input;
malEnvPtr replEnv(new malEnv);
installCore(replEnv);
installFunctions(replEnv);
makeArgv(replEnv, argc - 2, argv + 2);
if (argc > 1) {
String filename = escape(argv[1]);
safeRep(STRF("(load-file %s)", filename.c_str()), replEnv);
return 0;
}
while (s_readLine.get(prompt, input)) {
safeRep(input, replEnv);
}
return 0;
}
static void safeRep(const String& input, malEnvPtr env)
{
String out;
try {
out = rep(input, env);
}
catch (malEmptyInputException&) {
return;
}
catch (String& s) {
out = s;
};
std::cout << out << "\n";
}
static void makeArgv(malEnvPtr env, int argc, char* argv[])
{
malValueVec* args = new malValueVec();
for (int i = 0; i < argc; i++) {
args->push_back(mal::string(argv[i]));
}
env->set("*ARGV*", mal::list(args));
}
String rep(const String& input, malEnvPtr env)
{
return PRINT(EVAL(READ(input), env));
}
malValuePtr READ(const String& input)
{
return readStr(input);
}
malValuePtr EVAL(malValuePtr ast, malEnvPtr env)
{
while (1) {
const malList* list = DYNAMIC_CAST(malList, ast);
if (!list || (list->count() == 0)) {
return ast->eval(env);
}
// From here on down we are evaluating a non-empty list.
// First handle the special forms.
if (const malSymbol* symbol = DYNAMIC_CAST(malSymbol, list->item(0))) {
String special = symbol->value();
int argCount = list->count() - 1;
if (special == "def!") {
checkArgsIs("def!", 2, argCount);
const malSymbol* id = VALUE_CAST(malSymbol, list->item(1));
return env->set(id->value(), EVAL(list->item(2), env));
}
if (special == "do") {
checkArgsAtLeast("do", 1, argCount);
for (int i = 1; i < argCount; i++) {
EVAL(list->item(i), env);
}
ast = list->item(argCount);
continue; // TCO
}
if (special == "fn*") {
checkArgsIs("fn*", 2, argCount);
const malSequence* bindings =
VALUE_CAST(malSequence, list->item(1));
StringVec params;
for (int i = 0; i < bindings->count(); i++) {
const malSymbol* sym =
VALUE_CAST(malSymbol, bindings->item(i));
params.push_back(sym->value());
}
return mal::lambda(params, list->item(2), env);
}
if (special == "if") {
checkArgsBetween("if", 2, 3, argCount);
bool isTrue = EVAL(list->item(1), env)->isTrue();
if (!isTrue && (argCount == 2)) {
return mal::nilValue();
}
ast = list->item(isTrue ? 2 : 3);
continue; // TCO
}
if (special == "let*") {
checkArgsIs("let*", 2, argCount);
const malSequence* bindings =
VALUE_CAST(malSequence, list->item(1));
int count = checkArgsEven("let*", bindings->count());
malEnvPtr inner(new malEnv(env));
for (int i = 0; i < count; i += 2) {
const malSymbol* var =
VALUE_CAST(malSymbol, bindings->item(i));
inner->set(var->value(), EVAL(bindings->item(i+1), inner));
}
ast = list->item(2);
env = inner;
continue; // TCO
}
}
// Now we're left with the case of a regular list to be evaluated.
std::unique_ptr<malValueVec> items(list->evalItems(env));
malValuePtr op = items->at(0);
if (const malLambda* lambda = DYNAMIC_CAST(malLambda, op)) {
ast = lambda->getBody();
env = lambda->makeEnv(items->begin()+1, items->end());
continue; // TCO
}
else {
return APPLY(op, items->begin()+1, items->end(), env);
}
}
}
String PRINT(malValuePtr ast)
{
return ast->print(true);
}
malValuePtr APPLY(malValuePtr op, malValueIter argsBegin, malValueIter argsEnd,
malEnvPtr env)
{
const malApplicable* handler = DYNAMIC_CAST(malApplicable, op);
MAL_CHECK(handler != NULL,
"\"%s\" is not applicable", op->print(true).c_str());
return handler->apply(argsBegin, argsEnd, env);
}
static const char* malFunctionTable[] = {
"(def! list (fn* (& items) items))",
"(def! not (fn* (cond) (if cond false true)))",
"(def! >= (fn* (a b) (<= b a)))",
"(def! < (fn* (a b) (not (<= b a))))",
"(def! > (fn* (a b) (not (<= a b))))",
"(def! load-file (fn* (filename) \
(eval (read-string (str \"(do \" (slurp filename) \")\")))))",
};
static void installFunctions(malEnvPtr env) {
for (auto &function : malFunctionTable) {
rep(function, env);
}
}
// Added to keep the linker happy at step A
malValuePtr readline(const String& prompt)
{
String input;
if (s_readLine.get(prompt, input)) {
return mal::string(input);
}
return mal::nilValue();
}

257
cpp/step7_quote.cpp Normal file
View File

@ -0,0 +1,257 @@
#include "MAL.h"
#include "Environment.h"
#include "ReadLine.h"
#include "Types.h"
#include <iostream>
#include <memory>
malValuePtr READ(const String& input);
String PRINT(malValuePtr ast);
static void installFunctions(malEnvPtr env);
static void makeArgv(malEnvPtr env, int argc, char* argv[]);
static void safeRep(const String& input, malEnvPtr env);
static malValuePtr quasiquote(malValuePtr obj);
static ReadLine s_readLine("~/.mal-history");
int main(int argc, char* argv[])
{
String prompt = "user> ";
String input;
malEnvPtr replEnv(new malEnv);
installCore(replEnv);
installFunctions(replEnv);
makeArgv(replEnv, argc - 2, argv + 2);
if (argc > 1) {
String filename = escape(argv[1]);
safeRep(STRF("(load-file %s)", filename.c_str()), replEnv);
return 0;
}
while (s_readLine.get(prompt, input)) {
safeRep(input, replEnv);
}
return 0;
}
static void safeRep(const String& input, malEnvPtr env)
{
String out;
try {
out = rep(input, env);
}
catch (malEmptyInputException&) {
return;
}
catch (String& s) {
out = s;
};
std::cout << out << "\n";
}
static void makeArgv(malEnvPtr env, int argc, char* argv[])
{
malValueVec* args = new malValueVec();
for (int i = 0; i < argc; i++) {
args->push_back(mal::string(argv[i]));
}
env->set("*ARGV*", mal::list(args));
}
String rep(const String& input, malEnvPtr env)
{
return PRINT(EVAL(READ(input), env));
}
malValuePtr READ(const String& input)
{
return readStr(input);
}
malValuePtr EVAL(malValuePtr ast, malEnvPtr env)
{
while (1) {
const malList* list = DYNAMIC_CAST(malList, ast);
if (!list || (list->count() == 0)) {
return ast->eval(env);
}
// From here on down we are evaluating a non-empty list.
// First handle the special forms.
if (const malSymbol* symbol = DYNAMIC_CAST(malSymbol, list->item(0))) {
String special = symbol->value();
int argCount = list->count() - 1;
if (special == "def!") {
checkArgsIs("def!", 2, argCount);
const malSymbol* id = VALUE_CAST(malSymbol, list->item(1));
return env->set(id->value(), EVAL(list->item(2), env));
}
if (special == "do") {
checkArgsAtLeast("do", 1, argCount);
for (int i = 1; i < argCount; i++) {
EVAL(list->item(i), env);
}
ast = list->item(argCount);
continue; // TCO
}
if (special == "fn*") {
checkArgsIs("fn*", 2, argCount);
const malSequence* bindings =
VALUE_CAST(malSequence, list->item(1));
StringVec params;
for (int i = 0; i < bindings->count(); i++) {
const malSymbol* sym =
VALUE_CAST(malSymbol, bindings->item(i));
params.push_back(sym->value());
}
return mal::lambda(params, list->item(2), env);
}
if (special == "if") {
checkArgsBetween("if", 2, 3, argCount);
bool isTrue = EVAL(list->item(1), env)->isTrue();
if (!isTrue && (argCount == 2)) {
return mal::nilValue();
}
ast = list->item(isTrue ? 2 : 3);
continue; // TCO
}
if (special == "let*") {
checkArgsIs("let*", 2, argCount);
const malSequence* bindings =
VALUE_CAST(malSequence, list->item(1));
int count = checkArgsEven("let*", bindings->count());
malEnvPtr inner(new malEnv(env));
for (int i = 0; i < count; i += 2) {
const malSymbol* var =
VALUE_CAST(malSymbol, bindings->item(i));
inner->set(var->value(), EVAL(bindings->item(i+1), inner));
}
ast = list->item(2);
env = inner;
continue; // TCO
}
if (special == "quasiquote") {
checkArgsIs("quasiquote", 1, argCount);
ast = quasiquote(list->item(1));
continue; // TCO
}
if (special == "quote") {
checkArgsIs("quote", 1, argCount);
return list->item(1);
}
}
// Now we're left with the case of a regular list to be evaluated.
std::unique_ptr<malValueVec> items(list->evalItems(env));
malValuePtr op = items->at(0);
if (const malLambda* lambda = DYNAMIC_CAST(malLambda, op)) {
ast = lambda->getBody();
env = lambda->makeEnv(items->begin()+1, items->end());
continue; // TCO
}
else {
return APPLY(op, items->begin()+1, items->end(), env);
}
}
}
String PRINT(malValuePtr ast)
{
return ast->print(true);
}
malValuePtr APPLY(malValuePtr op, malValueIter argsBegin, malValueIter argsEnd,
malEnvPtr env)
{
const malApplicable* handler = DYNAMIC_CAST(malApplicable, op);
MAL_CHECK(handler != NULL,
"\"%s\" is not applicable", op->print(true).c_str());
return handler->apply(argsBegin, argsEnd, env);
}
static bool isSymbol(malValuePtr obj, const String& text)
{
const malSymbol* sym = DYNAMIC_CAST(malSymbol, obj);
return sym && (sym->value() == text);
}
static const malSequence* isPair(malValuePtr obj)
{
const malSequence* list = DYNAMIC_CAST(malSequence, obj);
return list && !list->isEmpty() ? list : NULL;
}
static malValuePtr quasiquote(malValuePtr obj)
{
const malSequence* seq = isPair(obj);
if (!seq) {
return mal::list(mal::symbol("quote"), obj);
}
if (isSymbol(seq->item(0), "unquote")) {
// (qq (uq form)) -> form
checkArgsIs("unquote", 1, seq->count() - 1);
return seq->item(1);
}
const malSequence* innerSeq = isPair(seq->item(0));
if (innerSeq && isSymbol(innerSeq->item(0), "splice-unquote")) {
checkArgsIs("splice-unquote", 1, innerSeq->count() - 1);
// (qq (sq '(a b c))) -> a b c
return mal::list(
mal::symbol("concat"),
innerSeq->item(1),
quasiquote(seq->rest())
);
}
else {
// (qq (a b c)) -> (list (qq a) (qq b) (qq c))
// (qq xs ) -> (cons (qq (car xs)) (qq (cdr xs)))
return mal::list(
mal::symbol("cons"),
quasiquote(seq->first()),
quasiquote(seq->rest())
);
}
}
static const char* malFunctionTable[] = {
"(def! list (fn* (& items) items))",
"(def! not (fn* (cond) (if cond false true)))",
"(def! >= (fn* (a b) (<= b a)))",
"(def! < (fn* (a b) (not (<= b a))))",
"(def! > (fn* (a b) (not (<= a b))))",
"(def! load-file (fn* (filename) \
(eval (read-string (str \"(do \" (slurp filename) \")\")))))",
};
static void installFunctions(malEnvPtr env) {
for (auto &function : malFunctionTable) {
rep(function, env);
}
}
// Added to keep the linker happy at step A
malValuePtr readline(const String& prompt)
{
String input;
if (s_readLine.get(prompt, input)) {
return mal::string(input);
}
return mal::nilValue();
}

316
cpp/step8_macros.cpp Normal file
View File

@ -0,0 +1,316 @@
#include "MAL.h"
#include "Environment.h"
#include "ReadLine.h"
#include "Types.h"
#include <iostream>
#include <memory>
malValuePtr READ(const String& input);
String PRINT(malValuePtr ast);
static void installFunctions(malEnvPtr env);
static void makeArgv(malEnvPtr env, int argc, char* argv[]);
static void safeRep(const String& input, malEnvPtr env);
static malValuePtr quasiquote(malValuePtr obj);
static malValuePtr macroExpand(malValuePtr obj, malEnvPtr env);
static void installMacros(malEnvPtr env);
static ReadLine s_readLine("~/.mal-history");
int main(int argc, char* argv[])
{
String prompt = "user> ";
String input;
malEnvPtr replEnv(new malEnv);
installCore(replEnv);
installFunctions(replEnv);
installMacros(replEnv);
makeArgv(replEnv, argc - 2, argv + 2);
if (argc > 1) {
String filename = escape(argv[1]);
safeRep(STRF("(load-file %s)", filename.c_str()), replEnv);
return 0;
}
while (s_readLine.get(prompt, input)) {
safeRep(input, replEnv);
}
return 0;
}
static void safeRep(const String& input, malEnvPtr env)
{
String out;
try {
out = rep(input, env);
}
catch (malEmptyInputException&) {
return;
}
catch (String& s) {
out = s;
};
std::cout << out << "\n";
}
static void makeArgv(malEnvPtr env, int argc, char* argv[])
{
malValueVec* args = new malValueVec();
for (int i = 0; i < argc; i++) {
args->push_back(mal::string(argv[i]));
}
env->set("*ARGV*", mal::list(args));
}
String rep(const String& input, malEnvPtr env)
{
return PRINT(EVAL(READ(input), env));
}
malValuePtr READ(const String& input)
{
return readStr(input);
}
malValuePtr EVAL(malValuePtr ast, malEnvPtr env)
{
while (1) {
const malList* list = DYNAMIC_CAST(malList, ast);
if (!list || (list->count() == 0)) {
return ast->eval(env);
}
ast = macroExpand(ast, env);
list = DYNAMIC_CAST(malList, ast);
if (!list || (list->count() == 0)) {
return ast->eval(env);
}
// From here on down we are evaluating a non-empty list.
// First handle the special forms.
if (const malSymbol* symbol = DYNAMIC_CAST(malSymbol, list->item(0))) {
String special = symbol->value();
int argCount = list->count() - 1;
if (special == "def!") {
checkArgsIs("def!", 2, argCount);
const malSymbol* id = VALUE_CAST(malSymbol, list->item(1));
return env->set(id->value(), EVAL(list->item(2), env));
}
if (special == "defmacro!") {
checkArgsIs("defmacro!", 2, argCount);
const malSymbol* id = VALUE_CAST(malSymbol, list->item(1));
malValuePtr body = EVAL(list->item(2), env);
const malLambda* lambda = VALUE_CAST(malLambda, body);
return env->set(id->value(), mal::macro(*lambda));
}
if (special == "do") {
checkArgsAtLeast("do", 1, argCount);
for (int i = 1; i < argCount; i++) {
EVAL(list->item(i), env);
}
ast = list->item(argCount);
continue; // TCO
}
if (special == "fn*") {
checkArgsIs("fn*", 2, argCount);
const malSequence* bindings =
VALUE_CAST(malSequence, list->item(1));
StringVec params;
for (int i = 0; i < bindings->count(); i++) {
const malSymbol* sym =
VALUE_CAST(malSymbol, bindings->item(i));
params.push_back(sym->value());
}
return mal::lambda(params, list->item(2), env);
}
if (special == "if") {
checkArgsBetween("if", 2, 3, argCount);
bool isTrue = EVAL(list->item(1), env)->isTrue();
if (!isTrue && (argCount == 2)) {
return mal::nilValue();
}
ast = list->item(isTrue ? 2 : 3);
continue; // TCO
}
if (special == "let*") {
checkArgsIs("let*", 2, argCount);
const malSequence* bindings =
VALUE_CAST(malSequence, list->item(1));
int count = checkArgsEven("let*", bindings->count());
malEnvPtr inner(new malEnv(env));
for (int i = 0; i < count; i += 2) {
const malSymbol* var =
VALUE_CAST(malSymbol, bindings->item(i));
inner->set(var->value(), EVAL(bindings->item(i+1), inner));
}
ast = list->item(2);
env = inner;
continue; // TCO
}
if (special == "macroexpand") {
checkArgsIs("macroexpand", 1, argCount);
return macroExpand(list->item(1), env);
}
if (special == "quasiquote") {
checkArgsIs("quasiquote", 1, argCount);
ast = quasiquote(list->item(1));
continue; // TCO
}
if (special == "quote") {
checkArgsIs("quote", 1, argCount);
return list->item(1);
}
}
// Now we're left with the case of a regular list to be evaluated.
std::unique_ptr<malValueVec> items(list->evalItems(env));
malValuePtr op = items->at(0);
if (const malLambda* lambda = DYNAMIC_CAST(malLambda, op)) {
ast = lambda->getBody();
env = lambda->makeEnv(items->begin()+1, items->end());
continue; // TCO
}
else {
return APPLY(op, items->begin()+1, items->end(), env);
}
}
}
String PRINT(malValuePtr ast)
{
return ast->print(true);
}
malValuePtr APPLY(malValuePtr op, malValueIter argsBegin, malValueIter argsEnd,
malEnvPtr env)
{
const malApplicable* handler = DYNAMIC_CAST(malApplicable, op);
MAL_CHECK(handler != NULL,
"\"%s\" is not applicable", op->print(true).c_str());
return handler->apply(argsBegin, argsEnd, env);
}
static bool isSymbol(malValuePtr obj, const String& text)
{
const malSymbol* sym = DYNAMIC_CAST(malSymbol, obj);
return sym && (sym->value() == text);
}
static const malSequence* isPair(malValuePtr obj)
{
const malSequence* list = DYNAMIC_CAST(malSequence, obj);
return list && !list->isEmpty() ? list : NULL;
}
static malValuePtr quasiquote(malValuePtr obj)
{
const malSequence* seq = isPair(obj);
if (!seq) {
return mal::list(mal::symbol("quote"), obj);
}
if (isSymbol(seq->item(0), "unquote")) {
// (qq (uq form)) -> form
checkArgsIs("unquote", 1, seq->count() - 1);
return seq->item(1);
}
const malSequence* innerSeq = isPair(seq->item(0));
if (innerSeq && isSymbol(innerSeq->item(0), "splice-unquote")) {
checkArgsIs("splice-unquote", 1, innerSeq->count() - 1);
// (qq (sq '(a b c))) -> a b c
return mal::list(
mal::symbol("concat"),
innerSeq->item(1),
quasiquote(seq->rest())
);
}
else {
// (qq (a b c)) -> (list (qq a) (qq b) (qq c))
// (qq xs ) -> (cons (qq (car xs)) (qq (cdr xs)))
return mal::list(
mal::symbol("cons"),
quasiquote(seq->first()),
quasiquote(seq->rest())
);
}
}
static const malLambda* isMacroApplication(malValuePtr obj, malEnvPtr env)
{
if (const malSequence* seq = isPair(obj)) {
if (malSymbol* sym = DYNAMIC_CAST(malSymbol, seq->first())) {
if (malEnvPtr symEnv = env->find(sym->value())) {
malValuePtr value = sym->eval(symEnv);
if (malLambda* lambda = DYNAMIC_CAST(malLambda, value)) {
return lambda->isMacro() ? lambda : NULL;
}
}
}
}
return NULL;
}
static malValuePtr macroExpand(malValuePtr obj, malEnvPtr env)
{
while (const malLambda* macro = isMacroApplication(obj, env)) {
const malSequence* seq = STATIC_CAST(malSequence, obj);
obj = macro->apply(seq->begin() + 1, seq->end(), env);
}
return obj;
}
static const char* macroTable[] = {
"(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)))))))",
"(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))))))))",
};
static void installMacros(malEnvPtr env)
{
for (auto &macro : macroTable) {
rep(macro, env);
}
}
static const char* malFunctionTable[] = {
"(def! list (fn* (& items) items))",
"(def! not (fn* (cond) (if cond false true)))",
"(def! >= (fn* (a b) (<= b a)))",
"(def! < (fn* (a b) (not (<= b a))))",
"(def! > (fn* (a b) (not (<= a b))))",
"(def! load-file (fn* (filename) \
(eval (read-string (str \"(do \" (slurp filename) \")\")))))",
};
static void installFunctions(malEnvPtr env) {
for (auto &function : malFunctionTable) {
rep(function, env);
}
}
// Added to keep the linker happy at step A
malValuePtr readline(const String& prompt)
{
String input;
if (s_readLine.get(prompt, input)) {
return mal::string(input);
}
return mal::nilValue();
}

359
cpp/step9_try.cpp Normal file
View File

@ -0,0 +1,359 @@
#include "MAL.h"
#include "Environment.h"
#include "ReadLine.h"
#include "Types.h"
#include <iostream>
#include <memory>
malValuePtr READ(const String& input);
String PRINT(malValuePtr ast);
static void installFunctions(malEnvPtr env);
static void makeArgv(malEnvPtr env, int argc, char* argv[]);
static void safeRep(const String& input, malEnvPtr env);
static malValuePtr quasiquote(malValuePtr obj);
static malValuePtr macroExpand(malValuePtr obj, malEnvPtr env);
static void installMacros(malEnvPtr env);
static ReadLine s_readLine("~/.mal-history");
int main(int argc, char* argv[])
{
String prompt = "user> ";
String input;
malEnvPtr replEnv(new malEnv);
installCore(replEnv);
installFunctions(replEnv);
installMacros(replEnv);
makeArgv(replEnv, argc - 2, argv + 2);
if (argc > 1) {
String filename = escape(argv[1]);
safeRep(STRF("(load-file %s)", filename.c_str()), replEnv);
return 0;
}
while (s_readLine.get(prompt, input)) {
safeRep(input, replEnv);
}
return 0;
}
static void safeRep(const String& input, malEnvPtr env)
{
String out;
try {
out = rep(input, env);
}
catch (malEmptyInputException&) {
return;
}
catch (String& s) {
out = s;
};
std::cout << out << "\n";
}
static void makeArgv(malEnvPtr env, int argc, char* argv[])
{
malValueVec* args = new malValueVec();
for (int i = 0; i < argc; i++) {
args->push_back(mal::string(argv[i]));
}
env->set("*ARGV*", mal::list(args));
}
String rep(const String& input, malEnvPtr env)
{
return PRINT(EVAL(READ(input), env));
}
malValuePtr READ(const String& input)
{
return readStr(input);
}
malValuePtr EVAL(malValuePtr ast, malEnvPtr env)
{
while (1) {
const malList* list = DYNAMIC_CAST(malList, ast);
if (!list || (list->count() == 0)) {
return ast->eval(env);
}
ast = macroExpand(ast, env);
list = DYNAMIC_CAST(malList, ast);
if (!list || (list->count() == 0)) {
return ast->eval(env);
}
// From here on down we are evaluating a non-empty list.
// First handle the special forms.
if (const malSymbol* symbol = DYNAMIC_CAST(malSymbol, list->item(0))) {
String special = symbol->value();
int argCount = list->count() - 1;
if (special == "def!") {
checkArgsIs("def!", 2, argCount);
const malSymbol* id = VALUE_CAST(malSymbol, list->item(1));
return env->set(id->value(), EVAL(list->item(2), env));
}
if (special == "defmacro!") {
checkArgsIs("defmacro!", 2, argCount);
const malSymbol* id = VALUE_CAST(malSymbol, list->item(1));
malValuePtr body = EVAL(list->item(2), env);
const malLambda* lambda = VALUE_CAST(malLambda, body);
return env->set(id->value(), mal::macro(*lambda));
}
if (special == "do") {
checkArgsAtLeast("do", 1, argCount);
for (int i = 1; i < argCount; i++) {
EVAL(list->item(i), env);
}
ast = list->item(argCount);
continue; // TCO
}
if (special == "fn*") {
checkArgsIs("fn*", 2, argCount);
const malSequence* bindings =
VALUE_CAST(malSequence, list->item(1));
StringVec params;
for (int i = 0; i < bindings->count(); i++) {
const malSymbol* sym =
VALUE_CAST(malSymbol, bindings->item(i));
params.push_back(sym->value());
}
return mal::lambda(params, list->item(2), env);
}
if (special == "if") {
checkArgsBetween("if", 2, 3, argCount);
bool isTrue = EVAL(list->item(1), env)->isTrue();
if (!isTrue && (argCount == 2)) {
return mal::nilValue();
}
ast = list->item(isTrue ? 2 : 3);
continue; // TCO
}
if (special == "let*") {
checkArgsIs("let*", 2, argCount);
const malSequence* bindings =
VALUE_CAST(malSequence, list->item(1));
int count = checkArgsEven("let*", bindings->count());
malEnvPtr inner(new malEnv(env));
for (int i = 0; i < count; i += 2) {
const malSymbol* var =
VALUE_CAST(malSymbol, bindings->item(i));
inner->set(var->value(), EVAL(bindings->item(i+1), inner));
}
ast = list->item(2);
env = inner;
continue; // TCO
}
if (special == "macroexpand") {
checkArgsIs("macroexpand", 1, argCount);
return macroExpand(list->item(1), env);
}
if (special == "quasiquote") {
checkArgsIs("quasiquote", 1, argCount);
ast = quasiquote(list->item(1));
continue; // TCO
}
if (special == "quote") {
checkArgsIs("quote", 1, argCount);
return list->item(1);
}
if (special == "try*") {
checkArgsIs("try*", 2, argCount);
malValuePtr tryBody = list->item(1);
const malList* catchBlock = VALUE_CAST(malList, list->item(2));
checkArgsIs("catch*", 2, catchBlock->count() - 1);
MAL_CHECK(VALUE_CAST(malSymbol,
catchBlock->item(0))->value() == "catch*",
"catch block must begin with catch*");
// We don't need excSym at this scope, but we want to check
// that the catch block is valid always, not just in case of
// an exception.
const malSymbol* excSym =
VALUE_CAST(malSymbol, catchBlock->item(1));
malValuePtr excVal;
try {
ast = EVAL(tryBody, env);
}
catch(String& s) {
excVal = mal::string(s);
}
catch (malEmptyInputException&) {
// Not an error, continue as if we got nil
ast = mal::nilValue();
}
catch(malValuePtr& o) {
excVal = o;
};
if (excVal) {
// we got some exception
env = malEnvPtr(new malEnv(env));
env->set(excSym->value(), excVal);
ast = catchBlock->item(2);
}
continue; // TCO
}
}
// Now we're left with the case of a regular list to be evaluated.
std::unique_ptr<malValueVec> items(list->evalItems(env));
malValuePtr op = items->at(0);
if (const malLambda* lambda = DYNAMIC_CAST(malLambda, op)) {
ast = lambda->getBody();
env = lambda->makeEnv(items->begin()+1, items->end());
continue; // TCO
}
else {
return APPLY(op, items->begin()+1, items->end(), env);
}
}
}
String PRINT(malValuePtr ast)
{
return ast->print(true);
}
malValuePtr APPLY(malValuePtr op, malValueIter argsBegin, malValueIter argsEnd,
malEnvPtr env)
{
const malApplicable* handler = DYNAMIC_CAST(malApplicable, op);
MAL_CHECK(handler != NULL,
"\"%s\" is not applicable", op->print(true).c_str());
return handler->apply(argsBegin, argsEnd, env);
}
static bool isSymbol(malValuePtr obj, const String& text)
{
const malSymbol* sym = DYNAMIC_CAST(malSymbol, obj);
return sym && (sym->value() == text);
}
static const malSequence* isPair(malValuePtr obj)
{
const malSequence* list = DYNAMIC_CAST(malSequence, obj);
return list && !list->isEmpty() ? list : NULL;
}
static malValuePtr quasiquote(malValuePtr obj)
{
const malSequence* seq = isPair(obj);
if (!seq) {
return mal::list(mal::symbol("quote"), obj);
}
if (isSymbol(seq->item(0), "unquote")) {
// (qq (uq form)) -> form
checkArgsIs("unquote", 1, seq->count() - 1);
return seq->item(1);
}
const malSequence* innerSeq = isPair(seq->item(0));
if (innerSeq && isSymbol(innerSeq->item(0), "splice-unquote")) {
checkArgsIs("splice-unquote", 1, innerSeq->count() - 1);
// (qq (sq '(a b c))) -> a b c
return mal::list(
mal::symbol("concat"),
innerSeq->item(1),
quasiquote(seq->rest())
);
}
else {
// (qq (a b c)) -> (list (qq a) (qq b) (qq c))
// (qq xs ) -> (cons (qq (car xs)) (qq (cdr xs)))
return mal::list(
mal::symbol("cons"),
quasiquote(seq->first()),
quasiquote(seq->rest())
);
}
}
static const malLambda* isMacroApplication(malValuePtr obj, malEnvPtr env)
{
if (const malSequence* seq = isPair(obj)) {
if (malSymbol* sym = DYNAMIC_CAST(malSymbol, seq->first())) {
if (malEnvPtr symEnv = env->find(sym->value())) {
malValuePtr value = sym->eval(symEnv);
if (malLambda* lambda = DYNAMIC_CAST(malLambda, value)) {
return lambda->isMacro() ? lambda : NULL;
}
}
}
}
return NULL;
}
static malValuePtr macroExpand(malValuePtr obj, malEnvPtr env)
{
while (const malLambda* macro = isMacroApplication(obj, env)) {
const malSequence* seq = STATIC_CAST(malSequence, obj);
obj = macro->apply(seq->begin() + 1, seq->end(), env);
}
return obj;
}
static const char* macroTable[] = {
"(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)))))))",
"(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))))))))",
};
static void installMacros(malEnvPtr env)
{
for (auto &macro : macroTable) {
rep(macro, env);
}
}
static const char* malFunctionTable[] = {
"(def! list (fn* (& items) items))",
"(def! not (fn* (cond) (if cond false true)))",
"(def! >= (fn* (a b) (<= b a)))",
"(def! < (fn* (a b) (not (<= b a))))",
"(def! > (fn* (a b) (not (<= a b))))",
"(def! load-file (fn* (filename) \
(eval (read-string (str \"(do \" (slurp filename) \")\")))))",
"(def! map (fn* (f xs) (if (empty? xs) xs \
(cons (f (first xs)) (map f (rest xs))))))",
};
static void installFunctions(malEnvPtr env) {
for (auto &function : malFunctionTable) {
rep(function, env);
}
}
// Added to keep the linker happy at step A
malValuePtr readline(const String& prompt)
{
String input;
if (s_readLine.get(prompt, input)) {
return mal::string(input);
}
return mal::nilValue();
}

359
cpp/stepA_mal.cpp Normal file
View File

@ -0,0 +1,359 @@
#include "MAL.h"
#include "Environment.h"
#include "ReadLine.h"
#include "Types.h"
#include <iostream>
#include <memory>
malValuePtr READ(const String& input);
String PRINT(malValuePtr ast);
static void installFunctions(malEnvPtr env);
static void makeArgv(malEnvPtr env, int argc, char* argv[]);
static void safeRep(const String& input, malEnvPtr env);
static malValuePtr quasiquote(malValuePtr obj);
static malValuePtr macroExpand(malValuePtr obj, malEnvPtr env);
static void installMacros(malEnvPtr env);
static ReadLine s_readLine("~/.mal-history");
int main(int argc, char* argv[])
{
String prompt = "user> ";
String input;
malEnvPtr replEnv(new malEnv);
installCore(replEnv);
installFunctions(replEnv);
installMacros(replEnv);
makeArgv(replEnv, argc - 2, argv + 2);
if (argc > 1) {
String filename = escape(argv[1]);
safeRep(STRF("(load-file %s)", filename.c_str()), replEnv);
return 0;
}
while (s_readLine.get(prompt, input)) {
safeRep(input, replEnv);
}
return 0;
}
static void safeRep(const String& input, malEnvPtr env)
{
String out;
try {
out = rep(input, env);
}
catch (malEmptyInputException&) {
return;
}
catch (String& s) {
out = s;
};
std::cout << out << "\n";
}
static void makeArgv(malEnvPtr env, int argc, char* argv[])
{
malValueVec* args = new malValueVec();
for (int i = 0; i < argc; i++) {
args->push_back(mal::string(argv[i]));
}
env->set("*ARGV*", mal::list(args));
}
String rep(const String& input, malEnvPtr env)
{
return PRINT(EVAL(READ(input), env));
}
malValuePtr READ(const String& input)
{
return readStr(input);
}
malValuePtr EVAL(malValuePtr ast, malEnvPtr env)
{
while (1) {
const malList* list = DYNAMIC_CAST(malList, ast);
if (!list || (list->count() == 0)) {
return ast->eval(env);
}
ast = macroExpand(ast, env);
list = DYNAMIC_CAST(malList, ast);
if (!list || (list->count() == 0)) {
return ast->eval(env);
}
// From here on down we are evaluating a non-empty list.
// First handle the special forms.
if (const malSymbol* symbol = DYNAMIC_CAST(malSymbol, list->item(0))) {
String special = symbol->value();
int argCount = list->count() - 1;
if (special == "def!") {
checkArgsIs("def!", 2, argCount);
const malSymbol* id = VALUE_CAST(malSymbol, list->item(1));
return env->set(id->value(), EVAL(list->item(2), env));
}
if (special == "defmacro!") {
checkArgsIs("defmacro!", 2, argCount);
const malSymbol* id = VALUE_CAST(malSymbol, list->item(1));
malValuePtr body = EVAL(list->item(2), env);
const malLambda* lambda = VALUE_CAST(malLambda, body);
return env->set(id->value(), mal::macro(*lambda));
}
if (special == "do") {
checkArgsAtLeast("do", 1, argCount);
for (int i = 1; i < argCount; i++) {
EVAL(list->item(i), env);
}
ast = list->item(argCount);
continue; // TCO
}
if (special == "fn*") {
checkArgsIs("fn*", 2, argCount);
const malSequence* bindings =
VALUE_CAST(malSequence, list->item(1));
StringVec params;
for (int i = 0; i < bindings->count(); i++) {
const malSymbol* sym =
VALUE_CAST(malSymbol, bindings->item(i));
params.push_back(sym->value());
}
return mal::lambda(params, list->item(2), env);
}
if (special == "if") {
checkArgsBetween("if", 2, 3, argCount);
bool isTrue = EVAL(list->item(1), env)->isTrue();
if (!isTrue && (argCount == 2)) {
return mal::nilValue();
}
ast = list->item(isTrue ? 2 : 3);
continue; // TCO
}
if (special == "let*") {
checkArgsIs("let*", 2, argCount);
const malSequence* bindings =
VALUE_CAST(malSequence, list->item(1));
int count = checkArgsEven("let*", bindings->count());
malEnvPtr inner(new malEnv(env));
for (int i = 0; i < count; i += 2) {
const malSymbol* var =
VALUE_CAST(malSymbol, bindings->item(i));
inner->set(var->value(), EVAL(bindings->item(i+1), inner));
}
ast = list->item(2);
env = inner;
continue; // TCO
}
if (special == "macroexpand") {
checkArgsIs("macroexpand", 1, argCount);
return macroExpand(list->item(1), env);
}
if (special == "quasiquote") {
checkArgsIs("quasiquote", 1, argCount);
ast = quasiquote(list->item(1));
continue; // TCO
}
if (special == "quote") {
checkArgsIs("quote", 1, argCount);
return list->item(1);
}
if (special == "try*") {
checkArgsIs("try*", 2, argCount);
malValuePtr tryBody = list->item(1);
const malList* catchBlock = VALUE_CAST(malList, list->item(2));
checkArgsIs("catch*", 2, catchBlock->count() - 1);
MAL_CHECK(VALUE_CAST(malSymbol,
catchBlock->item(0))->value() == "catch*",
"catch block must begin with catch*");
// We don't need excSym at this scope, but we want to check
// that the catch block is valid always, not just in case of
// an exception.
const malSymbol* excSym =
VALUE_CAST(malSymbol, catchBlock->item(1));
malValuePtr excVal;
try {
ast = EVAL(tryBody, env);
}
catch(String& s) {
excVal = mal::string(s);
}
catch (malEmptyInputException&) {
// Not an error, continue as if we got nil
ast = mal::nilValue();
}
catch(malValuePtr& o) {
excVal = o;
};
if (excVal) {
// we got some exception
env = malEnvPtr(new malEnv(env));
env->set(excSym->value(), excVal);
ast = catchBlock->item(2);
}
continue; // TCO
}
}
// Now we're left with the case of a regular list to be evaluated.
std::unique_ptr<malValueVec> items(list->evalItems(env));
malValuePtr op = items->at(0);
if (const malLambda* lambda = DYNAMIC_CAST(malLambda, op)) {
ast = lambda->getBody();
env = lambda->makeEnv(items->begin()+1, items->end());
continue; // TCO
}
else {
return APPLY(op, items->begin()+1, items->end(), env);
}
}
}
String PRINT(malValuePtr ast)
{
return ast->print(true);
}
malValuePtr APPLY(malValuePtr op, malValueIter argsBegin, malValueIter argsEnd,
malEnvPtr env)
{
const malApplicable* handler = DYNAMIC_CAST(malApplicable, op);
MAL_CHECK(handler != NULL,
"\"%s\" is not applicable", op->print(true).c_str());
return handler->apply(argsBegin, argsEnd, env);
}
static bool isSymbol(malValuePtr obj, const String& text)
{
const malSymbol* sym = DYNAMIC_CAST(malSymbol, obj);
return sym && (sym->value() == text);
}
static const malSequence* isPair(malValuePtr obj)
{
const malSequence* list = DYNAMIC_CAST(malSequence, obj);
return list && !list->isEmpty() ? list : NULL;
}
static malValuePtr quasiquote(malValuePtr obj)
{
const malSequence* seq = isPair(obj);
if (!seq) {
return mal::list(mal::symbol("quote"), obj);
}
if (isSymbol(seq->item(0), "unquote")) {
// (qq (uq form)) -> form
checkArgsIs("unquote", 1, seq->count() - 1);
return seq->item(1);
}
const malSequence* innerSeq = isPair(seq->item(0));
if (innerSeq && isSymbol(innerSeq->item(0), "splice-unquote")) {
checkArgsIs("splice-unquote", 1, innerSeq->count() - 1);
// (qq (sq '(a b c))) -> a b c
return mal::list(
mal::symbol("concat"),
innerSeq->item(1),
quasiquote(seq->rest())
);
}
else {
// (qq (a b c)) -> (list (qq a) (qq b) (qq c))
// (qq xs ) -> (cons (qq (car xs)) (qq (cdr xs)))
return mal::list(
mal::symbol("cons"),
quasiquote(seq->first()),
quasiquote(seq->rest())
);
}
}
static const malLambda* isMacroApplication(malValuePtr obj, malEnvPtr env)
{
if (const malSequence* seq = isPair(obj)) {
if (malSymbol* sym = DYNAMIC_CAST(malSymbol, seq->first())) {
if (malEnvPtr symEnv = env->find(sym->value())) {
malValuePtr value = sym->eval(symEnv);
if (malLambda* lambda = DYNAMIC_CAST(malLambda, value)) {
return lambda->isMacro() ? lambda : NULL;
}
}
}
}
return NULL;
}
static malValuePtr macroExpand(malValuePtr obj, malEnvPtr env)
{
while (const malLambda* macro = isMacroApplication(obj, env)) {
const malSequence* seq = STATIC_CAST(malSequence, obj);
obj = macro->apply(seq->begin() + 1, seq->end(), env);
}
return obj;
}
static const char* macroTable[] = {
"(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)))))))",
"(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))))))))",
};
static void installMacros(malEnvPtr env)
{
for (auto &macro : macroTable) {
rep(macro, env);
}
}
malValuePtr readline(const String& prompt)
{
String input;
if (s_readLine.get(prompt, input)) {
return mal::string(input);
}
return mal::nilValue();
}
static const char* malFunctionTable[] = {
"(def! list (fn* (& items) items))",
"(def! not (fn* (cond) (if cond false true)))",
"(def! >= (fn* (a b) (<= b a)))",
"(def! < (fn* (a b) (not (<= b a))))",
"(def! > (fn* (a b) (not (<= a b))))",
"(def! load-file (fn* (filename) \
(eval (read-string (str \"(do \" (slurp filename) \")\")))))",
"(def! map (fn* (f xs) (if (empty? xs) xs \
(cons (f (first xs)) (map f (rest xs))))))",
"(def! swap! (fn* (atom f & args) (reset! atom (apply f @atom args))))",
"(def! *host-language* \"c++\")",
};
static void installFunctions(malEnvPtr env) {
for (auto &function : malFunctionTable) {
rep(function, env);
}
}

View File

@ -1,5 +1,7 @@
# Mal/Make-a-Lisp FAQ
<a name="why_mal"></a>
### Why did you create mal/make-a-lisp?
### OR Why the name "mal"?
### OR Why?
@ -26,6 +28,8 @@ and "make-a-lisp" (and eventually just the latter given that the make
implementation is now just a small part of the whole).
<a name="code_split"></a>
### Why is some code split into steps and some code not?
The split between code that goes in steps and code that goes into other files
@ -58,6 +62,9 @@ modules are just language specific details (they may be the harder
than the Lisp part, but that is due to the nature of the target
language not because of Lisp functionality per se).
<a name="steps"></a>
### Why are the mal/make-a-lisp steps structured the way they are?
### OR Why is X functionality in step Y instead of step Z?
@ -107,6 +114,8 @@ process guide for step1 and to be clear that many of the types are
deferrable until later. But I am always open to suggestions.
<a name="add_implementation"></a>
### Will you add my new implementation?
Absolutely! I want mal to have a idiomatic implementation in every
@ -125,11 +134,13 @@ into the main repository:
marked as optional and not needed for self-hosting.
* Your implementation should follow the existing mal steps and
structure: Lisp-centric code in the step files, other code in
reader, printer, env, and core files. I encourage you to create
implementations that take mal in new directions for your own
learning and experimentation, but for it to be included in the main
repository I ask that it follows the steps and structure.
structure: Lisp-centric code (eval, eval_ast, quasiquote,
macroexpand) in the step files, other code in reader, printer, env,
and core files. See [code layout rationale](#code_split) above.
I encourage you to create implementations that take mal in new
directions for your own learning and experimentation, but for it to
be included in the main repository I ask that it follows the steps
and structure.
* Your implementation should stick as much as possible to the accepted
idioms and conventions in that language. Try to create an
@ -142,17 +153,18 @@ into the main repository:
improve it first.
* If you are creating a new implementation for an existing
implementation, then it is less likely that I will replace the
existing implementation with your new one. If you can make
a compelling argument that your implementation is more idiomatic or
significantly better than the existing implementation then I may
replace it. However, a better option would be to work with the me
and the original author (if it was not me) to improve the existing
implementation. This also implies that if somebody submits a high
quality implementation to me before you, your implementation may not
get merged. However, if you have alternate implementation and it
follows the rest of the guidelines, I am definitely willing to link
to it in the list of implementations.
implementation (or somebody beats you to the punch while you are
working on it), there is still a chance I will merge your
implementation. If you can make a compelling argument that your
implementation is more idiomatic or significantly better than the
existing implementation then I may replace the existing one.
However, if your approach is different or unique from the existing
implementation, there is still a good chance I will merge your
implementation side-by-side with the existing one. In that case
I will add your github username as a suffix to the language
implementation directory. At the very least, even if I decide not to
merge your implementation, I am certainly willing to link to you
implementation once it is completed.
* You do not need to implement line editing (i.e. readline)
functionality for your implementation, however, it is a nice

View File

@ -3,7 +3,6 @@ All:
- test that *ARGV* gets set properly
- test to make sure slurp captures final newline
- fix long line splitting in runtest
- Give runtest knowledge of optional tests and report as non-fatal
- regular expression matching in runtest
- add re (use in rep) everywhere and use that (to avoid printing)

View File

@ -28,7 +28,7 @@ public class step0_repl {
public static void main(String[] args) {
String prompt = "user> ";
if (args[0].equals("--raw")) {
if (args.length > 0 && args[0].equals("--raw")) {
readline.mode = readline.Mode.JAVA;
}
while (true) {

11
julia/Makefile Normal file
View File

@ -0,0 +1,11 @@
SOURCES_BASE = reader.jl printer.jl readline_mod.jl types.jl
SOURCES_LISP = env.jl core.jl stepA_mal.jl
SOURCES = $(SOURCES_BASE) $(SOURCES_LISP)
.PHONY: stats tests $(TESTS)
stats: $(SOURCES)
@wc $^
stats-lisp: $(SOURCES_LISP)
@wc $^

94
julia/core.jl Normal file
View File

@ -0,0 +1,94 @@
module core
import types
import reader
using printer
import readline_mod
export ns
function concat(args...)
res = {}
for a=args
res = [res, Any[a...]]
end
res
end
function do_apply(f, all_args...)
fn = isa(f,types.MalFunc) ? f.fn : f
args = concat(all_args[1:end-1], all_args[end])
fn(args...)
end
function with_meta(obj, meta)
new_obj = types.copy(obj)
new_obj.meta = meta
new_obj
end
ns = {
symbol("=") => (a,b) -> types.equal_Q(a, b),
:throw => (a) -> throw(types.MalException(a)),
symbol("nil?") => (a) -> a === nothing,
symbol("true?") => (a) -> a === true,
symbol("false?") => (a) -> a === false,
symbol("symbol") => (a) -> symbol(a),
symbol("symbol?") => (a) -> typeof(a) === Symbol,
symbol("keyword") => (a) -> a[1] == '\u029e' ? a : "\u029e$(a)",
symbol("keyword?") => (a) -> isa(a,String) && a[1] == '\u029e',
symbol("pr-str") => (a...) -> join(map((e)->pr_str(e, true),a)," "),
:str => (a...) -> join(map((e)->pr_str(e, false),a),""),
:prn => (a...) -> println(join(map((e)->pr_str(e, true),a)," ")),
:println => (a...) -> println(join(map((e)->pr_str(e, false),a)," ")),
symbol("read-string") => (a) -> reader.read_str(a),
:readline => readline_mod.do_readline,
:slurp => (a) -> readall(open(a)),
:< => <,
:<= => <=,
:> => >,
:>= => >=,
:+ => +,
:- => -,
symbol("*") => *,
:/ => div,
:list => (a...) -> Any[a...],
symbol("list?") => (a) -> isa(a, Array),
:vector => (a...) -> tuple(a...),
symbol("vector?") => (a) -> isa(a, Tuple),
symbol("hash-map") => types.hash_map,
symbol("map?") => (a) -> isa(a, Dict),
:assoc => (a, b...) -> merge(a, types.hash_map(b...)),
:dissoc => (a, b...) -> foldl((x,y) -> delete!(x,y),copy(a), b),
:get => (a,b) -> a === nothing ? nothing : get(a,b,nothing),
symbol("contains?") => haskey,
:keys => (a) -> {keys(a)...},
:vals => (a) -> {values(a)...},
symbol("sequential?") => types.sequential_Q,
:cons => (a,b) -> [Any[a], Any[b...]],
:concat => concat,
:nth => (a,b) -> b+1 > length(a) ? error("nth: index out of range") : a[b+1],
:first => (a) -> isempty(a) ? nothing : first(a),
:rest => (a) -> Any[a[2:end]...],
symbol("empty?") => isempty,
:count => (a) -> a == nothing ? 0 : length(a),
:apply => do_apply,
:map => (a,b) -> isa(a,types.MalFunc) ? {map(a.fn,b)...} : {map(a,b)...},
:conj => nothing,
:meta => (a) -> isa(a,types.MalFunc) ? a.meta : nothing,
symbol("with-meta") => with_meta,
:atom => (a) -> types.Atom(a),
symbol("atom?") => (a) -> isa(a,types.Atom),
:deref => (a) -> a.val,
:reset! => (a,b) -> a.val = b,
:swap! => (a,b,c...) -> a.val = do_apply(b, a.val, c),
}
end

55
julia/env.jl Normal file
View File

@ -0,0 +1,55 @@
module env
export Env, set, find, get
type Env
outer::Any
data::Dict{Symbol,Any}
end
function Env()
Env(nothing, Dict())
end
function Env(outer)
Env(outer, Dict())
end
function Env(outer, binds, exprs)
e = Env(outer, Dict())
for i=1:length(binds)
if binds[i] == :&
e.data[binds[i+1]] = exprs[i:end]
break
else
e.data[binds[i]] = exprs[i]
end
end
e
end
function set(env::Env, k::Symbol, v)
env.data[k] = v
end
function find(env::Env, k::Symbol)
if haskey(env.data, k)
env
elseif env.outer != nothing
find(env.outer, k)
else
nothing
end
end
function get(env::Env, k::Symbol)
e = find(env, k)
if e != nothing
e.data[k]
else
error("'$(string(k))' not found")
end
end
end

40
julia/printer.jl Normal file
View File

@ -0,0 +1,40 @@
module printer
import types
export pr_str
function pr_str(obj, print_readably=true)
_r = print_readably
if isa(obj, Array)
"($(join([pr_str(o, _r) for o=obj], " ")))"
elseif isa(obj, Tuple)
"[$(join([pr_str(o, _r) for o=obj], " "))]"
elseif isa(obj, Dict)
"{$(join(["$(pr_str(o[1],_r)) $(pr_str(o[2],_r))" for o=obj], " "))}"
elseif isa(obj, String)
if length(obj) > 0 && obj[1] == '\u029e'
":$(obj[2:end])"
elseif _r
str = replace(replace(replace(obj,
"\\", "\\\\"),
"\"", "\\\""),
"\n", "\\n")
"\"$(str)\""
else
obj
end
elseif obj == nothing
"nil"
elseif typeof(obj) == types.MalFunc
"(fn* $(pr_str(obj.params,true)) $(pr_str(obj.ast,true)))"
elseif typeof(obj) == types.Atom
"(atom $(pr_str(obj.val,true)))"
elseif typeof(obj) == Function
"#<native function: $(string(obj))>"
else
string(obj)
end
end
end

131
julia/reader.jl Normal file
View File

@ -0,0 +1,131 @@
module reader
export read_str
import types
type Reader
tokens
position::Int64
end
function next(rdr::Reader)
if rdr.position > length(rdr.tokens)
return nothing
end
rdr.position += 1
rdr.tokens[rdr.position-1]
end
function peek(rdr::Reader)
if rdr.position > length(rdr.tokens)
return nothing
end
rdr.tokens[rdr.position]
end
function tokenize(str)
re = r"[\s,]*(~@|[\[\]{}()'`~^@]|\"(?:\\.|[^\\\"])*\"|;.*|[^\s\[\]{}('\"`,;)]*)"
tokens = map((m) -> m.captures[1], eachmatch(re, str))
filter((t) -> t != "" && t[1] != ';', tokens)
end
function read_atom(rdr)
token = next(rdr)
if ismatch(r"^-?[0-9]+$", token)
int(token)
elseif ismatch(r"^-?[0-9][0-9.]*$", token)
float(token)
elseif ismatch(r"^\".*\"$", token)
replace(
replace(token[2:end-1],
"\\\"", "\""),
"\\n", "\n")
elseif token[1] == ':'
"\u029e$(token[2:end])"
elseif token == "nil"
nothing
elseif token == "true"
true
elseif token == "false"
false
else
symbol(token)
end
end
function read_list(rdr, start="(", last=")")
ast = Any[]
token = next(rdr)
if (token != start)
error("expected '$(start)'")
end
while ((token = peek(rdr)) != last)
if token == nothing
error("expected '$(last)', got EOF")
end
push!(ast, read_form(rdr))
end
next(rdr)
ast
end
function read_vector(rdr)
lst = read_list(rdr, "[", "]")
tuple(lst...)
end
function read_hash_map(rdr)
lst = read_list(rdr, "{", "}")
types.hash_map(lst...)
end
function read_form(rdr)
token = peek(rdr)
if token == "'"
next(rdr)
[[:quote], Any[read_form(rdr)]]
elseif token == "`"
next(rdr)
[[:quasiquote], Any[read_form(rdr)]]
elseif token == "~"
next(rdr)
[[:unquote], Any[read_form(rdr)]]
elseif token == "~@"
next(rdr)
[[symbol("splice-unquote")], Any[read_form(rdr)]]
elseif token == "^"
next(rdr)
meta = read_form(rdr)
[[symbol("with-meta")], Any[read_form(rdr)], Any[meta]]
elseif token == "@"
next(rdr)
[[symbol("deref")], Any[read_form(rdr)]]
elseif token == ")"
error("unexpected ')'")
elseif token == "("
read_list(rdr)
elseif token == "]"
error("unexpected ']'")
elseif token == "["
read_vector(rdr)
elseif token == "}"
error("unexpected '}'")
elseif token == "{"
read_hash_map(rdr)
else
read_atom(rdr)
end
end
function read_str(str)
tokens = tokenize(str)
if length(tokens) == 0
return nothing
end
read_form(Reader(tokens, 1))
end
end

15
julia/readline_mod.jl Normal file
View File

@ -0,0 +1,15 @@
module readline_mod
export do_readline
function do_readline(prompt)
print(prompt)
flush(STDOUT)
line = readline(STDIN)
if line == ""
return nothing
end
chomp(line)
end
end

29
julia/step0_repl.jl Executable file
View File

@ -0,0 +1,29 @@
#!/usr/bin/env julia
import readline_mod
# READ
function READ(str)
str
end
# EVAL
function EVAL(ast, env)
ast
end
# PRINT
function PRINT(exp)
exp
end
# REPL
function REP(str)
return PRINT(EVAL(READ(str), {}))
end
while true
line = readline_mod.do_readline("user> ")
if line === nothing break end
println(REP(line))
end

42
julia/step1_read_print.jl Executable file
View File

@ -0,0 +1,42 @@
#!/usr/bin/env julia
import readline_mod
import reader
import printer
# READ
function READ(str)
reader.read_str(str)
end
# EVAL
function EVAL(ast, env)
ast
end
# PRINT
function PRINT(exp)
printer.pr_str(exp)
end
# REPL
function REP(str)
return PRINT(EVAL(READ(str), {}))
end
while true
line = readline_mod.do_readline("user> ")
if line === nothing break end
try
println(REP(line))
catch e
if isa(e, ErrorException)
println("Error: $(e.msg)")
else
println("Error: $(string(e))")
end
bt = catch_backtrace()
Base.show_backtrace(STDERR, bt)
println()
end
end

63
julia/step2_eval.jl Executable file
View File

@ -0,0 +1,63 @@
#!/usr/bin/env julia
import readline_mod
import reader
import printer
# READ
function READ(str)
reader.read_str(str)
end
# EVAL
function eval_ast(ast, env)
if typeof(ast) == Symbol
env[ast]
elseif isa(ast, Array) || isa(ast, Tuple)
map((x) -> EVAL(x,env), ast)
elseif isa(ast, Dict)
[EVAL(x[1],env) => EVAL(x[2], env) for x=ast]
else
ast
end
end
function EVAL(ast, env)
if !isa(ast, Array) return eval_ast(ast, env) end
# apply
el = eval_ast(ast, env)
f, args = el[1], el[2:end]
f(args...)
end
# PRINT
function PRINT(exp)
printer.pr_str(exp)
end
# REPL
repl_env = {:+ => +,
:- => -,
:* => *,
:/ => div}
function REP(str)
return PRINT(EVAL(READ(str), repl_env))
end
while true
line = readline_mod.do_readline("user> ")
if line === nothing break end
try
println(REP(line))
catch e
if isa(e, ErrorException)
println("Error: $(e.msg)")
else
println("Error: $(string(e))")
end
bt = catch_backtrace()
Base.show_backtrace(STDERR, bt)
println()
end
end

75
julia/step3_env.jl Executable file
View File

@ -0,0 +1,75 @@
#!/usr/bin/env julia
import readline_mod
import reader
import printer
using env
# READ
function READ(str)
reader.read_str(str)
end
# EVAL
function eval_ast(ast, env)
if typeof(ast) == Symbol
get(env,ast)
elseif isa(ast, Array) || isa(ast, Tuple)
map((x) -> EVAL(x,env), ast)
elseif isa(ast, Dict)
[EVAL(x[1],env) => EVAL(x[2], env) for x=ast]
else
ast
end
end
function EVAL(ast, env)
if !isa(ast, Array) return eval_ast(ast, env) end
# apply
if :def! == ast[1]
set(env, ast[2], EVAL(ast[3], env))
elseif symbol("let*") == ast[1]
let_env = Env(env)
for i = 1:2:length(ast[2])
set(let_env, ast[2][i], EVAL(ast[2][i+1], let_env))
end
EVAL(ast[3], let_env)
else
el = eval_ast(ast, env)
f, args = el[1], el[2:end]
f(args...)
end
end
# PRINT
function PRINT(exp)
printer.pr_str(exp)
end
# REPL
repl_env = Env(nothing,
{:+ => +,
:- => -,
:* => *,
:/ => div})
function REP(str)
return PRINT(EVAL(READ(str), repl_env))
end
while true
line = readline_mod.do_readline("user> ")
if line === nothing break end
try
println(REP(line))
catch e
if isa(e, ErrorException)
println("Error: $(e.msg)")
else
println("Error: $(string(e))")
end
bt = catch_backtrace()
Base.show_backtrace(STDERR, bt)
println()
end
end

93
julia/step4_if_fn_do.jl Executable file
View File

@ -0,0 +1,93 @@
#!/usr/bin/env julia
import readline_mod
import reader
import printer
using env
import core
# READ
function READ(str)
reader.read_str(str)
end
# EVAL
function eval_ast(ast, env)
if typeof(ast) == Symbol
get(env,ast)
elseif isa(ast, Array) || isa(ast, Tuple)
map((x) -> EVAL(x,env), ast)
elseif isa(ast, Dict)
[EVAL(x[1],env) => EVAL(x[2], env) for x=ast]
else
ast
end
end
function EVAL(ast, env)
if !isa(ast, Array) return eval_ast(ast, env) end
# apply
if :def! == ast[1]
set(env, ast[2], EVAL(ast[3], env))
elseif symbol("let*") == ast[1]
let_env = Env(env)
for i = 1:2:length(ast[2])
set(let_env, ast[2][i], EVAL(ast[2][i+1], let_env))
end
EVAL(ast[3], let_env)
elseif :do == ast[1]
eval_ast(ast[2:end], env)[end]
elseif :if == ast[1]
cond = EVAL(ast[2], env)
if cond === nothing || cond === false
if length(ast) >= 4
EVAL(ast[4], env)
else
nothing
end
else
EVAL(ast[3], env)
end
elseif symbol("fn*") == ast[1]
(args...) -> EVAL(ast[3], Env(env, ast[2], args))
else
el = eval_ast(ast, env)
f, args = el[1], el[2:end]
f(args...)
end
end
# PRINT
function PRINT(exp)
printer.pr_str(exp)
end
# REPL
repl_env = nothing
function REP(str)
return PRINT(EVAL(READ(str), repl_env))
end
# core.jl: defined using Julia
repl_env = Env(nothing, core.ns)
# core.mal: defined using the language itself
REP("(def! not (fn* (a) (if a false true)))")
while true
line = readline_mod.do_readline("user> ")
if line === nothing break end
try
println(REP(line))
catch e
if isa(e, ErrorException)
println("Error: $(e.msg)")
else
println("Error: $(string(e))")
end
bt = catch_backtrace()
Base.show_backtrace(STDERR, bt)
println()
end
end

110
julia/step5_tco.jl Executable file
View File

@ -0,0 +1,110 @@
#!/usr/bin/env julia
import readline_mod
import reader
import printer
using env
import core
using types
# READ
function READ(str)
reader.read_str(str)
end
# EVAL
function eval_ast(ast, env)
if typeof(ast) == Symbol
get(env,ast)
elseif isa(ast, Array) || isa(ast, Tuple)
map((x) -> EVAL(x,env), ast)
elseif isa(ast, Dict)
[EVAL(x[1],env) => EVAL(x[2], env) for x=ast]
else
ast
end
end
function EVAL(ast, env)
while true
if !isa(ast, Array) return eval_ast(ast, env) end
# apply
if :def! == ast[1]
return set(env, ast[2], EVAL(ast[3], env))
elseif symbol("let*") == ast[1]
let_env = Env(env)
for i = 1:2:length(ast[2])
set(let_env, ast[2][i], EVAL(ast[2][i+1], let_env))
end
env = let_env
ast = ast[3]
# TCO loop
elseif :do == ast[1]
eval_ast(ast[2:end-1], env)
ast = ast[end]
# TCO loop
elseif :if == ast[1]
cond = EVAL(ast[2], env)
if cond === nothing || cond === false
if length(ast) >= 4
ast = ast[4]
# TCO loop
else
return nothing
end
else
ast = ast[3]
# TCO loop
end
elseif symbol("fn*") == ast[1]
return MalFunc(
(args...) -> EVAL(ast[3], Env(env, ast[2], args)),
ast[3], env, ast[2])
else
el = eval_ast(ast, env)
f, args = el[1], el[2:end]
if isa(f, MalFunc)
ast = f.ast
env = Env(f.env, f.params, args)
# TCO loop
else
return f(args...)
end
end
end
end
# PRINT
function PRINT(exp)
printer.pr_str(exp)
end
# REPL
repl_env = nothing
function REP(str)
return PRINT(EVAL(READ(str), repl_env))
end
# core.jl: defined using Julia
repl_env = Env(nothing, core.ns)
# core.mal: defined using the language itself
REP("(def! not (fn* (a) (if a false true)))")
while true
line = readline_mod.do_readline("user> ")
if line === nothing break end
try
println(REP(line))
catch e
if isa(e, ErrorException)
println("Error: $(e.msg)")
else
println("Error: $(string(e))")
end
bt = catch_backtrace()
Base.show_backtrace(STDERR, bt)
println()
end
end

118
julia/step6_file.jl Executable file
View File

@ -0,0 +1,118 @@
#!/usr/bin/env julia
import readline_mod
import reader
import printer
using env
import core
using types
# READ
function READ(str)
reader.read_str(str)
end
# EVAL
function eval_ast(ast, env)
if typeof(ast) == Symbol
get(env,ast)
elseif isa(ast, Array) || isa(ast, Tuple)
map((x) -> EVAL(x,env), ast)
elseif isa(ast, Dict)
[EVAL(x[1],env) => EVAL(x[2], env) for x=ast]
else
ast
end
end
function EVAL(ast, env)
while true
if !isa(ast, Array) return eval_ast(ast, env) end
# apply
if :def! == ast[1]
return set(env, ast[2], EVAL(ast[3], env))
elseif symbol("let*") == ast[1]
let_env = Env(env)
for i = 1:2:length(ast[2])
set(let_env, ast[2][i], EVAL(ast[2][i+1], let_env))
end
env = let_env
ast = ast[3]
# TCO loop
elseif :do == ast[1]
eval_ast(ast[2:end-1], env)
ast = ast[end]
# TCO loop
elseif :if == ast[1]
cond = EVAL(ast[2], env)
if cond === nothing || cond === false
if length(ast) >= 4
ast = ast[4]
# TCO loop
else
return nothing
end
else
ast = ast[3]
# TCO loop
end
elseif symbol("fn*") == ast[1]
return MalFunc(
(args...) -> EVAL(ast[3], Env(env, ast[2], args)),
ast[3], env, ast[2])
else
el = eval_ast(ast, env)
f, args = el[1], el[2:end]
if isa(f, MalFunc)
ast = f.ast
env = Env(f.env, f.params, args)
# TCO loop
else
return f(args...)
end
end
end
end
# PRINT
function PRINT(exp)
printer.pr_str(exp)
end
# REPL
repl_env = nothing
function REP(str)
return PRINT(EVAL(READ(str), repl_env))
end
# core.jl: defined using Julia
repl_env = Env(nothing, core.ns)
set(repl_env, :eval, (ast) -> EVAL(ast, repl_env))
set(repl_env, symbol("*ARGV*"), ARGS[2:end])
# core.mal: defined using the language itself
REP("(def! not (fn* (a) (if a false true)))")
REP("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))")
if length(ARGS) > 0
REP("(load-file \"$(ARGS[1])\")")
exit(0)
end
while true
line = readline_mod.do_readline("user> ")
if line === nothing break end
try
println(REP(line))
catch e
if isa(e, ErrorException)
println("Error: $(e.msg)")
else
println("Error: $(string(e))")
end
bt = catch_backtrace()
Base.show_backtrace(STDERR, bt)
println()
end
end

139
julia/step7_quote.jl Executable file
View File

@ -0,0 +1,139 @@
#!/usr/bin/env julia
import readline_mod
import reader
import printer
using env
import core
using types
# READ
function READ(str)
reader.read_str(str)
end
# EVAL
function ispair(ast)
(isa(ast, Array) || isa(ast, Tuple)) && length(ast) > 0
end
function quasiquote(ast)
if !ispair(ast)
[[:quote], Any[ast]]
elseif ast[1] == :unquote
ast[2]
elseif ispair(ast[1]) && ast[1][1] == symbol("splice-unquote")
[[:concat], Any[ast[1][2]], Any[quasiquote(ast[2:end])]]
else
[[:cons], Any[quasiquote(ast[1])], Any[quasiquote(ast[2:end])]]
end
end
function eval_ast(ast, env)
if typeof(ast) == Symbol
get(env,ast)
elseif isa(ast, Array) || isa(ast, Tuple)
map((x) -> EVAL(x,env), ast)
elseif isa(ast, Dict)
[EVAL(x[1],env) => EVAL(x[2], env) for x=ast]
else
ast
end
end
function EVAL(ast, env)
while true
if !isa(ast, Array) return eval_ast(ast, env) end
# apply
if :def! == ast[1]
return set(env, ast[2], EVAL(ast[3], env))
elseif symbol("let*") == ast[1]
let_env = Env(env)
for i = 1:2:length(ast[2])
set(let_env, ast[2][i], EVAL(ast[2][i+1], let_env))
end
env = let_env
ast = ast[3]
# TCO loop
elseif :quote == ast[1]
return ast[2]
elseif :quasiquote == ast[1]
ast = quasiquote(ast[2])
# TCO loop
elseif :do == ast[1]
eval_ast(ast[2:end-1], env)
ast = ast[end]
# TCO loop
elseif :if == ast[1]
cond = EVAL(ast[2], env)
if cond === nothing || cond === false
if length(ast) >= 4
ast = ast[4]
# TCO loop
else
return nothing
end
else
ast = ast[3]
# TCO loop
end
elseif symbol("fn*") == ast[1]
return MalFunc(
(args...) -> EVAL(ast[3], Env(env, ast[2], args)),
ast[3], env, ast[2])
else
el = eval_ast(ast, env)
f, args = el[1], el[2:end]
if isa(f, MalFunc)
ast = f.ast
env = Env(f.env, f.params, args)
# TCO loop
else
return f(args...)
end
end
end
end
# PRINT
function PRINT(exp)
printer.pr_str(exp)
end
# REPL
repl_env = nothing
function REP(str)
return PRINT(EVAL(READ(str), repl_env))
end
# core.jl: defined using Julia
repl_env = Env(nothing, core.ns)
set(repl_env, :eval, (ast) -> EVAL(ast, repl_env))
set(repl_env, symbol("*ARGV*"), ARGS[2:end])
# core.mal: defined using the language itself
REP("(def! not (fn* (a) (if a false true)))")
REP("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))")
if length(ARGS) > 0
REP("(load-file \"$(ARGS[1])\")")
exit(0)
end
while true
line = readline_mod.do_readline("user> ")
if line === nothing break end
try
println(REP(line))
catch e
if isa(e, ErrorException)
println("Error: $(e.msg)")
else
println("Error: $(string(e))")
end
bt = catch_backtrace()
Base.show_backtrace(STDERR, bt)
println()
end
end

168
julia/step8_macros.jl Executable file
View File

@ -0,0 +1,168 @@
#!/usr/bin/env julia
import readline_mod
import reader
import printer
using env
import core
using types
# READ
function READ(str)
reader.read_str(str)
end
# EVAL
function ispair(ast)
(isa(ast, Array) || isa(ast, Tuple)) && length(ast) > 0
end
function quasiquote(ast)
if !ispair(ast)
[[:quote], Any[ast]]
elseif ast[1] == :unquote
ast[2]
elseif ispair(ast[1]) && ast[1][1] == symbol("splice-unquote")
[[:concat], Any[ast[1][2]], Any[quasiquote(ast[2:end])]]
else
[[:cons], Any[quasiquote(ast[1])], Any[quasiquote(ast[2:end])]]
end
end
function ismacroCall(ast, env)
return isa(ast, Array) &&
isa(ast[1], Symbol) &&
find(env, ast[1]) != nothing &&
isa(get(env, ast[1]), MalFunc) &&
get(env, ast[1]).ismacro
end
function macroexpand(ast, env)
while ismacroCall(ast, env)
mac = get(env, ast[1])
ast = mac.fn(ast[2:end]...)
end
ast
end
function eval_ast(ast, env)
if typeof(ast) == Symbol
get(env,ast)
elseif isa(ast, Array) || isa(ast, Tuple)
map((x) -> EVAL(x,env), ast)
elseif isa(ast, Dict)
[EVAL(x[1],env) => EVAL(x[2], env) for x=ast]
else
ast
end
end
function EVAL(ast, env)
while true
#println("EVAL: $(printer.pr_str(ast,true))")
if !isa(ast, Array) return eval_ast(ast, env) end
# apply
ast = macroexpand(ast, env)
if !isa(ast, Array) return ast end
if :def! == ast[1]
return set(env, ast[2], EVAL(ast[3], env))
elseif symbol("let*") == ast[1]
let_env = Env(env)
for i = 1:2:length(ast[2])
set(let_env, ast[2][i], EVAL(ast[2][i+1], let_env))
end
env = let_env
ast = ast[3]
# TCO loop
elseif :quote == ast[1]
return ast[2]
elseif :quasiquote == ast[1]
ast = quasiquote(ast[2])
# TCO loop
elseif :defmacro! == ast[1]
func = EVAL(ast[3], env)
func.ismacro = true
return set(env, ast[2], func)
elseif :macroexpand == ast[1]
return macroexpand(ast[2], env)
elseif :do == ast[1]
eval_ast(ast[2:end-1], env)
ast = ast[end]
# TCO loop
elseif :if == ast[1]
cond = EVAL(ast[2], env)
if cond === nothing || cond === false
if length(ast) >= 4
ast = ast[4]
# TCO loop
else
return nothing
end
else
ast = ast[3]
# TCO loop
end
elseif symbol("fn*") == ast[1]
return MalFunc(
(args...) -> EVAL(ast[3], Env(env, ast[2], args)),
ast[3], env, ast[2])
else
el = eval_ast(ast, env)
f, args = el[1], el[2:end]
if isa(f, MalFunc)
ast = f.ast
env = Env(f.env, f.params, args)
# TCO loop
else
return f(args...)
end
end
end
end
# PRINT
function PRINT(exp)
printer.pr_str(exp)
end
# REPL
repl_env = nothing
function REP(str)
return PRINT(EVAL(READ(str), repl_env))
end
# core.jl: defined using Julia
repl_env = Env(nothing, core.ns)
set(repl_env, :eval, (ast) -> EVAL(ast, repl_env))
set(repl_env, symbol("*ARGV*"), ARGS[2:end])
# core.mal: defined using the language itself
REP("(def! not (fn* (a) (if a false true)))")
REP("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))")
REP("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))")
REP("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))")
if length(ARGS) > 0
REP("(load-file \"$(ARGS[1])\")")
exit(0)
end
while true
line = readline_mod.do_readline("user> ")
if line === nothing break end
try
println(REP(line))
catch e
if isa(e, ErrorException)
println("Error: $(e.msg)")
else
println("Error: $(string(e))")
end
bt = catch_backtrace()
Base.show_backtrace(STDERR, bt)
println()
end
end

186
julia/step9_try.jl Executable file
View File

@ -0,0 +1,186 @@
#!/usr/bin/env julia
import readline_mod
import reader
import printer
using env
import core
using types
# READ
function READ(str)
reader.read_str(str)
end
# EVAL
function ispair(ast)
(isa(ast, Array) || isa(ast, Tuple)) && length(ast) > 0
end
function quasiquote(ast)
if !ispair(ast)
[[:quote], Any[ast]]
elseif ast[1] == :unquote
ast[2]
elseif ispair(ast[1]) && ast[1][1] == symbol("splice-unquote")
[[:concat], Any[ast[1][2]], Any[quasiquote(ast[2:end])]]
else
[[:cons], Any[quasiquote(ast[1])], Any[quasiquote(ast[2:end])]]
end
end
function ismacroCall(ast, env)
return isa(ast, Array) &&
isa(ast[1], Symbol) &&
find(env, ast[1]) != nothing &&
isa(get(env, ast[1]), MalFunc) &&
get(env, ast[1]).ismacro
end
function macroexpand(ast, env)
while ismacroCall(ast, env)
mac = get(env, ast[1])
ast = mac.fn(ast[2:end]...)
end
ast
end
function eval_ast(ast, env)
if typeof(ast) == Symbol
get(env,ast)
elseif isa(ast, Array) || isa(ast, Tuple)
map((x) -> EVAL(x,env), ast)
elseif isa(ast, Dict)
[EVAL(x[1],env) => EVAL(x[2], env) for x=ast]
else
ast
end
end
function EVAL(ast, env)
while true
#println("EVAL: $(printer.pr_str(ast,true))")
if !isa(ast, Array) return eval_ast(ast, env) end
# apply
ast = macroexpand(ast, env)
if !isa(ast, Array) return ast end
if :def! == ast[1]
return set(env, ast[2], EVAL(ast[3], env))
elseif symbol("let*") == ast[1]
let_env = Env(env)
for i = 1:2:length(ast[2])
set(let_env, ast[2][i], EVAL(ast[2][i+1], let_env))
end
env = let_env
ast = ast[3]
# TCO loop
elseif :quote == ast[1]
return ast[2]
elseif :quasiquote == ast[1]
ast = quasiquote(ast[2])
# TCO loop
elseif :defmacro! == ast[1]
func = EVAL(ast[3], env)
func.ismacro = true
return set(env, ast[2], func)
elseif :macroexpand == ast[1]
return macroexpand(ast[2], env)
elseif symbol("try*") == ast[1]
try
return EVAL(ast[2], env)
catch exc
e = string(exc)
if isa(exc, MalException)
e = exc.malval
elseif isa(exc, ErrorException)
e = exc.msg
else
e = string(e)
end
if length(ast) > 2 && ast[3][1] == symbol("catch*")
return EVAL(ast[3][3], Env(env, {ast[3][2]}, {e}))
else
rethrow(exc)
end
end
elseif :do == ast[1]
eval_ast(ast[2:end-1], env)
ast = ast[end]
# TCO loop
elseif :if == ast[1]
cond = EVAL(ast[2], env)
if cond === nothing || cond === false
if length(ast) >= 4
ast = ast[4]
# TCO loop
else
return nothing
end
else
ast = ast[3]
# TCO loop
end
elseif symbol("fn*") == ast[1]
return MalFunc(
(args...) -> EVAL(ast[3], Env(env, ast[2], args)),
ast[3], env, ast[2])
else
el = eval_ast(ast, env)
f, args = el[1], el[2:end]
if isa(f, MalFunc)
ast = f.ast
env = Env(f.env, f.params, args)
# TCO loop
else
return f(args...)
end
end
end
end
# PRINT
function PRINT(exp)
printer.pr_str(exp)
end
# REPL
repl_env = nothing
function REP(str)
return PRINT(EVAL(READ(str), repl_env))
end
# core.jl: defined using Julia
repl_env = Env(nothing, core.ns)
set(repl_env, :eval, (ast) -> EVAL(ast, repl_env))
set(repl_env, symbol("*ARGV*"), ARGS[2:end])
# core.mal: defined using the language itself
REP("(def! not (fn* (a) (if a false true)))")
REP("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))")
REP("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))")
REP("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))")
if length(ARGS) > 0
REP("(load-file \"$(ARGS[1])\")")
exit(0)
end
while true
line = readline_mod.do_readline("user> ")
if line === nothing break end
try
println(REP(line))
catch e
if isa(e, ErrorException)
println("Error: $(e.msg)")
else
println("Error: $(string(e))")
end
bt = catch_backtrace()
Base.show_backtrace(STDERR, bt)
println()
end
end

188
julia/stepA_mal.jl Executable file
View File

@ -0,0 +1,188 @@
#!/usr/bin/env julia
import readline_mod
import reader
import printer
using env
import core
using types
# READ
function READ(str)
reader.read_str(str)
end
# EVAL
function ispair(ast)
(isa(ast, Array) || isa(ast, Tuple)) && length(ast) > 0
end
function quasiquote(ast)
if !ispair(ast)
[[:quote], Any[ast]]
elseif ast[1] == :unquote
ast[2]
elseif ispair(ast[1]) && ast[1][1] == symbol("splice-unquote")
[[:concat], Any[ast[1][2]], Any[quasiquote(ast[2:end])]]
else
[[:cons], Any[quasiquote(ast[1])], Any[quasiquote(ast[2:end])]]
end
end
function ismacroCall(ast, env)
return isa(ast, Array) &&
isa(ast[1], Symbol) &&
find(env, ast[1]) != nothing &&
isa(get(env, ast[1]), MalFunc) &&
get(env, ast[1]).ismacro
end
function macroexpand(ast, env)
while ismacroCall(ast, env)
mac = get(env, ast[1])
ast = mac.fn(ast[2:end]...)
end
ast
end
function eval_ast(ast, env)
if typeof(ast) == Symbol
get(env,ast)
elseif isa(ast, Array) || isa(ast, Tuple)
map((x) -> EVAL(x,env), ast)
elseif isa(ast, Dict)
[EVAL(x[1],env) => EVAL(x[2], env) for x=ast]
else
ast
end
end
function EVAL(ast, env)
while true
#println("EVAL: $(printer.pr_str(ast,true))")
if !isa(ast, Array) return eval_ast(ast, env) end
# apply
ast = macroexpand(ast, env)
if !isa(ast, Array) return ast end
if :def! == ast[1]
return set(env, ast[2], EVAL(ast[3], env))
elseif symbol("let*") == ast[1]
let_env = Env(env)
for i = 1:2:length(ast[2])
set(let_env, ast[2][i], EVAL(ast[2][i+1], let_env))
end
env = let_env
ast = ast[3]
# TCO loop
elseif :quote == ast[1]
return ast[2]
elseif :quasiquote == ast[1]
ast = quasiquote(ast[2])
# TCO loop
elseif :defmacro! == ast[1]
func = EVAL(ast[3], env)
func.ismacro = true
return set(env, ast[2], func)
elseif :macroexpand == ast[1]
return macroexpand(ast[2], env)
elseif symbol("try*") == ast[1]
try
return EVAL(ast[2], env)
catch exc
e = string(exc)
if isa(exc, MalException)
e = exc.malval
elseif isa(exc, ErrorException)
e = exc.msg
else
e = string(e)
end
if length(ast) > 2 && ast[3][1] == symbol("catch*")
return EVAL(ast[3][3], Env(env, {ast[3][2]}, {e}))
else
rethrow(exc)
end
end
elseif :do == ast[1]
eval_ast(ast[2:end-1], env)
ast = ast[end]
# TCO loop
elseif :if == ast[1]
cond = EVAL(ast[2], env)
if cond === nothing || cond === false
if length(ast) >= 4
ast = ast[4]
# TCO loop
else
return nothing
end
else
ast = ast[3]
# TCO loop
end
elseif symbol("fn*") == ast[1]
return MalFunc(
(args...) -> EVAL(ast[3], Env(env, ast[2], args)),
ast[3], env, ast[2])
else
el = eval_ast(ast, env)
f, args = el[1], el[2:end]
if isa(f, MalFunc)
ast = f.ast
env = Env(f.env, f.params, args)
# TCO loop
else
return f(args...)
end
end
end
end
# PRINT
function PRINT(exp)
printer.pr_str(exp)
end
# REPL
repl_env = nothing
function REP(str)
return PRINT(EVAL(READ(str), repl_env))
end
# core.jl: defined using Julia
repl_env = Env(nothing, core.ns)
set(repl_env, :eval, (ast) -> EVAL(ast, repl_env))
set(repl_env, symbol("*ARGV*"), ARGS[2:end])
# core.mal: defined using the language itself
REP("(def! *host-language* \"julia\")")
REP("(def! not (fn* (a) (if a false true)))")
REP("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))")
REP("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))")
REP("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))")
if length(ARGS) > 0
REP("(load-file \"$(ARGS[1])\")")
exit(0)
end
REP("(println (str \"Mal [\" *host-language* \"]\"))")
while true
line = readline_mod.do_readline("user> ")
if line === nothing break end
try
println(REP(line))
catch e
if isa(e, ErrorException)
println("Error: $(e.msg)")
else
println("Error: $(string(e))")
end
bt = catch_backtrace()
Base.show_backtrace(STDERR, bt)
println()
end
end

63
julia/types.jl Normal file
View File

@ -0,0 +1,63 @@
module types
export MalException, MalFunc, sequential_Q, equal_Q, hash_map, Atom
import Base.copy
type MalException <: Exception
malval
end
type MalFunc
fn::Function
ast
env
params
ismacro
meta
end
# ismacro default to false
function MalFunc(fn, ast, env, params)
MalFunc(fn, ast, env, params, false, nothing)
end
function copy(f::MalFunc)
MalFunc(f.fn, f.ast, f.env, f.params, f.ismacro, f.meta)
end
function sequential_Q(obj)
isa(obj, Array) || isa(obj, Tuple)
end
function equal_Q(a, b)
ota = typeof(a)
otb = typeof(b)
if !(ota === otb || (sequential_Q(a) && sequential_Q(b)))
return false
end
if sequential_Q(a)
tuple(a...) == tuple(b...)
elseif isa(a,String)
a == b
else
a === b
end
end
function hash_map(lst...)
hm = Dict()
for i = 1:2:length(lst)
hm[lst[i]] = lst[i+1]
end
hm
end
type Atom
val
end
end

30
mal/step0_repl.mal Normal file
View File

@ -0,0 +1,30 @@
;; read
(def! READ (fn* [strng]
strng))
;; eval
(def! EVAL (fn* [ast env]
ast))
;; print
(def! PRINT (fn* [exp] exp))
;; repl
(def! rep (fn* [strng]
(PRINT (EVAL (READ strng) {}))))
;; repl loop
(def! repl-loop (fn* []
(let* [line (readline "mal-user> ")]
(if line
(do
(if (not (= "" line))
(try*
(println (rep line))
(catch* exc
(println "Uncaught exception:" exc))))
(repl-loop))))))
(def! -main (fn* [& args]
(repl-loop)))
(-main)

View File

@ -49,7 +49,7 @@ proc first(xs: varargs[MalType]): MalType =
proc rest(xs: varargs[MalType]): MalType =
if xs[0].kind in {List, Vector} and xs[0].list.len > 0:
list xs[0].list[1 .. -1]
list xs[0].list[1 .. ^1]
else: list()
proc throw(xs: varargs[MalType]): MalType =

View File

@ -6,7 +6,7 @@ proc initEnv*(outer: Env = nil, binds, exprs: MalType = nilObj): Env =
if binds.kind in {List, Vector}:
for i, e in binds.list:
if e.str == "&":
result.data[binds.list[i+1].str] = list(exprs.list[i .. -1])
result.data[binds.list[i+1].str] = list(exprs.list[i .. ^1])
break
else:
result.data[e.str] = exprs.list[i]

View File

@ -1,2 +1 @@
deadCodeElim: off
gc: markandsweep

View File

@ -25,7 +25,7 @@ proc eval(ast: MalType, env: Table[string, MalType]): MalType =
case ast.kind
of List:
let el = ast.eval_ast(env)
el.list[0].fun(el.list[1 .. -1])
el.list[0].fun(el.list[1 .. ^1])
else:
ast.eval_ast(env)

View File

@ -41,7 +41,7 @@ proc eval(ast: MalType, env: var Env): MalType =
result = a2.eval(letEnv)
else:
let el = ast.eval_ast(env)
result = el.list[0].fun(el.list[1 .. -1])
result = el.list[0].fun(el.list[1 .. ^1])
else:
result = ast.eval_ast(env)

View File

@ -47,7 +47,7 @@ proc eval(ast: MalType, env: var Env): MalType =
result = a2.eval(letEnv)
of "do":
let el = (list ast.list[1 .. -1]).eval_ast(env)
let el = (list ast.list[1 .. ^1]).eval_ast(env)
result = el.list[el.list.high]
of "if":
@ -72,11 +72,11 @@ proc eval(ast: MalType, env: var Env): MalType =
else:
let el = ast.eval_ast(env)
result = el.list[0].fun(el.list[1 .. -1])
result = el.list[0].fun(el.list[1 .. ^1])
else:
let el = ast.eval_ast(env)
result = el.list[0].fun(el.list[1 .. -1])
result = el.list[0].fun(el.list[1 .. ^1])
else:
result = ast.eval_ast(env)

View File

@ -28,9 +28,9 @@ proc eval(ast: MalType, env: var Env): MalType =
case f.kind
of MalFun:
ast = f.malfun.ast
env = initEnv(f.malfun.env, f.malfun.params, list(el.list[1 .. -1]))
env = initEnv(f.malfun.env, f.malfun.params, list(el.list[1 .. ^1]))
else:
return f.fun(el.list[1 .. -1])
return f.fun(el.list[1 .. ^1])
while true:
if ast.kind != List: return ast.eval_ast(env)

View File

@ -28,9 +28,9 @@ proc eval(ast: MalType, env: var Env): MalType =
case f.kind
of MalFun:
ast = f.malfun.ast
env = initEnv(f.malfun.env, f.malfun.params, list(el.list[1 .. -1]))
env = initEnv(f.malfun.env, f.malfun.params, list(el.list[1 .. ^1]))
else:
return f.fun(el.list[1 .. -1])
return f.fun(el.list[1 .. ^1])
while true:
if ast.kind != List: return ast.eval_ast(env)

View File

@ -12,9 +12,9 @@ proc quasiquote(ast: MalType): MalType =
return ast.list[1]
elif ast.list[0].is_pair and ast.list[0].list[0] == symbol "splice-unquote":
return list(symbol "concat", ast.list[0].list[1],
quasiquote(list ast.list[1 .. -1]))
quasiquote(list ast.list[1 .. ^1]))
else:
return list(symbol "cons", quasiquote(ast.list[0]), quasiquote(list(ast.list[1 .. -1])))
return list(symbol "cons", quasiquote(ast.list[0]), quasiquote(list(ast.list[1 .. ^1])))
proc eval(ast: MalType, env: var Env): MalType
@ -42,9 +42,9 @@ proc eval(ast: MalType, env: var Env): MalType =
case f.kind
of MalFun:
ast = f.malfun.ast
env = initEnv(f.malfun.env, f.malfun.params, list(el.list[1 .. -1]))
env = initEnv(f.malfun.env, f.malfun.params, list(el.list[1 .. ^1]))
else:
return f.fun(el.list[1 .. -1])
return f.fun(el.list[1 .. ^1])
while true:
if ast.kind != List: return ast.eval_ast(env)

View File

@ -12,9 +12,9 @@ proc quasiquote(ast: MalType): MalType =
return ast.list[1]
elif ast.list[0].is_pair and ast.list[0].list[0] == symbol "splice-unquote":
return list(symbol "concat", ast.list[0].list[1],
quasiquote(list ast.list[1 .. -1]))
quasiquote(list ast.list[1 .. ^1]))
else:
return list(symbol "cons", quasiquote(ast.list[0]), quasiquote(list(ast.list[1 .. -1])))
return list(symbol "cons", quasiquote(ast.list[0]), quasiquote(list(ast.list[1 .. ^1])))
proc is_macro_call(ast: MalType, env: Env): bool =
ast.kind == List and ast.list[0].kind == Symbol and
@ -24,7 +24,7 @@ proc macroexpand(ast: MalType, env: Env): MalType =
result = ast
while result.is_macro_call(env):
let mac = env.get(result.list[0].str)
result = mac.malfun.fn(result.list[1 .. -1]).macroexpand(env)
result = mac.malfun.fn(result.list[1 .. ^1]).macroexpand(env)
proc eval(ast: MalType, env: Env): MalType
@ -53,9 +53,9 @@ proc eval(ast: MalType, env: Env): MalType =
case f.kind
of MalFun:
ast = f.malfun.ast
env = initEnv(f.malfun.env, f.malfun.params, list(el.list[1 .. -1]))
env = initEnv(f.malfun.env, f.malfun.params, list(el.list[1 .. ^1]))
else:
return f.fun(el.list[1 .. -1])
return f.fun(el.list[1 .. ^1])
while true:
if ast.kind != List: return ast.eval_ast(env)

View File

@ -12,9 +12,9 @@ proc quasiquote(ast: MalType): MalType =
return ast.list[1]
elif ast.list[0].is_pair and ast.list[0].list[0] == symbol "splice-unquote":
return list(symbol "concat", ast.list[0].list[1],
quasiquote(list ast.list[1 .. -1]))
quasiquote(list ast.list[1 .. ^1]))
else:
return list(symbol "cons", quasiquote(ast.list[0]), quasiquote(list(ast.list[1 .. -1])))
return list(symbol "cons", quasiquote(ast.list[0]), quasiquote(list(ast.list[1 .. ^1])))
proc is_macro_call(ast: MalType, env: Env): bool =
ast.kind == List and ast.list[0].kind == Symbol and
@ -24,7 +24,7 @@ proc macroexpand(ast: MalType, env: Env): MalType =
result = ast
while result.is_macro_call(env):
let mac = env.get(result.list[0].str)
result = mac.malfun.fn(result.list[1 .. -1]).macroexpand(env)
result = mac.malfun.fn(result.list[1 .. ^1]).macroexpand(env)
proc eval(ast: MalType, env: Env): MalType
@ -53,9 +53,9 @@ proc eval(ast: MalType, env: Env): MalType =
case f.kind
of MalFun:
ast = f.malfun.ast
env = initEnv(f.malfun.env, f.malfun.params, list(el.list[1 .. -1]))
env = initEnv(f.malfun.env, f.malfun.params, list(el.list[1 .. ^1]))
else:
return f.fun(el.list[1 .. -1])
return f.fun(el.list[1 .. ^1])
while true:
if ast.kind != List: return ast.eval_ast(env)

View File

@ -12,9 +12,9 @@ proc quasiquote(ast: MalType): MalType =
return ast.list[1]
elif ast.list[0].is_pair and ast.list[0].list[0] == symbol "splice-unquote":
return list(symbol "concat", ast.list[0].list[1],
quasiquote(list ast.list[1 .. -1]))
quasiquote(list ast.list[1 .. ^1]))
else:
return list(symbol "cons", quasiquote(ast.list[0]), quasiquote(list(ast.list[1 .. -1])))
return list(symbol "cons", quasiquote(ast.list[0]), quasiquote(list(ast.list[1 .. ^1])))
proc is_macro_call(ast: MalType, env: Env): bool =
ast.kind == List and ast.list.len > 0 and ast.list[0].kind == Symbol and
@ -24,7 +24,7 @@ proc macroexpand(ast: MalType, env: Env): MalType =
result = ast
while result.is_macro_call(env):
let mac = env.get(result.list[0].str)
result = mac.malfun.fn(result.list[1 .. -1]).macroexpand(env)
result = mac.malfun.fn(result.list[1 .. ^1]).macroexpand(env)
proc eval(ast: MalType, env: Env): MalType
@ -53,9 +53,9 @@ proc eval(ast: MalType, env: Env): MalType =
case f.kind
of MalFun:
ast = f.malfun.ast
env = initEnv(f.malfun.env, f.malfun.params, list(el.list[1 .. -1]))
env = initEnv(f.malfun.env, f.malfun.params, list(el.list[1 .. ^1]))
else:
return f.fun(el.list[1 .. -1])
return f.fun(el.list[1 .. ^1])
while true:
if ast.kind != List: return ast.eval_ast(env)

View File

@ -188,7 +188,7 @@ Congratulations! You have just completed the first step of the
make-a-lisp process.
#### Deferrable:
#### Optional:
* Add full line editing and command history support to your
interpreter REPL. Many languages have a library/module that provide
@ -370,16 +370,37 @@ and each step will give progressively more bang for the buck.
true.
* Add support for the other mal types: keyword, vector, hash-map, and
atom. TODO/TBD
* keyword: just a string stored with unicode prefix (or char 127 if
no unicode support).
* vector: can be implemented with same underlying type as list if
there is some mechanism for marking/distinguishing from a list.
* hash-map: only need to implement string keys (which enables
keyword keys since they are just special strings).
atom.
* keyword: a keyword is a token that begins with a colon. A keyword
can just be stored as a string with special unicode prefix like
0x29E (or char 0xff/127 if the target language does not have good
unicode support) and the printer translates strings with that
prefix back to the keyword representation. This makes it easy to
use keywords as hash map keys in most languages. You can also
store keywords as a unique data type, but you will need to make
sure they can be used as hash map keys (which may involve doing
a similar prefixed translation anyways).
* vector: a vector can be implemented with same underlying
type as a list as long as there is some mechanism to keep track of
the difference. You can use the same reader function for both
lists and vectors by adding parameters for the starting and ending
tokens.
* hash-map: a hash-map is an associative data structure that maps
strings to other mal values. If you implement keywords as prefixed
strings, then you only need a native associative data structure
which supports string keys. Clojure allows any value to be a hash
map key, but the base functionality in mal is to support strings
and keyword keys. Because of the representation of hash-maps as
an alternating sequence of keys and values, you can probably use
the same reader function for hash-maps as lists and vectors with
parameters to indicate the starting and ending tokens. The odd
tokens are then used for keys with the corresponding even tokens
as the values.
* Add support for reader macros which are special forms that are
transformed into other forms during the read phase.
* Add support for reader macros which are forms that are
transformed into other forms during the read phase. Refer to
`tests/step1_read_print.mal` for the form that these macros should
take (they are just simple transformations of the token stream).
* Add comment support to your reader. The tokenizer should ignore
tokens that start with ";". Your `read_str` function will need to
@ -1195,6 +1216,12 @@ implementation. Let us continue!
![step9_try architecture](step9_try.png)
In this step you will implement the final mal special form for
error/exception handling: `try*/catch*`. You will also add several core
functions to you implementation. In particular, you will enhance the
functional programming pedigree of you implementation by adding the
`apply` and `map` core functions.
Compare the pseudocode for step 8 and step 9 to get a basic idea of
the changes that will be made during this step:
```
@ -1203,18 +1230,152 @@ diff -urp ../process/step8_macros.txt ../process/step9_try.txt
* Copy `step8_macros.qx` to `step9_try.qx`.
* TODO/TBD.
* In step 5, if you did not add the original function (`fn`) to the
returned structure returned from `fn*`, the you will need to do so
now.
* Add the `try*/catch*` special form to the EVAL function. The
try catch form looks like this: `(try* A (catch* B C))`. The form
`A` is evaluated, if it throws an exception, then form `C` is
evaluated with a new environment that binds the symbol B to the
value of the exception that was thrown.
* If your target language has built-in try/catch style exception
handling then you are already 90% of the way done. Add a
(native language) try/catch block that calls evaluates `A` within
the try block and catches all exceptions. If an exception is
caught, then translate it to a mal type/value. For native
exceptions this is either the message string or a mal hash-map
that contains the message string and other attributes of the
exception. When a regular mal types/values is used as an
exception, you will probably need to store it within a native
exception type in order to be able to convey/transport it using
the native try/catch mechanism. Then you will extract the mal
type/value from the native exception. Create a new mal environment
that binds B to the value of the exception. Finally, evaluate `C`
using that new environment.
* If your target language does not have built-in try/catch style
exception handling then you have some extra work to do. One of the
most straightforward approaches is to create a a global error
variable that stores the thrown mal type/value. The complication
is that there are a bunch of places where you must check to see if
the global error state is set and return without proceeding. The
rule of thumb is that this check should happen at the top of your
EVAL function and also right after any call to EVAL (and after any
function call that might happen to call EVAL further down the
chain). Yes, it is ugly, but you were warned in the section on
picking a language.
* Add the `throw` core function.
* If your language supports try/catch style exception handling, then
this function takes a mal type/value and throws/raises it as an
exception. In order to do this, you may need to create a custom
exception object that wraps a mal value/type.
* If your language does not support try/catch style exception
handling, then set the global error state to the mal type/value.
* Add the `apply` and `map` core functions. In step 5, if you did not
add the original function (`fn`) to the structure returned from
`fn*`, the you will need to do so now.
* `apply`: takes at least two arguments. The first argument is
a function and the last argument is list (or vector). The
arguments between the function and the last arguemnt (if there are
any) are concatenated with the final argument to create the
arguments that are used to call the function. The apply
function allows a function to be called with arguments that are
contained in a list (or vector). In other words, `(apply F A B [C
D])` is equivalent to `(F A B C D)`.
* `map`: takes a function and a list (or vector) and evaluates the
function against every element of the list (or vector) one at
a time and returns the results as a list.
* Add some type predicates core functions. In Lisp, predicates are
functions that return true/false (or true value/nil) and typically
end in "?" or "p".
* `nil?`: takes a single argument and returns true (mal true value)
if the argument is nil (mal nil value).
* `true?`: takes a single argument and returns true (mal true value)
if the argument is a true value (mal true value).
* `false?`: takes a single argument and returns true (mal true
value) if the argument is a false value (mal false value).
* `symbol?`: takes a single argument and returns true (mal true
value) if the argument is a symbol (mal symbol value).
Now go to the top level, run the step 9 tests:
```
make test^quux^step9
```
Your mal implementation is now essentially a fully featured Lisp
interpreter. But if you stop now you will miss one of the most
satisfying and enlightening aspects of creating a mal implementation:
self-hosting.
### Deferrable
* Add the following new core functions:
* `symbol`: takes a string and returns a new symbol with the string
as its name.
* `keyword`: takes a string and returns a keyword with the same name
(usually just be prepending the special keyword
unicode symbol). This function should also detect if the argument
is already a keyword and just return it.
* `keyword?`: takes a single argument and returns true (mal true
value) if the argument is a keyword, otherwise returns false (mal
false value).
* `vector`: takes a variable number of arguments and returns
a vector containing those arguments.
* `vector?`: takes a single argument and returns true (mal true
value) if the argument is a vector, otherwise returns false (mal
false value).
* `hash-map`: takes a variable but even number of arguments and
returns a new mal hash-map value with keys from the odd arguments
and values from the even arguments respectively. This is basically
the functional form of the `{}` reader literal syntax.
* `map?`: takes a single argument and returns true (mal true
value) if the argument is a hash-map, otherwise returns false (mal
false value).
* `assoc`: takes a hash-map as the first argument and the remaining
arguments are odd/even key/value pairs to "associate" (merge) into
the hash-map. Note that the original hash-map is unchanged
(remember, mal values are immutable), and a new hash-map
containing the old hash-maps key/values plus the merged key/value
arguments is returned.
* `dissoc`: takes a hash-map and a list of keys to remove from the
hash-map. Again, note that the original hash-map is unchanged and
a new hash-map with the keys removed is returned. Key arguments
that do not exist in the hash-map are ignored.
* `get`: takes a hash-map and a key and returns the value of looking
up that key in the hash-map. If the key is not found in the
hash-map then nil is returned.
* `contains?`: takes a hash-map and a key and returns true (mal true
value) if the key exists in the hash-map and false (mal false
value) otherwise.
* `keys`: takes a hash-map and returns a list (mal list value) of
all the keys in the hash-map.
* `vals`: takes a hash-map and returns a list (mal list value) of
all the values in the hash-map.
* `sequential?`: takes a single arguments and returns true (mal true
value) if it is a list or a vector, otherwise returns false (mal
false value).
<a name="stepA"></a>
### Step A: Interop and Self-hosting
### Step A: Mutation, Self-hosting and Interop
![stepA_mal architecture](stepA_mal.png)
You have reached the final step of your mal implementation. This step
is kind of a catchall for things that did not fit into other steps.
But most importantly, the changes you make in this step will unlock
the magical power known as "self-hosting". You might have noticed
that one of the languages that mal is implemented in is "mal". Any mal
implementation that is complete enough can run the mal implementation
of mal. You might need to pull out your hammock and ponder this for
a while if you have never built a compiler or interpreter before. Look
at the step source files for the mal implementation of mal (it is not
cheating now that you have reached step A).
If you deferred the implementation of keywords, vectors and hash-maps,
now is the time to go back and implement them if you want your
implementation to self-host.
Compare the pseudocode for step 9 and step A to get a basic idea of
the changes that will be made during this step:
```
@ -1223,19 +1384,76 @@ diff -urp ../process/step9_try.txt ../process/stepA_mal.txt
* Copy `step9_try.qx` to `stepA_mal.qx`.
* TODO/TBD
* Self-hosted tests
* Add meta-data support to mal functions. TODO. Should be separate
from the function macro flag.
* Add atom data type and supporting core functions. This is the only
mal data type/value that is mutable. TODO
* Add the `readline` core function. TODO
Now go to the top level, run the step 9 tests:
```
make test^quux^step9
```
Once you have passed all the non-optional step A tests, it is time to
try self-hosting. Run your step A implementation as normal, but use
the file argument mode you added in step 6 to run a each of the step
from the mal implementation:
```
./stepA_mal.qx ../mal/step1_read_print.mal
./stepA_mal.qx ../mal/step2_eval.mal
...
./stepA_mal.qx ../mal/step9_try.mal
./stepA_mal.qx ../mal/stepA_mal.mal
```
There is a very good change that you will encounter an error at some
point while trying to run the mal in mal implementation steps above.
Debugging failures that happen while self-hosting is MUCH more
difficult and mind bending. One of the best approaches I have
personally found is to add prn statements to the mal implemenation
step (not your own implementation of mal) that is causing problems.
Another approach I have frequently used is to pull out the code from
the mal implementation that is causing the problem and simplify it
step by step until you have a simple piece of mal code that still
reproduces the problem. Once the reproducer is simple enough you will
probably know where in your own implementation that problem is likely
to be. Please add your simple reproducer as a test case so that future
implementers will fix similar issues in their code before they get to
self-hosting when it is much more difficult to track down and fix.
Once you can manually run all the self-hosted steps, it is time to run
all the tests in self-hosted mode:
```
make MAL_IMPL=quux test^mal
```
When you run into problems (which you almost certainly will), use the
same process described above to debug them.
Congratulations!!! When all the tests pass, you should pause for
a moment and consider what you have accomplished. You have implemented
a Lisp interpreter that is powerful and complete enough to run a large
mal program which is itself an implementation of the mal language. You
might even be asking if you can continue the "inception" by using your
implementation to run a mal implementation which itself runs the mal
implementation.
### Optional
* Add metadata support to composite data types, symbols and native
functions. TODO
* `time-ms` TODO
* `conj` TODO
## TODO:
* simplify: "X argument (list element Y)" -> ast[Y]
* step 8 summary (power of macros, warning about macros, almost to
self-hosting)
* step 9
* step A
* more info on hash-map and keyword implementation. Hash-maps just
need to support string keys.
* list of types with metadata: list, vector, hash-map, mal functions
* more clarity about when to peek and poke in read_list and read_form
* tokenizer: use first group rather than whole match (to eliminate

View File

@ -1,5 +1,5 @@
% read
/_readline { print flush (%stdin) (r) file 99 string readline } def
/_readline { print flush (%stdin) (r) file 1024 string readline } def
/READ {
% just "return" the input string

View File

@ -10,6 +10,13 @@
exc)))
;; Sequence functions
(define do_apply
(lambda a
(let* ([f (first a)]
[lst (_to_list (last a))]
[args (append (take (drop a 1) (- (length a) 2)) lst)])
(apply f args))))
(define conj
(lambda a
(if (vector? (first a))
@ -88,7 +95,7 @@
'rest _rest
'empty? _empty?
'count _count
'apply apply
'apply do_apply
'map (lambda (f s) (_to_list (_map f s)))
'conj conj

View File

@ -2,11 +2,14 @@
import os, sys, re
import argparse, time
import signal, atexit
import pty, signal, atexit
from subprocess import Popen, STDOUT, PIPE
from select import select
# Pseudo-TTY and terminal manipulation
import pty, array, fcntl, termios
IS_PY_3 = sys.version_info[0] == 3
# TODO: do we need to support '\n' too
@ -26,8 +29,6 @@ parser.add_argument('--pre-eval', default=None, type=str,
help="Mal code to evaluate prior to running the test")
parser.add_argument('--no-pty', action='store_true',
help="Use direct pipes instead of pseudo-tty")
parser.add_argument('--mono', action='store_true',
help="Use workarounds Mono/.Net Console misbehaviors, implies --no-pty")
parser.add_argument('test_file', type=argparse.FileType('r'),
help="a test file formatted as with mal test data")
@ -36,27 +37,42 @@ parser.add_argument('mal_cmd', nargs="*",
"specify a Mal command line with dashed options.")
class Runner():
def __init__(self, args, no_pty=False, mono=False):
def __init__(self, args, no_pty=False):
#print "args: %s" % repr(args)
if mono: no_pty = True
self.no_pty = no_pty
self.mono = mono
# Cleanup child process on exit
atexit.register(self.cleanup)
self.p = None
env = os.environ
env['TERM'] = 'dumb'
env['INPUTRC'] = '/dev/null'
env['PERL_RL'] = 'false'
if no_pty:
self.p = Popen(args, bufsize=0,
stdin=PIPE, stdout=PIPE, stderr=STDOUT,
preexec_fn=os.setsid)
preexec_fn=os.setsid,
env=env)
self.stdin = self.p.stdin
self.stdout = self.p.stdout
else:
# provide tty to get 'interactive' readline to work
master, slave = pty.openpty()
# Set terminal size large so that readline will not send
# ANSI/VT escape codes when the lines are long.
buf = array.array('h', [100, 200, 0, 0])
fcntl.ioctl(master, termios.TIOCSWINSZ, buf, True)
self.p = Popen(args, bufsize=0,
stdin=slave, stdout=slave, stderr=STDOUT,
preexec_fn=os.setsid)
preexec_fn=os.setsid,
env=env)
# Now close slave so that we will get an exception from
# read when the child exits early
# http://stackoverflow.com/questions/11165521
os.close(slave)
self.stdin = os.fdopen(master, 'r+b', 0)
self.stdout = self.stdin
@ -70,8 +86,8 @@ class Runner():
[outs,_,_] = select([self.stdout], [], [], 1)
if self.stdout in outs:
new_data = self.stdout.read(1)
#print "new_data: '%s'" % new_data
new_data = new_data.decode("utf-8") if IS_PY_3 else new_data
#print "new_data: '%s'" % new_data
if self.no_pty:
self.buf += new_data.replace("\n", "\r\n")
else:
@ -92,9 +108,6 @@ class Runner():
return bytes(s, "utf-8") if IS_PY_3 else s
self.stdin.write(_to_bytes(str + "\n"))
if self.mono:
# Simulate echo
self.buf += _to_bytes(str + "\r\n")
def cleanup(self):
#print "cleaning up"
@ -111,7 +124,7 @@ test_data = args.test_file.read().split('\n')
if args.rundir: os.chdir(args.rundir)
r = Runner(args.mal_cmd, no_pty=args.no_pty, mono=args.mono)
r = Runner(args.mal_cmd, no_pty=args.no_pty)
test_idx = 0
@ -181,7 +194,12 @@ while test_data:
break
sys.stdout.write("TEST: %s -> [%s,%s]" % (form, repr(out), repr(ret)))
sys.stdout.flush()
expected = "%s%s%s%s" % (form, sep, out, ret)
# The repeated form is to get around an occasional OS X issue
# where the form is repeated.
# https://github.com/kanaka/mal/issues/30
expected = ["%s%s%s%s" % (form, sep, out, ret),
"%s%s%s%s%s%s" % (form, sep, form, sep, out, ret)]
r.writeline(form)
try:
@ -189,20 +207,17 @@ while test_data:
'\r\nmal-user> ', '\nmal-user> '],
timeout=args.test_timeout)
#print "%s,%s,%s" % (idx, repr(p.before), repr(p.after))
if ret == "*" or res == expected:
if ret == "*" or res in expected:
print(" -> SUCCESS")
else:
print(" -> FAIL (line %d):" % line_num)
print(" Expected : %s" % repr(expected))
print(" Got : %s" % repr(res))
fail_cnt += 1
except KeyboardInterrupt:
print("\nKeyboard interrupt.")
print("Output so far:\n%s" % r.buf)
sys.exit(1)
except:
_, exc, _ = sys.exc_info()
print("\nException: %s" % repr(exc.message))
print("\nException: %s" % repr(exc))
print("Output before exception:\n%s" % r.buf)
sys.exit(1)
if fail_cnt > 0:

211
swift/Makefile Normal file
View File

@ -0,0 +1,211 @@
################################################################################
#
# Makefile for the Swift implementation of MAL.
#
# The MAL project consists of building up a dialect/subset of Clojure over a
# series of steps. Each step implements a new feature or concept in an easily
# understandable and approachable manner. Each step can be built on its own and
# tested. Each step is built from a step-specific "step.swift" file and a set
# of files common to all steps.
#
# The general approach in this file is to discover the set of "step" source
# files (step0_repl.swift, etc.), and build corresponding executable files
# (step0_repl, etc) from them and from the set of supporting Swift files.
# Since the set of "step" files is discovered on-the-fly, the rules to make
# those files are also generated on-the-fly using $(eval).
#
# The various "step0_repl.swift", etc., source files are actually generated
# from a file called "templates/step.swift". Since each "step" file
# incrementally builds towards the final, complete "step" file,
# "templates/step.swift" is -- for the most part -- a copy of this final "step"
# file with each line annotated with the step in which that line is introduced.
# Through the use of a simple filter program, the "templates/step.swift" file
# can then be processed to produce each intermediate "step" file. This Makefile
# takes care of performing that processing any time "templates/step.swift"
# changes.
#
# MAKE TARGETS:
#
# all:
# Make all step targets, (re)generating source files if needed.
# alls:
# (Re)generate source files, if needed.
# step0_repl, step1_read_print, etc.:
# Make the corresponding step target.
# s0...sN:
# Shortcuts for the previous targets.
# step0_repl.swift, step1_read_print.swift, etc.:
# (Re)generate source files for the corresponding step target, if
# needed.
# ss0...ssN:
# Shortcuts for the previous targets.
# clean:
# Delete all built executables. Generated source files are *not*
# deleted.
# dump:
# Print some Make variables for debugging.
#
# TODO:
# * Compile each .swift file into an intermediate .o file and link the .o
# files, rather than performing a complete build of all files any time
# any one of them is out-of-date. Here are the commands generated when
# using `swiftc -v`:
#
# /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swift \
# -frontend \
# -c \
# -primary-file stepA_mal.swift \
# ./core.swift \
# ./env.swift \
# ./main.swift \
# ./printer.swift \
# ./reader.swift \
# ./readline.swift \
# ./types.swift \
# -target x86_64-apple-darwin14.1.0 \
# -target-cpu core2 \
# -sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.10.sdk \
# -import-objc-header ./bridging-header.h \
# -color-diagnostics \
# -Onone \
# -ledit \
# -module-name stepA_mal \
# -o /var/folders/dj/p3tx6v852sl88g79qvhhc2ch0000gp/T/stepA_mal-e0a836.o
# ... Similar for each source file...
# /usr/bin/ld \
# /var/folders/dj/p3tx6v852sl88g79qvhhc2ch0000gp/T/stepA_mal-e0a836.o \
# /var/folders/dj/p3tx6v852sl88g79qvhhc2ch0000gp/T/core-28b620.o \
# /var/folders/dj/p3tx6v852sl88g79qvhhc2ch0000gp/T/env-5d8422.o \
# /var/folders/dj/p3tx6v852sl88g79qvhhc2ch0000gp/T/main-e79633.o \
# /var/folders/dj/p3tx6v852sl88g79qvhhc2ch0000gp/T/printer-cdd3e5.o \
# /var/folders/dj/p3tx6v852sl88g79qvhhc2ch0000gp/T/reader-bb188a.o \
# /var/folders/dj/p3tx6v852sl88g79qvhhc2ch0000gp/T/readline-53df55.o \
# /var/folders/dj/p3tx6v852sl88g79qvhhc2ch0000gp/T/types-7cb250.o \
# -L /usr/lib \
# -ledit \
# -syslibroot \
# /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.10.sdk \
# -lSystem \
# -arch x86_64 \
# -L /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/macosx \
# -rpath /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/macosx \
# -macosx_version_min 10.10.0 \
# -no_objc_category_merging \
# -o stepA_mal
#
# * Consider adding a clean-dist (or similar) that deletes the generated
# "step" source files.
#
################################################################################
#
# Discover the set of "step" source files (those having the form
# "step<#>_foo.swift")
#
SRCS := $(wildcard ./step*.swift)
#
# From the set of "step" source files, generate the set of executable files
# (those having the form "step<#>_foo")
#
EXES := $(patsubst %.swift,%,$(SRCS))
#
# Given a name like "./step<#>_foo", return <#>.
#
# (Is there a better way to do this? $(patsubst) seems to be the most
# appropriate built-in command, but it doesn't seem powerful enough.)
#
# (I've included a `sed` version in case relying on bash is contraindicated.)
#
#get_step_number = $(shell echo $(1) | sed -e "s/.*step\(.\).*/\1/")
get_step_number = $(shell [[ $(1) =~ step(.)_.* ]] ; echo $${BASH_REMATCH[1]})
#
# Working from the list of discovered "step<#>_foo.swift" files, generate the
# list of step numbers.
#
get_all_step_numbers = $(foreach SRC,$(SRCS),$(call get_step_number,$(SRC)))
#
# Generate the dependencies for the "all" target. This list has the form
# "s0 s1 ... sN" for all N returned by get_all_step_numbers. That is:
#
# all: s0 s1 ... sN
#
# Also create an "alls" target that just regenerates all the "step" files from
# the corresponding template file.
#
$(eval all: $(patsubst %,s%,$(call get_all_step_numbers)))
$(eval alls: $(patsubst %,ss%,$(call get_all_step_numbers)))
#
# Generate the dependencies for the ".PHONY" target. That is:
#
# .PHONY: all clean dump s0 s1 ... sN
#
$(eval .PHONY: all clean dump $(patsubst %,s%,$(call get_all_step_numbers)))
#
# Define the "EZ" targets, where "s0" builds "step0_repl", "s1" builds
# "step1_read_print", etc. That is:
#
# s0: step0_repl
# s1: step1_read_print
# ...
# sN: stepN_foo
#
# Also create corresponding targets that rebuild the sources files:
#
# ss0: step0_repl.swift
# ss1: step1_read_print.swift
# ...
# ssN: stepN_foo.swift
#
$(foreach EXE,$(EXES),$(eval s$(call get_step_number,$(EXE)): $(EXE)))
$(foreach SRC,$(SRCS),$(eval ss$(call get_step_number,$(SRC)): $(SRC)))
#
# Various helpful variables.
#
SWIFT := swiftc
STEP_TEMPLATE := ./templates/step.swift
FILTER := ./templates/filter_steps.sh
UTIL_SRC := $(filter-out $(STEP_TEMPLATE) $(SRCS),$(wildcard ./*.swift))
SDKROOT ?= $(shell xcrun --show-sdk-path)
OPT := #-Ounchecked
DEBUG := #-g
EXTRA := #-v
COMMON := $(UTIL_SRC) $(OPT) $(DEBUG) $(EXTRA) -import-objc-header ./bridging-header.h -L /usr/lib -ledit -sdk $(SDKROOT)
#
# Build the executable from the input sources consisting of the appropriate
# "step" file and the supporting files in $(UTIL_SRC).
#
$(EXES) : % : %.swift $(UTIL_SRC)
@echo "Making : $@"
@$(SWIFT) $< $(COMMON) -o $@
#
# Build the "step" source file ("step<#>_foo.swift") from the step template
# file that combines all the steps in one file.
#
$(SRCS) : % : $(STEP_TEMPLATE) ./Makefile
@echo "Generating: $@"
@$(FILTER) $(call get_step_number,$@) $< $@
#
# Delete all of the built executables.
#
clean:
@rm -f $(EXES)
#
# Display some variables for debugging.
#
dump:
@echo " SRCS = $(SRCS)"
@echo " EXES = $(EXES)"
@echo " UTIL = $(UTIL_SRC)"
@echo "SDKROOT = $(SDKROOT)"
@echo " STEPS = $(call get_all_step_numbers)"

15
swift/bridging-header.h Normal file
View File

@ -0,0 +1,15 @@
// This is the "bridging" file for the Swift version of MAL. A bridging file
// brings in C/ObjC types and makes them available to Swift source code, using
// the type conversion process described in:
//
// https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/InteractingWithCAPIs.html#//apple_ref/doc/uid/TP40014216-CH8-XID_11
//
// The mechanism for creating and using a bridging file is only documented for
// Xcode users. However, the following article describes how to specify a
// bridging file on the command line:
//
// http://stackoverflow.com/questions/24131476/compiling-and-linking-swift-plus-objective-c-code-from-the-os-x-command-line
//
#include <stdlib.h>
#include <editline/readline.h>

678
swift/core.swift Normal file
View File

@ -0,0 +1,678 @@
//******************************************************************************
// MAL - core
//******************************************************************************
import Foundation
typealias MalVarArgs = Slice<MalVal>
func fn_eq(obj1: MalVal, obj2: MalVal) -> Bool {
return obj1 == obj2
}
func fn_throw(exception: MalVal) -> MalVal {
return MalError(object: exception)
}
func fn_nilQ(obj: MalVal) -> Bool {
return is_nil(obj)
}
func fn_trueQ(obj: MalVal) -> Bool {
return is_true(obj)
}
func fn_falseQ(obj: MalVal) -> Bool {
return is_false(obj)
}
func fn_symbol(s: String) -> MalVal {
return MalSymbol(symbol: s)
}
func fn_symbolQ(obj: MalVal) -> Bool {
return is_symbol(obj)
}
func fn_keyword(s: MalVal) -> MalVal {
if is_keyword(s) {
return s
}
if is_string(s) {
return MalKeyword(keyword: (s as MalString).value)
}
return MalError(message: "expected string or keyword")
}
func fn_keywordQ(obj: MalVal) -> Bool {
return is_keyword(obj)
}
func fn_prstr(args: MalVarArgs) -> String {
let args_str_array = args.map { pr_str($0, true) }
return " ".join(args_str_array)
}
func fn_str(args: MalVarArgs) -> String {
let args_str_array = args.map { pr_str($0, false) }
return "".join(args_str_array)
}
func fn_prn(args: MalVarArgs) {
let args_str_array = args.map { pr_str($0, true) }
let args_str = " ".join(args_str_array)
println(args_str)
}
func fn_println(args: MalVarArgs) {
let args_str_array = args.map { pr_str($0, false) }
let args_str = " ".join(args_str_array)
println(args_str)
}
func fn_readstring(s: String) -> MalVal {
return read_str(s)
}
func fn_readline(s: String) -> String? {
return _readline(s)
}
func fn_slurp(s: String) -> MalVal {
var err: NSError? = nil
let result = String(contentsOfFile:s, encoding: NSUTF8StringEncoding, error:&err)
if result == nil {
if err != nil {
return MalError(message: "error reading file \(s): \(err!.localizedDescription)")
}
return MalError(message: "unknown error reading file \(s)")
}
return MalString(unescaped: result!);
}
func fn_lt(arg1: Int64, arg2: Int64) -> Bool {
return arg1 < arg2
}
func fn_lte(arg1: Int64, arg2: Int64) -> Bool {
return arg1 <= arg2
}
func fn_gt(arg1: Int64, arg2: Int64) -> Bool {
return arg1 > arg2
}
func fn_gte(arg1: Int64, arg2: Int64) -> Bool {
return arg1 >= arg2
}
func fn_add(arg1: Int64, arg2: Int64) -> Int64 {
return arg1 + arg2
}
func fn_subtract(arg1: Int64, arg2: Int64) -> Int64 {
return arg1 - arg2
}
func fn_multiply(arg1: Int64, arg2: Int64) -> Int64 {
return arg1 * arg2
}
func fn_divide(arg1: Int64, arg2: Int64) -> Int64 {
return arg1 / arg2
}
func fn_timems() -> Int64 {
var time = timeval(tv_sec:0, tv_usec:0)
let res = gettimeofday(&time, nil)
if res == 0 {
return (Int64(time.tv_sec) * 1_000_000 + Int64(time.tv_usec)) / 1000
}
return -1
}
func fn_list(args: MalVarArgs) -> MalVal {
return MalList(slice: args)
}
func fn_listQ(obj: MalVal) -> Bool {
return is_list(obj)
}
func fn_vector(args: MalVarArgs) -> MalVal {
return MalVector(slice: args)
}
func fn_vectorQ(obj: MalVal) -> Bool {
return is_vector(obj)
}
func fn_hashmap(args: MalVarArgs) -> MalVal {
return MalHashMap(slice: args)
}
func fn_hashmapQ(obj: MalVal) -> Bool {
return is_hashmap(obj)
}
func fn_assoc(hash: MalHashMap, args: MalVarArgs) -> MalVal {
if args.count % 2 != 0 {
return MalError(message: "expected even number of elements, got \(args.count)")
}
var new_dictionary = hash.hash
for var index = 0; index < args.count; index += 2 {
new_dictionary[args[index]] = args[index + 1]
}
return MalHashMap(hash: new_dictionary)
}
func fn_dissoc(hash: MalHashMap, args: MalVarArgs) -> MalVal {
var new_dictionary = hash.hash
for value in args {
new_dictionary.removeValueForKey(value)
}
return MalHashMap(hash: new_dictionary)
}
func fn_get(obj: MalVal, key: MalVal) -> MalVal {
if is_vector(obj) {
if !is_integer(key) { return MalError(message: "expected integer key for get(vector), got \(key)") }
let as_vector = obj as MalVector
let index = key as MalInteger
if Int(index.value) >= as_vector.count { return MalError(message: "index out of range: \(index) >= \(as_vector.count)") }
return as_vector[Int(index.value)]
}
if is_hashmap(obj) {
let as_hash = obj as MalHashMap
if let value = as_hash[key] { return value }
return MalNil()
}
if is_nil(obj) {
return obj
}
return MalError(message: "get called on unsupported type: \(obj)")
}
func fn_containsQ(obj: MalVal, key: MalVal) -> MalVal {
if is_vector(obj) {
if !is_integer(key) { return MalError(message: "expected integer key for contains(vector), got \(key)") }
let as_vector = obj as MalVector
let index = key as MalInteger
return Int(index.value) < as_vector.count ? MalTrue() : MalFalse()
}
if is_hashmap(obj) {
let as_hash = obj as MalHashMap
return as_hash[key] != nil ? MalTrue() : MalFalse()
}
return MalError(message: "contains? called on unsupported type: \(obj)")
}
func fn_keys(hash: MalHashMap) -> MalVal {
return MalList(array: hash.hash.keys.array)
}
func fn_values(hash: MalHashMap) -> MalVal {
return MalList(array: hash.hash.values.array)
}
func fn_sequentialQ(obj: MalVal) -> Bool {
return is_sequence(obj)
}
func fn_cons(first:MalVal, rest:MalSequence) -> MalVal {
var new_elements = [MalVal]([first])
new_elements.extend(rest.slice)
return MalList(array: new_elements)
}
func fn_concat(args: MalVarArgs) -> MalVal {
var result = [MalVal]()
for arg in args {
if !is_sequence(arg) { return MalError(message: "expected list, got \(arg)") }
result.extend((arg as MalSequence).slice)
}
return MalList(array: result)
}
func fn_nth(list: MalSequence, index: Int) -> MalVal {
return list[index]
}
func fn_first(arg: MalVal) -> MalVal {
if is_nil(arg) {
return arg
}
if is_sequence(arg) {
let list = arg as MalSequence
return list.first()
}
return MalError(message: "expected list, got \(arg)")
}
func fn_rest(list: MalSequence) -> MalVal {
return MalList(slice: list.rest().slice)
}
func fn_emptyQ(obj: MalVal) -> Bool {
if is_sequence(obj) {
let list = obj as MalSequence
return list.isEmpty
}
return true
}
func fn_count(obj: MalVal) -> Int64 {
if is_nil(obj) {
return 0
}
if is_sequence(obj) {
let as_seq = obj as MalSequence
return Int64(as_seq.count)
}
if is_hashmap(obj) {
let hash = obj as MalHashMap
return Int64(hash.count)
}
if is_string(obj) {
let string = obj as MalString
return Int64(string.value.utf16Count)
}
return 0
}
func fn_apply(args: MalVarArgs) -> MalVal {
if args.count < 2 { return MalError(message: "expected at least 2 arguments to apply, got \(args.count)") }
let first = args[0]
var middle = args[1 ..< args.count - 1]
let last = args[args.count - 1]
if !is_function(first) { return MalError(message: "expected function for first argument to apply, got \(first)") }
if !is_sequence(last) { return MalError(message: "expected sequence for last argument to apply, got \(last)") }
middle.extend((last as MalSequence).slice)
return (first as MalFunction).apply(MalList(slice: middle))
}
func fn_map(fn: MalFunction, list: MalSequence) -> MalVal {
var result = [MalVal]()
result.reserveCapacity(list.count)
for var index = 0; index < list.count; ++index {
let apply_res = fn.apply(MalList(slice: list.slice[index...index]))
if is_error(apply_res) { return apply_res }
result.append(apply_res)
}
return MalList(array: result)
}
func fn_conj(first: MalSequence, rest: MalVarArgs) -> MalVal {
var result = [MalVal]()
result.reserveCapacity(first.count + rest.count)
if is_list(first) {
for var index = 0; index < rest.count; ++index {
result.append(rest[rest.count - 1 - index])
}
result.extend(first.slice)
return MalList(array: result)
} else {
result.extend(first.slice)
result.extend(rest)
return MalVector(array: result)
}
}
func fn_meta(obj: MalVal) -> MalVal {
return obj.meta != nil ? obj.meta! : MalNil()
}
func fn_withmeta(form:MalVal, meta:MalVal) -> MalVal {
var new_form = form.clone()
new_form.meta = meta
return new_form
}
func fn_atom(obj: MalVal) -> MalVal {
return MalAtom(object: obj)
}
func fn_atomQ(obj: MalVal) -> Bool {
return is_atom(obj)
}
func fn_deref(form:MalVal) -> MalVal {
if !is_atom(form) { return MalError(message: "expected atom, got \(form)") }
return (form as MalAtom).value
}
func fn_resetBang(atom: MalAtom, obj: MalVal) -> MalVal {
atom.value = obj
return obj
}
func fn_swapBang(var atom: MalAtom, fn: MalFunction, rest: MalVarArgs) -> MalVal {
var new_args = [MalVal]([atom.value])
new_args.extend(rest)
let result = fn.apply(MalList(array: new_args))
if is_error(result) { return result }
atom.value = result
return result
}
//******************************************************************************
//
// The facility for invoking built-in functions makes use of a name ->
// function-pointer table (defined down below). The function-pointers accept a
// sequence of MalVals and return a MalVal as a result. Each built-in function
// that does actual work, on the other hand, may expect a different set of
// parameters of different types, and may naturally return a result of any type.
// In order to convert between these two types of interfaces, we have these
// unwrap functions. These functions implement the (MalSequence) -> MalVal
// interface expected by EVAL, and convert that information into Ints, Strings,
// etc. expected by the built-in functions.
//
//******************************************************************************
func with_one_parameter(args: MalSequence, fn: (MalVal) -> MalVal) -> MalVal {
if args.count < 1 { return MalError(message: "expected at least 1 parameter, got \(args.count)") }
let arg1 = args[0]
//let rest = args[1..<args.count]
return fn(arg1)
}
func with_two_parameters(args: MalSequence, fn: (MalVal, MalVal) -> MalVal) -> MalVal {
if args.count < 2 { return MalError(message: "expected at least 2 parameter, got \(args.count)") }
let arg1 = args[0]
let arg2 = args[1]
//let rest = args[2..<args.count]
return fn(arg1, arg2)
}
// ========== 0-parameter functions ==========
// () -> Int64
func unwrap(args: MalSequence, fn: () -> Int64) -> MalVal {
return MalInteger(value: fn())
}
// () -> MalVal
func unwrap(args: MalSequence, fn: () -> MalVal) -> MalVal {
return fn()
}
// ========== 1-parameter functions ==========
// (MalHashMap) -> MalVal
func unwrap(args: MalSequence, fn: (MalHashMap) -> MalVal) -> MalVal {
return with_one_parameter(args) { (arg1) -> MalVal in
if !is_hashmap(arg1) { return MalError(message: "expected hashmap, got \(arg1)") }
return fn(arg1 as MalHashMap)
}
}
// (MalSequence) -> MalVal
func unwrap(args: MalSequence, fn: (MalSequence) -> MalVal) -> MalVal {
return with_one_parameter(args) { (arg1) -> MalVal in
if !is_sequence(arg1) { return MalError(message: "expected list, got \(arg1)") }
return fn(arg1 as MalSequence)
}
}
// (MalVal) -> Bool
func unwrap(args: MalSequence, fn: (MalVal) -> Bool) -> MalVal {
return with_one_parameter(args) { (arg1) -> MalVal in
return fn(arg1) ? MalTrue() : MalFalse()
}
}
// (MalVal) -> Int64
func unwrap(args: MalSequence, fn: (MalVal) -> Int64) -> MalVal {
return with_one_parameter(args) { (arg1) -> MalVal in
return MalInteger(value: fn(arg1))
}
}
// (MalVal) -> MalVal
func unwrap(args: MalSequence, fn: (MalVal) -> MalVal) -> MalVal {
return with_one_parameter(args) { (arg1) -> MalVal in
return fn(arg1)
}
}
// (String) -> MalVal
func unwrap(args: MalSequence, fn: (String) -> MalVal) -> MalVal {
return with_one_parameter(args) { (arg1) -> MalVal in
if !is_string(arg1) { return MalError(message: "expected string, got \(arg1)") }
return fn((arg1 as MalString).value)
}
}
// (String) -> MalVal?
func unwrap(args: MalSequence, fn: (String) -> MalVal?) -> MalVal {
return with_one_parameter(args) { (arg1) -> MalVal in
if !is_string(arg1) { return MalError(message: "expected string, got \(arg1)") }
let res = fn((arg1 as MalString).value)
return res != nil ? res! : MalNil()
}
}
// (String) -> String
func unwrap(args: MalSequence, fn: (String) -> String) -> MalVal {
return with_one_parameter(args) { (arg1) -> MalVal in
if !is_string(arg1) { return MalError(message: "expected string, got \(arg1)") }
return MalString(unescaped: fn((arg1 as MalString).value))
}
}
// (String) -> String?
func unwrap(args: MalSequence, fn: (String) -> String?) -> MalVal {
return with_one_parameter(args) { (arg1) -> MalVal in
if !is_string(arg1) { return MalError(message: "expected string, got \(arg1)") }
let res = fn((arg1 as MalString).value)
return res != nil ? MalString(unescaped:res!) : MalNil()
}
}
// ========== 2-parameter functions ==========
// (Int64, Int64) -> Bool
func unwrap(args: MalSequence, fn: (Int64, Int64) -> Bool) -> MalVal {
return with_two_parameters(args) { (arg1, arg2) -> MalVal in
if !is_integer(arg1) { return MalError(message: "expected number, got \(arg1)") }
if !is_integer(arg2) { return MalError(message: "expected number, got \(arg2)") }
return fn((arg1 as MalInteger).value, (arg2 as MalInteger).value) ? MalTrue() : MalFalse()
}
}
// (Int64, Int64) -> Int64
func unwrap(args: MalSequence, fn: (Int64, Int64) -> Int64) -> MalVal {
return with_two_parameters(args) { (arg1, arg2) -> MalVal in
if !is_integer(arg1) { return MalError(message: "expected number, got \(arg1)") }
if !is_integer(arg2) { return MalError(message: "expected number, got \(arg2)") }
return MalInteger(value: fn((arg1 as MalInteger).value, (arg2 as MalInteger).value))
}
}
// (MalAtom, MalVal) -> MalVal
func unwrap(args: MalSequence, fn: (MalAtom, MalVal) -> MalVal) -> MalVal {
return with_two_parameters(args) { (arg1, arg2) -> MalVal in
if !is_atom(arg1) { return MalError(message: "expected atom, got \(arg1)") }
return fn((arg1 as MalAtom), arg2)
}
}
// (MalFunction, MalSequence) -> MalVal
func unwrap(args: MalSequence, fn: (MalFunction, MalSequence) -> MalVal) -> MalVal {
return with_two_parameters(args) { (arg1, arg2) -> MalVal in
if !is_function(arg1) { return MalError(message: "expected function, got \(arg1)") }
if !is_sequence(arg2) { return MalError(message: "expected sequence, got \(arg2)") }
return fn((arg1 as MalFunction), (arg2 as MalSequence))
}
}
// (MalSequence, Int) -> MalVal
func unwrap(args: MalSequence, fn: (MalSequence, Int) -> MalVal) -> MalVal {
return with_two_parameters(args) { (arg1, arg2) -> MalVal in
if !is_sequence(arg1) { return MalError(message: "expected sequence, got \(arg1)") }
if !is_integer(arg2) { return MalError(message: "expected number, got \(arg2)") }
return fn((arg1 as MalSequence), Int((arg2 as MalInteger).value))
}
}
// (MalVal, MalSequence) -> MalVal
func unwrap(args: MalSequence, fn: (MalVal, MalSequence) -> MalVal) -> MalVal {
return with_two_parameters(args) { (arg1, arg2) -> MalVal in
if !is_sequence(arg2) { return MalError(message: "expected sequence, got \(arg2)") }
return fn(arg1, (arg2 as MalSequence))
}
}
// (MalVal, MalVal) -> Bool
func unwrap(args: MalSequence, fn: (MalVal, MalVal) -> Bool) -> MalVal {
return with_two_parameters(args) { (arg1, arg2) -> MalVal in
return fn(arg1, arg2) ? MalTrue() : MalFalse()
}
}
// (MalVal, MalVal) -> MalVal
func unwrap(args: MalSequence, fn: (MalVal, MalVal) -> MalVal) -> MalVal {
return with_two_parameters(args) { (arg1, arg2) -> MalVal in
return fn(arg1, arg2)
}
}
// ========== Variadic functions ==========
// (MalVarArgs) -> ()
func unwrap(args: MalSequence, fn: (MalVarArgs) -> ()) -> MalVal {
fn(args.slice)
return MalNil()
}
// (MalVarArgs) -> String
func unwrap(args: MalSequence, fn: (MalVarArgs) -> String) -> MalVal {
return MalString(unescaped: fn(args.slice))
}
// (MalVarArgs) -> MalVal
func unwrap(args: MalSequence, fn: (MalVarArgs) -> MalVal) -> MalVal {
return fn(args.slice)
}
// (MalAtom, MalFunction, MalVarArgs) -> MalVal
func unwrap(args: MalSequence, fn: (MalAtom, MalFunction, MalVarArgs) -> MalVal) -> MalVal {
return with_two_parameters(args) { (arg1, arg2) -> MalVal in
if !is_atom(arg1) { return MalError(message: "expected atom, got \(arg1)") }
if !is_function(arg2) { return MalError(message: "expected function, got \(arg2)") }
return fn((arg1 as MalAtom), (arg2 as MalFunction), args[2..<args.count])
}
}
// (MalHashMap, MalVarArgs) -> MalVal
func unwrap(args: MalSequence, fn: (MalHashMap, MalVarArgs) -> MalVal) -> MalVal {
return with_one_parameter(args) { (arg1) -> MalVal in
if !is_hashmap(arg1) { return MalError(message: "expected hashmap, got \(arg1)") }
return fn((arg1 as MalHashMap), args[1..<args.count])
}
}
// (MalSequence, MalVarArgs) -> MalVal
func unwrap(args: MalSequence, fn: (MalSequence, MalVarArgs) -> MalVal) -> MalVal {
return with_one_parameter(args) { (arg1) -> MalVal in
if !is_sequence(arg1) { return MalError(message: "expected sequence, got \(arg1)") }
return fn((arg1 as MalSequence), args[1..<args.count])
}
}
// *o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*o*
let ns: [String: MalBuiltin.BuiltinSignature] = [
"=": { unwrap($0, fn_eq) },
"throw": { unwrap($0, fn_throw) },
"nil?": { unwrap($0, fn_nilQ) },
"true?": { unwrap($0, fn_trueQ) },
"false?": { unwrap($0, fn_falseQ) },
"symbol": { unwrap($0, fn_symbol) },
"symbol?": { unwrap($0, fn_symbolQ) },
"keyword": { unwrap($0, fn_keyword) },
"keyword?": { unwrap($0, fn_keywordQ) },
"pr-str": { unwrap($0, fn_prstr) },
"str": { unwrap($0, fn_str) },
"prn": { unwrap($0, fn_prn) },
"println": { unwrap($0, fn_println) },
"read-string": { unwrap($0, fn_readstring) },
"readline": { unwrap($0, fn_readline) },
"slurp": { unwrap($0, fn_slurp) },
"<": { unwrap($0, fn_lt) },
"<=": { unwrap($0, fn_lte) },
">": { unwrap($0, fn_gt) },
">=": { unwrap($0, fn_gte) },
"+": { unwrap($0, fn_add) },
"-": { unwrap($0, fn_subtract) },
"*": { unwrap($0, fn_multiply) },
"/": { unwrap($0, fn_divide) },
"time-ms": { unwrap($0, fn_timems) },
"list": { unwrap($0, fn_list) },
"list?": { unwrap($0, fn_listQ) },
"vector": { unwrap($0, fn_vector) },
"vector?": { unwrap($0, fn_vectorQ) },
"hash-map": { unwrap($0, fn_hashmap) },
"map?": { unwrap($0, fn_hashmapQ) },
"assoc": { unwrap($0, fn_assoc) },
"dissoc": { unwrap($0, fn_dissoc) },
"get": { unwrap($0, fn_get) },
"contains?": { unwrap($0, fn_containsQ) },
"keys": { unwrap($0, fn_keys) },
"vals": { unwrap($0, fn_values) },
"sequential?": { unwrap($0, fn_sequentialQ) },
"cons": { unwrap($0, fn_cons) },
"concat": { unwrap($0, fn_concat) },
"nth": { unwrap($0, fn_nth) },
"first": { unwrap($0, fn_first) },
"rest": { unwrap($0, fn_rest) },
"empty?": { unwrap($0, fn_emptyQ) },
"count": { unwrap($0, fn_count) },
"apply": { unwrap($0, fn_apply) },
"map": { unwrap($0, fn_map) },
"conj": { unwrap($0, fn_conj) },
"meta": { unwrap($0, fn_meta) },
"with-meta": { unwrap($0, fn_withmeta) },
"atom": { unwrap($0, fn_atom) },
"atom?": { unwrap($0, fn_atomQ) },
"deref": { unwrap($0, fn_deref) },
"reset!": { unwrap($0, fn_resetBang) },
"swap!": { unwrap($0, fn_swapBang) },
]
func load_builtins(env: Environment) {
for (name, fn) in ns {
env.set(MalSymbol(symbol: name), MalBuiltin(function: fn))
}
}

62
swift/env.swift Normal file
View File

@ -0,0 +1,62 @@
//******************************************************************************
// MAL - env
//******************************************************************************
import Foundation
typealias EnvironmentVars = [MalSymbol: MalVal]
let kSymbolAmpersand = MalSymbol(symbol: "&")
class Environment {
init(outer: Environment?) {
self.outer = outer
}
func set_bindings(binds: MalSequence, with_exprs exprs: MalSequence) -> MalVal {
for var index = 0; index < binds.count; ++index {
if !is_symbol(binds[index]) { return MalError(message: "an entry in binds was not a symbol: index=\(index), binds[index]=\(binds[index])") }
let sym = binds[index] as MalSymbol
if sym != kSymbolAmpersand {
if index < exprs.count {
set(sym, exprs[index])
} else {
set(sym, MalNil())
}
continue
}
// I keep getting messed up by the following, so here's an
// explanation. We are iterating over two lists, and are at this
// point:
//
// index
// |
// v
// binds: (... & name)
// exprs: (... a b c d e ...)
//
// In the following, we increment index to get to "name", and then
// later decrement it to get to (a b c d e ...)
if ++index >= binds.count { return MalError(message: "found & but no symbol") }
if !is_symbol(binds[index]) { return MalError(message: "& was not followed by a symbol: index=\(index), binds[index]=\(binds[index])") }
let rest_sym = binds[index--] as MalSymbol
let rest = exprs[index..<exprs.count]
set(rest_sym, MalList(slice: rest))
break
}
return MalNil()
}
func set(sym: MalSymbol, _ value: MalVal) -> MalVal {
data[sym] = value
return value
}
func get(sym: MalSymbol) -> MalVal? {
if let val = data[sym] { return val }
return outer?.get(sym)
}
private var outer: Environment?
private var data = EnvironmentVars()
}

18
swift/main.swift Normal file
View File

@ -0,0 +1,18 @@
//******************************************************************************
// MAL - main
//******************************************************************************
// Swift requires that main() be invoked from a file named "main.swift". See the
// paragraph "Application Entry Points and main.swift on
// https://developer.apple.com/swift/blog/?id=7:
//
// Youll notice that earlier we said top-level code isnt allowed in most
// of your apps source files. The exception is a special file named
// main.swift, which behaves much like a playground file, but is built
// with your apps source code. The main.swift file can contain top-level
// code, and the order-dependent rules apply as well. In effect, the first
// line of code to run in main.swift is implicitly defined as the main
// entrypoint for the program. This allows the minimal Swift program to be
// a single line as long as that line is in main.swift.
main()

19
swift/printer.swift Normal file
View File

@ -0,0 +1,19 @@
//******************************************************************************
// MAL - printer
//******************************************************************************
import Foundation
func with_print_readably<T>(print_readably: Bool, fn: () -> T) -> T {
let old = MalValPrintReadably
MalValPrintReadably = print_readably
let result = fn()
MalValPrintReadably = old
return result
}
func pr_str(m: MalVal, print_readably: Bool) -> String {
return with_print_readably(print_readably) {
m.description
}
}

209
swift/reader.swift Normal file
View File

@ -0,0 +1,209 @@
//******************************************************************************
// MAL - reader
//******************************************************************************
import Foundation
let kSymbolWithMeta = MalSymbol(symbol: "with-meta")
let kSymbolDeref = MalSymbol(symbol: "deref")
let token_pattern =
"[[:space:],]*" + // Skip whitespace: a sequence of zero or more commas or [:space:]'s
"(" +
"~@" + // Literal "~@"
"|" +
"[\\[\\]{}()`'~^@]" + // Punctuation: Any one of []{}()`'~^@
"|" +
"\"(?:\\\\.|[^\\\\\"])*\"" + // Quoted string: characters other than \ or ", or any escaped characters
"|" +
";.*" + // Comment: semicolon followed by anything
"|" +
"[^[:space:]\\[\\]{}()`'\",;]*" + // Symbol, keyword, number, nil, true, false: any sequence of chars but [:space:] or []{}()`'",;
")"
let atom_pattern =
"(^;.*$)" + // Comment
"|" +
"(^-?[0-9]+$)" + // Integer
"|" +
"(^-?[0-9][0-9.]*$)" + // Float
"|" +
"(^nil$)" + // nil
"|" +
"(^true$)" + // true
"|" +
"(^false$)" + // false
"|" +
"(^\".*\"$)" + // String
"|" +
"(:.*)" + // Keyword
"|" +
"(^[^\"]*$)" // Symbol
var token_regex: NSRegularExpression = NSRegularExpression(pattern:token_pattern, options:.allZeros, error:nil)!
var atom_regex: NSRegularExpression = NSRegularExpression(pattern:atom_pattern, options:.allZeros, error:nil)!
class Reader {
init(_ tokens: [String]) {
self.tokens = tokens
self.index = 0
}
func next() -> String? {
let token = peek()
increment()
return token
}
func peek() -> String? {
if index < tokens.count {
return tokens[index]
}
return nil
}
private func increment() {
++index
}
private let tokens: [String]
private var index: Int
}
func tokenizer(s: String) -> [String] {
var tokens = [String]()
let range = NSMakeRange(0, s.utf16Count)
let matches = token_regex.matchesInString(s, options:.allZeros, range:range)
for match in matches as [NSTextCheckingResult] {
if match.range.length > 0 {
let token = (s as NSString).substringWithRange(match.rangeAtIndex(1))
tokens.append(token)
}
}
return tokens
}
private func have_match_at(match: NSTextCheckingResult, index:Int) -> Bool {
return Int64(match.rangeAtIndex(index).location) < LLONG_MAX
}
func read_atom(token: String) -> MalVal {
let range = NSMakeRange(0, token.utf16Count)
let matches = atom_regex.matchesInString(token, options:.allZeros, range:range)
for match in matches as [NSTextCheckingResult] {
if have_match_at(match, 1) { // Comment
return MalComment(comment: token)
} else if have_match_at(match, 2) { // Integer
if let value = NSNumberFormatter().numberFromString(token)?.longLongValue {
return MalInteger(value: value)
}
return MalError(message: "invalid integer: \(token)")
} else if have_match_at(match, 3) { // Float
if let value = NSNumberFormatter().numberFromString(token)?.doubleValue {
return MalFloat(value: value)
}
return MalError(message: "invalid float: \(token)")
} else if have_match_at(match, 4) { // nil
return MalNil()
} else if have_match_at(match, 5) { // true
return MalTrue()
} else if have_match_at(match, 6) { // false
return MalFalse()
} else if have_match_at(match, 7) { // String
return MalString(escaped: token)
} else if have_match_at(match, 8) { // Keyword
return MalKeyword(keyword: dropFirst(token))
} else if have_match_at(match, 9) { // Symbol
return MalSymbol(symbol: token)
}
}
return MalError(message: "Unknown token=\(token)")
}
func read_elements(r: Reader, open: String, close: String) -> ([MalVal]?, MalVal?) {
var list = [MalVal]()
while let token = r.peek() {
if token == close {
r.increment() // Consume the closing paren
return (list, nil)
} else {
let item = read_form(r)
if is_error(item) {
return (nil, item)
}
if item.type != .TypeComment {
list.append(item)
}
}
}
return (nil, MalError(message: "ran out of tokens -- possibly unbalanced ()'s"))
}
func read_list(r: Reader) -> MalVal {
let (list, err) = read_elements(r, "(", ")")
return err != nil ? err! : MalList(array: list!)
}
func read_vector(r: Reader) -> MalVal {
let (list, err) = read_elements(r, "[", "]")
return err != nil ? err! : MalVector(array: list!)
}
func read_hashmap(r: Reader) -> MalVal {
let (list, err) = read_elements(r, "{", "}")
return err != nil ? err! : MalHashMap(array: list!)
}
func common_quote(r: Reader, symbol: String) -> MalVal {
let next = read_form(r)
if is_error(next) { return next }
return MalList(objects: MalSymbol(symbol: symbol), next)
}
func read_form(r: Reader) -> MalVal {
if let token = r.next() {
switch token {
case "(":
return read_list(r)
case ")":
return MalError(message: "unexpected \")\"")
case "[":
return read_vector(r)
case "]":
return MalError(message: "unexpected \"]\"")
case "{":
return read_hashmap(r)
case "}":
return MalError(message: "unexpected \"}\"")
case "`":
return common_quote(r, "quasiquote")
case "'":
return common_quote(r, "quote")
case "~":
return common_quote(r, "unquote")
case "~@":
return common_quote(r, "splice-unquote")
case "^":
let meta = read_form(r)
if is_error(meta) { return meta }
let form = read_form(r)
if is_error(form) { return form }
return MalList(objects: kSymbolWithMeta, form, meta)
case "@":
let form = read_form(r)
if is_error(form) { return form }
return MalList(objects: kSymbolDeref, form)
default:
return read_atom(token)
}
}
return MalError(message: "ran out of tokens -- possibly unbalanced ()'s")
}
func read_str(s: String) -> MalVal {
let tokens = tokenizer(s)
let reader = Reader(tokens)
let obj = read_form(reader)
return obj
}

46
swift/readline.swift Normal file
View File

@ -0,0 +1,46 @@
//******************************************************************************
// MAL - readline
//******************************************************************************
import Foundation
private let HISTORY_FILE = "~/.mal-history"
func with_history_file(do_to_history_file:(UnsafePointer<Int8>) -> ()) {
HISTORY_FILE.withCString {
(c_str) -> () in
let abs_path = tilde_expand(UnsafeMutablePointer<Int8>(c_str))
if abs_path != nil {
do_to_history_file(abs_path)
free(abs_path)
}
}
}
func load_history_file() {
using_history()
with_history_file {
let _ = read_history($0)
}
}
func save_history_file() {
// Do this? stifle_history(1000)
with_history_file {
let _ = write_history($0)
}
}
func _readline(prompt: String) -> String? {
let line = prompt.withCString {
(c_str) -> UnsafeMutablePointer<Int8> in
return readline(c_str)
}
if line != nil {
if let result = String(UTF8String: line) {
add_history(line)
return result
}
}
return nil
}

64
swift/step0_repl.swift Normal file
View File

@ -0,0 +1,64 @@
//******************************************************************************
// MAL - step 0 - repl
//******************************************************************************
// This file is automatically generated from templates/step.swift. Rather than
// editing it directly, it's probably better to edit templates/step.swift and
// regenerate this file. Otherwise, your change might be lost if/when someone
// else performs that process.
//******************************************************************************
import Foundation
// Parse the string into an AST.
//
func READ(str: String) -> String {
return str
}
// Walk the AST and completely evaluate it, handling macro expansions, special
// forms and function calls.
//
func EVAL(ast: String) -> String {
return ast
}
// Convert the value into a human-readable string for printing.
//
func PRINT(exp: String) -> String {
return exp
}
// Perform the READ and EVAL steps. Useful for when you don't care about the
// printable result.
//
func RE(text: String) -> String {
let ast = READ(text)
let exp = EVAL(ast)
return exp
}
// Perform the full READ/EVAL/PRINT, returning a printable string.
//
func REP(text: String) -> String {
let exp = RE(text)
return PRINT(exp)
}
// Perform the full REPL.
//
func REPL() {
while true {
if let text = _readline("user> ") {
println("\(REP(text))")
} else {
println()
break
}
}
}
func main() {
load_history_file()
REPL()
save_history_file()
}

View File

@ -0,0 +1,78 @@
//******************************************************************************
// MAL - step 1 - read/print
//******************************************************************************
// This file is automatically generated from templates/step.swift. Rather than
// editing it directly, it's probably better to edit templates/step.swift and
// regenerate this file. Otherwise, your change might be lost if/when someone
// else performs that process.
//******************************************************************************
import Foundation
// Parse the string into an AST.
//
func READ(str: String) -> MalVal {
return read_str(str)
}
// Walk the AST and completely evaluate it, handling macro expansions, special
// forms and function calls.
//
func EVAL(ast: MalVal) -> MalVal {
if is_error(ast) { return ast }
return ast
}
// Convert the value into a human-readable string for printing.
//
func PRINT(exp: MalVal) -> String? {
if is_error(exp) { return nil }
return pr_str(exp, true)
}
// Perform the READ and EVAL steps. Useful for when you don't care about the
// printable result.
//
func RE(text: String) -> MalVal? {
if text.isEmpty { return nil }
let ast = READ(text)
if is_error(ast) {
println("Error parsing input: \(ast)")
return nil
}
let exp = EVAL(ast)
if is_error(exp) {
println("Error evaluating input: \(exp)")
return nil
}
return exp
}
// Perform the full READ/EVAL/PRINT, returning a printable string.
//
func REP(text: String) -> String? {
let exp = RE(text)
if exp == nil { return nil }
return PRINT(exp!)
}
// Perform the full REPL.
//
func REPL() {
while true {
if let text = _readline("user> ") {
if let output = REP(text) {
println("\(output)")
}
} else {
println()
break
}
}
}
func main() {
load_history_file()
REPL()
save_history_file()
}

175
swift/step2_eval.swift Normal file
View File

@ -0,0 +1,175 @@
//******************************************************************************
// MAL - step 2 - eval
//******************************************************************************
// This file is automatically generated from templates/step.swift. Rather than
// editing it directly, it's probably better to edit templates/step.swift and
// regenerate this file. Otherwise, your change might be lost if/when someone
// else performs that process.
//******************************************************************************
import Foundation
// Parse the string into an AST.
//
func READ(str: String) -> MalVal {
return read_str(str)
}
// Perform a simple evaluation of the `ast` object. If it's a symbol,
// dereference it and return its value. If it's a collection, call EVAL on all
// elements (or just the values, in the case of the hashmap). Otherwise, return
// the object unchanged.
//
func eval_ast(ast: MalVal, env: Environment) -> MalVal {
if is_symbol(ast) {
let symbol = ast as MalSymbol
if let val = env.get(symbol) {
return val
}
return MalError(message: "'\(symbol)' not found") // Specific text needed to match MAL unit tests
}
if is_list(ast) {
let list = ast as MalList
var result = [MalVal]()
result.reserveCapacity(list.count)
for item in list {
let eval = EVAL(item, env)
if is_error(eval) { return eval }
result.append(eval)
}
return MalList(array: result)
}
if is_vector(ast) {
let vec = ast as MalVector
var result = [MalVal]()
result.reserveCapacity(vec.count)
for item in vec {
let eval = EVAL(item, env)
if is_error(eval) { return eval }
result.append(eval)
}
return MalVector(array: result)
}
if is_hashmap(ast) {
let hash = ast as MalHashMap
var result = [MalVal]()
result.reserveCapacity(hash.count * 2)
for (k, v) in hash {
let new_v = EVAL(v, env)
if is_error(new_v) { return new_v }
result.append(k)
result.append(new_v)
}
return MalHashMap(array: result)
}
return ast
}
// Walk the AST and completely evaluate it, handling macro expansions, special
// forms and function calls.
//
func EVAL(var ast: MalVal, var env: Environment) -> MalVal {
if is_error(ast) { return ast }
// Special handling if it's a list.
if is_list(ast) {
var list = ast as MalList
if list.isEmpty {
return ast
}
// Standard list to be applied. Evaluate all the elements first.
let eval = eval_ast(ast, env)
if is_error(eval) { return eval }
// The result had better be a list and better be non-empty.
let eval_list = eval as MalList
if eval_list.isEmpty {
return eval_list
}
// Get the first element of the list and execute it.
let first = eval_list.first()
let rest = eval_list.rest()
if is_builtin(first) {
let fn = first as MalBuiltin
let answer = fn.apply(rest)
return answer
}
// The first element wasn't a function to be executed. Return an
// error saying so.
return MalError(message: "first list item does not evaluate to a function: \(first)")
}
// Not a list -- just evaluate and return.
let answer = eval_ast(ast, env)
return answer
}
// Convert the value into a human-readable string for printing.
//
func PRINT(exp: MalVal) -> String? {
if is_error(exp) { return nil }
return pr_str(exp, true)
}
// Perform the READ and EVAL steps. Useful for when you don't care about the
// printable result.
//
func RE(text: String, env: Environment) -> MalVal? {
if text.isEmpty { return nil }
let ast = READ(text)
if is_error(ast) {
println("Error parsing input: \(ast)")
return nil
}
let exp = EVAL(ast, env)
if is_error(exp) {
println("Error evaluating input: \(exp)")
return nil
}
return exp
}
// Perform the full READ/EVAL/PRINT, returning a printable string.
//
func REP(text: String, env: Environment) -> String? {
let exp = RE(text, env)
if exp == nil { return nil }
return PRINT(exp!)
}
// Perform the full REPL.
//
func REPL(env: Environment) {
while true {
if let text = _readline("user> ") {
if let output = REP(text, env) {
println("\(output)")
}
} else {
println()
break
}
}
}
func main() {
var env = Environment(outer: nil)
load_history_file()
load_builtins(env)
REPL(env)
save_history_file()
}

228
swift/step3_env.swift Normal file
View File

@ -0,0 +1,228 @@
//******************************************************************************
// MAL - step 3 - env
//******************************************************************************
// This file is automatically generated from templates/step.swift. Rather than
// editing it directly, it's probably better to edit templates/step.swift and
// regenerate this file. Otherwise, your change might be lost if/when someone
// else performs that process.
//******************************************************************************
import Foundation
let kSymbolDef = MalSymbol(symbol: "def!")
let kSymbolLet = MalSymbol(symbol: "let*")
// Parse the string into an AST.
//
func READ(str: String) -> MalVal {
return read_str(str)
}
// Perform a simple evaluation of the `ast` object. If it's a symbol,
// dereference it and return its value. If it's a collection, call EVAL on all
// elements (or just the values, in the case of the hashmap). Otherwise, return
// the object unchanged.
//
func eval_ast(ast: MalVal, env: Environment) -> MalVal {
if is_symbol(ast) {
let symbol = ast as MalSymbol
if let val = env.get(symbol) {
return val
}
return MalError(message: "'\(symbol)' not found") // Specific text needed to match MAL unit tests
}
if is_list(ast) {
let list = ast as MalList
var result = [MalVal]()
result.reserveCapacity(list.count)
for item in list {
let eval = EVAL(item, env)
if is_error(eval) { return eval }
result.append(eval)
}
return MalList(array: result)
}
if is_vector(ast) {
let vec = ast as MalVector
var result = [MalVal]()
result.reserveCapacity(vec.count)
for item in vec {
let eval = EVAL(item, env)
if is_error(eval) { return eval }
result.append(eval)
}
return MalVector(array: result)
}
if is_hashmap(ast) {
let hash = ast as MalHashMap
var result = [MalVal]()
result.reserveCapacity(hash.count * 2)
for (k, v) in hash {
let new_v = EVAL(v, env)
if is_error(new_v) { return new_v }
result.append(k)
result.append(new_v)
}
return MalHashMap(array: result)
}
return ast
}
// Walk the AST and completely evaluate it, handling macro expansions, special
// forms and function calls.
//
func EVAL(var ast: MalVal, var env: Environment) -> MalVal {
if is_error(ast) { return ast }
// Special handling if it's a list.
if is_list(ast) {
var list = ast as MalList
if list.isEmpty {
return ast
}
let arg1 = list.first()
if is_symbol(arg1) {
let fn_symbol = arg1 as MalSymbol
// Check for special forms, where we want to check the operation
// before evaluating all of the parameters.
if fn_symbol == kSymbolDef {
if list.count != 3 {
return MalError(message: "expected 2 arguments to def!, got \(list.count - 1)")
}
let arg1 = list[1]
let arg2 = list[2]
if !is_symbol(arg1) {
return MalError(message: "expected symbol for first argument to def!")
}
let sym = arg1 as MalSymbol
let value = EVAL(arg2, env)
if is_error(value) { return value }
return env.set(sym, value)
} else if fn_symbol == kSymbolLet {
if list.count != 3 {
return MalError(message: "expected 2 arguments to let*, got \(list.count - 1)")
}
let arg1 = list[1]
let arg2 = list[2]
if !is_sequence(arg1) {
return MalError(message: "expected list for first argument to let*")
}
let bindings = arg1 as MalSequence
if bindings.count % 2 == 1 {
return MalError(message: "expected even number of elements in bindings to let*, got \(bindings.count)")
}
var new_env = Environment(outer: env)
for var index = 0; index < bindings.count; index += 2 {
let binding_name = bindings[index]
let binding_value = bindings[index + 1]
if !is_symbol(binding_name) {
return MalError(message: "expected symbol for first element in binding pair")
}
let binding_symbol = binding_name as MalSymbol
let evaluated_value = EVAL(binding_value, new_env)
if is_error(evaluated_value) { return evaluated_value }
new_env.set(binding_symbol, evaluated_value)
}
return EVAL(arg2, new_env)
}
}
// Standard list to be applied. Evaluate all the elements first.
let eval = eval_ast(ast, env)
if is_error(eval) { return eval }
// The result had better be a list and better be non-empty.
let eval_list = eval as MalList
if eval_list.isEmpty {
return eval_list
}
// Get the first element of the list and execute it.
let first = eval_list.first()
let rest = eval_list.rest()
if is_builtin(first) {
let fn = first as MalBuiltin
let answer = fn.apply(rest)
return answer
}
// The first element wasn't a function to be executed. Return an
// error saying so.
return MalError(message: "first list item does not evaluate to a function: \(first)")
}
// Not a list -- just evaluate and return.
let answer = eval_ast(ast, env)
return answer
}
// Convert the value into a human-readable string for printing.
//
func PRINT(exp: MalVal) -> String? {
if is_error(exp) { return nil }
return pr_str(exp, true)
}
// Perform the READ and EVAL steps. Useful for when you don't care about the
// printable result.
//
func RE(text: String, env: Environment) -> MalVal? {
if text.isEmpty { return nil }
let ast = READ(text)
if is_error(ast) {
println("Error parsing input: \(ast)")
return nil
}
let exp = EVAL(ast, env)
if is_error(exp) {
println("Error evaluating input: \(exp)")
return nil
}
return exp
}
// Perform the full READ/EVAL/PRINT, returning a printable string.
//
func REP(text: String, env: Environment) -> String? {
let exp = RE(text, env)
if exp == nil { return nil }
return PRINT(exp!)
}
// Perform the full REPL.
//
func REPL(env: Environment) {
while true {
if let text = _readline("user> ") {
if let output = REP(text, env) {
println("\(output)")
}
} else {
println()
break
}
}
}
func main() {
var env = Environment(outer: nil)
load_history_file()
load_builtins(env)
REPL(env)
save_history_file()
}

Some files were not shown because too many files have changed in this diff Show More