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:
commit
d993b22f43
28
Makefile
28
Makefile
@ -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))
|
||||
|
64
README.md
64
README.md
@ -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.
|
||||
|
@ -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
|
||||
|
2
c/core.c
2
c/core.c
@ -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"
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
|
@ -1,5 +1,6 @@
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "types.h"
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -1,5 +1,6 @@
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "types.h"
|
||||
|
@ -1,5 +1,6 @@
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "types.h"
|
||||
|
@ -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));
|
||||
|
@ -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));
|
||||
|
@ -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));
|
||||
|
@ -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));
|
||||
|
@ -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));
|
||||
|
@ -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);
|
||||
|
14
c/types.h
14
c/types.h
@ -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
5
cpp/.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
.deps
|
||||
*.o
|
||||
*.a
|
||||
step0_repl
|
||||
step1_read_print
|
428
cpp/Core.cpp
Normal file
428
cpp/Core.cpp
Normal 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
45
cpp/Debug.h
Normal 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
11
cpp/Dockerfile
Normal 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
73
cpp/Environment.cpp
Normal 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
29
cpp/Environment.h
Normal 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
33
cpp/MAL.h
Normal 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
60
cpp/Makefile
Normal 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
39
cpp/README.md
Normal 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
34
cpp/ReadLine.cpp
Normal 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
17
cpp/ReadLine.h
Normal 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
228
cpp/Reader.cpp
Normal 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 ¯o : 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
77
cpp/RefCountedPtr.h
Normal 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
50
cpp/StaticList.h
Normal 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
88
cpp/String.cpp
Normal 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
18
cpp/String.h
Normal 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
500
cpp/Types.cpp
Normal 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
378
cpp/Types.h
Normal 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
33
cpp/Validation.cpp
Normal 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
16
cpp/Validation.h
Normal 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
34
cpp/docker.sh
Executable 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
42
cpp/step0_repl.cpp
Normal 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
66
cpp/step1_read_print.cpp
Normal 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
119
cpp/step2_eval.cpp
Normal 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
111
cpp/step3_env.cpp
Normal 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
167
cpp/step4_if_fn_do.cpp
Normal 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
174
cpp/step5_tco.cpp
Normal 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
199
cpp/step6_file.cpp
Normal 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
257
cpp/step7_quote.cpp
Normal 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
316
cpp/step8_macros.cpp
Normal 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 ¯o : 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
359
cpp/step9_try.cpp
Normal 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 ¯o : 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
359
cpp/stepA_mal.cpp
Normal 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 ¯o : 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);
|
||||
}
|
||||
}
|
44
docs/FAQ.md
44
docs/FAQ.md
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
11
julia/Makefile
Normal 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
94
julia/core.jl
Normal 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
55
julia/env.jl
Normal 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
40
julia/printer.jl
Normal 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
131
julia/reader.jl
Normal 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
15
julia/readline_mod.jl
Normal 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
29
julia/step0_repl.jl
Executable 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
42
julia/step1_read_print.jl
Executable 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
63
julia/step2_eval.jl
Executable 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
75
julia/step3_env.jl
Executable 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
93
julia/step4_if_fn_do.jl
Executable 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
110
julia/step5_tco.jl
Executable 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
118
julia/step6_file.jl
Executable 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
139
julia/step7_quote.jl
Executable 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
168
julia/step8_macros.jl
Executable 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
186
julia/step9_try.jl
Executable 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
188
julia/stepA_mal.jl
Executable 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
63
julia/types.jl
Normal 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
30
mal/step0_repl.mal
Normal 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)
|
@ -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 =
|
||||
|
@ -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]
|
||||
|
@ -1,2 +1 @@
|
||||
deadCodeElim: off
|
||||
gc: markandsweep
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
264
process/guide.md
264
process/guide.md
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
55
runtest.py
55
runtest.py
@ -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
211
swift/Makefile
Normal 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
15
swift/bridging-header.h
Normal 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
678
swift/core.swift
Normal 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
62
swift/env.swift
Normal 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
18
swift/main.swift
Normal 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:
|
||||
//
|
||||
// You’ll notice that earlier we said top-level code isn’t allowed in most
|
||||
// of your app’s source files. The exception is a special file named
|
||||
// “main.swift”, which behaves much like a playground file, but is built
|
||||
// with your app’s 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
19
swift/printer.swift
Normal 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
209
swift/reader.swift
Normal 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
46
swift/readline.swift
Normal 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
64
swift/step0_repl.swift
Normal 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()
|
||||
}
|
78
swift/step1_read_print.swift
Normal file
78
swift/step1_read_print.swift
Normal 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
175
swift/step2_eval.swift
Normal 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
228
swift/step3_env.swift
Normal 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
Loading…
Reference in New Issue
Block a user